Voxelization of mesh

voxelization
mesh

(Junxi Zhu) #1

Is there a way in 3D Slicer to voxelize a 3D mesh (preferably through python interpreter)? I would also like to be able to specify the voxel size and shape, if possible. BTW, my complete mesh consists of 100+ separate .vtk mesh files, so is there a way to merge all these separate meshes into one single mesh (preferably through python interpreter) in order to do the voxelization? Thank you for your time!


(Csaba Pinter) #2

Yes you can do this through the Segmentations infrastructure. What you need to do is similar to this
https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Create_a_segmentation_from_a_labelmap_volume_and_display_in_3D
but the opposite way: going from closed surface to labelmap. So instead of ImportLabelmapToSegmentationNode you need to use ImportModelToSegmentationNode, and instead of CreateClosedSurfaceRepresentation you need to call CreateBinaryLabelmapRepresentation. You can load the 100+ vtk files as 100+ models in Slicer, call the ImportModelToSegmentationNode 100+ times on the same segmentation. Then you can convert them together with the CreateBinaryLabelmapRepresentation function. Finally, you can merge them together using a Segment Editor effect called Logical operators (refer to this one for help: https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#How_to_run_segment_editor_effects_from_a_script).

To specify the voxel size, you can set the conversion parameter before doing the conversion (the CreateBinary…call). You can do it like this: https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationGeometryWidget.cxx#L445-L448 . However, for this you’ll need to know the geometry exactly. I suggest you try using the segmentation geometry widget and see how it works (Segment Editor box icon in the row of the Master volume).


(Junxi Zhu) #3

Hi Csaba,

Thank you for the quick response. I still have a few questions:

  1. Could you please tell me where ImportModelToSegmentationNode is? I could not seem to find it under slicer.logic(). And how do I check what are the conversion parameters (names and their default value) in python?
  2. Do I need to create an empty volume and set it as the master volume in segmentation node before calling CreateBinaryLabelmapRepresentation function or using the editor effect? If so, how should I do it in python?
  3. How do I extract the numpy array from the resulting labelmap/voxel representation?

Thank you for your time!


(Csaba Pinter) #4
  1. In slicer.modules.segmentations.logic() (as I said it’s to replace the ImportLabelmapToSegmentationNode call in the snippet I linked to). It’s important to set the master representation to closed surface before you do the import so that you can do the conversion yourself (vtkMRMLSegmentationNode::SetMasterRepresentationToClosedSurface)
  2. Not necessarily. All you need is the conversion parameter which is a string. It is easier to set it if you have an actual volume, but not necessary. The conversion parameter describes volume geometry, from which the position and dimensions components are not necessary (as it will be calculated for each segment), only the spacing and directions. So for example if you want a labelmap which is aligned to the RAS directions and has 1x1x1.3mm spacing, then this will be your conversion parameter: ‘1;0;0;0;0;1;0;0;0;0;1.3;0;0;0;0;1;0;0;0;0;0;0;’ (direction matrix first row then second, third, fourth, then extent xMin, xMax etc - but you only need the spacing information, that’s why everything else is 0)
  3. Why do you need it? There are probably already modules in Slicer that can calculate what you want.

(Junxi Zhu) #5

Thank you for the reply. For the third point, I would like to save the resulting labelmap (i.e., the voxelized result) into a numpy array so that I could perform other operations in python (not necessarily in slicer). Am I going to get a labelmapNode after doing the logical union operation (to put together all the separate meshes)?


(Andras Lasso) #6

In Slicer you have access to numpy, SimpleITK, VTK, etc. and can use Slicer directly from a Jupyter notebook kernel, etc. so you should be able to do all processing there (let us know if there are any processing operations that you don’t find).

Anyway, if you must process the data outside of Slicer then you can save the labelmap it in a standard medical image volume file format, which stores all essential metadata (image origin, spacing, axis directions). We most commonly use nrrd for simple images but mha (metaimage) or nii (nifti) work, too. All these file formats have several file reader/writer implementations in Python.


(Junxi Zhu) #7

Thank you Andras for your reply!


(Junxi Zhu) #8

Hi Csaba,

I am having some problems with applying the editor effects. Below is my current code:

def voxelizeMeshes():
    # Close current scene
    slicer.mrmlScene.Clear(0)
    
    # Load files
    dataDirectory = dirname + 'brainVesselMeshes/'
    filenameList = glob.glob(dataDirectory + '*.vtk')
    nodeNameList = loadFiles(filenameList)

    nodeCollection = slicer.mrmlScene.GetNodesByClass('vtkMRMLModelNode')
    nodeNameList = [node.GetName() for node in list(nodeCollection) if 'SW' in node.GetName()]
    
    # Create empy volume
    masterVolumeNode = mt.createEmptyVolume([500, 500, 500], [0.1, 0.1, 0.1], 'volume')

    segmentationNodeName = 'seg'
    segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode", segmentationNodeName)
    segmentationNode.CreateDefaultDisplayNodes() # only needed for display
    segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)
   
    
    # Import model to segmentation
    for nodeName in nodeNameList:
        modelNode = slicer.util.getNode(nodeName)
        slicer.modules.segmentations.logic().ImportModelToSegmentationNode(modelNode, segmentationNode)
    
    segmentationNode.CreateBinaryLabelmapRepresentation()
    
    # Create segment editor to get access to effects
    segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
    segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
    segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
    segmentEditorWidget.setSegmentationNode(segmentationNode)
    segmentEditorWidget.setMasterVolumeNode(masterVolumeNode)

    # Logical operators
    segmentEditorWidget.setActiveEffectByName("Logical operators")
    effect = segmentEditorWidget.activeEffect()
    effect.setParameter(......)
    effect.self().onApply()

Then I am not sure how should I programmatically add all the meshes together. I tried to add two segments together in the GUI, but nothing seems to happen after I clicked the ‘Apply’ button (i.e., I don’t see any new nodes containing the results). Could you help me with that? Thank you for your time!


(Csaba Pinter) #9

Look at this function https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Segmentations/EditorEffects/Python/SegmentEditorLogicalEffect.py#L105

What you’ll need to do is set the parameters as they are used here. For example

effect.setParameter('Operation', 'UNION')

By the way in your code you use the variable segmentEditorNode without defining it so that will be an error.


(Junxi Zhu) #10

Hi Csaba,

I took a look at the file you referred to and I see that I can specify the modifier segment by

effect.setParameter("ModifierSegmentID", modifierSegmentIDs)

From the comment on Line 154, modifierSegmentIDs is a semicolon-separated list. However, I could not choose more than one modifier segment within the GUI. Does that mean I have to add segment2 to segment1, segment3 to segment1… and keep repeating this process until all the segments are merged together?

I am also wondering how to specify the selectedSegmentID. In the file that you referred to, it is set by self.scriptedEffect.parameterSetNode().GetSelectedSegmentID(). But I would like to know how I can set it in my code.

And how do I get the merged result, i.e., the labelmap, out of the segmentation node?

Thank you for your time!


(Junxi Zhu) #11

Hi Andras,

I took a look at the file Csaba referred to and I see that I can specify the modifier segment by

effect.setParameter("ModifierSegmentID", modifierSegmentIDs)

From the comment on Line 154, modifierSegmentIDs is a semicolon-separated list. However, I could not choose more than one modifier segment within the GUI. Does that mean I have to add segment2 to segment1, segment3 to segment1… and keep repeating this process until all the segments are merged together?

I am also wondering how to specify the selectedSegmentID. In the file that you referred to, it is set by self.scriptedEffect.parameterSetNode().GetSelectedSegmentID(). But I would like to know how I can set it in my code.

And how do I get the merged result, i.e., the labelmap, out of the segmentation node?

Since Csaba did not reply to my post, I am wondering if you could help me with this. Thank you for your time!