Then, I call self.scriptedEffect.defaultModifierLabelmap(), which I thought would initialize the labelmap to the geometry of the master volume (via bool qMRMLSegmentEditorWidgetPrivate::resetModifierLabelmapToDefault()), however
the call to segmentArray = slicer.util.arrayFromSegmentInternalBinaryLabelmap(segmentationNode, currentSegmentId) fails with:
Traceback (most recent call last):
File "D:/Misc/TestProjects/SegmentationTest/SegmentationTest/SegmentationTest/Lib/SegmentEditorEffect.py", line 152, in onApply
segmentArray = slicer.util.arrayFromSegmentInternalBinaryLabelmap(segmentationNode, currentSegmentId)
File "D:\Apps\Slicer 4.11.0-2020-02-26\bin\Python\slicer\util.py", line 1201, in arrayFromSegmentInternalBinaryLabelmap
narray = vtk.util.numpy_support.vtk_to_numpy(vimage.GetPointData().GetScalars()).reshape(nshape)
File "D:\Apps\Slicer 4.11.0-2020-02-26\bin\Lib\site-packages\vtkmodules\util\numpy_support.py", line 216, in vtk_to_numpy
typ = vtk_array.GetDataType()
AttributeError: 'NoneType' object has no attribute 'GetDataType'
What do I have to do to determine whether a segment already has contents, and, if not, to correctly initialize it according to the geometry of the master volume?
I know now how the segmentation infrastructure is meant to be used. While the actual segment was still empty (and therefore caused the error), the defaultModifierLabelmap is meant as a “temporary labelmap” that can be used to store the intermediate results of an effect and has the extents of the master volume (or, at least of the ReferenceGeometryImage; however, I don’t know exactly the difference between those two yet).
So, I manipulate the modifierLabelmap and then apply it with self.scriptedEffect.modifySelectedSegmentByLabelmap(...). Easy.
However, what I found was, that the function def arrayFromSegmentInternalBinaryLabelmap(segmentationNode, segmentId)
does not return a numpy array with the same extents as the master volume, but with extents as large as the content of the segment, which confused me a bit. Moreover, an empty segment causes a crash (see first post) that could be avoided by a small check inside the method.
Having access to a defaultModifierLabelmap is a convenience feature, it is a blank image data initialized to the reference image geometry. Reference image geometry is determined automatically from the first selected master volume (or, if you created a segmentation from a model then from that).
It gives you low-level access to the internal labelmap - whatever it is. If you need simpler access in a specific volume’s geometry then you can export to a labelmap volume node.
It is not a crash but a nice and clean Python exception. It is a bit safer to raise an exception than just return an invalid value (if just an invalid value is returned then you may realize much later that something went wrong) and in the exception you can also give detailed explanation of what went wrong. In this case, the exception text is not that helpful, that should be fixed, but maybe we should also provide a bit higher level access functions to segmentations in numpy.
Sure it is an exception, sorry for the wrong terminology. Maybe I am simply used to the approach of of not using exceptions that Qt (and the rest of Slicer) follows, which I actually like a lot.
If the segment is empty, I probably would have expected to receive an empty NumPy array from this method.
Anyway, thanks for the additional information.
Exceptions are great in Python, but quite disruptive in C++ (it crashes the application if uncaught) and can easily put the application in an inconsistent state (it takes lots of extra effort to write exception-safe code, although with modern C++ it is becoming somewhat easier). So, we will keep using exceptions in Python, but I don’t think we will start using exceptions in C++ code in the near future.