Skip to content

Instantly share code, notes, and snippets.

@jcreinhold
Last active August 1, 2023 18:23
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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())
@jcreinhold
Copy link
Author

@liuhh02 No problem and thanks for raising the issue and providing the feedback!

@Aliktk
Copy link

Aliktk commented Feb 15, 2020

hello Sir, Does it work on windows application of deep learning IDE like spyder, or jupiter etc?
2nd question; I have 3D files having dim of 256,256,166 with format of nii does it supports nii or or only nii.gz?
if it support nii.gz than how i convert my files to nii.gz as I downloaded from ADNI .
Please help in this regard.

@jcreinhold
Copy link
Author

Hi @Aliktk this script is meant to be run from the command line, but you should be able to adapt it to run from Jupyter (i.e., a python console) with a little work on your part (if you are familiar with python). This script supports both .nii and .nii.gz files.

@Aliktk
Copy link

Aliktk commented Feb 16, 2020

When I use this repo in Jupiter notebook than I got this error please help me or show me how to run on Jupiter
here is the error:

`usage: ipykernel_launcher.py [-h] [-a AXIS] [-p PCT_RANGE PCT_RANGE]
C:/Users/Ali ktk/Downloads/Data Science/Nii to
tiff C:/Users/Ali ktk/Downloads/Data Science/Nii
to tiff/out
ipykernel_launcher.py: error: the following arguments are required: C:/Users/Ali ktk/Downloads/Data Science/Nii to tiff/out
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2`

@jcreinhold
Copy link
Author

Hi @Aliktk. Sorry I wasn't more clear. This script is not compatible with Jupyter. It is meant to be run from the command line.

@Aliktk
Copy link

Aliktk commented Feb 21, 2020

hello sir again I am running this repo on another laptop where everything is installed but got this error please help me and guide me where I would change this repo
I means where I change the specified code and directory, Thanks
Here is the error

warnings.warn(msg)
usage: nii_to_tif.py [-h] [-a AXIS] [-p PCT_RANGE PCT_RANGE] img_dir out_dir
nii_to_tif.py: error: the following arguments are required: img_dir, out_dir

as I gave the directory exactly as required

Here is the screenshot of the error.

WhatsApp Image 2020-02-21 at 2 21 26 PM

In this picture I have change multiple thing so my point is to where I am wrong this scenario.

@jcreinhold
Copy link
Author

Hi @Aliktk, you need to specify the directory containing the nifti files you want to convert into tif files, and you also have to specify the directory in which you want the tif files to be output into. So the command line call would be something like:

python nii_to_tif.py /path/to/nifti/files/ /path/to/tif/files

@Aliktk
Copy link

Aliktk commented Feb 21, 2020

can I send you one file and than you convert it for me if possible?
As I can't understand and could not resolve my issue.
thanks

@jcreinhold
Copy link
Author

Hi @Aliktk, unfortunately I won't be able to do that. If you are running into a bug with the code, then I can possibly help.

@Aliktk
Copy link

Aliktk commented Feb 24, 2020

ok no issue with that but clear me one thing sir.
how much slices it will convert I mean to say that in every MRI image contains 700+ slices (Axil, coronal, sigattal) and every one containing 256, 256 and 166 slices per view.
Can this repo convert all these slices at once of each volume of single MRI?

@jcreinhold
Copy link
Author

@Aliktk You can run this script three times changing the --axis argument to create slices over the whole image in each image plane (axial, coronal, and sagittal). You would change the --axis argument to equal 0 in one run, 1 in the next run, and 2 in the final run.

I'm not sure if I answered your question, but hopefully that is helpful.

@mccrinbc
Copy link

Hey Jacob, Thanks for sharing this work!

As a general classification implementation question, would you obtain slices of the brain in each direction (Axial, Coronal, and Sagittal) and use all three sets in your model? Would you classify each slice in each direction as one (1) of the potentially many classes you're interested in, or would you take the slices and classify each slice individually based on its content?

@jcreinhold
Copy link
Author

@mccrinbc you can use all three orientations as input to the network or train three separate models; there are papers that have successfully done both, and I'm not sure which is more optimal—the optimal choice would depend on the task/resource-availability. Let me know if my response didn't answer your question.

@mccrinbc
Copy link

Thanks for the reply Jacob!

It makes sense that you could use either method depending on the problem you're trying to solve. But, does it make sense to classify all of these slices from a single brain as one class? In the case of a binary classification problem, depending on the disease pathology, there could be sections of the brain that are unaffected. From this, you could have a large amount of data "incorrectly classified". Have you classified all of the slices you've obtained from nii_to_tif.py as a single class, or have you been more meticulous to further label your data based on what's shown within the slice? Or is this interpretation flawed?

This is very specific, so please feel free to remove this discussion as you see fit. Thank you!

@jcreinhold
Copy link
Author

@mccrinbc I guess I'm confused as to what you mean by classifying "all slices from a single brain as one class." I think you have a specific application in mind, e.g., classifying an image as diseased or not diseased; however, there are many reasons to create 2D slices of a 3D image, and this script is not application-specific. For example, I've used this script to create 2D slices for input to a deep neural network for segmentation and image-to-image translation. If you have registered labels and you use this script to create slices, using the same options, with the source and label/target image, you'll maintain the correspondence between voxels/pixels.

If you only have a weak label like: "this image contains disease," I think your intuition is correct that it would be flawed to use this label on all slices because some of the slices may only contain healthy-appearing tissue. This particular problem isn't my research area, but I'm familiar with some work dealing with this problem, e.g., this paper.

Hope that helps.

@mccrinbc
Copy link

Jacob,
This was incredibly helpful, thank you for your insight!

@saranggoel
Copy link

saranggoel commented Nov 26, 2020

I was wondering something. I thought that this was an amazing tool overall, however, it appears that my tif files aren't coming out too well. However, when I convert the tif files back to nii (with the other code), it comes out perfect and normal. Do you know what might be the problem?

Captusasare
^^Here is what I got

wewqe
^^Here is what I was expecting

THANK YOU so much for all your work! It really helps me with my research!

@jcreinhold
Copy link
Author

jcreinhold commented Nov 26, 2020

@saranggoel Can you upload the output TIF image? Perhaps your window-level setting is bad. Or have you already looked at that?

@jcreinhold
Copy link
Author

Given that the nifti image reconstructed from the TIF images is fine, I'm almost sure that this problem is just a window-level setting problem. Open up the TIF image with some problem like mipav, some other medical imaging viewer, or matlab/matplotlib and adjust the window-level settings and you should see the image you're expecting.

I'm guessing you are trying to view the image with a GUI-based image viewer in whatever your operating system is. However, the default window-level settings probably don't work for the TIF image which has a large range of intensitites (i.e., hounsfield units).

You shouldn't use this script if you want to convert to jpg as the final output though. The reason I built this script is that I wanted to convert a nifti image into a collection of 2d images while preserving the exact intensity value at every voxel. When you convert to jpg or png you need to quantize the intensity values (i.e., convert them to uint8) which is—in general—a lossy process.

Hope this helps.

@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