Skip to content

Instantly share code, notes, and snippets.

@jcreinhold
Last active November 23, 2022 15:46
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jcreinhold/dfe43f54b6dfd8bb7e9f293c0007e15d to your computer and use it in GitHub Desktop.
Save jcreinhold/dfe43f54b6dfd8bb7e9f293c0007e15d to your computer and use it in GitHub Desktop.
Convert TIFF image directory (of one image) to NIfTI
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
tif_to_nii
command line executable to convert a directory of tif images
(from one image) to a nifti image stacked along a user-specified axis
call as: python tif_to_nii.py /path/to/tif/ /path/to/nifti
(append optional arguments to the call as desired)
Author: Jacob Reinhold (jacob.reinhold@jhu.edu)
"""
import argparse
from glob import glob
import os
from pathlib import Path
import sys
from PIL import Image
import nibabel as nib
import numpy as np
def arg_parser():
parser = argparse.ArgumentParser(description='merge 2d tif images into a 3d image')
parser.add_argument('img_dir', type=str,
help='path to tiff image directory')
parser.add_argument('out_dir', type=str,
help='path to output the corresponding tif image slices')
parser.add_argument('-a', '--axis', type=int, default=2,
help='axis on which to stack the 2d images')
return parser
def split_filename(filepath):
path = os.path.dirname(filepath)
filename = os.path.basename(filepath)
base, ext = os.path.splitext(filename)
if ext == '.gz':
base, ext2 = os.path.splitext(base)
ext = ext2 + ext
return path, base, ext
def main():
try:
args = arg_parser().parse_args()
img_dir = Path(args.img_dir)
fns = sorted([str(fn) for fn in img_dir.glob('*.tif*')])
if not fns:
raise ValueError(f'img_dir ({args.img_dir}) does not contain any .tif or .tiff images.')
imgs = []
for fn in fns:
_, base, ext = split_filename(fn)
img = np.asarray(Image.open(fn)).astype(np.float32).squeeze()
if img.ndim != 2:
raise Exception(f'Only 2D data supported. File {base}{ext} has dimension {img.ndim}.')
imgs.append(img)
img = np.stack(imgs, axis=args.axis)
nib.Nifti1Image(img,None).to_filename(os.path.join(args.out_dir, f'{base}.nii.gz'))
return 0
except Exception as e:
print(e)
return 1
if __name__ == "__main__":
sys.exit(main())
@jcreinhold
Copy link
Author

If you created an image using the nii_to_tif script (link), then you can invert the process here on an individual subject-by-subject basis. That is, if you converted 3 images with the nii_to_tif script and you want to convert them back to NIfTI files, you'll have to move all of the TIFF images corresponding to one subject into its own directory and then run this script, using the same axis argument.

You can extend this to invert the whole directory of TIFF images since the last four digits of the filename correspond to the slice. Feel free to do so. I did not include that here because of time and the fact that people often append keywords to the end of their filenames to designate the file has been processed in a certain way, so the scheme I am using here to gather the slices and stack them would be brittle.

@harish-de
Copy link

Thank you for sharing !!
I have got the following error in the line: "img = np.asarray(Image.open(fn)).astype(np.float32).squeeze()"

Error:
float() argument must be a string or a number, not 'TiffImageFile'

@jcreinhold
Copy link
Author

@harish-de can you provide the exact command used to call the script and the full error message?

Please make sure you are using a recent version of numpy and python (>=3.6). Also, make sure that you are supplying the script with the directory containing the TIFF images as described in my previous comment.

@MaleehaKhan
Copy link

how to fix:
This code is using an older version of pydicom, which is no longer
maintained as of Jan 2017. You can access the new pydicom features and API
by installing pydicom from PyPI.
See 'Transitioning to pydicom 1.x' section at pydicom.readthedocs.org
for more information.

warnings.warn(msg)
'Namespace' object has no attribute 'img_dir'

@jcreinhold
Copy link
Author

Hi @MaleehaKhan. This script will take a directory of TIFF images and stack them into a numpy array which is then saved as a NIfTI file. The error you are receiving in your second post might be due to the fact that you are using a directory that is either empty (i.e., you made a typo when entering the directory into img_dir) or the directory does not have files with the file-type: .tif or .tiff.

@MaleehaKhan
Copy link

Hi @jcreinhold how can i do the reverse like converting nii to tiff

@jcreinhold
Copy link
Author

jcreinhold commented Feb 21, 2020

Hi @MaleehaKhan, use this script to convert from NIfTI to TIFF. Please read the comments there for information on how to use the script.

@MaleehaKhan
Copy link

Thanks a lot @jcreinhold

@chamomile-Z
Copy link

Hi, I have a set of .tiff 2D images with right slices order, actually the right slices order will organize as a 3D image, and I want to transfer it from .tiff to .nii.gz. However, I don't very understand about the 'parser.add_argument('-a', '--axis', type=int, default=2, help='axis on which to stack the 2d images')'. And after I added the img_dir and out_dir, I got 'need at least one array to stack', I don't know what happened. Do you have any advice?

@jcreinhold
Copy link
Author

@chamomile-Z Can you post the exact command you used to run the script?

@MaleehaKhan
Copy link

MaleehaKhan commented Sep 4, 2020 via email

@chamomile-Z
Copy link

@chamomile-Z Can you post the exact command you used to run the script?

Thank you for reply! I created two folder, one is img_dir (img_dir\cellimages\s1.tif, s2.tif, s3.tif...) and another one is out_dir. The command I run is: tif_to_nii.py 'img_dir\cellimages' 'out_dir'
I am totally new about this part. Maybe there are some mistakes in my operation or understanding for the code. Very thank you for the help!

@chamomile-Z
Copy link

I think your data is not properly read, place the input and output directories in the same file where code is present and the right scile will not organize it in 3D it will be 2D, I have not worked much I would suggest write your error below the comments and you may get reply by the author 🙂 Maleeha Khalid Khan

________________________________ From: chamomile-Z notifications@github.com Sent: Friday, September 4, 2020 10:53 PM To: jcreinhold jcreinhold@noreply.github.com Cc: MaleehaKhan maleehakhalidkhan@hotmail.com; Mention mention@noreply.github.com Subject: Re: jcreinhold/tif_to_nii.py @chamomile-Z commented on this gist.
________________________________ Hi, I have a set of .tiff 2D images with right slices order, actually the right slices order will organize as a 3D image, and I want to transfer it from .tiff to .nii.gz. However, I don't very understand about the 'parser.add_argument('-a', '--axis', type=int, default=2, help='axis on which to stack the 2d images')'. And after I added the img_dir and out_dir, I got 'need at least one array to stack', I don't know what happened. Do you have any advice? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://gist.github.com/dfe43f54b6dfd8bb7e9f293c0007e15d#gistcomment-3442392, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AGU6FFUNOIB7BG5HVDKJ7OTSEESQPANCNFSM4JQFP63A.

Thank you for your comment. I could try again for my data.

@jcreinhold
Copy link
Author

@chamomile-Z Try using the full path to the image directory instead of the relative path. The full path will start with /

Also, it shouldn't matter but remove the single quotes around the directory names.

@chamomile-Z
Copy link

Hi , I tried to edit the path, but looks like it doesn't help. the command I run is: py prog.py '\img_dir\6-97-1T8' '\out_dir'
and the error is: need at least one array to stack

Another question is what I want to do is that the data I have is a stack of tiff images (3D image obtained in one direction slices), I would like to transfer it to .nii.gz format. I don't know if your code will work for this purpose? And also I am not very understand about the part: parser.add_argument('-a', '--axis', type=int, default=2, help='axis on which to stack the 2d images') , how do you decide the axis? If you could help tp answer my question, It will be great! Thanks!

@jcreinhold
Copy link
Author

@chamomile-Z Can you please copy the command you run (please use the full path, you can anonymize the specific directories if you are worried with an X) along with the entire stack trace (e.g., up to where the error is printed).

Are you running this script with Linux or OS X, or are you using Windows?

--axis controls the axis on which to stack the images to make the 3D volume.

@jcreinhold
Copy link
Author

jcreinhold commented Sep 7, 2020

@chamomile-Z I overlooked the direction of the slash for your directories before, but now I am fairly confident that you must be using Windows. The backslashes in the path were likely causing the error. I believe I fixed the issue with the update, but I cannot test on Windows so I'm not sure. Please try it out (re-download the script and use it instead of the version you currently have) and let me know if you still have an error. If so, please use the full path and copy and paste the entire command and stack trace as mentioned before.

For more background on axes, look at the numpy documentation. Here is an example showing what setting the axis does:

>>> import numpy as np
>>> x = np.array([1,2])
>>> y = np.array([3,4])
>>> np.stack((x,y), axis=0)
array([[1, 2],
       [3, 4]])
>>> np.stack((x,y), axis=1)
array([[1, 3],
       [2, 4]])

@chamomile-Z
Copy link

@chamomile-Z I overlooked the direction of the slash for your directories before, but now I am fairly confident that you must be using Windows. The backslashes in the path were likely causing the error. I believe I fixed the issue with the update, but I cannot test on Windows so I'm not sure. Please try it out (re-download the script and use it instead of the version you currently have) and let me know if you still have an error. If so, please use the full path and copy and paste the entire command and stack trace as mentioned before.

For more background on axes, look at the numpy documentation. Here is an example showing what setting the axis does:

>>> import numpy as np
>>> x = np.array([1,2])
>>> y = np.array([3,4])
>>> np.stack((x,y), axis=0)
array([[1, 2],
       [3, 4]])
>>> np.stack((x,y), axis=1)
array([[1, 3],
       [2, 4]])

Thank you so much! Yes, I am using Windows. I will try it these days and let you know! I am appreciate your help!

@chamomile-Z
Copy link

Hi, I tried again based on the comment you give me.
I run the code in pycharm. I have 14 2D tiff images, the full path I stored the images are: 'E:\AA\3D_data\venv\img_dir\6-97-1T8' (under 6-97-1T8 folder, there are 14 tiff images), the full path I built for out_dir is: 'E:\IrisCode\3D_data\venv\out_dir'
When I run this script, I wrote the command like this: py tif_to_nii.py 'E:/IrisCode/3D_data/venv/img_dir/6-97-1T8' 'E:/IrisCode/3D_data/venv/out_dir'
And the error I got is: [WinError 123] The filename, directory name, or volume label syntax is incorrect: "'E:\IrisCode\3D_data\venv\img_dir\6-97-1T8'"

So this is the update. I will try more about the script, any suggestions will be appreciate! Also wonder " parser.add_argument('-a', '--axis', type=int, default=2,
help='axis on which to stack the 2d images')" in this part, I saw the default number is 2, do I need to change based on different data?

@jcreinhold
Copy link
Author

Sorry, I don't have access to a Windows computer so I can't test and fix this error. I don't know how important the use of backslashes are in writing a path in Windows, but your directories that you copied use forward-slashes and the command you typed uses backslashes.

I don't think that's the problem, but perhaps it is. I'm not sure and I can't test it. If I were you, I'd just copy the relevant code that you want from the script and write your own script specifically for your data. The code in the script is pretty simple; all it does is read a set of TIF files and stack the results and writes it to a NIfTI file.

The axis should be changed based on the data. You'll have to look at the numpy documentation to understand what is going on if my examples haven't helped so far. If you have 2D images that need to be combined into a 3D volume, there are 3 ways to combine those images into the 3D volume. You can stack them along the x, y, or z axis which correspond to axis=0, axis=1, axis=2, respectively. Note that the x, y, z axis is not well-defined here, so you need to figure out which one is appropriate for your data. Try all three and see which one works if you don't know.

@chamomile-Z
Copy link

Thank you so much for your comment! And one more question, I checked some of .nii.gz files, they have slices from three different directions, but my data only from one direction. And when using the Nifti1Image, there exists some problems. Just wonder if you have any suggestions for this part? Thank you so much if you have any comment. But already thanks a lot!

@jcreinhold
Copy link
Author

@chamomile-Z I don't quite understand the first question regarding direction. Perhaps try changing the axis argument and make sure your TIF image directory is setup according to the first comment in this gist (that is, all of the TIF images in a directory correspond to 1 3D volume). This script is really only meant to be used as a (somewhat poor) inverse operation to the nii_to_tif script.

Regarding problems with Nifti1Image, please see the nibabel documentation.

I'm unfortunately too busy to continue debugging unless it specifically pertains a bug in the code I've provided and can be reproduced on OS X or Linux. Hopefully what I've answered helps!

@chamomile-Z
Copy link

@chamomile-Z I don't quite understand the first question regarding direction. Perhaps try changing the axis argument and make sure your TIF image directory is setup according to the first comment in this gist (that is, all of the TIF images in a directory correspond to 1 3D volume). This script is really only meant to be used as a (somewhat poor) inverse operation to the nii_to_tif script.

Regarding problems with Nifti1Image, please see the nibabel documentation.

I'm unfortunately too busy to continue debugging unless it specifically pertains a bug in the code I've provided and can be reproduced on OS X or Linux. Hopefully what I've answered helps!

Thanks a lot for your explanation! It is really help a lot for me! Thank you and have a nice day!

@JacobBumgarner
Copy link

Thanks for sharing Jacob. Very helpful.

@harun-cu
Copy link

harun-cu commented Dec 23, 2020

@jcreinhold
Hi,
Thanks for your nice job!

I am new in this field. Here I do not understand, where & how can I define input_dir and out_dir?
This way I tried in pyCharm (Windows):

parser.add_argument('img_dir = G:/All/uu/working/hasta_dicom/4OLGU_A1448042/mm', type=str,
help='path to tiff image directory')
parser.add_argument('out_dir = G:/All/uu/working/hasta_dicom/4OLGU_A1448042', type=str,
help='path to output the corresponding tif image slices')
parser.add_argument('-a', '--axis', type=int, default=2,
help='axis on which to stack the 2d images')

Would you please give any guidelines for this purpose?
Thanks in advance.

@jcreinhold
Copy link
Author

Hello @harun-cu. This is a command line script. If you open up a terminal, you would type:

python tif_to_nii.py path/to/tiff/images path/to/output/nii/images -a 2

(In your case, I'm assuming that the path/to/tiff/images corresponds to G:/All/uu/working/hasta_dicom/4OLGU_A1448042/mm, and the path/to/output/nii/images corresponds to G:/All/uu/working/hasta_dicom/4OLGU_A1448042.)

Note that the -a 2 is the axis option, and is data-dependent; the axis specifies which axis to concatenate the TIFF images.

I have not tried this script with Windows, so I'm afraid it might not work for you. However, you should be able to copy and modify the code inside the main function and get it to work.

Hope that helps.

@harun-cu
Copy link

harun-cu commented Dec 24, 2020

@jcreinhold
Thank you very much.
It's working on pycharm and Windows.
But converted images turn into 90 degrees right and vertical.
Why did this happen?

and it converted just one image, is it possible to convert a full-stack/directory image at a time?

Again thanks for your previous reply.

@jcreinhold
Copy link
Author

jcreinhold commented Dec 24, 2020

@harun-cu

Glad to hear you got it working. The images may appear rotated in a medical image viewer because the NIfTI header is blank; you cannot recover the header from the TIFF images alone.

If you want to add back in the header to the converted image, you should create a python script to:

  1. load the image with the correct header
  2. extract the header from the image
  3. load the converted image
  4. create a new NIfTI with the same data array as the converted image, but with the correct header
  5. save the new NIfTI

If you use nibabel to load and save the images, you need to extract the affine matrix along with the header. Here is documentation to the relevant NIfTI class.

The script should look something like this:

import nibabel as nib

img_with_correct_header = nib.load('path/to/img/with/correct/header.nii.gz')
affine = img_with_correct_header.affine
header = img_with_correct_header.header
converted_img = nib.load('path/to/converted/img.nii.gz')
converted_img_data = converted_img.get_fdata()
out_path = 'path/to/save/new/img.nii.gz'
converted_img_with_correct_header = nib.Nifti1Image(converted_img_data, affine, header)
converted_img_with_correct_header.to_filename(out_path)

I haven't checked to see if that code runs, but it should be a good starting point. Best of luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment