Updating segmentationNode with oversampled labelmap

Hello,

I’m trying to do the following:
(1) oversample a labelmap
(2) modify it in the higher resolution voxel space
(3) update the segmentation node with the modified labelmap

I figured out (1) and (2), but I couldn’t find info on (3).

Any help would be greatly appreciated.

Below is a demo of what I mean:

def apply_oversampling_on_segmentationNode(segmentationNode, inputVolumeNode, oversampling_factor):
    segmentationGeometryLogic = slicer.vtkSlicerSegmentationGeometryLogic()
    if segmentationGeometryLogic.GetOversamplingFactor() != oversampling_factor:
        segmentationGeometryLogic.SetInputSegmentationNode(segmentationNode)
        segmentationGeometryLogic.SetSourceGeometryNode(inputVolumeNode)
        segmentationGeometryLogic.SetOversamplingFactor(oversampling_factor)
        segmentationGeometryLogic.CalculateOutputGeometry()
        geometryImageData = segmentationGeometryLogic.GetOutputGeometryImageData()
        geometryString = slicer.vtkSegmentationConverter.SerializeImageGeometry(geometryImageData)
        segmentationNode.GetSegmentation().SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), geometryString)
        segmentationGeometryLogic.ResampleLabelmapsInSegmentationNode()
    else:
        geometryImageData = segmentationGeometryLogic.GetOutputGeometryImageData()
    return geometryImageData

def get_dims_spacing_origin_from_vtkImageData(geometryImageData):
    dims = geometryImageData.GetDimensions()
    spacing = geometryImageData.GetSpacing()
    origin = geometryImageData.GetOrigin()
    return dims, spacing, origin

def modifySegment(segmentArray):
    # some random modification
    return np.clip(segmentArray + np.random.rand(*segmentArray.shape), 0, 1)

# (1) works
geometryImageData = apply_oversampling_on_segmentationNode(segmentationNode, inputVolumeNode, oversampling_factor=3)
dims, spacing, origin = get_dims_spacing_origin_from_vtkImageData(geometryImageData) # I can use this to get the correct closed surface representation

# (2) works
segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segment_name)
segmentArray = slicer.util.arrayFromSegmentInternalBinaryLabelmap(segmentationNode, segmentId)

newSegmentArray = modifySegment(segmentArray.copy())

# (3) how do I do this properly??
slicer.util.updateSegmentBinaryLabelmapFromArray(newSegmentArray, segmentationNode, segmentId, referenceVolumeNode=inputVolumeNode)

See a complete solution in this topic (using vtkSlicerSegmentationGeometryLogic):

Thank you for the response.

This actually looks like a repeat of my posted code: apply_oversampling_on_segmentationNode

I want to add a new modified oversampled labelmap to segmentationNode.

How can I do this properly?

Once the segmentation has the resolution that you need, I would recommend to import the new segments from a labelmap node and delete the old segment (or use Logical operators effect’s Copy method).

You could use slicer.vtkOrientedImageDataResample.ModifyImage to manipulate an existing segment, but it is very hard to do it correctly due to multiple segments sharing a labelmap and further complicated by masking settings.

I am able to get the correct resolution using segGeomLogic.ResampleLabelmapsInSegmentationNode(), but when I try to edit the segment with the new resolution (brush, erase, etc.), it seems to return to the original resolution.

How can I prevent this from happening?

Make sure the master (source) representation is binary labelmap. If it is then give us more information so that we can reproduce what your see and can tell if it is the expected behaviour or something is wrong.

If you are referring to the master representation setting as shown below, I believe I do have binary labelmap as my master representation.
image

I’m trying to do the following:

  1. Resample label maps programmatically using the code below
  2. Edit the resulting segments with segment editor (brush, erase, etc.)

Expected behavior: Labelmap geometry stays as resampled
Observed behavior: Labelmap geometry converts back to the original geometry before resampling
Test: Specifying Geometry using this button (image) results in the expected behavior

segmentationGeometryLogic = slicer.vtkSlicerSegmentationGeometryLogic()
segmentationGeometryLogic.SetInputSegmentationNode(segmentationNode)
segmentationGeometryLogic.SetSourceGeometryNode(inputVolumeNode)
segmentationGeometryLogic.SetOversamplingFactor(oversampling_factor)
segmentationGeometryLogic.CalculateOutputGeometry()
geometryImageData = segmentationGeometryLogic.GetOutputGeometryImageData()
segmentationGeometryLogic.ResampleLabelmapsInSegmentationNode()

With respect to this suggestion:

  1. I successfully created a new segment using slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmap_vtkImageData, segmentationNode, segmentName).

  2. But the stored labelmap is clipped to the effective extent. What’s the best way to retrieve the full extent numpy array from the original vtkImageData?

I would use convenience functions in slicer.util or specify reference volume for the segmentation export.

Note to myself and maybe future developers:

  1. I tried to directly work with vtkImageData without creating a new volume node, but everything worked much better if I just create a volume node for the reference geometry.

Updating labelmap:
slicer.util.updateSegmentBinaryLabelmapFromArray(segmentArray.transpose([2,1,0]), segmentationNode, segmentId, referenceVolumeNode=geometryVolumeNode)

Retrieving labelmap numpy array:
segmentArray = slicer.util.arrayFromSegmentBinaryLabelmap(segmentationNode, segmentId, referenceVolumeNode=geometryVolumeNode)

  1. For labelmaps with different resolutions, it’s much easier if I just create separate segmentationNodes.
1 Like

Thanks for sharing your experience. Indeed, it makes things simpler if the reference image geometry that is set in the segmentation is the same as the current segmentation geometry.