Slicer  4.11
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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()