Skip to content

Commit afed4fa

Browse files
authored
Add ROI placement widget for scribbles in Slicer plugin (#798)
* Add ROI placement widget for scribbles in Slicer plugin Signed-off-by: Adam Aji <[email protected]> * Scribbles without ROI fix Signed-off-by: Adam Aji <[email protected]> * Update ROI opacity Signed-off-by: Adam Aji <[email protected]> * Update QLabel text to 'ROI:' Signed-off-by: Adam Aji <[email protected]> * ROI cleanup and restoration like segmentation node Signed-off-by: Adam Aji <[email protected]> * Remove legacy annotation lookup Signed-off-by: Adam Aji <[email protected]> * Remove observer in lieu of ForcePlaceSingleMarkup Signed-off-by: Adam Aji <[email protected]> * Remove check on roi node class Signed-off-by: Adam Aji <[email protected]>
1 parent 214bff7 commit afed4fa

File tree

2 files changed

+62
-21
lines changed

2 files changed

+62
-21
lines changed

plugins/slicer/MONAILabel/MONAILabel.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def __init__(self, parent=None):
203203
self._parameterNode = None
204204
self._volumeNode = None
205205
self._segmentNode = None
206+
self._scribblesROINode = None
206207
self._volumeNodes = []
207208
self._updatingGUIFromParameterNode = False
208209
self._scribblesEditorWidget = None
@@ -331,6 +332,11 @@ def setup(self):
331332
self.ui.scribblesSelector.addItem(self.icon("bg_red.png"), "Background")
332333
self.ui.scribblesSelector.setCurrentIndex(0)
333334

335+
# ROI placement for scribbles
336+
self.ui.scribblesPlaceWidget.setButtonsVisible(False)
337+
self.ui.scribblesPlaceWidget.placeButton().show()
338+
self.ui.scribblesPlaceWidget.setMRMLScene(slicer.mrmlScene)
339+
334340
# start with scribbles section disabled
335341
self.ui.scribblesCollapsibleButton.setEnabled(False)
336342
self.ui.scribblesCollapsibleButton.collapsed = True
@@ -383,6 +389,7 @@ def onSceneStartClose(self, caller, event):
383389
self.setParameterNode(None)
384390
self.current_sample = None
385391
self.samples.clear()
392+
self._scribblesROINode = None
386393

387394
self.resetPointList(
388395
self.ui.dgPositiveControlPointPlacementWidget,
@@ -1272,6 +1279,9 @@ def initSample(self, sample, autosegment=True):
12721279
segmentEditorWidget.setSegmentationNode(self._segmentNode)
12731280
segmentEditorWidget.setMasterVolumeNode(self._volumeNode)
12741281

1282+
self.createScribblesROINode()
1283+
self.ui.scribblesPlaceWidget.setCurrentNode(self._scribblesROINode)
1284+
12751285
# check if user allows overlapping segments
12761286
if slicer.util.settingsValue("MONAILabel/allowOverlappingSegments", False, converter=slicer.util.toBool):
12771287
# set segment editor to allow overlaps
@@ -1600,6 +1610,19 @@ def createSegmentNode(self):
16001610
self._segmentNode.SetReferenceImageGeometryParameterFromVolumeNode(self._volumeNode)
16011611
self._segmentNode.SetName(name)
16021612

1613+
def createScribblesROINode(self):
1614+
if self._volumeNode is None:
1615+
return
1616+
if self._scribblesROINode is None:
1617+
scribblesROINode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsROINode")
1618+
scribblesROINode.SetName("Scribbles ROI")
1619+
scribblesROINode.CreateDefaultDisplayNodes()
1620+
scribblesROINode.GetDisplayNode().SetFillOpacity(0.4)
1621+
scribblesROINode.GetDisplayNode().SetSelectedColor(1, 1, 1)
1622+
scribblesROINode.GetDisplayNode().SetColor(1, 1, 1)
1623+
scribblesROINode.GetDisplayNode().SetActiveColor(1, 1, 1)
1624+
self._scribblesROINode = scribblesROINode
1625+
16031626
def getLabelColor(self, name):
16041627
color = GenericAnatomyColors.get(name.lower())
16051628
return [c / 255.0 for c in color] if color else None
@@ -1856,13 +1879,11 @@ def onUpdateScribbles(self):
18561879
self.updateServerSettings()
18571880
self.reportProgress(60)
18581881

1859-
# try to first fetch vtkMRMLAnnotationROINode
1860-
roiNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLAnnotationROINode")
1861-
if roiNode is None: # if vtkMRMLAnnotationROINode not present, then check for vtkMRMLMarkupsROINode node
1862-
roiNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsROINode")
1863-
1864-
# if roi node found, then try to get roi
1865-
selected_roi = self.getROIPointsXYZ(roiNode)
1882+
# try to get roi if placed
1883+
roiNode = self.ui.scribblesPlaceWidget.currentNode()
1884+
selected_roi = []
1885+
if roiNode and roiNode.GetControlPointPlacementComplete():
1886+
selected_roi = self.getROIPointsXYZ(roiNode)
18661887

18671888
# send scribbles + label to server along with selected scribbles method
18681889
params = self.getParamsFromConfig("infer", scribblesMethod)
@@ -1905,20 +1926,10 @@ def getROIPointsXYZ(self, roiNode):
19051926
v.GetRASToIJKMatrix(RasToIjkMatrix)
19061927

19071928
roi_points_ras = [0.0] * 6
1908-
if roiNode.__class__.__name__ == "vtkMRMLMarkupsROINode":
1909-
# for vtkMRMLMarkupsROINode
1910-
print(roiNode.__class__.__name__)
1911-
center = [0] * 3
1912-
roiNode.GetCenter(center)
1913-
roi_points_ras = [(x - s / 2, x + s / 2) for x, s in zip(center, roiNode.GetSize())]
1914-
roi_points_ras = [item for sublist in roi_points_ras for item in sublist]
1915-
elif roiNode.__class__.__name__ == "vtkMRMLAnnotationROINode":
1916-
# for vtkMRMLAnnotationROINode (old method)
1917-
print(roiNode.__class__.__name__)
1918-
roiNode.GetBounds(roi_points_ras)
1919-
else:
1920-
# if none found then best to return empty list
1921-
return []
1929+
center = [0] * 3
1930+
roiNode.GetCenter(center)
1931+
roi_points_ras = [(x - s / 2, x + s / 2) for x, s in zip(center, roiNode.GetSize())]
1932+
roi_points_ras = [item for sublist in roi_points_ras for item in sublist]
19221933

19231934
min_points_ras = [roi_points_ras[0], roi_points_ras[2], roi_points_ras[4], 1.0]
19241935
max_points_ras = [roi_points_ras[0 + 1], roi_points_ras[2 + 1], roi_points_ras[4 + 1], 1.0]
@@ -1960,11 +1971,17 @@ def onClearScribblesSegmentNodes(self):
19601971
segmentation.SetDisplayVisibility(False)
19611972
segmentation.SetDisplayVisibility(True)
19621973

1974+
def resetScribblesROI(self):
1975+
if self._scribblesROINode:
1976+
self._scribblesROINode.RemoveAllControlPoints()
1977+
19631978
def onClearScribbles(self):
19641979
# for clearing scribbles and resetting tools to default
19651980
# remove "scribbles" segments from label
19661981
self.onClearScribblesSegmentNodes()
19671982

1983+
self.resetScribblesROI()
1984+
19681985
self.ui.paintScribblesButton.setChecked(True)
19691986
self.ui.eraseScribblesButton.setChecked(False)
19701987

plugins/slicer/MONAILabel/Resources/UI/MONAILabel.ui

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,30 @@
645645
<item row="2" column="3">
646646
<widget class="QComboBox" name="scribLabelComboBox"/>
647647
</item>
648+
<item row="0" column="6">
649+
<layout class="QHBoxLayout" name="horizontalLayout_4">
650+
<item>
651+
<widget class="QLabel" name="scribblesROILabel">
652+
<property name="text">
653+
<string>ROI:</string>
654+
</property>
655+
</widget>
656+
</item>
657+
<item>
658+
<widget class="qSlicerMarkupsPlaceWidget" name="scribblesPlaceWidget">
659+
<property name="placeMultipleMarkups">
660+
<enum>qSlicerMarkupsPlaceWidget::ForcePlaceSingleMarkup</enum>
661+
</property>
662+
<property name="sizePolicy">
663+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
664+
<horstretch>0</horstretch>
665+
<verstretch>0</verstretch>
666+
</sizepolicy>
667+
</property>
668+
</widget>
669+
</item>
670+
</layout>
671+
</item>
648672
</layout>
649673
</item>
650674
</layout>

0 commit comments

Comments
 (0)