Skip to content

Commit 832f8c2

Browse files
Simplify segment editor widget usage in Slicer plugin (#806)
* Simplify segment editor widget usage in Slicer plugin Use only one segment editor widget (self.ui.embeddedSegmentEditorWidget) for all editing operations. Use only one segment editor node (the same that Segment Editor core module uses). Signed-off-by: Andras Lasso <[email protected]> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent eaa76e5 commit 832f8c2

File tree

1 file changed

+58
-78
lines changed

1 file changed

+58
-78
lines changed

plugins/slicer/MONAILabel/MONAILabel.py

Lines changed: 58 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ def __init__(self, parent=None):
206206
self._scribblesROINode = None
207207
self._volumeNodes = []
208208
self._updatingGUIFromParameterNode = False
209-
self._scribblesEditorWidget = None
210209

211210
self.info = {}
212211
self.models = OrderedDict()
@@ -345,6 +344,7 @@ def setup(self):
345344
self.ui.embeddedSegmentEditorWidget.setMRMLScene(slicer.mrmlScene)
346345
self.ui.embeddedSegmentEditorWidget.setSegmentationNodeSelectorVisible(False)
347346
self.ui.embeddedSegmentEditorWidget.setMasterVolumeNodeSelectorVisible(False)
347+
self.ui.embeddedSegmentEditorWidget.setMRMLSegmentEditorNode(self.logic.get_segment_editor_node())
348348

349349
self.initializeParameterNode()
350350
self.updateServerUrlGUIFromSettings()
@@ -651,10 +651,6 @@ def updateGUIFromParameterNode(self, caller=None, event=None):
651651
self.ui.dgUpdateCheckBox.setEnabled(self.ui.deepgrowModelSelector.currentText and self._segmentNode)
652652
self.ui.dgUpdateButton.setEnabled(self.ui.deepgrowModelSelector.currentText and self._segmentNode)
653653

654-
self.ui.embeddedSegmentEditorWidget.setMRMLSegmentEditorNode(
655-
slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentEditorNode")
656-
)
657-
658654
# All the GUI updates are done
659655
self._updatingGUIFromParameterNode = False
660656

@@ -1275,17 +1271,16 @@ def initSample(self, sample, autosegment=True):
12751271

12761272
# Create Empty Segments for all labels for this node
12771273
self.createSegmentNode()
1278-
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
1279-
segmentEditorWidget.setSegmentationNode(self._segmentNode)
1280-
segmentEditorWidget.setMasterVolumeNode(self._volumeNode)
1274+
self.ui.embeddedSegmentEditorWidget.setSegmentationNode(self._segmentNode)
1275+
self.ui.embeddedSegmentEditorWidget.setMasterVolumeNode(self._volumeNode)
12811276

12821277
self.createScribblesROINode()
12831278
self.ui.scribblesPlaceWidget.setCurrentNode(self._scribblesROINode)
12841279

12851280
# check if user allows overlapping segments
12861281
if slicer.util.settingsValue("MONAILabel/allowOverlappingSegments", False, converter=slicer.util.toBool):
12871282
# set segment editor to allow overlaps
1288-
slicer.util.getNodesByClass("vtkMRMLSegmentEditorNode")[0].SetOverwriteMode(2)
1283+
self.logic.get_segment_editor_node().SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
12891284

12901285
if self.info.get("labels"):
12911286
self.updateSegmentationMask(None, self.info.get("labels"))
@@ -1693,12 +1688,11 @@ def updateSegmentationMask(self, in_file, labels, sliceIndex=None, freeze=None):
16931688
if freeze and label not in freeze:
16941689
print(f"Discard label update for: {label}")
16951690
elif label in existing_label_ids:
1696-
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
1697-
segmentEditorWidget.setSegmentationNode(segmentationNode)
1698-
segmentEditorWidget.setMasterVolumeNode(self._volumeNode)
1699-
segmentEditorWidget.setCurrentSegmentID(existing_label_ids[label])
1691+
self.ui.embeddedSegmentEditorWidget.setSegmentationNode(segmentationNode)
1692+
self.ui.embeddedSegmentEditorWidget.setMasterVolumeNode(self._volumeNode)
1693+
self.ui.embeddedSegmentEditorWidget.setCurrentSegmentID(existing_label_ids[label])
17001694

1701-
effect = segmentEditorWidget.effectByName("Logical operators")
1695+
effect = self.ui.embeddedSegmentEditorWidget.effectByName("Logical operators")
17021696
labelmap = slicer.vtkOrientedImageData()
17031697
segmentationNode.GetBinaryLabelmapRepresentation(segmentId, labelmap)
17041698

@@ -1795,53 +1789,33 @@ def scribblesLayersPresent(self):
17951789
scribbles_exist = sum(int("scribbles" in sid) for sid in segmentIds) > 0
17961790
return scribbles_exist
17971791

1798-
def onStartScribbling(self):
1799-
if not self._segmentNode:
1792+
def ensureScribblesLayersPresent(self):
1793+
if (not self._segmentNode) or self.scribblesLayersPresent():
18001794
return
18011795

1802-
logging.debug("Scribbles start event")
1803-
if (not self.scribblesLayersPresent()) and (self._scribblesEditorWidget is None):
1804-
# add background, layer index = -2 [2], color = red
1805-
self._segmentNode.GetSegmentation().AddEmptySegment(
1806-
"background_scribbles", "background_scribbles", [1.0, 0.0, 0.0]
1807-
)
1808-
1809-
# add foreground, layer index = -1 [3], color = green
1810-
self._segmentNode.GetSegmentation().AddEmptySegment(
1811-
"foreground_scribbles", "foreground_scribbles", [0.0, 1.0, 0.0]
1812-
)
1813-
1814-
# change segmentation display properties to "see through" the scribbles
1815-
# further explanation at:
1816-
# https://apidocs.slicer.org/master/classvtkMRMLSegmentationDisplayNode.html
1817-
segmentationDisplayNode = self._segmentNode.GetDisplayNode()
1818-
1819-
# background
1820-
opacity = 0.2
1821-
segmentationDisplayNode.SetSegmentOpacity2DFill("background_scribbles", opacity)
1822-
segmentationDisplayNode.SetSegmentOpacity2DOutline("background_scribbles", opacity)
1796+
# add background, layer index = -2 [2], color = red
1797+
self._segmentNode.GetSegmentation().AddEmptySegment(
1798+
"background_scribbles", "background_scribbles", [1.0, 0.0, 0.0]
1799+
)
18231800

1824-
# foreground
1825-
segmentationDisplayNode.SetSegmentOpacity2DFill("foreground_scribbles", opacity)
1826-
segmentationDisplayNode.SetSegmentOpacity2DOutline("foreground_scribbles", opacity)
1801+
# add foreground, layer index = -1 [3], color = green
1802+
self._segmentNode.GetSegmentation().AddEmptySegment(
1803+
"foreground_scribbles", "foreground_scribbles", [0.0, 1.0, 0.0]
1804+
)
18271805

1828-
# create segmentEditorWidget to access "Paint" and "Erase" segmentation tools
1829-
# these will be used to draw scribbles
1830-
self._scribblesEditorWidget = slicer.qMRMLSegmentEditorWidget()
1831-
self._scribblesEditorWidget.setMRMLScene(slicer.mrmlScene)
1832-
segmentEditorNode = slicer.vtkMRMLSegmentEditorNode()
1806+
# change segmentation display properties to "see through" the scribbles
1807+
# further explanation at:
1808+
# https://apidocs.slicer.org/master/classvtkMRMLSegmentationDisplayNode.html
1809+
segmentationDisplayNode = self._segmentNode.GetDisplayNode()
18331810

1834-
# adding new scribbles can overwrite a new one-hot vector, hence erase any existing
1835-
# labels - this is not a desired behaviour hence we swith to overlay mode that enables drawing
1836-
# scribbles without changing existing labels. Further explanation at:
1837-
# https://discourse.slicer.org/t/how-can-i-set-masking-settings-on-a-segment-editor-effect-in-python/4406/7
1838-
segmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
1811+
# background
1812+
opacity = 0.2
1813+
segmentationDisplayNode.SetSegmentOpacity2DFill("background_scribbles", opacity)
1814+
segmentationDisplayNode.SetSegmentOpacity2DOutline("background_scribbles", opacity)
18391815

1840-
# add all nodes to the widget
1841-
slicer.mrmlScene.AddNode(segmentEditorNode)
1842-
self._scribblesEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
1843-
self._scribblesEditorWidget.setSegmentationNode(self._segmentNode)
1844-
self._scribblesEditorWidget.setMasterVolumeNode(self._volumeNode)
1816+
# foreground
1817+
segmentationDisplayNode.SetSegmentOpacity2DFill("foreground_scribbles", opacity)
1818+
segmentationDisplayNode.SetSegmentOpacity2DOutline("foreground_scribbles", opacity)
18451819

18461820
def onUpdateScribbles(self):
18471821
logging.info("Scribbles update event")
@@ -1992,9 +1966,6 @@ def onResetScribbles(self):
19921966
# reset scribbles mode
19931967
self.scribblesMode = None
19941968

1995-
# clear scribbles editor widget
1996-
self._scribblesEditorWidget = None
1997-
19981969
# remove "scribbles" segments from label
19991970
self.onClearScribblesSegmentNodes()
20001971

@@ -2006,29 +1977,29 @@ def onResetScribbles(self):
20061977
self.ui.scribLabelComboBox.setCurrentIndex(0)
20071978
self.ignoreScribblesLabelChangeEvent = False
20081979

2009-
def checkAndInitialiseScribbles(self):
1980+
def updateScribToolLayerFromMode(self):
20101981
if not self._segmentNode:
20111982
return
20121983

2013-
if self._scribblesEditorWidget is None:
2014-
self.onStartScribbling()
1984+
logging.info(f"Scribbles mode {self.scribblesMode} ")
20151985

20161986
if self.scribblesMode is None:
20171987
self.changeScribblesMode(tool="Paint", layer="foreground_scribbles")
20181988
self.updateScribToolLayerFromMode()
20191989

2020-
def updateScribToolLayerFromMode(self):
2021-
if not self._segmentNode:
2022-
return
2023-
2024-
logging.info(f"Scribbles mode {self.scribblesMode} ")
2025-
self.checkAndInitialiseScribbles()
2026-
20271990
# update tool/layer select for scribblesEditorWidget
20281991
tool, layer = self.getToolAndLayerFromScribblesMode()
2029-
if self._scribblesEditorWidget:
2030-
self._scribblesEditorWidget.setActiveEffectByName(tool)
2031-
self._scribblesEditorWidget.setCurrentSegmentID(layer)
1992+
if self.scribblesMode is not None:
1993+
self.ensureScribblesLayersPresent()
1994+
1995+
# adding new scribbles can overwrite a new one-hot vector, hence erase any existing
1996+
# labels - this is not a desired behaviour hence we swith to overlay mode that enables drawing
1997+
# scribbles without changing existing labels. Further explanation at:
1998+
# https://discourse.slicer.org/t/how-can-i-set-masking-settings-on-a-segment-editor-effect-in-python/4406/7
1999+
self.logic.get_segment_editor_node().SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
2000+
2001+
self.ui.embeddedSegmentEditorWidget.setActiveEffectByName(tool)
2002+
self.ui.embeddedSegmentEditorWidget.setCurrentSegmentID(layer)
20322003

20332004
# update brush type from checkbox
20342005
if tool in ("Paint", "Erase"):
@@ -2088,18 +2059,14 @@ def onSelectScribblesLabel(self):
20882059

20892060
def on3dBrushCheckbox(self, state):
20902061
logging.info(f"3D brush update {state}")
2091-
self.checkAndInitialiseScribbles()
2092-
effect = self._scribblesEditorWidget.activeEffect()
2093-
20942062
# enable scribbles in 3d using a sphere brush
2063+
effect = self.ui.embeddedSegmentEditorWidget.effectByName("Paint")
20952064
effect.setParameter("BrushSphere", state)
20962065

20972066
def updateBrushSize(self, value):
20982067
logging.info(f"brush size update {value}")
2099-
if self.ui.paintScribblesButton.checked or self.ui.eraseScribblesButton.checked:
2100-
self.checkAndInitialiseScribbles()
2101-
effect = self._scribblesEditorWidget.activeEffect()
2102-
effect.setParameter("BrushAbsoluteDiameter", value)
2068+
effect = self.ui.embeddedSegmentEditorWidget.effectByName("Paint")
2069+
effect.setParameter("BrushAbsoluteDiameter", value)
21032070

21042071

21052072
class MONAILabelLogic(ScriptedLoadableModuleLogic):
@@ -2137,6 +2104,19 @@ def reportProgress(self, progress):
21372104
if self.progress_callback:
21382105
self.progress_callback(progress)
21392106

2107+
def get_segment_editor_node(self):
2108+
# Use the Segment Editor module's parameter node for the embedded segment editor widget.
2109+
# This ensures that if the user switches to the Segment Editor then the selected
2110+
# segmentation node, volume node, etc. are the same.
2111+
segmentEditorSingletonTag = "SegmentEditor"
2112+
segmentEditorNode = slicer.mrmlScene.GetSingletonNode(segmentEditorSingletonTag, "vtkMRMLSegmentEditorNode")
2113+
if segmentEditorNode is None:
2114+
segmentEditorNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLSegmentEditorNode")
2115+
segmentEditorNode.UnRegister(None)
2116+
segmentEditorNode.SetSingletonTag(segmentEditorSingletonTag)
2117+
segmentEditorNode = slicer.mrmlScene.AddNode(segmentEditorNode)
2118+
return segmentEditorNode
2119+
21402120
def info(self):
21412121
return MONAILabelClient(self.server_url, self.tmpdir, self.client_id).info()
21422122

0 commit comments

Comments
 (0)