How to expand a segment size via Python

Hi everyone,

I would like to create a new segment from an existing segment. The idea would be to expand the existing segment in the three directions, adding different margins to each of the directions (e.g. x = 1 mm, y = 2 mm and z = 3 mm) via Python script.

I found the margin effect of the Segment Editor module a possibility, but I think the margin for growth has to be the same in all directions. Am I wrong? Is there any workaround? Thank you in advance.

We use distance map transform for fast grow/shrink (regardless of margin size), which requires isotropic growth. However, you can use morphological operations to grow by different amount along each axis - see Margin effect’s old implementation.

What is the reason you would like to increase the margin by different amount along different axes?

Thank you for your response.

I would like to increase the margin by different amounts along different axes for radiotherapy purposes. During radiation treatments, geometrical uncertainties are taking into account by adding a margin to the target volume. And my question comes up since I want to grow a segment by a margin that is related to the 3D movement of the target volume. I was expected to use the SegmentMorphology module but the SlicerRT extension does not have this module anymore.

Is there any surrogate module for 3D slicer version 4.11?

Hi Andras,

I tried to use the Margin effect’s implementation to increase the segment margins, but the argument of the SetDilateValue function must be a real number and not a list.

erodeDilate = vtk.vtkImageDilateErode3D()

erodeDilate.SetInputData(contour.GetSegmentation().GetSegmentRepresentation(target, 'Binary labelmap'))

marginX = patient.ptvMargins[0]
marginY = patient.ptvMargins[1]
marginZ = patient.ptvMargins[2]

margins = [marginX, marginY, marginZ]
erodeDilate.SetDilateValue(margins)

What I really needed is something like the ContourMorphology module of SlicerRT extension. But this module is no longer available in the latest nightly version.

How can I get the same output? I would really appreciate your help.

Expanding a segment with anisotropic margin size using vtkImageDilateErode3D is just a few lines of code. I’ve included a link above that shows the exact code that you can use: https://github.com/Slicer/Slicer/blob/1b64b5cc4131a613271cffe7d513a1101567916f/Modules/Loadable/Segmentations/EditorEffects/Python/SegmentEditorMarginEffect.py#L142-L151

However, I would not recommend to simulate effect of motion by applying anisotropic margin growing along imaging axes, because in general the motion axis directions and magnitudes are not the same as imaging axes, and you cannot simulate rotations at all.

Instead, I would recommend to actually simulate motion. Copy a segment to a separate segmentation node, apply a small transform to that node, copy the segment back to the original segmentation node, and combine it with the existing segment using Logical operators effect. Repeat this with slightly different translations, rotations (a set of representative typical displacements).

Thanks a lot for your reply.

The margins that I want to add take into account the 3D movement of the tumour. They were calculated using the B-spline image registration method and the transform module available in 3D Slicer.

I tried to use the aforementioned code but there was no change in the segment. The parameters and the code that I am using are the following:

kernelsize = [3,3,6]
segment spacing = (0.0731959, 0.0731959, 0.0731959)

selectedSegmentLabelmap = contour.GetSegmentation().GetSegmentRepresentation(target, 'Binarylabelmap')
marginX = patient.ptvMargins[0]
marginY = patient.ptvMargins[1]
marginZ = patient.ptvMargins[2]

margins = [marginX, marginY, marginZ]
kernelSizePixel = [int(round(margins[0])), int(round(margins[1])), int(round(margins[2]))]
         
labelValue = 1
backgroundValue = 0
thresh = vtk.vtkImageThreshold()
thresh.SetInputData(selectedSegmentLabelmap)
thresh.ThresholdByLower(0)
thresh.SetInValue(backgroundValue)
thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())

erodeDilate = vtk.vtkImageDilateErode3D()
erodeDilate.SetInputConnection(thresh.GetOutputPort())

for margin in kernelSizePixel:
	if margin > 0:
		# grow
            erodeDilate.SetDilateValue(labelValue)
            erodeDilate.SetErodeValue(backgroundValue)

erodeDilate.SetKernelSize(kernelSizePixel[0],kernelSizePixel[1],kernelSizePixel[2])
erodeDilate.Update()
modifierLabelmap = vtkSegmentationCore.vtkOrientedImageData()
modifierLabelmap.DeepCopy(erodeDilate.GetOutput())

# Save modifications

name = patient.ID + "_PTV_S" + str(SSigma) + "_s" + str(Rsigma)
SegmentationNode = slicer.vtkMRMLSegmentationNode()
slicer.mrmlScene.AddNode(SegmentationNode)
SegmentationNode.SetName(name)
          
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(modifierLabelmap,SegmentationNode)

return "Created PTV with name: " + name

Am I doing something wrong? Thank you!

If you want to modify a segment then you can do this (instead of what you currently do in # Save modifications):

self.scriptedEffect.modifySelectedSegmentByLabelmap(
    modifierLabelmap,
    slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)

Thank you for your quick reply.
I have the following output: ‘FindMarginsLogic’ object has no attribute ‘scriptedEffect’

You need to run self.scriptedEffect.modifySelectedSegmentByLabelmap from inside a segment editor effect (as it is done in SegmentEditorSmoothingEffect.py). If you don’t want to put your code into a segment editor effect then you can call its methods from outside.

If you want to write a script that is independent from the Segment Editor then the simplest is to export to labelmap volume, apply the image processing operations on it, then import the modified labelmap volume into the segmentation node. I added a complete example that demonstrates this to the script repository: Script repository — 3D Slicer documentation

1 Like

Thank you a lot, it worked!
I have another question: is it not possible to expand the segment with a float margin?
As an example, I want to add these specific margins ([3,483 ; 3,494 ; 6,270] mm) but the kernel size must be integer ([3,3,6] mm).
Is there any way to apply the image processing operations with these float margins?

Thank you again for your help.

Segmentation is represented as a binary volume, so you the added margin is always a multiple of the spacing (=voxel size) of this volume. You can change segmentation’s internal volume spacing by clicking on the “Specify geometry” button next to the master volume selector.

Thank you for your help.
I ended up changing the segmentation geometry by using the referenceVolumeNode geometry with the code:

slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds,labelmapVolumeNode,referenceVolumeNode)

1 Like

Hi Andras,
I would like to write a python script that use vtk.vtkImageDilateErode3D() function to dilate my segment size independently from the Segment Editor Module of 3D Slicer.
The link you provided in this comment is no longer available, can you please tell me where I would be able to find the complete example you have cited ?

Thank you in advance.

The script repository was moved to Script repository — 3D Slicer documentation and you can find the relevant section by searching for words in the full link. I’ve updated the link in my post above to point to the new location.