How to Create of Surface Meshes from Parallel Polygons using VTK and Python

Hello
Dear Developers and Users

I have several polygons that are parallel to each other and spaced at certain intervals. These are of type “model” and their mesh type is Surface Mesh (vtkPolyData). This data has been placed along with CT in [Google Drive]. An image of these models is shown below.

It is obvious that the Number of Points, Number of Cells, and Volume may vary between them. Now I want to create a new Surface Mesh from these Surface Meshes in a way that its boundaries consist of the initial and final polygons of this set, and its body is composed of these polygons.

Can this be implemented using the available functions in VTK and in Python?

Please guide me,
Best regards.
Shahrokh

My first try would be to write a Python script that does the following:

  • Copy the contents of the model nodes in one vtkPolyData using vtkAppendPolyDataFilter
  • Create a new segmentation node, and set the source representation to ‘Planar contour’ (Slicer/Libs/vtkSegmentationCore/vtkSegmentationConverter.h at main · Slicer/Slicer · GitHub)
  • Set the appended polydata as the Planar contour representation of the segmentation node
  • Call GetClosedSurfaceRepresentation on the segmentation node. Then the underlying conversion rule will do this interpolation for you

Note that you will need the SlicerRT extension to be installed first, because the mentioned conversion rule is provided there.

It is probably useful to check out the third figure in the page Image Segmentation — 3D Slicer documentation. You will see that this type of representation is often the original input when you work with radiation therapy data. This interpolator has been stable for quite a while now, so you should be able to use it safely.

Dear Csaba Pinter

Thanks a lot for your guidance completely…

I’ve completed up to the second step of thinking, and I wasn’t successful in implementing the third step… Hopefully, I’ll be able to continue the work next week…

The following lines are my code at this point.

import vtk
import slicer
import vtkSegmentationCorePython

nodeNames = ["Model_-407.5", "Model_-405.0", "Model_-400.0", "Model_-395.0", "Model_-390.0", "Model_-385.0", "Model_-380.0", "Model_-375.0", "Model_-372.5"]

appendFilter = vtk.vtkAppendPolyData()

for nodeName in nodeNames:
    node = slicer.util.getNode(nodeName)
    if node:
        # Get the vtkPolyData from the node
        polyData = node.GetPolyData()
        if polyData:
            # Add the polyData to the appendFilter
            appendFilter.AddInputData(polyData)

appendFilter.Update()

outputPolyData = appendFilter.GetOutput()

newModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "outputPolyData")

newModelNode.SetAndObservePolyData(outputPolyData)

segmentationConverter = vtkSegmentationCorePython.vtkSegmentationConverter()
planarContourName = segmentationConverter.GetSegmentationPlanarContourRepresentationName()
print("Planar contour representation name:", planarContourName)

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

Best regaeds.
Shahrokh

First, I must thank Dear Csaba Pinter for the guidance provided in solving the issue I raised. In implementing this guidance, I encounter two issues:

First Issue:

Executing the above commands in the Python Console in 3DSlicer (repeated below) creates a node named “Segmentation”.

import vtk
import slicer
import vtkSegmentationCorePython

nodeNames = ["Model_-407.5", "Model_-405.0", "Model_-400.0", "Model_-395.0", "Model_-390.0", "Model_-385.0", "Model_-380.0", "Model_-375.0", "Model_-372.5"]

appendFilter = vtk.vtkAppendPolyData()

for nodeName in nodeNames:
    node = slicer.util.getNode(nodeName)
    if node:
        # Get the vtkPolyData from the node
        polyData = node.GetPolyData()
        if polyData:
            # Add the polyData to the appendFilter
            appendFilter.AddInputData(polyData)

appendFilter.Update()

outputPolyData = appendFilter.GetOutput()

newModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "outputPolyData")

newModelNode.SetAndObservePolyData(outputPolyData)

segmentationConverter = vtkSegmentationCorePython.vtkSegmentationConverter()
planarContourName = segmentationConverter.GetSegmentationPlanarContourRepresentationName()
print("Planar contour representation name:", planarContourName)

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

As seen in the following figure, its Representation type is Binary labelmap, not Planar contour. I expected that by executing the above commands, the Representation type would be Planar contour.

How can I set the Representation type to Planar contour according to the provided guidance by Csaba Pinter?

Second Issue:
In implementing the third step, Csaba Pinter told that:
Set the appended polydata as the Planar contour representation of the segmentation node…

I have a problem. I can do this by setting Planar contour and importing outputPolyData in Segmentations module (not in Python Console of 3DSlicer), but I encounter the following error. How can I perform this third step?

Please guide me to do step #2 and #3.
Best regards.
Shahrokh

The code seems so far so good, but not complete. As you said you’ll need to set the proper source representation, and then the representation itself. By the way I suggest using the latest Slicer, 5.2.2 is now years old, and the API has changed a bit since then too (e.g. “master” vs “source” representation).

segmentationNode.GetSegmentation().SetSourceRepresentationName(planarContourName)
seg = slicer.vtkSegment()
seg.SetName('NameYouWant')
seg.AddRepresentation(planarContourName, newModelNode.GetPolyData())
segmentationNode.GetSegmentation().AddSegment(seg)

A few comments:

  • You don’t need import vtkSegmentationCorePython, you can simply do slicer.vtkSegmentationConverter. Also, you don’t need to instantiate it, so just slicer.vtkSegmentationConverter.GetSegmentationPlanarContourRepresentationName()
  • You don’t need to create a new model node. Just have the poly data. So in the above snippet, you can replace newModelNode.GetPolyData() with appendFilter.GetOutput()

Thank you very much to Csaba Pinter for the guidance in implementing the code. As you mentioned, I made the changes in the code as follows:

import vtk
import slicer

# Step #1
nodeNames = ["Model_-407.5", "Model_-405.0", "Model_-400.0", "Model_-395.0", "Model_-390.0", "Model_-385.0", "Model_-380.0", "Model_-375.0", "Model_-372.5"]

appendFilter = vtk.vtkAppendPolyData()

for nodeName in nodeNames:
    node = slicer.util.getNode(nodeName)
    if node:
        # Get the vtkPolyData from the node
        polyData = node.GetPolyData()
        if polyData:
            # Add the polyData to the appendFilter
            appendFilter.AddInputData(polyData)

appendFilter.Update()

outputPolyData = appendFilter.GetOutput()

newModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "outputPolyData")

newModelNode.SetAndObservePolyData(outputPolyData)

# Step #2
segmentationConverter = slicer.vtkSegmentationConverter()
planarContourName = slicer.vtkSegmentationConverter.GetSegmentationPlanarContourRepresentationName()
segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")

# Step #3 (the guidance of Csaba Pinter)
segmentationNode.GetSegmentation().SetSourceRepresentationName(planarContourName)
seg = slicer.vtkSegment()
seg.SetName('bolusSegment')
seg.AddRepresentation(planarContourName, appendFilter.GetOutput())
segmentationNode.GetSegmentation().AddSegment(seg)

Now, in the implementation of the fourth step, I encountered the following error:

# Step #4
id = segmentationNode.GetID()
segmentationNode.GetClosedSurfaceRepresentation(id, outputPolyData)

The output I obtained by executing the last command is as follows:

False
[VTK] GetClosedSurfaceRepresentation: Invalid segment

Unfortunately, I don’t understand why I encountered this error. I even used the CreateClosedSurfaceRepresentation() method because it was explained in relation to GetClosedSurfaceRepresentation that:

“Get a segment as a surface mesh. If representation does not exist yet then CreateClosedSurfaceRepresentation() must be called before this method. This function returns a copy of the segment closed surface in outputClosedSurface. Returns true on success.”

Please guide me.
Best regards.
Shahrokh

As mentioned in the help on built-in function GetClosedSurfaceRepresentation:

GetClosedSurfaceRepresentation(...) method of MRMLCorePython.vtkMRMLSegmentationNode instance
GetClosedSurfaceRepresentation(self, segmentId:str,
outputClosedSurface:vtkPolyData) -> bool
C++: virtual bool GetClosedSurfaceRepresentation(
const std::string segmentId, vtkPolyData *outputClosedSurface)
Get a segment as a surface mesh. If representation does not exist
yet then CreateClosedSurfaceRepresentation() must be called
before this method. This function returns a copy of the segment
closed surface in outputClosedSurface. Returns true on success.

As mentioned above, If the representation does not exist, the function CreateClosedSurfaceRepresentation must be called before using this method. Excuse me, but I didn’t understand why Csaba Pinter mentioned that the representation type should be Planar contour in guidance. Anyway, I continued my efforts according to this help (although I believe this is not correct):

>>> segmentationNode.CreateClosedSurfaceRepresentation()
True

Then , I call GetClosedSurfaceRepresentation on the segmentation node.

>>> segmentationNode.GetClosedSurfaceRepresentation(segmentationNode.GetID(), outputPolyData)
False
[VTK] GetClosedSurfaceRepresentation: Invalid segment
>>> 

As mentioned above, the problem is related to the first argument of the CreateClosedSurfaceRepresentation method.
Excuse me… How can I set the two arguments of GetClosedSurfaceRepresentation so that it creates and covers all contours of my models (Model_-407.5, Model_-405.0, ..., Model_-372.5) with a solid model?

Please guide me.
Best regards.
Shahrokh

During my attempt to call GetClosedSurfaceRepresentation, I encountered an interesting coincidence. Upon executing the following codes, when I run the commands the section of Step #4, 3DSlicer suddenly crashes and closes.

import vtk
import slicer

# Step #1
nodeNames = ["Model_-407.5", "Model_-405.0", "Model_-400.0", "Model_-395.0", "Model_-390.0", "Model_-385.0", "Model_-380.0", "Model_-375.0", "Model_-372.5"]

appendFilter = vtk.vtkAppendPolyData()

for nodeName in nodeNames:
    node = slicer.util.getNode(nodeName)
    if node:
        # Get the vtkPolyData from the node
        polyData = node.GetPolyData()
        if polyData:
            # Add the polyData to the appendFilter
            appendFilter.AddInputData(polyData)

appendFilter.Update()

outputPolyData = appendFilter.GetOutput()

newModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", "outputPolyData")

newModelNode.SetAndObservePolyData(outputPolyData)

# Step #2
segmentationConverter = slicer.vtkSegmentationConverter()
planarContourName = slicer.vtkSegmentationConverter.GetSegmentationPlanarContourRepresentationName()
segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")

# Step #3 (the guidance of Csaba Pinter)
segmentationNode.GetSegmentation().SetSourceRepresentationName(planarContourName)
seg = slicer.vtkSegment()
seg.SetName('bolusSegment')
seg.AddRepresentation(planarContourName, appendFilter.GetOutput())
segmentationNode.GetSegmentation().AddSegment(seg)


# Step #4
segmentation_node = slicer.util.getNode('Segmentation')
segment_id = segmentation_node.GetSegmentation().GetSegmentIdBySegmentName('bolusSegment')
segmentation_node.CreateClosedSurfaceRepresentation()
segmentation_node.GetClosedSurfaceRepresentation(segment_id, outputPolyData)

I think that I am near to implementing of the fourth step (with the minor error)
Please guide me to solve it.
Thanks a lot.
Best regards.
Shahrokh

I’ve implemented something like this for OsiriX ROI importer module. You might find the code useful: SlicerSandbox/ImportOsirixROI/ImportOsirixROI.py at master · PerkLab/SlicerSandbox · GitHub