I am working on resampling and interpolating some CT scans to have isotropic voxel spacing ([1, 1, 1]). I have tried two approaches that both execute without error, but I am not sure why the first approach looks odd when viewed in Slicer.
The first approach uses the original CT as a reference image but overwrites to isotropic spacing. This approach keeps the same size (e.g. 512x512x134).
While the second approach derives a new size based on the desired spacing, such that the resampled image has the same volume in physical space (mm).
import SimpleITK as sitk
file = "example.nrrd"
img = sitk.ReadImage(file)
## First Approach (keep the same size)
rs = sitk.ResampleImageFilter()
rs.SetReferenceImage(img)
rs.SetOutputSpacing([1, 1, 1])
img2 = rs.Execute(img)
sitk.WriteImage(img2, "imgSameSize.nrrd")
I’m hoping that someone here could offer insight into whether I have made any mistakes in either approach? I come from the R world so I apologize if my code is not adhering to python best practices.
The first approach creates a bigger image, creating gray bars (default value 0) beyond the physical extent of the original image. To make it look less weird, you could specify default value of -1000 in order to get black bars around. The second approach seems correct.
Adding rs.SetDefaultPixelValue(-1000) to the first approach didn’t seem to fix the “gray bar” issue; I’m not sure why. Also, the coronal and sagittal reconstructions are cropped, which makes me think there are other issues with this first approach. Any idea why this might occur?
The first approach is telling the resmapler to use the original image’s size and the new spacing. As a result the resampling grid does not cover the same physical extent as the original image. You need to compute a new output size when you have a new output spacing in order to cover the same physical region.
Thanks both of you for the responses. This is very helpful.
You need to compute a new output size when you have a new output spacing in order to cover the same physical region.
So it seems like I have done this correctly in the second approach. It looks like my code (albeit less concise) is doing the same thing as the make_isotropic() function.
When we resample images, we usually determine the fill color (that will be passed to the image resampler to fill the empty regions) as the median value of the 8 corner values of the volume.
I guess you need this resampling to normalize your input data for deep learning. If this is the case then I would recommend to have a look at torchio and monai, as they may already have robust, well-tested, and optimized implementation of all the data normalization and augmentation steps you plan to perform. Torchio uses ITK internally, maybe monai, too.
import torchio as tio
image = tio.ScalarImage("example.nrrd")
resample = tio.Resample() # default is 1 mm isotropic
resampled = resample(image)
resampled.save("imgDiffSize.nrrd")
As @lassoan said, TorchIO uses SimpleITK under the hood for this type of operations.
Thanks for the input, @fepegar. I’ve actually spent most of today learning and playing around with torchio and I have found it extremely user-friendly and powerful. I am very glad @lassoan brought it to my attention.