Slicer  4.8
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorThresholdEffect.py
Go to the documentation of this file.
1 import os
2 import vtk, qt, ctk, slicer
3 import logging
4 from SegmentEditorEffects import *
5 
7  """ ThresholdEffect is an Effect implementing the global threshold
8  operation in the segment editor
9 
10  This is also an example for scripted effects, and some methods have no
11  function. The methods that are not needed (i.e. the default implementation in
12  qSlicerSegmentEditorAbstractEffect is satisfactory) can simply be omitted.
13  """
14 
15  def __init__(self, scriptedEffect):
16  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
17  scriptedEffect.name = 'Threshold'
18 
19  # Effect-specific members
20  self.timer = qt.QTimer()
21  self.previewState = 0
22  self.previewStep = 1
23  self.previewSteps = 5
24  self.timer.connect('timeout()', self.preview)
25 
26  self.previewPipelines = {}
27  self.setupPreviewDisplay()
28 
29  def clone(self):
30  import qSlicerSegmentationsEditorEffectsPythonQt as effects
31  clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
32  clonedEffect.setPythonSource(__file__.replace('\\','/'))
33  return clonedEffect
34 
35  def icon(self):
36  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Threshold.png')
37  if os.path.exists(iconPath):
38  return qt.QIcon(iconPath)
39  return qt.QIcon()
40 
41  def helpText(self):
42  return """<html>Fill segment based on master volume intensity range<br>. Options:<p>
43 <ul style="margin: 0">
44 <li><b>Use for masking:</b> set the selected intensity range as <dfn>Editable intensity range</dfn> and switch to Paint effect.</li>
45 <li><b>Apply:</b> set the previewed segmentation in the selected segment. Previous contents of the segment is overwritten.</li>
46 </ul><p></html>"""
47 
48  def activate(self):
49  # Save segment opacity and set it to zero
50  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
51  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
52  displayNode = segmentationNode.GetDisplayNode()
53  if displayNode is not None:
54  self.segment2DFillOpacity = displayNode.GetSegmentOpacity2DFill(segmentID)
55  self.segment2DOutlineOpacity = displayNode.GetSegmentOpacity2DOutline(segmentID)
56  displayNode.SetSegmentOpacity2DFill(segmentID, 0)
57  displayNode.SetSegmentOpacity2DOutline(segmentID, 0)
58 
59  # Update intensity range
61 
62  # Setup and start preview pulse
63  self.setupPreviewDisplay()
64  self.timer.start(200)
65 
66  def deactivate(self):
67  # Restore segment opacity
68  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
69  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
70  displayNode = segmentationNode.GetDisplayNode()
71  if (displayNode is not None) and (segmentID is not None):
72  displayNode.SetSegmentOpacity2DFill(segmentID, self.segment2DFillOpacity)
73  displayNode.SetSegmentOpacity2DOutline(segmentID, self.segment2DOutlineOpacity)
74 
75  # Clear preview pipeline and stop timer
76  self.clearPreviewDisplay()
77  self.timer.stop()
78 
79  def setupOptionsFrame(self):
80  self.thresholdSliderLabel = qt.QLabel("Threshold Range:")
81  self.thresholdSliderLabel.setToolTip("Set the range of the background values that should be labeled.")
82  self.scriptedEffect.addOptionsWidget(self.thresholdSliderLabel)
83 
84  self.thresholdSlider = ctk.ctkRangeWidget()
85  self.thresholdSlider.spinBoxAlignment = qt.Qt.AlignTop
86  self.thresholdSlider.singleStep = 0.01
87  self.scriptedEffect.addOptionsWidget(self.thresholdSlider)
88 
89  self.useForPaintButton = qt.QPushButton("Use for masking")
90  self.useForPaintButton.setToolTip("Use specified intensity range for masking and switch to Paint effect.")
91  self.scriptedEffect.addOptionsWidget(self.useForPaintButton)
92 
93  self.applyButton = qt.QPushButton("Apply")
94  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
95  self.applyButton.setToolTip("Fill selected segment in regions that are in the specified intensity range.")
96  self.scriptedEffect.addOptionsWidget(self.applyButton)
97 
98  self.useForPaintButton.connect('clicked()', self.onUseForPaint)
99  self.thresholdSlider.connect('valuesChanged(double,double)', self.onThresholdValuesChanged)
100  self.applyButton.connect('clicked()', self.onApply)
101 
102  def createCursor(self, widget):
103  # Turn off effect-specific cursor for this effect
104  return slicer.util.mainWindow().cursor
105 
107  # Set scalar range of master volume image data to threshold slider
108  import vtkSegmentationCorePython as vtkSegmentationCore
109  masterImageData = self.scriptedEffect.masterVolumeImageData()
110  if masterImageData:
111  lo, hi = masterImageData.GetScalarRange()
112  self.thresholdSlider.setRange(lo, hi)
113  self.thresholdSlider.singleStep = (hi - lo) / 1000.
114  if (self.scriptedEffect.doubleParameter("MinimumThreshold") == self.scriptedEffect.doubleParameter("MaximumThreshold")):
115  # has not been initialized yet
116  self.scriptedEffect.setParameter("MinimumThreshold", lo+(hi-lo)*0.25)
117  self.scriptedEffect.setParameter("MaximumThreshold", hi)
118 
119  def layoutChanged(self):
120  self.setupPreviewDisplay()
121 
122  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
123  return False # For the sake of example
124 
125  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
126  pass # For the sake of example
127 
128  def setMRMLDefaults(self):
129  self.scriptedEffect.setParameterDefault("MinimumThreshold", 0.)
130  self.scriptedEffect.setParameterDefault("MaximumThreshold", 0)
131 
132  def updateGUIFromMRML(self):
133  self.thresholdSlider.blockSignals(True)
134  self.thresholdSlider.setMinimumValue(self.scriptedEffect.doubleParameter("MinimumThreshold"))
135  self.thresholdSlider.setMaximumValue(self.scriptedEffect.doubleParameter("MaximumThreshold"))
136  self.thresholdSlider.blockSignals(False)
137 
138  def updateMRMLFromGUI(self):
139  self.scriptedEffect.setParameter("MinimumThreshold", self.thresholdSlider.minimumValue)
140  self.scriptedEffect.setParameter("MaximumThreshold", self.thresholdSlider.maximumValue)
141 
142  #
143  # Effect specific methods (the above ones are the API methods to override)
144  #
145  def onThresholdValuesChanged(self,min,max):
146  self.scriptedEffect.updateMRMLFromGUI()
147 
148  def onUseForPaint(self):
149  parameterSetNode = self.scriptedEffect.parameterSetNode()
150  parameterSetNode.MasterVolumeIntensityMaskOn()
151  parameterSetNode.SetMasterVolumeIntensityMaskRange(self.thresholdSlider.minimumValue, self.thresholdSlider.maximumValue)
152  # Switch to paint effect
153  self.scriptedEffect.selectEffect("Paint")
154 
155  def onApply(self):
156  try:
157  # Get master volume image data
158  import vtkSegmentationCorePython as vtkSegmentationCore
159  masterImageData = self.scriptedEffect.masterVolumeImageData()
160  # Get modifier labelmap
161  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
162  originalImageToWorldMatrix = vtk.vtkMatrix4x4()
163  modifierLabelmap.GetImageToWorldMatrix(originalImageToWorldMatrix)
164  # Get parameters
165  min = self.scriptedEffect.doubleParameter("MinimumThreshold")
166  max = self.scriptedEffect.doubleParameter("MaximumThreshold")
167 
168  self.scriptedEffect.saveStateForUndo()
169 
170  # Perform thresholding
171  thresh = vtk.vtkImageThreshold()
172  thresh.SetInputData(masterImageData)
173  thresh.ThresholdBetween(min, max)
174  thresh.SetInValue(1)
175  thresh.SetOutValue(0)
176  thresh.SetOutputScalarType(modifierLabelmap.GetScalarType())
177  thresh.Update()
178  modifierLabelmap.DeepCopy(thresh.GetOutput())
179  except IndexError:
180  logging.error('apply: Failed to threshold master volume!')
181  pass
182 
183  # Apply changes
184  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
185 
186  # De-select effect
187  self.scriptedEffect.selectEffect("")
188 
190  for sliceWidget, pipeline in self.previewPipelines.iteritems():
191  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
192  self.previewPipelines = {}
193 
195  # Clear previous pipelines before setting up the new ones
196  self.clearPreviewDisplay()
197 
198  layoutManager = slicer.app.layoutManager()
199  if layoutManager is None:
200  return
201 
202  # Add a pipeline for each 2D slice view
203  for sliceViewName in layoutManager.sliceViewNames():
204  sliceWidget = layoutManager.sliceWidget(sliceViewName)
205  if not self.scriptedEffect.segmentationDisplayableInView(sliceWidget.mrmlSliceNode()):
206  continue
207  renderer = self.scriptedEffect.renderer(sliceWidget)
208  if renderer is None:
209  logging.error("setupPreviewDisplay: Failed to get renderer!")
210  continue
211 
212  # Create pipeline
213  pipeline = PreviewPipeline()
214  self.previewPipelines[sliceWidget] = pipeline
215 
216  # Add actor
217  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
218 
219  def preview(self):
220  opacity = 0.5 + self.previewState / (2. * self.previewSteps)
221  min = self.scriptedEffect.doubleParameter("MinimumThreshold")
222  max = self.scriptedEffect.doubleParameter("MaximumThreshold")
223 
224  # Get color of edited segment
225  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
226  displayNode = segmentationNode.GetDisplayNode()
227  if displayNode is None:
228  logging.error("preview: Invalid segmentation display node!")
229  color = [0.5,0.5,0.5]
230  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
231  r,g,b = segmentationNode.GetSegmentation().GetSegment(segmentID).GetColor()
232 
233  # Set values to pipelines
234  for sliceWidget in self.previewPipelines:
235  pipeline = self.previewPipelines[sliceWidget]
236  pipeline.lookupTable.SetTableValue(1, r, g, b, opacity)
237  sliceLogic = sliceWidget.sliceLogic()
238  backgroundLogic = sliceLogic.GetBackgroundLayer()
239  pipeline.thresholdFilter.SetInputConnection(backgroundLogic.GetReslice().GetOutputPort())
240  pipeline.thresholdFilter.ThresholdBetween(min, max)
241  pipeline.actor.VisibilityOn()
242  sliceWidget.sliceView().scheduleRender()
243 
244  self.previewState += self.previewStep
245  if self.previewState >= self.previewSteps:
246  self.previewStep = -1
247  if self.previewState <= 0:
248  self.previewStep = 1
249 
250 #
251 # PreviewPipeline
252 #
254  """ Visualization objects and pipeline for each slice view for threshold preview
255  """
256 
257  def __init__(self):
258  self.lookupTable = vtk.vtkLookupTable()
259  self.lookupTable.SetRampToLinear()
260  self.lookupTable.SetNumberOfTableValues(2)
261  self.lookupTable.SetTableRange(0, 1)
262  self.lookupTable.SetTableValue(0, 0, 0, 0, 0)
263  self.colorMapper = vtk.vtkImageMapToRGBA()
264  self.colorMapper.SetOutputFormatToRGBA()
265  self.colorMapper.SetLookupTable(self.lookupTable)
266  self.thresholdFilter = vtk.vtkImageThreshold()
267  self.thresholdFilter.SetInValue(1)
268  self.thresholdFilter.SetOutValue(0)
269  self.thresholdFilter.SetOutputScalarTypeToUnsignedChar()
270 
271  # Feedback actor
272  self.mapper = vtk.vtkImageMapper()
273  self.dummyImage = vtk.vtkImageData()
274  self.dummyImage.AllocateScalars(vtk.VTK_UNSIGNED_INT, 1)
275  self.mapper.SetInputData(self.dummyImage)
276  self.actor = vtk.vtkActor2D()
277  self.actor.VisibilityOff()
278  self.actor.SetMapper(self.mapper)
279  self.mapper.SetColorWindow(255)
280  self.mapper.SetColorLevel(128)
281 
282  # Setup pipeline
283  self.colorMapper.SetInputConnection(self.thresholdFilter.GetOutputPort())
284  self.mapper.SetInputConnection(self.colorMapper.GetOutputPort())
def processViewNodeEvents(self, callerViewNode, eventId, viewWidget)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)