Detecting which segments have been modified

I’m observing vtkSegmentation.SourceRepresentationModified and in the callback method I want to figure out which of the segments have been modified. Does anyone know a good way to do this?

I tried checking the MTimes of the vtkSegments but they don’t change when I edit. If I call

representation = segmentation.GetSourceRepresentationName()
segmentation.GetSegment(segmentID).GetRepresentation(representation).GetMTime())

then the MTime changes, but since all the segments share the same representation I can’t tell which one changed.

I’d rather not have to look into the representation and compare the data.

The event vtkSegmentation::SegmentModified is probably what you are looking for, the call data will be segment identifier.

Thanks @jcfr, I did look at that. From what is posted here that is only fired when the segment metadata is modified, not the contents. For now I’ve just disable the feature I was hoping to use.

The easiest way to check if a segment was modified is to use the segment status. It is changed from the default NotStarted to InProgress whenever the user modifies a segment.

I used this to speed up saving of nnUnetTotalSegmentator-training-data-style (single segment per file) segmentations after manual editing. I submitted a pull request that implements the complete workflow of iterating through TotalSegmentator datasets and allows the user to fix up the segmentations using nnInteractive and save the modified segments. This would have revitalized the CaseIterator a bit (and the changes to existing code were quite minimal) but the PR was not merged. This triggered the whole discussion about transferring the ownership of CaseIterator.

@pieper It would be great if you could make use of this code in your new AI training data editor extension.

1 Like

But this would not recognize a case when a segment is not modified since it is last saved (because it would be still InProgess unless the user chooses another state)?

When a segment is not modified then it does not have to be saved. It is useful for editing TotalSegmentator training data, in which each segment is stored in a separate file. Skipping saving of unchanged segments saves a lot of time when iterating through images.

If all the segments are stored in a single file then the segment state does not matter much, because then all the segments are saved into the file anyway.

Maybe this is what you want Steve:

# add a sample CT and open the segment editor 
#  and add a few segments

@vtk.calldata_type(vtk.VTK_LONG)
def myfunc(caller, eventId, callData):
  print('segment modified: ', callData)

seg = getNode('Segmentation')
seg.AddObserver(slicer.vtkSegmentation.SourceRepresentationModified, myfunc)

# modify segments and check out the printed number by myfunc

Tested on Slicer Preview

Thanks for all the help everyone.

@mau_igna_06 that looks pretty good, and I get a unique value for each segment I tried but the call data seems to be a pointer like 4048792338560069170 or 3834588781771632178 and it’s not clear now to map that to segment IDs.

@lassoan thanks for the pointer to the iterator code. It helped to see how the Status is used.

For my use case I am loading segmentations that already exist, so the Status may already be something other than NotStarted but since we aren’t using the status for anything else at this point I can set them all to NotStarted with segmentationLogic.SetSegmentStatus(segment, segmentationLogic.NotStarted) and then check the status later.

As a side note, before I learned about GetSegmentStatus I thought I would need to access the vtkSegment tags directly but I didn’t see a way to access them directly through python. It would be nice to have a method signature that wraps. Or is there another way to call it?

>>> status = ""
>>> seg.GetTag('Segmentation.Status', status)
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: GetTag argument 2:

The API is Python-wrapped, see how it is used here:

But this mutable variable is quite esoteric, so I agree it would be nice to have a simpler, more Pythonic API (and a method to get all the tag names).

Using vtk.reference() seems to do the trick too. I have no idea how it differs from vtk.mutable().

There’s a nice vtkSegment::GetTags() in C++, it does not seem to be wrapped for Python indeed.

I’m also interested in detecting a change in a segment selectively and didn’t find a way for that. I’ll dig more about @lassoan 's solution above.

For the record, here’s what I ended up doing for now:

Thanks again everyone!