Issue with NIfTI Image Orientation After DICOM Conversion

Operating system: Windows
Slicer version: 5.4.0.

Hello,

I’m having an issue with the orientation of NIfTI images that I’ve created from DICOM series using custom code. After dividing the DICOMs into batches of 64 and reconverting to NIfTI files (to be used later on a PyTorch model), the resulting images appear to be rotated 90 degrees on axial view and flipped on the x-axis on the coronal and sagittal view when viewed in 3D Slicer 5.4.0.

I’ve compared the DICOM files created with my code to those created with 3D Slicer’s “Create DICOM series” module, and adjusted the ImageOrientationPatient, ImagePositionPatient, PixelSpacing, and SliceThickness attributes in my DICOM files to match those from 3D Slicer as they, after being reconverted to NIfTI files with the same code, appear to be oriented correctly. However, this didn’t resolve the issue.

I also compared the headers of the resulting NIfTI files and adjusted the qform_code, sform_code, quatern_b, quatern_c, quatern_d, qoffset_x, qoffset_y, qoffset_z, pixdim, and xyzt_units to match a NIfTI file that appears correctly oriented in 3D Slicer. Unfortunately, this didn’t resolve the issue either.

I’ve tried using numpy’s flip and rot90 functions, as well as SimpleITK, to rotate the NIfTI files. Flipping on the x-axis worked, but any rotation resulted in distorted images in 3D Slicer.

I’m at a loss for what else to try. Has anyone else encountered this issue or have any suggestions for how to resolve it? This is my first time working with 3D Slicer and 3D images in general. I am doing all of this in Google Colab.

Thank you in advance for your help!

I’ve managed to solve this issue - I created a rotation matrix for a 90 degree rotation and applied it to the affine matrix of the NIfTI file, created a new NIfTI with the rotated affine matrix and it fixed the issue, or at least it appears so when viewing the file in 3D Slicer.

Image orientation can be a surprisingly complicated topic. Depending on the software used, assumptions may be made about the order of image voxels in memory, the relationship between presumed anatomical directions and image axes (e.g. RAS vs LPS), and the orientation and location of image axes in scanner space, anatomical space, a standardized space, or some other coordinate system. 3D Slicer handles all of these specifications well starting from DICOM or from NIfTI images, so if you are observing orientation differences in images you have created versus originals, the problem likely comes from the custom code you have developed to create the derived images. It sounds like you have empirically found a rotation which may fix the incorrect re-orientation in your example case, but it also sounds like you don’t know why the orientation change happens. I would encourage you to keep investigating to try to figure out the root cause of the problem, because depending on what it is, the empirical rotation you found may not always be the right thing to do. For example, a different reorientation correction might be needed if the patient is oriented differently in the scanner or if the image voxels were stored in a different order. You mentioned that x-axis flipping in numpy appeared to work, and also that a certain 90 deg rotation appeared to work. Note that these are incompatible transformations; flipping means mirror image reflection, and rotation means no reflection. In one of those two cases, you are viewing the mirror image of the patient, and in one of them you are viewing the patient. Because humans are roughly bilaterally symmetrical, it may not be obvious which is which. At a minimum, even if you decide that your input images will be very consistent in type, format, and orientation and so the same empirical transformation is likely to work all the time, you should ensure that your transformation is giving you the patient image, and not the mirror image of the patient. Find a unique feature on the right or left side of the original images, and make sure it ends up on the same side in your generated and reoriented images.

One issue which you may not have considered so far is that numpy array indexing of image volumes from Slicer has the indices in reverse order from what might be expected. The indices are arr[k,j,i] rather than arr[i,j,k]. If you are taking subscripted chunks out of image volumes, this could certainly lead to a different orientation than you were expecting.

Thank you for your answer and explanations - after rotating the image 90 degrees around the z axis using a rotation matrix and applying it to the affine matrix of the NIfTI file as I mentioned, I noticed it was still flipped on the y axis compared to the original file, so I flipped it and now they are exactly the same when viewed in 3D slicer in axial and coronal view, however, in sagittal view, the R <—> L values now go from -532.1969mm to -172.9mm compared to the original -172.9mm to 186mm. I’m guessing the rotation somehow messed with the x values of the ImagePositionPatient attribute of the dicom files? Is there a way I could only rotate the image around the x axis to somehow fix the sagittal view? I’m still trying to find out where in the nifti to dicom series conversion I’m messing up.
The original NIfTI files have the LAS orientation, and the code I’m using slices them on the z-axis, so that would be in the axial plane, and I am also restacking them in the order of ImagePositionPatient[2], which, as I understand, is also the z-coordinate. I tried stacking them in reversed order and it didn’t work.
I apologize as I’m really having trouble understanding this and thank you again for your answers.

Might it be possible to use Slicer to create the NIfTI exports? You could use the CropVolume module and a series of ROI’s to gather whatever slabs you want. That way you could let Slicer handle all the complicated parts of maintaining the coordinate system consistency.

The R-L range shift is probably because of the location of the origin of the image you are rotating around, which is the center of the one of the corner voxels. When you rotate, it will be around this point, and therefore the image volume ends up in a different part of space. To bring it back to the original location in space you will need to apply a translation also.

It might be easier to provide more helpful suggestions if we knew more about what you are trying to accomplish at a little higher level. What are the inputs and what are your desired outputs? Are you breaking the image data into chunks for AI training purposes, memory management purposes, or some other reason?

Thank you, I will look into those options as I am running out of ideas and have tried everything I could find.
I downloaded NIfTI files from kaggle for PyTorch model training. However, these volumes have a varying number of slices (from 75 to over 900). I saw someone handling this by transforming these files into dicom series using 3D Slicer’s module and then dividing the series into batches of 64 dicoms, then converting these batches back to NIfTI files using dicom2nifti.dicom_series_to_nifti().

I found a Python function that should create dicom series and wanted to use it as there are 130 images and putting them all through 3D Slicer to convert to dicom series takes very long. However, after using it, even though it did successfully create dicom series that I could divide into equally sized batches, after turning them back to NIfTi files using the mentioned dicom2nifti function and viewing them in 3D Slicer, the images seemed rotated.

I did modify the code and set all of the additional attributes (ImagePositionPatient, ImageOrientationPatient, PixelSpacing, SliceThickness) of the template dicom file that was used to create the dicom series to match those of dicom files that were created by 3D Slicer as those presented normally when converted back to NIfTIs, and I changed the resulting NIfTIs’ header file information to match the ones that were oriented correctly when viewing. Nothing worked except for what I had mentioned above (rotating and flipping).

I am confused as I am constantly checking that all of the dicoms’ attributes are the same and the NIfTI s’ header file and orientation are the same, yet one group looks fine when viewing it (the one created from dicoms that were created with 3D Slicer), the other looks rotated. I tried slicing the images on a different plane, stacking them on a different plane, and nothing changes. The resulting images are rotated and flipped all over the place, sometimes with very confusing x, y and z coordinates. I even tried rotating the image on the z-axis and then re-rotating individual 2D slices on the sagittal plane back but that resulted in more confusion.

Hi Sara!
I have a question after reading your post. I am dealing with converting the dcm world system coordinate to MNI space coordinate. I can get the transformation matrix of nifti to MNI by FSL FLIRT. However, I’m quite confuse about the relationship between the Dcm xyz coordinate and nifti xyz coordinate. What is the relationship between the Dcm xyz coordinate and nifti coordinate? I’m using the GE scanner by the way. Thank you so much for any help you could offer!!

Hi,
sorry for answering so late. When you have a NIfTI array [x, y, z], the z coordinate represents the number of 2D slices in the 3D image, while the z coordinate in a dicom file gives you the position of the image slice in the up-down direction (superior-inferior axis).
The x and y coordinates in a NIfTI tell you the location of a voxel in the image volume. For example, when I was converting a NIfTI file into dicom series, and I also saw this elsewhere, I applied the NIfTI affine matrix onto point (0, 0, i), where i is the index of a slice. This gives you Image Position Patient attribute of a dicom file. The (0, 0) for the x and y coordinates means you’re looking at the voxel in the center of the image in the x-y plane.
After applying the affine matrix to this point, I would get, for example (265, 365, 0). This means the center of the image is 265mm to the left and 365mm to the posterior of the scanner center in the horizontal plane. This is LPS orientation, so that means 265mm to the body’s left side and 365mm to the body’s back side.
The z coordinate would increment/decrement by the slice’s thickness. At least in my case, I found it was consistent. So essentially, if slice thickness is 2mm, incrementing the index by 1 would move me +2mm from the feet to the head.
I apologize, as it also took me a long time to figure out the relationship between the coordinates, and I was using Slicer to view the images. I might not be completely correct on my understanding, but this is what I have found when searching on the topic and experimenting a lot with code. Then I started college and forgot the details, to be completely honest. Happy holidays.

V V sob., 30. dec. 2023 ob 14:42 je oseba Xiaoying Sun via 3D Slicer Community <notifications@slicer.discoursemail.com> napisala:

Dear Sara:
Thank you so much for your articulate reply!! I’ll try to solve my problems by combining your well-explained understanding and other information I got online. Thanks again!! It’s so sweet of you! :smiling_face_with_three_hearts: :smiling_face_with_three_hearts:

If you are using FSL FLIRT, the post below might also be helpful. Slicer and FSL think about transformation matrices and coordinate systems differently, and you can’t just use an FSL transformation matrix in Slicer and expect it to do what you want. It took me a while to figure out the exact relationship between them, but I hope this can be helpful to you:

1 Like