dicom nrrd nifti spacing, origin mismatch.

Hello I am working with dicom and nrrd image file formats. Usually nrrd files are segmentations and in different spacing and origin compared to the dicoms. So my workflow is:

  1. resample the nrrd file on dicom to match dicom’s spacing and origin and other image properties.
  2. Then perform some morphological operation on the resampled nrrd file.
  3. convert both dicom and resampled nrrd to nifti format maintaining the properties.

For these 3 steps I am using the following:

reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames(dcmCasePath)
reader.SetFileNames(dicom_names)
dicom = reader.Execute() 

nrrd = sitk.ReadImage(nrrdFilePath)

use the following function to resample nrrd on dicom:

def Realign(self, image, reference_image, def_value=0.0):
        """
        :param image: image to resample or align(.nrrd files)
        :param reference_image: .dcm files
        :param def_value: values to fill the resampled image
        :return: aligned image that matches reference image origin, spacing etc.
        """
        return sitk.Resample(image,
                             reference_image,
                             sitk.Transform(),
                             sitk.sitkLinear,
                             def_value,
                             reference_image.GetPixelID())
data = [dicom, nrrd]
alignedNrd= Realign(data[1], data[0])

Then I perform some morphological operations on NumPy-based aligned image.

SegBN = np.zeros_like(sitk.GetArrayFromImage(alignedNrd))
SegBN[np.where(sitk.GetArrayFromImage(alignedNrd) > 0)] = 1

Finally saving the image in nifti:

nrd2niiPath = os.path.splitext(nrdpath)[0]+'_{}'.format(ig)+'.nii.gz'
sitk.WriteImage(sitk.GetImageFromArray(SegBN), nrd2niiPath)
dcm2niiPath = os.path.join(dcmPath, os.path.basename(dcmPath)) +'.nii.gz'
sitk.WriteImage(dicom, dcm2niiPath)

But the spacing and origin in the nifti images are totally different. Despite the nrrd image was resampled in reference to the dicom.

To check:

dcmimage.GetSpacing(), alignedNrrd.GetSpacing()
((0.3613280058, 0.3613280058, 0.625), (0.3613280058, 0.3613280058, 0.625))

dcmimage.GetOrigin(), alignedNrrd.GetOrigin()
((-69.19999695, -109.8000031, -240.375),
 (-69.19999695, -109.8000031, -240.375))

dcmimage.GetSize(), alignedNrrd.GetSize()
((512, 512, 224), (512, 512, 224))

imgnb.get_affine(), segnb.get_affine()
(array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]),
 array([[-1.,  0.,  0., -0.],
        [ 0., -1.,  0., -0.],
        [ 0.,  0.,  1.,  0.],
        [ 0.,  0.,  0.,  1.]]))

As it can be seen the nifti formats are in different spacings.
To visualize the nifti image and segmentation separately in ITKsnap:


For some reason, the segmentation is isotropic and completely in different spacing and origin, despite the image size are the same.

What is happening after the morphological operation or saving niftis that changes the image origin, spacing and all?

Converting your image to NumPy arrays loses all the metadata information.

When you create SegBN, you lose image metadata. Try this:

SegBN = sitk.GetArrayFromImage(alignedNrd)
SegBN[np.where(sitk.GetArrayFromImage(alignedNrd) > 0)] = 1

It didn’t solve the spacing problem.

>> imgnb.affine, segnb.affine
(array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]),
 array([[-1.,  0.,  0., -0.],
        [ 0., -1.,  0., -0.],
        [ 0.,  0.,  1.,  0.],
        [ 0.,  0.,  0.,  1.]]))

Additionally, I can’t change the morphological operations. I have to create a binary mask, and the one you suggested creates a map with 3 values(-1, 0, 1).

So are there ways to perform morphological operations without converting resampled nrrd images to NumPy arrays?

Perhaps than copy metadata after you create the segmentation:

SegITK = sitk.GetImageFromArray(SegBN)
SegITK.CopyInformation(alignedNrd)
...
sitk.WriteImage(SegITK, nrd2niiPath)
1 Like

It looks like you’re doing a binary threshold on alignedNrd, right?

If so, you should be able to do something like this:

SegBN = (alignedNrd > 0)

This does the same thing in SimpleITK, without having to go to NumPy.

1 Like

This solution worked perfectly:

(array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]),
 array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]))

Also shows same properties in ITKsnap. Didn’t know ITKsnap changes the spacing for visualization.
Thanks!

1 Like

This also works perfectly.

>> imgnb.affine, segnb2.affine
(array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]),
 array([[  -0.36132801,    0.        ,    0.        ,   69.19999695],
        [   0.        ,   -0.36132801,    0.        ,  109.80000305],
        [   0.        ,    0.        ,    0.625     , -240.375     ],
        [   0.        ,    0.        ,    0.        ,    1.        ]]))

Thanks!

1 Like

Hello @dzenanz
When I try to write the Binary SegITK image as nifti file, I get the following error:

sitk.WriteImage(SegItk, nrd2niiPath)
  File "/home/banikr2/miniconda3/envs/carotid36/lib/python3.6/site-packages/SimpleITK/extra.py", line 394, in WriteImage
    return writer.Execute(image)
  File "/home/banikr2/miniconda3/envs/carotid36/lib/python3.6/site-packages/SimpleITK/SimpleITK.py", line 5518, in Execute
    return _SimpleITK.ImageFileWriter_Execute(self, *args)
TypeError: Wrong number or type of arguments for overloaded function 'ImageFileWriter_Execute'.
  Possible C/C++ prototypes are:
    itk::simple::ImageFileWriter::Execute(itk::simple::Image const &)
    itk::simple::ImageFileWriter::Execute(itk::simple::Image const &,std::string const &,bool,int)

Any idea how to solve it?