Skip to content

Commit

Permalink
ENH: Make Grow from seeds effect input requirements more clear
Browse files Browse the repository at this point in the history
Grow from seeds effect requires at least 2 visible segments as input if no editable region is specified, and at least 1 visible segment if editable region is specified.
When the requirement was not fulfilled then the user did not know what was wrong, because there was no visible notification on the GUI (only in the application log),
see for example https://discourse.slicer.org/t/grow-from-seeds-does-not-work-if-painting-only-one-segment/32452/6

This commit adds display of the error in a popup window if there are not enough input segments.
A notification on the status bar is added if segmentation is canceled due to removal of an input segment.
  • Loading branch information
lassoan committed Oct 31, 2023
1 parent f622bc8 commit b050d39
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ def __init__(self, scriptedEffect):
scriptedEffect.perSegment = False
AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)

# Number of segments required when editable area is not specified
self.minimumNumberOfSegments = 1
# Number of segments required when editable area is specified
self.minimumNumberOfSegmentsWithEditableArea = 1
self.clippedMasterImageDataRequired = False
self.clippedMaskImageDataRequired = False

Expand Down Expand Up @@ -168,8 +171,9 @@ def onSegmentationModified(self, caller, event):
segment = segmentation.GetSegment(segmentID)
if not segment:
# selected segment was deleted, cancel segmentation
logging.debug("Segmentation cancelled because an input segment was deleted")
logging.debug("Segmentation operation is cancelled because an input segment was deleted")
self.onCancel()
slicer.util.showStatusMessage(_("Segmentation operation is cancelled because an input segment was deleted."), 3000)
return
segmentLabelmap = segment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
if segmentID in self.selectedSegmentModifiedTimes \
Expand Down Expand Up @@ -228,7 +232,9 @@ def observeSegmentation(self, observationEnabled):

def getPreviewNode(self):
previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
if previewNode and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name:
if (previewNode
and self.scriptedEffect.parameterDefined("SegmentationResultPreviewOwnerEffect")
and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name):
# another effect owns this preview node
return None
return previewNode
Expand Down Expand Up @@ -279,13 +285,10 @@ def onPreview(self):

slicer.util.showStatusMessage(_("Running {effectName} auto-complete...").format(effectName=self.scriptedEffect.name), 2000)
try:
# This can be a long operation - indicate it to the user
qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
self.preview()
with slicer.util.tryWithErrorDisplay(_("Segmentation operation failed:"), waitCursor=True):
self.preview()
finally:
qt.QApplication.restoreOverrideCursor()

self.previewComputationInProgress = False
self.previewComputationInProgress = False

def reset(self):
self.delayedAutoUpdateTimer.stop()
Expand Down Expand Up @@ -436,10 +439,28 @@ def preview(self):
if self.selectedSegmentIds is None:
self.selectedSegmentIds = vtk.vtkStringArray()
segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
if self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
logging.error(f"Auto-complete operation skipped: at least {self.minimumNumberOfSegments} visible segments are required")

if self.minimumNumberOfSegments != self.minimumNumberOfSegmentsWithEditableArea:
editableAreaSpecified = (
self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
or self.scriptedEffect.parameterSetNode().GetMaskMode() != slicer.vtkMRMLSegmentationNode.EditAllowedEverywhere)
if editableAreaSpecified and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegmentsWithEditableArea:
logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments are required when editable area is defined")
raise RuntimeError(
_("Minimum {minimumNumberOfSegments} visible segments are required.").format(
minimumNumberOfSegments=self.minimumNumberOfSegmentsWithEditableArea))
elif (not editableAreaSpecified) and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
logging.error(f"Auto-complete operation skipped: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments or setting of editable area is required")
raise RuntimeError(
_("Minimum {minimumNumberOfSegments} visible segments (or specification of editable area or intensity range) is required.").format(
minimumNumberOfSegments=self.minimumNumberOfSegments))
elif self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
# Same number of input segments required regardless of editable area
logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegments} visible segments are required")
self.selectedSegmentIds = None
return
raise RuntimeError(
_("Minimum {minimumNumberOfSegments} visible segments are required.").format(
minimumNumberOfSegments=self.minimumNumberOfSegments))

# Compute merged labelmap extent (effective extent slightly expanded)
if not self.mergedLabelmapGeometryImage:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self, scriptedEffect):
scriptedEffect.name = 'Grow from seeds' # no tr (don't translate it because modules find effects by name)
scriptedEffect.title = _('Grow from seeds')
self.minimumNumberOfSegments = 2
self.minimumNumberOfSegmentsWithEditableArea = 1 # if mask is specified then one input segment is sufficient
self.clippedMasterImageDataRequired = True # source volume intensities are used by this effect
self.clippedMaskImageDataRequired = True # masking is used
self.growCutFilter = None
Expand Down

0 comments on commit b050d39

Please sign in to comment.