Skip to content

Instantly share code, notes, and snippets.

@jcreinhold
Last active August 1, 2023 18:23
Show Gist options
  • Save jcreinhold/01daf54a6002de7bd8d58bad78b4022b to your computer and use it in GitHub Desktop.
Save jcreinhold/01daf54a6002de7bd8d58bad78b4022b to your computer and use it in GitHub Desktop.
Convert NIfTI images into multiple TIF images
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
nii_to_tif
command line executable to convert 3d nifti images to
individual tiff images along a user-specified axis
call as: python nii_to_tif.py /path/to/nifti /path/to/tif
(append optional arguments to the call as desired)
Author: Jacob Reinhold (jacob.reinhold@jhu.edu)
"""
import argparse
from glob import glob
import os
import sys
from PIL import Image
import nibabel as nib
import numpy as np
def arg_parser():
parser = argparse.ArgumentParser(description='split 3d image into multiple 2d images')
parser.add_argument('img_dir', type=str,
help='path to nifti 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 of the 3d image array on which to sample the slices')
parser.add_argument('-p', '--pct-range', nargs=2, type=float, default=(0.2,0.8),
help=('range of indices, as a percentage, from which to sample '
'in each 3d image volume. used to avoid creating blank tif '
'images if there is substantial empty space along the ends '
'of the chosen axis'))
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()
fns = glob(os.path.join(args.img_dir, '*.nii*'))
for fn in fns:
_, base, ext = split_filename(fn)
img = nib.load(fn).get_data().astype(np.float32).squeeze()
if img.ndim != 3:
print(f'Only 3D data supported. File {base}{ext} has dimension {img.ndim}. Skipping.')
continue
start = int(args.pct_range[0] * img.shape[args.axis])
end = int(args.pct_range[1] * img.shape[args.axis])
for i in range(start, end):
I = Image.fromarray(img[i,:,:], mode='F') if args.axis == 0 else \
Image.fromarray(img[:,i,:], mode='F') if args.axis == 1 else \
Image.fromarray(img[:,:,i], mode='F')
I.save(os.path.join(args.out_dir, f'{base}_{i:04}.tif'))
return 0
except Exception as e:
print(e)
return 1
if __name__ == "__main__":
sys.exit(main())
@saranggoel
Copy link

Thank you for your prompt reply! Yes, opening it in a medical viewer software made it look exactly as expected! Thank you so much!

@yastrip
Copy link

yastrip commented Feb 3, 2021

Hi Jacob!
I am trying to run the code but I keep encountering the following error:

$ python nii_to_tif.py brainmask/ TIF/
File "nii_to_tif.py", line 125
print(f'Only 3D data supported. File {base}{ext} has dimension {img.ndim}. Skipping.')
^
SyntaxError: invalid syntax

Do you know what might be the error? I have tried different 3D nifti files but none of them work.
brainmask/ is a folder containing a 3D nifti file
TIF/ is an empty folder
Thank you very much!

@saranggoel
Copy link

saranggoel commented Feb 3, 2021

Actually, there is some syntax error with your print statement in the code. If you are very sure your nii files are 3D, then just delete the following lines:

        if img.ndim != 3:
            print(f'Only 3D data supported. File {base}{ext} has dimension {img.ndim}. Skipping.')
            continue

Hi Jacob!
I am trying to run the code but I keep encountering the following error:

$ python nii_to_tif.py brainmask/ TIF/
File "nii_to_tif.py", line 125
print(f'Only 3D data supported. File {base}{ext} has dimension {img.ndim}. Skipping.')
^
SyntaxError: invalid syntax

Do you know what might be the error? I have tried different 3D nifti files but none of them work.
brainmask/ is a folder containing a 3D nifti file
TIF/ is an empty folder
Thank you very much!

@jcreinhold
Copy link
Author

jcreinhold commented Feb 3, 2021

@yastrip The reason you are receiving a syntax error is because you are using a version of python <3.6. The f-string format was added in python 3.6, so previous versions will not recognize the syntax. You can either remove the like as suggested by @saranggoel, or you can change the print statement to:

print('Only 3D data supported. File {}{} has dimension {}. Skipping.'.format(base, ext, img.ndim))

A similar question was already asked. See here. Hope this helps.

@ozturkoktay
Copy link

ozturkoktay commented Mar 25, 2021

Hi, I'm trying to convert 3 dim. nii file to multiple images with your script but it only gives multiple blank tiff files. When I convert nii to png files everything is fine. Can you help me about that?

@jcreinhold
Copy link
Author

Hi @ozturkoktay, the TIFF images are probably fine. You just need to open them in a medical image viewer like MIPAV or plot them in matplotlib. This has been discussed above here and here.

@mccrinbc
Copy link

Hey Jacob,
I would like to confirm that the conversion from nii to tiff is lossless. Is there a simple way to do this?

Thanks!
Brian

@jcreinhold
Copy link
Author

jcreinhold commented Aug 10, 2021

@mccrinbc Load your nifti image with nibabel and extract the data (.get_fdata(dtype=np.float32)). Open up all the corresponding tiff images and concatenate them along the appropriate axis. Check for equality with np.allclose(nifti_data, tiff_data))

Note that this conversion is only lossless (up to numerical precision) if the original nifti image is 32-bit floating point.

@mccrinbc
Copy link

Beauty - Thanks dude!

@sujeongEOM
Copy link

sujeongEOM commented Oct 7, 2021

Hello! This script is very helpful. The code worked well.
However, I have a question.
My nifti file is z-normalized. So its pixel range has negative values and doesn't go up to 255 like other image arrays.
As my purpose is to use imagenet transfer learning model (such as resnet, inception etc), I have to make input shape to 3 channel (ex; (220,220,3) ). So I did so. However when I show this 3 channel image through matoplotlib, it looks bad. If I show each channel alone, it shows fine.
Is z-normalized the problem? Thank you.

-3 channel image
image
-only one channel shown (image[:,:,1])
image

@jcreinhold
Copy link
Author

@sujeongEOM see up above (e.g., here) for a discussion of this issue.

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