For each scan, I have a mask that was segmented out on a cropped and downsampled version of that scan. Loading such a mask in Slicer automatically upsamples it and places it correctly in the physical space of the original scan.
I wish to mimic that with SimpleITK. While I did manage to convert the mask to the same size and spacing as the original scan, the mask itself is grainy. How can I interpolate the mask to achieve the same quality as Slicer does?
Which interpolator enumeration are you specifying when doing the resampling? For segmentation images you can either use sitkNearestNeighbor or sitkLabelGaussian for a smoother looking interpolation.
Another option is to use a distance map:
low_res_dist_map = sitk.SignedMaurerDistanceMap(low_res_binary_seg, squaredDistance=False, useImageSpacing=True)
high_res_distance_map = sitk.Resample(low_res_dist_map, high_res_image, interpolator = sitk.sitkLinear) # or use a higher order interpolator as desired.
high_res_binary_seg = high_res_distance_map<=0
Thanks for the answer. I’m dealing with multi-label masks so I treated each mask as a separate binary mask and did what you proposed. However, the resulting labels overlap, and I would like to avoid that as that was not the case in the original mask. Do you have an idea of how to handle it gracefully?
Thanks everyone, ended up diving into the slicer scripting for the first time and it was worth it. In case anyone’s interested:
from pathlib import Path
import slicer
import SimpleITK as sitk
def export_segmentation_with_adjusted_geometry(segmentation_path, reference_volume_path):
"""Process the given segmentation so that it matches the geometry of a reference volume.
The adjusted segmentation is exported to the same folder as the input segmentation, and
its filename is the same except for the additional `_adjusted` suffix.
"""
segmentation_path = Path(segmentation_path)
reference_volume_path = Path(reference_volume_path)
# Ensures that the processed segmentation is saved in the same folder as the original
slicer.mrmlScene.SetRootDirectory(str(segmentation_path.parent))
# Load the reference volume and the segmentation
volume = slicer.util.loadVolume(str(reference_volume_path))
segmentation = slicer.util.loadSegmentation(str(segmentation_path))
# Reference volume has to be a VTK's OrientedImageData object
volume = slicer.vtkSlicerSegmentationsModuleLogic.CreateOrientedImageDataFromVolumeNode(volume)
# Get the reference volume's geometry
volume_geometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(volume)
param = slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName()
# Resample the segmentation to the reference volume's geometry
segmentation.GetSegmentation().SetConversionParameter(param, volume_geometry)
# The destination folder is defined at the start of this function using SetRootDirectory()
filename = segmentation_path.name.split('.')[0] + "_adjusted.seg.nrrd"
slicer.util.saveNode(segmentation, filename)
slicer.mrmlScene.Clear()