I’m having some trouble using the Model Maker module. I’m trying to create a surface model from a binary labelmap mask of a brain (see screenshot01). It appears to work correctly but when I check the number of cells in the Model module (screenshot02) it is different to the debugging output of Model Maker (screenshot01). Moreover, if I save and reload the same model in STL format I get the correct number of cells (see screenshot04). This is not the case if I save and reload using VTK format (see screenshot03).
Can anyone explain what might be happening here?
I need to use the model generated by Model Maker as the input to another module that we are developing (SlicerCBM/SurfaceTriangulation at master · SlicerCBM/SlicerCBM · GitHub). Our module fails if the model is used directly (probably has something to do with converting VTK to PyVista mesh data type) but if the model is saved first as STL and reloaded everything works as expected.
It appears to me that this might be a bug in 3D Slicer. The model generated by Model Maker has 146930 triangles, but in the Models module, the number of cells is shown as 21879. If I save and reload the model using the default VTK format, the Models module still shows the number of cells as 21879. However, If I save and reload the same model using STL format, the Models module shows the correct number of 146930 cells. The number of points from both files is the same. Can anyone confirm this behavior?
The triangles and their points are merged or duplicated as needed, so it is normal that their numbers change. For example, when you compute surface normals then you may want to merge some points but to preserve sharp edges you need to duplicate points. STL is a very limited file format, it can only store individual triangles, so when you save into this format then your mesh may need to be converted to an all-triangle mesh.
Recommendations:
If you want to save data without changes then you need to use a more capable format than STL. VTK and VTP can store a VTK mesh without any modifications and including point and cell arrays, such as surface normals.
Use Segmentations module for converting between labelmaps and models. It has many advantages over the legacy Model maker module.
Note that you do not need to add a new module for ACVD triangulation. Uniform remeshing by ACVD is already available in Slicer core in Surface Toolbox module:
I agree, we developed the module before ACVD remeshing was added to Slicer. However, if I try to use the model produced by Model Maker directly or by saving and reloading as VTK format I get this error:
[Python] Failed to compute output model: Clean tolerance is too high. Empty mesh returned.
Traceback (most recent call last):
File "/opt/slicer/Slicer-5.2.2-linux-amd64/bin/../lib/Slicer-5.2/qt-scripted-modules/SurfaceToolbox.py", line 278, in onApplyButton
self.logic.applyFilters(self._parameterNode)
File "/opt/slicer/Slicer-5.2.2-linux-amd64/bin/../lib/Slicer-5.2/qt-scripted-modules/SurfaceToolbox.py", line 576, in applyFilters
SurfaceToolboxLogic.remesh(outputModel, outputModel,
File "/opt/slicer/Slicer-5.2.2-linux-amd64/bin/../lib/Slicer-5.2/qt-scripted-modules/SurfaceToolbox.py", line 412, in remesh
clus.subdivide(subdivide)
File "/opt/slicer/Slicer-5.2.2-linux-amd64/lib/Python/lib/python3.9/site-packages/pyacvd/clustering.py", line 80, in subdivide
self.mesh.copy_from(_subdivide(self.mesh, nsub))
File "/opt/slicer/Slicer-5.2.2-linux-amd64/lib/Python/lib/python3.9/site-packages/pyacvd/clustering.py", line 354, in _subdivide
sub_mesh.clean(inplace=True)
File "/opt/slicer/Slicer-5.2.2-linux-amd64/lib/Python/lib/python3.9/site-packages/pyvista/core/filters/poly_data.py", line 1847, in clean
raise ValueError('Clean tolerance is too high. Empty mesh returned.')
ValueError: Clean tolerance is too high. Empty mesh returned.
If I change some of the parameters I get a result that looks like this:
I don’t remember why we used Model Maker instead of the Segmentations module. I think it was because some of the parameters, such as the amount of smoothing, could be adjusted.
Thank you, we will try using this instead. What are the main advantages of using the Segmentations module? It is not clear to me from the documentation why Model Maker should not be used. I looks like it has more features/options than the export to model function in the Segmentations model:
All of those are also adjustable in Segmentations module. The main benefit is that the model you see in 3D viewer will be the model you will be getting out of it too. The workflow typically is import your labelmap as segmentation, adjust in the segment editor -if necessary-, and export it as a 3D model.
I don’t think it is normal in this case. To me it looks like a bug in Model Maker. If I load both the VTK and STL files in ParaView both have 73,485 points and both surfaces look identical. However, the VTK mesh has 21,879 cells (wrong), whereas the STL mesh has 146,930 cells (same as the number of cells output in Model Maker debug window).
Looking at the original VTK file it has 21,879 TRIANGLE_STRIPS. If I save as STL and then save the STL file as VTK using Slicer I get a VTK file with 146,930 POLYGONS. Using the Segmentations module instead of Model Maker I get a similar VTK file with 73,486 points and 146,932 POLYGONS.
It appears that PyACVD cannot remesh the file that has TRIANGLE_STRIPS instead of triangles defined as POLYGONS.
EDIT: It looks like this is not a bug but the intended behavior of Model Maker:
Can someone please explain why Model Maker uses triangle strips instead of triangles/polygons?
Triangle strips are much more efficient to store and render than triangle meshes, as you only need to have one point for each triangle, while in a triangle mesh you need to specify 3 points for each triangle. STL file format is very limited - it does not support triangle strip representation, therefore we convert polygons, triangle strips, etc. to triangle mesh before writing to this format.
You can convert to/from triangle strip representation anytime using vtkStripper/vtkTriangleFilter.
Thanks for the explanation @lassoan. I wasn’t aware of triangle strips. However, it seems that the Segmentations module uses triangles/polygons whereas Model Maker uses triangle strips. The “Uniform remesh” tool of the Surface Toolbox works if I create the model using the Segmentations module. If I use Model Maker, I need to first save to STL and reload, which converts the triangle strips to triangles/polygons. Otherwise, it fails with this error:
Failed to compute output model: Clean tolerance is too high. Empty mesh returned.
Thanks for the additional information. It seems that all we need to do is run the model node’s polydata through a vtkTriangleFilter (3-4 lines of code). It would be great if you could add it and if it works well for you then make the change here and submit a pull request.
Triangle stripping was critically important when the model maker was developed 15-20 years ago because every bit of performance had to be squeezed out from the graphics hardware. Using techniques such as triangle stripping and backface culling nowadays rarely lead to noticeable rendering speed difference, that’s why we don’t use them much (and can even afford to enable by default computationally expensive techniques, such as depth peeling).
What is the recommended way to develop and test a module that is already installed in Slicer? I cloned the SlicerSurfaceToolbox code and tried adding the SurfaceToolbox module in “Additional module paths” of settings, but still, the installed module is used.