Upsample a mask the same way that Slicer does

Hi all,

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?

I think you could try to use Slicer console execution to do this processing. You just need to use the Slicer’s CLI module "Crop Volume

Hope it helps

Mauro

Hello @ibro45,

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
3 Likes

Hi @zivy ,

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?

Thank you!

The sitkNearestNeighbor or sitkLabelGaussian interpolators should be used on the multi-label mask not multiple binary masks.

Alternatively, if each mask is (0,id), where id it the label id, Then something as simple as a “max” filter could naively resolve the overlap.

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()

2 Likes