Slicer  4.11
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorMarginEffect.py
Go to the documentation of this file.
1 import os
2 import vtk, qt, ctk, slicer
3 import logging
4 import math
5 from SegmentEditorEffects import *
6 
8  """ MaringEffect grows or shrinks the segment by a specified margin
9  """
10 
11  def __init__(self, scriptedEffect):
12  scriptedEffect.name = 'Margin'
13  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
14 
15  def clone(self):
16  import qSlicerSegmentationsEditorEffectsPythonQt as effects
17  clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
18  clonedEffect.setPythonSource(__file__.replace('\\','/'))
19  return clonedEffect
20 
21  def icon(self):
22  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Margin.png')
23  if os.path.exists(iconPath):
24  return qt.QIcon(iconPath)
25  return qt.QIcon()
26 
27  def helpText(self):
28  return "Grow or shrink selected segment by specified margin size."
29 
30  def setupOptionsFrame(self):
31 
32  operationLayout = qt.QVBoxLayout()
33 
34  self.shrinkOptionRadioButton = qt.QRadioButton("Shrink")
35  self.growOptionRadioButton = qt.QRadioButton("Grow")
36  operationLayout.addWidget(self.shrinkOptionRadioButton)
37  operationLayout.addWidget(self.growOptionRadioButton)
38  self.growOptionRadioButton.setChecked(True)
39 
40  self.scriptedEffect.addLabeledOptionsWidget("Operation:", operationLayout)
41 
42  self.marginSizeMMSpinBox = slicer.qMRMLSpinBox()
43  self.marginSizeMMSpinBox.setMRMLScene(slicer.mrmlScene)
44  self.marginSizeMMSpinBox.setToolTip("Segment boundaries will be shifted by this distance. Positive value means the segments will grow, negative value means segment will shrink.")
45  self.marginSizeMMSpinBox.quantity = "length"
46  self.marginSizeMMSpinBox.value = 3.0
47  self.marginSizeMMSpinBox.singleStep = 1.0
48 
49  self.marginSizeLabel = qt.QLabel()
50  self.marginSizeLabel.setToolTip("Size change in pixel. Computed from the segment's spacing and the specified margin size.")
51 
52  marginSizeFrame = qt.QHBoxLayout()
53  marginSizeFrame.addWidget(self.marginSizeMMSpinBox)
54  self.marginSizeMMLabel = self.scriptedEffect.addLabeledOptionsWidget("Margin size:", marginSizeFrame)
55  self.scriptedEffect.addLabeledOptionsWidget("", self.marginSizeLabel)
56 
57  self.applyButton = qt.QPushButton("Apply")
58  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
59  self.applyButton.setToolTip("Grows or shrinks selected segment by the specified margin.")
60  self.scriptedEffect.addOptionsWidget(self.applyButton)
61 
62  self.applyButton.connect('clicked()', self.onApply)
63  self.marginSizeMMSpinBox.connect("valueChanged(double)", self.updateMRMLFromGUI)
64  self.growOptionRadioButton.connect("toggled(bool)", self.growOperationToggled)
65  self.shrinkOptionRadioButton.connect("toggled(bool)", self.shrinkOperationToggled)
66 
67  def createCursor(self, widget):
68  # Turn off effect-specific cursor for this effect
69  return slicer.util.mainWindow().cursor
70 
71  def setMRMLDefaults(self):
72  self.scriptedEffect.setParameterDefault("MarginSizeMm", 3)
73 
74  def getMarginSizePixel(self):
75  selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
76  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
77  if selectedSegmentLabelmap:
78  selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
79 
80  marginSizeMM = abs(self.scriptedEffect.doubleParameter("MarginSizeMm"))
81  marginSizePixel = [int(math.floor(marginSizeMM / spacing)) for spacing in selectedSegmentLabelmapSpacing]
82  return marginSizePixel
83 
84  def updateGUIFromMRML(self):
85  marginSizeMM = self.scriptedEffect.doubleParameter("MarginSizeMm")
86  wasBlocked = self.marginSizeMMSpinBox.blockSignals(True)
87  self.marginSizeMMSpinBox.value = abs(marginSizeMM)
88  self.marginSizeMMSpinBox.blockSignals(wasBlocked)
89 
90  wasBlocked = self.growOptionRadioButton.blockSignals(True)
91  self.growOptionRadioButton.setChecked(marginSizeMM > 0)
92  self.growOptionRadioButton.blockSignals(wasBlocked)
93 
94  wasBlocked = self.shrinkOptionRadioButton.blockSignals(True)
95  self.shrinkOptionRadioButton.setChecked(marginSizeMM < 0)
96  self.shrinkOptionRadioButton.blockSignals(wasBlocked)
97 
98  selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
99  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
100  if selectedSegmentLabelmap:
101  selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
102  marginSizePixel = self.getMarginSizePixel()
103  if marginSizePixel[0] < 1 or marginSizePixel[1] < 1 or marginSizePixel[2] < 1:
104  self.marginSizeLabel.text = "Not feasible at current resolution."
105  self.applyButton.setEnabled(False)
106  else:
107  marginSizeMM = self.getMarginSizeMM()
108  self.marginSizeLabel.text = "Actual: {0} x {1} x {2} mm ({3}x{4}x{5} pixel)".format(*marginSizeMM, *marginSizePixel)
109  self.applyButton.setEnabled(True)
110  else:
111  self.marginSizeLabel.text = "Empty segment"
112 
113  self.setWidgetMinMaxStepFromImageSpacing(self.marginSizeMMSpinBox, self.scriptedEffect.selectedSegmentLabelmap())
114 
115  def growOperationToggled(self, toggled):
116  if toggled:
117  self.scriptedEffect.setParameter("MarginSizeMm", self.marginSizeMMSpinBox.value)
118 
119  def shrinkOperationToggled(self, toggled):
120  if toggled:
121  self.scriptedEffect.setParameter("MarginSizeMm", -self.marginSizeMMSpinBox.value)
122 
123  def updateMRMLFromGUI(self):
124  marginSizeMM = (self.marginSizeMMSpinBox.value) if self.growOptionRadioButton.checked else (-self.marginSizeMMSpinBox.value)
125  self.scriptedEffect.setParameter("MarginSizeMm", marginSizeMM)
126 
127  def getMarginSizeMM(self):
128  selectedSegmentLabelmapSpacing = [1.0, 1.0, 1.0]
129  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
130  if selectedSegmentLabelmap:
131  selectedSegmentLabelmapSpacing = selectedSegmentLabelmap.GetSpacing()
132 
133  marginSizePixel = self.getMarginSizePixel()
134  marginSizeMM = [abs((marginSizePixel[i])*selectedSegmentLabelmapSpacing[i]) for i in range(3)]
135  for i in range(3):
136  if marginSizeMM[i] > 0:
137  marginSizeMM[i] = round(marginSizeMM[i], max(int(-math.floor(math.log10(marginSizeMM[i]))),1))
138  return marginSizeMM
139 
140  def onApply(self):
141  # Make sure the user wants to do the operation, even if the segment is not visible
142  if not self.scriptedEffect.confirmCurrentSegmentVisible():
143  return
144 
145  self.scriptedEffect.saveStateForUndo()
146 
147  # Get modifier labelmap and parameters
148  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
149  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
150 
151  marginSizeMM = self.scriptedEffect.doubleParameter("MarginSizeMm")
152 
153  # We need to know exactly the value of the segment voxels, apply threshold to make force the selected label value
154  labelValue = 1
155  backgroundValue = 0
156  thresh = vtk.vtkImageThreshold()
157  thresh.SetInputData(selectedSegmentLabelmap)
158  thresh.ThresholdByLower(0)
159  thresh.SetInValue(backgroundValue)
160  thresh.SetOutValue(labelValue)
161  thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
162  if (marginSizeMM < 0):
163  # The distance filter used in the margin filter starts at zero at the border voxels,
164  # so if we need to shrink the margin, it is more accurate to invert the labelmap and
165  # use positive distance when calculating the margin
166  thresh.SetInValue(labelValue)
167  thresh.SetOutValue(backgroundValue)
168 
169  import vtkITK
170  margin = vtkITK.vtkITKImageMargin()
171  margin.SetInputConnection(thresh.GetOutputPort())
172  margin.CalculateMarginInMMOn()
173  margin.SetOuterMarginMM(abs(marginSizeMM))
174  margin.Update()
175 
176  if marginSizeMM >= 0:
177  modifierLabelmap.ShallowCopy(margin.GetOutput())
178  else:
179  # If we are shrinking then the result needs to be inverted.
180  thresh = vtk.vtkImageThreshold()
181  thresh.SetInputData(margin.GetOutput())
182  thresh.ThresholdByLower(0)
183  thresh.SetInValue(labelValue)
184  thresh.SetOutValue(backgroundValue)
185  thresh.SetOutputScalarType(selectedSegmentLabelmap.GetScalarType())
186  thresh.Update()
187  modifierLabelmap.ShallowCopy(thresh.GetOutput())
188 
189  # Apply changes
190  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
191 
192  qt.QApplication.restoreOverrideCursor()