Create cylinder shell shaped segment

Hello, I am using the following code to create a cylinder shell, for segmentation. Running it causes the 3DSlicer to close. Any ideas on why is this happening?

    segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
    segmentationNode.CreateDefaultDisplayNodes()  # only needed for display
    segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(inputVolume)

    segmentationCylinder1 = vtk.vtkCylinderSource()
    segmentationCylinder1.SetRadius(1.02 * diameter / 2)
    segmentationCylinder1.SetHeight(len(slicer.util.arrayFromVolume(inputVolume)) * inputVolume.GetSpacing()[2] * 1.2)
    segmentationCylinder1.SetResolution(100)

    segmentationCylinder2 = vtk.vtkCylinderSource()
    segmentationCylinder2.SetRadius(0.98 * diameter / 2)
    segmentationCylinder2.SetHeight(len(slicer.util.arrayFromVolume(inputVolume)) * inputVolume.GetSpacing()[2] * 1.5)
    segmentationCylinder2.SetResolution(100)

    filter = vtk.vtkBooleanOperationPolyDataFilter()
    filter.SetOperationToDifference()
    filter.SetInputData(0, segmentationCylinder1.GetOutput());
    filter.SetInputData(1, segmentationCylinder2.GetOutput());

    filter.Update()
    segmentationNode.AddSegmentFromClosedSurfaceRepresentation(filter.GetOutput(), "Segmentation", [0.0, 1.0, 0.0])

I am suspecting it’s a problem with VTK itself, not Slicer.

1 Like

Oh well, I ended up constructing the cylinder shell using vtkRotationalExtrusionFilter, but the segmentation result was not I was expecting. I’ll post a screenshot of what I got:

I wanted to segment just inside the thin shell volume, but I got the whole cylinder instead. Any suggestions?

Unfortunately, Boolean operations in VTK do not work robustly. There have been a few alternatives developed but they are not yet in VTK master.

Probably you would need to use vktLinearExtrusionFilter instead (with appending two circles, one with clockwise and the other with counter-clockwise ordered points). But if you don’t need sharp edges then you can create a shell from a cylinder using vtkImplicitModeller:

cyl = vtk.vtkCylinderSource()
cyl.SetRadius(20.0)
cyl.SetHeight(60.0)
cyl.SetResolution(60)
cyl.CappingOff()

modeller = vtk.vtkImplicitModeller()
modeller.SetInputConnection(cyl.GetOutputPort())
modeller.SetAdjustDistance(0.20) # make sure the thickened model fits in output bounds

thickness = 5.0
contourFilter = vtk.vtkContourFilter()
contourFilter.SetInputConnection(modeller.GetOutputPort())
contourFilter.SetValue(0, thickness/2.0)
contourFilter.Update()

segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
segmentationNode.CreateDefaultDisplayNodes() 
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(contourFilter.GetOutput(), "Cylinder", [0.0, 1.0, 0.0])
1 Like

It worked. The following line was causing trouble though (the cylinder was rendered in separated strips), and I just deleted:

modeller.SetAdjustDistance(0.20)

Thanks again Andras,

Giovanni

This parameter was necessary to avoid clipping of sides when cylinder radius was 20, height 60, and thickness 5. If you have thinner wall or different aspect ratio then size adjustment may not be needed and/or you need to change other parameters from their default (e.g., resolution).

1 Like

Hello Andras, I noticed that even if I increase the cylinder resolution a lot (using .SetResolution(…)), it still seems to reach a max resolution (I get rough edges, more than two pixels in some places). I would like a finer segmentation… is that possible?

The cylinder resolution should not matter much. You need to make fine enough the resolution of the segmentation’s labelmap representation. You can click on the segmentation geometry button (next to the master volume selector) to adjust this resolution.

1 Like

I’ll place some pictures of what I am getting. I tried to set the resolution as you told, but no success. I could only downgrade the resolution, not get a finer one for the segmentation cylinder.

Here is the original volume rendered, it is smooth:

Here is the segmentation cylinder created:

Anota%C3%A7%C3%A3o%202019-10-08%20131144

And here is the resulting volume extracted using a Mask:

As you can see, it is very different from the segmentation surface and the original volume. It has lost its “resolution”.

I tried smoothing the segmentation, using the segment editor, but also no success. Any ideas?

Thanks!

You need to set the finer resolution in the segmentation before you add the cylinder segment.

1 Like

Hmm, interesting, I’ve seem to hit a bug. The steps are:

  • load a volume (DICOM);

  • create a segmentation and change the oversampling factor, of my volume as source geometry, to a value larger than 1.00 (I tried 1.25 and 1.5);

  • use the Scissors tool with a circle, to fill inside. The segmentation indeed becomes smoother than without setting the oversampling;

  • use this segmentation as a Mask, with some fill outside value. The resulting new volume doesn’t appear at all.

I repeated those steps without changing the oversampling factor, and got a resulting volume. These tests were done with Slicer 4.10.2 and Slicer 4.11.0-2019-09-01.

Do you have any error in the application log?

No. No errors. The masked volume just doesn’t appear.

I’ve found the issue and fixed it in rev28538 (available tomorrow in Preview Release).

Until then, a workaround is to create segments after you’ve updated the segmentation geometry.

1 Like

Thank you again Andras. One more question. How could I set the oversampling factor programmatically? I’ve tried this, but got no modifications to my segmentation:

    segmentationGeometryLogic = slicer.vtkSlicerSegmentationGeometryLogic()
    segmentationGeometryLogic.SetInputSegmentationNode(segmentationNode)
    segmentationGeometryLogic.SetOversamplingFactor(0.1)
    segmentationGeometryLogic.SetSourceGeometryNode(inputVolume)
    segmentationGeometryLogic.CalculateOutputGeometry()

Edit: I have placed this code just after creating my segmentation node, and before creating any segments.

Well, tried other path, I was sure it would work, but now I am kind of out of options. My code is as shown (note that despite telling to show the Segmentation Geometry Widget, it doesn’t appear):

    segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")

    # create segment editor to get access to effects
    segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
    segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
    segmentEditorWidget.show()  # just to check if everything is setup correctly
    slicer.savedWidget = segmentEditorWidget
    segmentEditorNode = slicer.vtkMRMLSegmentEditorNode()
    slicer.mrmlScene.AddNode(segmentEditorNode)
    segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)

    segmentationGeometryWidget = slicer.qMRMLSegmentationGeometryWidget()
    segmentationGeometryWidget.setMRMLScene(slicer.mrmlScene)
    segmentationGeometryWidget.setParent(segmentEditorWidget)
    segmentEditorWidget.show()  # just to check if everything is setup correctly
    segmentationGeometryWidget.setOversamplingFactor(0.05)
    segmentationGeometryWidget.setSegmentationNode(segmentationNode)
    segmentationGeometryWidget.setSourceNode(inputVolume)
    segmentationGeometryWidget.setParent(segmentEditorWidget)
    segmentationGeometryWidget.setReferenceImageGeometryForSegmentationNode()
    segmentationGeometryWidget.updateGeometry()
    segmentationGeometryWidget.update()

I have called a lot of methods, in the hope they would apply the new Oversampling I wanted to test.

You never call show() for segmentationGeometryWidget, so it is not displayed. Anyway, you only need qMRMLSegmentationGeometryWidget if you want users to interactively change parameters on a GUI. If you just want to change the geometry from a script (without user interaction) then you can use vtkSlicerSegmentationGeometryLogic class instead.

My bad, indeed it was supposed to be a call to show the Geometry Widget… copy, paste and some tiredness I guess. I thought I was supposed to use the Geometry Widget because I am using the Segment Editor Widget to apply some effects, so I went the same route.

Despite that, my first solution, just above my last answer was what you told. The code snippet is:

    # changing the segmentation resolution
    segmentationGeometryLogic = slicer.vtkSlicerSegmentationGeometryLogic()
    segmentationGeometryLogic.SetInputSegmentationNode(segmentationNode)
    segmentationGeometryLogic.SetOversamplingFactor(0.1)
    segmentationGeometryLogic.SetSourceGeometryNode(inputVolume)
    segmentationGeometryLogic.CalculateOutputGeometry()

This code is inserted before any segments are created, but it doesn’t take effect on my segments added later in the code. I don’t know what else I could set to make it work. I tried to find some example script and searched the forum, but nothing, so I am kinda lost on this matter.

You calculated the output geometry but did not set it in the segmentation. You can do it like this:

segmentationGeometryLogic = slicer.vtkSlicerSegmentationGeometryLogic()
segmentationGeometryLogic.SetInputSegmentationNode(segmentationNode)
segmentationGeometryLogic.SetSourceGeometryNode(segmentationNode)
segmentationGeometryLogic.SetUserSpacing([0.5,0.5,0.5])
segmentationGeometryLogic.CalculateOutputGeometry()
geometryImageData = segmentationGeometryLogic.GetOutputGeometryImageData()
geometryString = slicer.vtkSegmentationConverter.SerializeImageGeometry(geometryImageData)
segmentationNode.GetSegmentation().SetConversionParameter(
    slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), geometryString)

If you just want to prescribe some fixed spacing then you can do it even simpler, just setting the reference image geometry directly. A complete example:

# Create segmentation node

segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
segmentationNode.CreateDefaultDisplayNodes() 

# Set segmentation resolution (for closed surface to binary labelmap conversion)
geometryImageData = slicer.vtkOrientedImageData()
geometryImageData.SetSpacing(0.15, 0.15, 0.15)
geometryString = slicer.vtkSegmentationConverter.SerializeImageGeometry(geometryImageData)
segmentationNode.GetSegmentation().SetConversionParameter(
    slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), geometryString)

# Add segment from polydata

cyl = vtk.vtkCylinderSource()
cyl.SetRadius(20.0)
cyl.SetHeight(60.0)
cyl.SetResolution(60)
cyl.CappingOff()

modeller = vtk.vtkImplicitModeller()
modeller.SetInputConnection(cyl.GetOutputPort())
modeller.SetAdjustDistance(0.20) # make sure the thickened model fits in output bounds

thickness = 5.0
contourFilter = vtk.vtkContourFilter()
contourFilter.SetInputConnection(modeller.GetOutputPort())
contourFilter.SetValue(0, thickness/2.0)
contourFilter.Update()

segmentationNode.AddSegmentFromClosedSurfaceRepresentation(contourFilter.GetOutput(), "Cylinder", [0.0, 1.0, 0.0])
1 Like

It worked Andras. It would be difficult to find this out on my own at my current stage of understanding about the Slicer and VTK. Thank you!

1 Like

Segmentation internal labelmap representation geometry is unfortunately quite complicated. We are still improving the API to make common tasks easier to do. For example setting the segment resolution should be achievable by a single line of code (instead of the current 4 lines).

2 Likes