Slicer 5.9
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
SegmentEditorMarginEffect.py
Go to the documentation of this file.
1import logging
2import math
3import os
4
5import qt
6import vtk
7
8import slicer
9from slicer.i18n import tr as _
10
11from SegmentEditorEffects import *
12
13
15 """MaringEffect grows or shrinks the segment by a specified margin"""
16
17 def __init__(self, scriptedEffect):
18 scriptedEffect.name = "Margin" # no tr (don't translate it because modules find effects by name)
19 scriptedEffect.title = _("Margin")
20 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
21
22 def clone(self):
23 import qSlicerSegmentationsEditorEffectsPythonQt as effects
24
25 clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
26 clonedEffect.setPythonSource(__file__.replace("\\", "/"))
27 return clonedEffect
28
29 def icon(self):
30 iconPath = os.path.join(os.path.dirname(__file__), "Resources/Icons/Margin.png")
31 if os.path.exists(iconPath):
32 return qt.QIcon(iconPath)
33 return qt.QIcon()
34
35 def helpText(self):
36 return _("Grow or shrink selected segment by specified margin size.")
37
38 def setupOptionsFrame(self):
39 operationLayout = qt.QVBoxLayout()
40
41 self.shrinkOptionRadioButton = qt.QRadioButton(_("Shrink"))
42 self.growOptionRadioButton = qt.QRadioButton(_("Grow"))
43 operationLayout.addWidget(self.shrinkOptionRadioButton)
44 operationLayout.addWidget(self.growOptionRadioButton)
45 self.growOptionRadioButton.setChecked(True)
46
47 self.scriptedEffect.addLabeledOptionsWidget(_("Operation:"), operationLayout)
48
49 self.marginSizeMMSpinBox = slicer.qMRMLSpinBox()
50 self.marginSizeMMSpinBox.setMRMLScene(slicer.mrmlScene)
51 self.marginSizeMMSpinBox.setToolTip(_("Segment boundaries will be shifted by this distance. "
52 "Positive value means the segments will grow, negative value means segment will shrink."))
53 self.marginSizeMMSpinBox.quantity = "length"
54 self.marginSizeMMSpinBox.value = 3.0
55 self.marginSizeMMSpinBox.singleStep = 1.0
56
57 self.marginSizeLabel = qt.QLabel()
58 self.marginSizeLabel.setToolTip(_("Size change in pixel. Computed from the segment's spacing and the specified margin size."))
59
60 marginSizeFrame = qt.QHBoxLayout()
61 marginSizeFrame.addWidget(self.marginSizeMMSpinBox)
62 self.marginSizeMMLabel = self.scriptedEffect.addLabeledOptionsWidget(_("Margin size:"), marginSizeFrame)
63 self.scriptedEffect.addLabeledOptionsWidget("", self.marginSizeLabel)
64
67 _("Grow or shrink all visible segments in this segmentation node. This operation may take a while."))
68 self.applyToAllVisibleSegmentsCheckBox.objectName = self.__class__.__name__ + "ApplyToAllVisibleSegments"
69 self.applyToAllVisibleSegmentsLabel = self.scriptedEffect.addLabeledOptionsWidget(_("Apply to visible segments:"),
71
72 self.applyButton = qt.QPushButton(_("Apply"))
73 self.applyButton.objectName = self.__class__.__name__ + "Apply"
74 self.applyButton.setToolTip(_("Grows or shrinks selected segment /default) or all segments (checkbox) by the specified margin."))
75 self.scriptedEffect.addOptionsWidget(self.applyButton)
76
77 self.applyButton.connect("clicked()", self.onApply)
78 self.marginSizeMMSpinBox.connect("valueChanged(double)", self.updateMRMLFromGUI)
79 self.growOptionRadioButton.connect("toggled(bool)", self.growOperationToggled)
80 self.shrinkOptionRadioButton.connect("toggled(bool)", self.shrinkOperationToggled)
81 self.applyToAllVisibleSegmentsCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUI)
82
83 def createCursor(self, widget):
84 # Turn off effect-specific cursor for this effect
85 return slicer.util.mainWindow().cursor
86
87 def setMRMLDefaults(self):
88 self.scriptedEffect.setParameterDefault("ApplyToAllVisibleSegments", 0)
89 self.scriptedEffect.setParameterDefault("MarginSizeMm", 3)
90
91 def getMarginSizePixel(self):
92 selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
93 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
94 if selectedSegmentLabelmap:
95 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
96
97 marginSizeMM = abs(self.scriptedEffect.doubleParameter("MarginSizeMm"))
98 marginSizePixel = [math.floor(marginSizeMM / spacing) for spacing in selectedSegmentLabelmapSpacing]
99 return marginSizePixel
100
101 def updateGUIFromMRML(self):
102 marginSizeMM = self.scriptedEffect.doubleParameter("MarginSizeMm")
103 wasBlocked = self.marginSizeMMSpinBox.blockSignals(True)
104 self.marginSizeMMSpinBox.value = abs(marginSizeMM)
105 self.marginSizeMMSpinBox.blockSignals(wasBlocked)
106
107 wasBlocked = self.growOptionRadioButton.blockSignals(True)
108 self.growOptionRadioButton.setChecked(marginSizeMM > 0)
109 self.growOptionRadioButton.blockSignals(wasBlocked)
110
111 wasBlocked = self.shrinkOptionRadioButton.blockSignals(True)
112 self.shrinkOptionRadioButton.setChecked(marginSizeMM < 0)
113 self.shrinkOptionRadioButton.blockSignals(wasBlocked)
114
115 selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
116 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
117 if selectedSegmentLabelmap:
118 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
119 marginSizePixel = self.getMarginSizePixel()
120 if marginSizePixel[0] < 1 or marginSizePixel[1] < 1 or marginSizePixel[2] < 1:
121 self.marginSizeLabel.text = _("Not feasible at current resolution.")
122 self.applyButton.setEnabled(False)
123 else:
124 marginSizeMM = self.getMarginSizeMM()
125 self.marginSizeLabel.text = _("Actual:") + " {} x {} x {} mm ({}x{}x{} pixel)".format(*marginSizeMM, *marginSizePixel)
126 self.applyButton.setEnabled(True)
127 else:
128 self.marginSizeLabel.text = _("Empty segment")
129
130 applyToAllVisibleSegments = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("ApplyToAllVisibleSegments") == 0 else qt.Qt.Checked
131 wasBlocked = self.applyToAllVisibleSegmentsCheckBox.blockSignals(True)
132 self.applyToAllVisibleSegmentsCheckBox.setCheckState(applyToAllVisibleSegments)
133 self.applyToAllVisibleSegmentsCheckBox.blockSignals(wasBlocked)
134
135 self.setWidgetMinMaxStepFromImageSpacing(self.marginSizeMMSpinBox, self.scriptedEffect.selectedSegmentLabelmap())
136
137 def growOperationToggled(self, toggled):
138 if toggled:
139 self.scriptedEffect.setParameter("MarginSizeMm", self.marginSizeMMSpinBox.value)
140
141 def shrinkOperationToggled(self, toggled):
142 if toggled:
143 self.scriptedEffect.setParameter("MarginSizeMm", -self.marginSizeMMSpinBox.value)
144
145 def updateMRMLFromGUI(self):
146 marginSizeMM = (self.marginSizeMMSpinBox.value) if self.growOptionRadioButton.checked else (-self.marginSizeMMSpinBox.value)
147 self.scriptedEffect.setParameter("MarginSizeMm", marginSizeMM)
148 applyToAllVisibleSegments = 1 if self.applyToAllVisibleSegmentsCheckBox.isChecked() else 0
149 self.scriptedEffect.setParameter("ApplyToAllVisibleSegments", applyToAllVisibleSegments)
150
151 def getMarginSizeMM(self):
152 selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
153 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
154 if selectedSegmentLabelmap:
155 selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
156
157 marginSizePixel = self.getMarginSizePixel()
158 marginSizeMM = [abs((marginSizePixel[i]) * selectedSegmentLabelmapSpacing[i]) for i in range(3)]
159 for i in range(3):
160 if marginSizeMM[i] > 0:
161 marginSizeMM[i] = round(marginSizeMM[i], max(int(-math.floor(math.log10(marginSizeMM[i]))), 1))
162 return marginSizeMM
163
164 def showStatusMessage(self, msg, timeoutMsec=500):
165 slicer.util.showStatusMessage(msg, timeoutMsec)
166 slicer.app.processEvents()
167
168 def processMargin(self):
169 # Get modifier labelmap and parameters
170 modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
171 selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
172
173 marginSizeMM = self.scriptedEffect.doubleParameter("MarginSizeMm")
174
175 # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value
176 labelValue = 1
177 backgroundValue = 0
178 thresh = vtk.vtkImageThreshold()
179 thresh.SetInputData(selectedSegmentLabelmap)
180 thresh.ThresholdByLower(0)
181 thresh.SetInValue(backgroundValue)
182 thresh.SetOutValue(labelValue)
183 thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
184 if marginSizeMM < 0:
185 # The distance filter used in the margin filter starts at zero at the border voxels,
186 # so if we need to shrink the margin, it is more accurate to invert the labelmap and
187 # use positive distance when calculating the margin
188 thresh.SetInValue(labelValue)
189 thresh.SetOutValue(backgroundValue)
190
191 import vtkITK
192
193 margin = vtkITK.vtkITKImageMargin()
194 margin.SetInputConnection(thresh.GetOutputPort())
195 margin.CalculateMarginInMMOn()
196 margin.SetOuterMarginMM(abs(marginSizeMM))
197 margin.Update()
198
199 if marginSizeMM >= 0:
200 modifierLabelmap.ShallowCopy(margin.GetOutput())
201 else:
202 # If we are shrinking then the result needs to be inverted.
203 thresh = vtk.vtkImageThreshold()
204 thresh.SetInputData(margin.GetOutput())
205 thresh.ThresholdByLower(0)
206 thresh.SetInValue(labelValue)
207 thresh.SetOutValue(backgroundValue)
208 thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
209 thresh.Update()
210 modifierLabelmap.ShallowCopy(thresh.GetOutput())
211
212 # Apply changes
213 self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
214
215 def onApply(self):
216 # Make sure the user wants to do the operation, even if the segment is not visible
217 if not self.scriptedEffect.confirmCurrentSegmentVisible():
218 return
219
220 try:
221 # This can be a long operation - indicate it to the user
222 qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
223 self.scriptedEffect.saveStateForUndo()
224
225 applyToAllVisibleSegments = int(self.scriptedEffect.parameter("ApplyToAllVisibleSegments")) != 0 \
226 if self.scriptedEffect.parameter("ApplyToAllVisibleSegments") else False
227
228 if applyToAllVisibleSegments:
229 # Smooth all visible segments
230 inputSegmentIDs = vtk.vtkStringArray()
231 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
232 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(inputSegmentIDs)
233 # store which segment was selected before operation
234 selectedStartSegmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
235 if inputSegmentIDs.GetNumberOfValues() == 0:
236 logging.info("Margin operation skipped: there are no visible segments.")
237 return
238 # select input segments one by one, process
239 for index in range(inputSegmentIDs.GetNumberOfValues()):
240 segmentID = inputSegmentIDs.GetValue(index)
241 self.showStatusMessage(_("Processing {segmentName}...")
242 .format(segmentName=segmentationNode.GetSegmentation().GetSegment(segmentID).GetName()))
243 self.scriptedEffect.parameterSetNode().SetSelectedSegmentID(segmentID)
244 self.processMargin()
245 # restore segment selection
246 self.scriptedEffect.parameterSetNode().SetSelectedSegmentID(selectedStartSegmentID)
247 else:
248 self.processMargin()
249
250 finally:
251 qt.QApplication.restoreOverrideCursor()