Slicer  4.11
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 
21  self.previewedSegmentID = None
22 
23  # Effect-specific members
24  import vtkITK
25  self.autoThresholdCalculator = vtkITK.vtkITKImageThresholdCalculator()
26 
27  self.timer = qt.QTimer()
28  self.previewState = 0
29  self.previewStep = 1
30  self.previewSteps = 5
31  self.timer.connect('timeout()', self.preview)
32 
33  self.previewPipelines = {}
34  self.histogramPipeline = None
35  self.setupPreviewDisplay()
36 
37  # Histogram stencil setup
38  self.stencil = vtk.vtkPolyDataToImageStencil()
39 
40  # Histogram reslice setup
41  self.reslice = vtk.vtkImageReslice()
42  self.reslice.AutoCropOutputOff()
43  self.reslice.SetOptimization(1)
44  self.reslice.SetOutputOrigin(0, 0, 0)
45  self.reslice.SetOutputSpacing(1, 1, 1)
46  self.reslice.SetOutputDimensionality(3)
47  self.reslice.GenerateStencilOutputOn()
48 
49  self.imageAccumulate = vtk.vtkImageAccumulate()
50  self.imageAccumulate.SetInputConnection(0, self.reslice.GetOutputPort())
51  self.imageAccumulate.SetInputConnection(1, self.stencil.GetOutputPort())
52 
55 
56  def clone(self):
57  import qSlicerSegmentationsEditorEffectsPythonQt as effects
58  clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
59  clonedEffect.setPythonSource(__file__.replace('\\','/'))
60  return clonedEffect
61 
62  def icon(self):
63  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Threshold.png')
64  if os.path.exists(iconPath):
65  return qt.QIcon(iconPath)
66  return qt.QIcon()
67 
68  def helpText(self):
69  return """<html>Fill segment based on master volume intensity range<br>. Options:<p>
70 <ul style="margin: 0">
71 <li><b>Use for masking:</b> set the selected intensity range as <dfn>Editable intensity range</dfn> and switch to Paint effect.</li>
72 <li><b>Apply:</b> set the previewed segmentation in the selected segment. Previous contents of the segment is overwritten.</li>
73 </ul><p></html>"""
74 
75  def activate(self):
77 
78  # Update intensity range
80 
81  # Setup and start preview pulse
82  self.setupPreviewDisplay()
83  self.timer.start(200)
84 
85  def deactivate(self):
87 
88  # Clear preview pipeline and stop timer
89  self.clearPreviewDisplay()
91  self.timer.stop()
92 
94  """Save current segment opacity and set it to zero
95  to temporarily hide the segment so that threshold preview
96  can be seen better.
97  It also restores opacity of previously previewed segment.
98  Call restorePreviewedSegmentTransparency() to restore original
99  opacity.
100  """
101  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
102  if not segmentationNode:
103  return
104  displayNode = segmentationNode.GetDisplayNode()
105  if not displayNode:
106  return
107  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
108 
109  if segmentID == self.previewedSegmentID:
110  # already previewing the current segment
111  return
112 
113  # If an other segment was previewed before, restore that.
114  if self.previewedSegmentID:
116 
117  # Make current segment fully transparent
118  if segmentID:
119  self.segment2DFillOpacity = displayNode.GetSegmentOpacity2DFill(segmentID)
120  self.segment2DOutlineOpacity = displayNode.GetSegmentOpacity2DOutline(segmentID)
121  self.previewedSegmentID = segmentID
122  displayNode.SetSegmentOpacity2DFill(segmentID, 0)
123  displayNode.SetSegmentOpacity2DOutline(segmentID, 0)
124 
126  """Restore previewed segment's opacity that was temporarily
127  made transparen by calling setCurrentSegmentTransparent()."""
128  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
129  if not segmentationNode:
130  return
131  displayNode = segmentationNode.GetDisplayNode()
132  if not displayNode:
133  return
134  if not self.previewedSegmentID:
135  # already previewing the current segment
136  return
137  displayNode.SetSegmentOpacity2DFill(self.previewedSegmentID, self.segment2DFillOpacity)
138  displayNode.SetSegmentOpacity2DOutline(self.previewedSegmentID, self.segment2DOutlineOpacity)
139  self.previewedSegmentID = None
140 
141  def setupOptionsFrame(self):
142  self.thresholdSliderLabel = qt.QLabel("Threshold Range:")
143  self.thresholdSliderLabel.setToolTip("Set the range of the background values that should be labeled.")
144  self.scriptedEffect.addOptionsWidget(self.thresholdSliderLabel)
145 
146  self.thresholdSlider = ctk.ctkRangeWidget()
147  self.thresholdSlider.spinBoxAlignment = qt.Qt.AlignTop
148  self.thresholdSlider.singleStep = 0.01
149  self.scriptedEffect.addOptionsWidget(self.thresholdSlider)
150 
151  self.autoThresholdModeSelectorComboBox = qt.QComboBox()
152  self.autoThresholdModeSelectorComboBox.addItem("auto->maximum", MODE_SET_LOWER_MAX)
153  self.autoThresholdModeSelectorComboBox.addItem("minimum->auto", MODE_SET_MIN_UPPER)
154  self.autoThresholdModeSelectorComboBox.addItem("as lower", MODE_SET_LOWER)
155  self.autoThresholdModeSelectorComboBox.addItem("as upper", MODE_SET_UPPER)
156  self.autoThresholdModeSelectorComboBox.setToolTip("How to set lower and upper threshold values. Current refers to keeping the current value.")
157 
159  self.autoThresholdMethodSelectorComboBox.addItem("Otsu", METHOD_OTSU)
160  self.autoThresholdMethodSelectorComboBox.addItem("Huang", METHOD_HUANG)
161  self.autoThresholdMethodSelectorComboBox.addItem("IsoData", METHOD_ISO_DATA)
162  # Kittler-Illingworth sometimes fails with an exception, but it does not cause any major issue,
163  # it just logs an error message and does not compute a new threshold value
164  self.autoThresholdMethodSelectorComboBox.addItem("Kittler-Illingworth", METHOD_KITTLER_ILLINGWORTH)
165  # Li sometimes crashes (index out of range error in
166  # ITK/Modules/Filtering/Thresholding/include/itkLiThresholdCalculator.hxx#L94)
167  # We can add this method back when issue is fixed in ITK.
168  #self.autoThresholdMethodSelectorComboBox.addItem("Li", METHOD_LI)
169  self.autoThresholdMethodSelectorComboBox.addItem("Maximum entropy", METHOD_MAXIMUM_ENTROPY)
170  self.autoThresholdMethodSelectorComboBox.addItem("Moments", METHOD_MOMENTS)
171  self.autoThresholdMethodSelectorComboBox.addItem("Renyi entropy", METHOD_RENYI_ENTROPY)
172  self.autoThresholdMethodSelectorComboBox.addItem("Shanbhag", METHOD_SHANBHAG)
173  self.autoThresholdMethodSelectorComboBox.addItem("Triangle", METHOD_TRIANGLE)
174  self.autoThresholdMethodSelectorComboBox.addItem("Yen", METHOD_YEN)
175  self.autoThresholdMethodSelectorComboBox.setToolTip("Select method to compute threshold value automatically.")
176 
177  self.selectPreviousAutoThresholdButton = qt.QToolButton()
178  self.selectPreviousAutoThresholdButton.text = "<"
179  self.selectPreviousAutoThresholdButton.setToolTip("Select previous thresholding method and set thresholds."
180  +" Useful for iterating through all available methods.")
181 
182  self.selectNextAutoThresholdButton = qt.QToolButton()
183  self.selectNextAutoThresholdButton.text = ">"
184  self.selectNextAutoThresholdButton.setToolTip("Select next thresholding method and set thresholds."
185  +" Useful for iterating through all available methods.")
186 
187  self.setAutoThresholdButton = qt.QPushButton("Set")
188  self.setAutoThresholdButton.setToolTip("Set threshold using selected method.")
189 
190  # qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
191  # fails on some systems, therefore set the policies using separate method calls
192  qSize = qt.QSizePolicy()
193  qSize.setHorizontalPolicy(qt.QSizePolicy.Expanding)
194  self.setAutoThresholdButton.setSizePolicy(qSize)
195 
196  autoThresholdFrame = qt.QHBoxLayout()
197  autoThresholdFrame.addWidget(self.autoThresholdModeSelectorComboBox)
198  autoThresholdFrame.addWidget(self.autoThresholdMethodSelectorComboBox)
199  autoThresholdFrame.addWidget(self.selectPreviousAutoThresholdButton)
200  autoThresholdFrame.addWidget(self.selectNextAutoThresholdButton)
201  autoThresholdFrame.addWidget(self.setAutoThresholdButton)
202 
203  autoThresholdGroupBox = ctk.ctkCollapsibleGroupBox()
204  autoThresholdGroupBox.setTitle("Automatic threshold")
205  autoThresholdGroupBox.setLayout(autoThresholdFrame)
206  autoThresholdGroupBox.collapsed = True
207  self.scriptedEffect.addOptionsWidget(autoThresholdGroupBox)
208 
209  histogramFrame = qt.QVBoxLayout()
210 
211  histogramBrushFrame = qt.QHBoxLayout()
212  histogramFrame.addLayout(histogramBrushFrame)
213 
214  self.histogramBrushButtonGroup = qt.QButtonGroup()
215  self.histogramBrushButtonGroup.setExclusive(True)
216 
217  self.boxROIButton = qt.QPushButton()
218  self.boxROIButton.setText("Box")
219  self.boxROIButton.setCheckable(True)
220  self.boxROIButton.clicked.connect(self.updateMRMLFromGUI)
221  histogramBrushFrame.addWidget(self.boxROIButton)
222  self.histogramBrushButtonGroup.addButton(self.boxROIButton)
223 
224  self.circleROIButton = qt.QPushButton()
225  self.circleROIButton.setText("Circle")
226  self.circleROIButton.setCheckable(True)
227  self.circleROIButton.clicked.connect(self.updateMRMLFromGUI)
228  histogramBrushFrame.addWidget(self.circleROIButton)
229  self.histogramBrushButtonGroup.addButton(self.circleROIButton)
230 
231  self.drawROIButton = qt.QPushButton()
232  self.drawROIButton.setText("Draw")
233  self.drawROIButton.setCheckable(True)
234  self.drawROIButton.clicked.connect(self.updateMRMLFromGUI)
235  histogramBrushFrame.addWidget(self.drawROIButton)
236  self.histogramBrushButtonGroup.addButton(self.drawROIButton)
237 
238  self.lineROIButton = qt.QPushButton()
239  self.lineROIButton.setText("Line")
240  self.lineROIButton.setCheckable(True)
241  self.lineROIButton.clicked.connect(self.updateMRMLFromGUI)
242  histogramBrushFrame.addWidget(self.lineROIButton)
243  self.histogramBrushButtonGroup.addButton(self.lineROIButton)
244 
245  self.histogramView = ctk.ctkTransferFunctionView()
246  histogramFrame.addWidget(self.histogramView)
247  scene = self.histogramView.scene()
248 
249  self.histogramFunction = vtk.vtkPiecewiseFunction()
250  self.histogramFunctionContainer = ctk.ctkVTKPiecewiseFunction(self.scriptedEffect)
251  self.histogramFunctionContainer.setPiecewiseFunction(self.histogramFunction)
252  self.histogramFunctionItem = ctk.ctkTransferFunctionBarsItem(self.histogramFunctionContainer)
253  self.histogramFunctionItem.barWidth = 1.0
254  self.histogramFunctionItem.logMode = ctk.ctkTransferFunctionBarsItem.NoLog
255  self.histogramFunctionItem.setZValue(1)
256  scene.addItem(self.histogramFunctionItem)
257 
259  self.histogramEventFilter.setThresholdEffect(self)
260  self.histogramFunctionItem.installEventFilter(self.histogramEventFilter)
261 
262  self.minMaxFunction = vtk.vtkPiecewiseFunction()
263  self.minMaxFunctionContainer = ctk.ctkVTKPiecewiseFunction(self.scriptedEffect)
264  self.minMaxFunctionContainer.setPiecewiseFunction(self.minMaxFunction)
265  self.minMaxFunctionItem = ctk.ctkTransferFunctionBarsItem(self.minMaxFunctionContainer)
266  self.minMaxFunctionItem.barWidth = 0.03
267  self.minMaxFunctionItem.logMode = ctk.ctkTransferFunctionBarsItem.NoLog
268  self.minMaxFunctionItem.barColor = qt.QColor(200, 0, 0)
269  self.minMaxFunctionItem.setZValue(0)
270  scene.addItem(self.minMaxFunctionItem)
271 
272  self.averageFunction = vtk.vtkPiecewiseFunction()
273  self.averageFunctionContainer = ctk.ctkVTKPiecewiseFunction(self.scriptedEffect)
274  self.averageFunctionContainer.setPiecewiseFunction(self.averageFunction)
275  self.averageFunctionItem = ctk.ctkTransferFunctionBarsItem(self.averageFunctionContainer)
276  self.averageFunctionItem.barWidth = 0.03
277  self.averageFunctionItem.logMode = ctk.ctkTransferFunctionBarsItem.NoLog
278  self.averageFunctionItem.barColor = qt.QColor(225, 150, 0)
279  self.averageFunctionItem.setZValue(-1)
280  scene.addItem(self.averageFunctionItem)
281 
282  # Window level gradient
283  self.backgroundColor = [1.0, 1.0, 0.7]
284  self.backgroundFunction = vtk.vtkColorTransferFunction()
285  self.backgroundFunctionContainer = ctk.ctkVTKColorTransferFunction(self.scriptedEffect)
286  self.backgroundFunctionContainer.setColorTransferFunction(self.backgroundFunction)
287  self.backgroundFunctionItem = ctk.ctkTransferFunctionGradientItem(self.backgroundFunctionContainer)
288  self.backgroundFunctionItem.setZValue(-2)
289  scene.addItem(self.backgroundFunctionItem)
290 
291  histogramItemFrame = qt.QHBoxLayout()
292  histogramFrame.addLayout(histogramItemFrame)
293 
294 
296 
297  lowerGroupBox = qt.QGroupBox("Lower")
298  lowerHistogramLayout = qt.QHBoxLayout()
299  lowerGroupBox.setLayout(lowerHistogramLayout)
300  histogramItemFrame.addWidget(lowerGroupBox)
301  self.histogramLowerMethodButtonGroup = qt.QButtonGroup()
302  self.histogramLowerMethodButtonGroup.setExclusive(True)
303 
304  self.histogramLowerThresholdMinimumButton = qt.QPushButton()
305  self.histogramLowerThresholdMinimumButton.setText("Minimum")
306  self.histogramLowerThresholdMinimumButton.setCheckable(True)
308  lowerHistogramLayout.addWidget(self.histogramLowerThresholdMinimumButton)
310 
311  self.histogramLowerThresholdLowerButton = qt.QPushButton()
312  self.histogramLowerThresholdLowerButton.setText("Lower")
313  self.histogramLowerThresholdLowerButton.setCheckable(True)
314  self.histogramLowerThresholdLowerButton.clicked.connect(self.updateMRMLFromGUI)
315  lowerHistogramLayout.addWidget(self.histogramLowerThresholdLowerButton)
317 
318  self.histogramLowerThresholdAverageButton = qt.QPushButton()
319  self.histogramLowerThresholdAverageButton.setText("Average")
320  self.histogramLowerThresholdAverageButton.setCheckable(True)
322  lowerHistogramLayout.addWidget(self.histogramLowerThresholdAverageButton)
324 
325 
327 
328  upperGroupBox = qt.QGroupBox("Upper")
329  upperHistogramLayout = qt.QHBoxLayout()
330  upperGroupBox.setLayout(upperHistogramLayout)
331  histogramItemFrame.addWidget(upperGroupBox)
332  self.histogramUpperMethodButtonGroup = qt.QButtonGroup()
333  self.histogramUpperMethodButtonGroup.setExclusive(True)
334 
335  self.histogramUpperThresholdAverageButton = qt.QPushButton()
336  self.histogramUpperThresholdAverageButton.setText("Average")
337  self.histogramUpperThresholdAverageButton.setCheckable(True)
339  upperHistogramLayout.addWidget(self.histogramUpperThresholdAverageButton)
341 
342  self.histogramUpperThresholdUpperButton = qt.QPushButton()
343  self.histogramUpperThresholdUpperButton.setText("Upper")
344  self.histogramUpperThresholdUpperButton.setCheckable(True)
345  self.histogramUpperThresholdUpperButton.clicked.connect(self.updateMRMLFromGUI)
346  upperHistogramLayout.addWidget(self.histogramUpperThresholdUpperButton)
348 
349  self.histogramUpperThresholdMaximumButton = qt.QPushButton()
350  self.histogramUpperThresholdMaximumButton.setText("Maximum")
351  self.histogramUpperThresholdMaximumButton.setCheckable(True)
353  upperHistogramLayout.addWidget(self.histogramUpperThresholdMaximumButton)
355 
356  histogramGroupBox = ctk.ctkCollapsibleGroupBox()
357  histogramGroupBox.setTitle("Local histogram")
358  histogramGroupBox.setLayout(histogramFrame)
359  histogramGroupBox.collapsed = True
360  self.scriptedEffect.addOptionsWidget(histogramGroupBox)
361 
362  self.useForPaintButton = qt.QPushButton("Use for masking")
363  self.useForPaintButton.setToolTip("Use specified intensity range for masking and switch to Paint effect.")
364  self.scriptedEffect.addOptionsWidget(self.useForPaintButton)
365 
366  self.applyButton = qt.QPushButton("Apply")
367  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
368  self.applyButton.setToolTip("Fill selected segment in regions that are in the specified intensity range.")
369  self.scriptedEffect.addOptionsWidget(self.applyButton)
370 
371  self.useForPaintButton.connect('clicked()', self.onUseForPaint)
372  self.thresholdSlider.connect('valuesChanged(double,double)', self.onThresholdValuesChanged)
373  self.autoThresholdMethodSelectorComboBox.connect("activated(int)", self.onSelectedAutoThresholdMethod)
374  self.autoThresholdModeSelectorComboBox.connect("activated(int)", self.onSelectedAutoThresholdMethod)
376  self.selectNextAutoThresholdButton.connect('clicked()', self.onSelectNextAutoThresholdMethod)
377  self.setAutoThresholdButton.connect('clicked()', self.onAutoThreshold)
378  self.applyButton.connect('clicked()', self.onApply)
379 
380  def createCursor(self, widget):
381  # Turn off effect-specific cursor for this effect
382  return slicer.util.mainWindow().cursor
383 
385  # Set scalar range of master volume image data to threshold slider
386  import vtkSegmentationCorePython as vtkSegmentationCore
387  masterImageData = self.scriptedEffect.masterVolumeImageData()
388  if masterImageData:
389  lo, hi = masterImageData.GetScalarRange()
390  self.thresholdSlider.setRange(lo, hi)
391  self.thresholdSlider.singleStep = (hi - lo) / 1000.
392  if (self.scriptedEffect.doubleParameter("MinimumThreshold") == self.scriptedEffect.doubleParameter("MaximumThreshold")):
393  # has not been initialized yet
394  self.scriptedEffect.setParameter("MinimumThreshold", lo+(hi-lo)*0.25)
395  self.scriptedEffect.setParameter("MaximumThreshold", hi)
396 
397  def layoutChanged(self):
398  self.setupPreviewDisplay()
399 
400  def setMRMLDefaults(self):
401  self.scriptedEffect.setParameterDefault("MinimumThreshold", 0.)
402  self.scriptedEffect.setParameterDefault("MaximumThreshold", 0)
403  self.scriptedEffect.setParameterDefault("AutoThresholdMethod", METHOD_OTSU)
404  self.scriptedEffect.setParameterDefault("AutoThresholdMode", MODE_SET_LOWER_MAX)
405  self.scriptedEffect.setParameterDefault(HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME, HISTOGRAM_BRUSH_TYPE_CIRCLE)
406  self.scriptedEffect.setParameterDefault(HISTOGRAM_SET_LOWER_PARAMETER_NAME, HISTOGRAM_SET_LOWER)
407  self.scriptedEffect.setParameterDefault(HISTOGRAM_SET_UPPER_PARAMETER_NAME, HISTOGRAM_SET_UPPER)
408 
409  def updateGUIFromMRML(self):
410  self.thresholdSlider.blockSignals(True)
411  self.thresholdSlider.setMinimumValue(self.scriptedEffect.doubleParameter("MinimumThreshold"))
412  self.thresholdSlider.setMaximumValue(self.scriptedEffect.doubleParameter("MaximumThreshold"))
413  self.thresholdSlider.blockSignals(False)
414 
415  autoThresholdMethod = self.autoThresholdMethodSelectorComboBox.findData(self.scriptedEffect.parameter("AutoThresholdMethod"))
416  wasBlocked = self.autoThresholdMethodSelectorComboBox.blockSignals(True)
417  self.autoThresholdMethodSelectorComboBox.setCurrentIndex(autoThresholdMethod)
418  self.autoThresholdMethodSelectorComboBox.blockSignals(wasBlocked)
419 
420  autoThresholdMode = self.autoThresholdModeSelectorComboBox.findData(self.scriptedEffect.parameter("AutoThresholdMode"))
421  wasBlocked = self.autoThresholdModeSelectorComboBox.blockSignals(True)
422  self.autoThresholdModeSelectorComboBox.setCurrentIndex(autoThresholdMode)
423  self.autoThresholdModeSelectorComboBox.blockSignals(wasBlocked)
424 
425  histogramBrushType = self.scriptedEffect.parameter(HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME)
426  self.boxROIButton.checked = (histogramBrushType == HISTOGRAM_BRUSH_TYPE_BOX)
427  self.circleROIButton.checked = (histogramBrushType == HISTOGRAM_BRUSH_TYPE_CIRCLE)
428  self.drawROIButton.checked = (histogramBrushType == HISTOGRAM_BRUSH_TYPE_DRAW)
429  self.lineROIButton.checked = (histogramBrushType == HISTOGRAM_BRUSH_TYPE_LINE)
430 
431  histogramSetModeLower = self.scriptedEffect.parameter(HISTOGRAM_SET_LOWER_PARAMETER_NAME)
432  self.histogramLowerThresholdMinimumButton.checked = (histogramSetModeLower == HISTOGRAM_SET_MINIMUM)
433  self.histogramLowerThresholdLowerButton.checked = (histogramSetModeLower == HISTOGRAM_SET_LOWER)
434  self.histogramLowerThresholdAverageButton.checked = (histogramSetModeLower == HISTOGRAM_SET_AVERAGE)
435 
436  histogramSetModeUpper = self.scriptedEffect.parameter(HISTOGRAM_SET_UPPER_PARAMETER_NAME)
437  self.histogramUpperThresholdAverageButton.checked = (histogramSetModeUpper == HISTOGRAM_SET_AVERAGE)
438  self.histogramUpperThresholdUpperButton.checked = (histogramSetModeUpper == HISTOGRAM_SET_UPPER)
439  self.histogramUpperThresholdMaximumButton.checked = (histogramSetModeUpper == HISTOGRAM_SET_MAXIMUM)
440 
442 
443  def updateMRMLFromGUI(self):
444  with slicer.util.NodeModify(self.scriptedEffect.parameterSetNode()):
445  self.scriptedEffect.setParameter("MinimumThreshold", self.thresholdSlider.minimumValue)
446  self.scriptedEffect.setParameter("MaximumThreshold", self.thresholdSlider.maximumValue)
447 
448  methodIndex = self.autoThresholdMethodSelectorComboBox.currentIndex
449  autoThresholdMethod = self.autoThresholdMethodSelectorComboBox.itemData(methodIndex)
450  self.scriptedEffect.setParameter("AutoThresholdMethod", autoThresholdMethod)
451 
452  modeIndex = self.autoThresholdModeSelectorComboBox.currentIndex
453  autoThresholdMode = self.autoThresholdModeSelectorComboBox.itemData(modeIndex)
454  self.scriptedEffect.setParameter("AutoThresholdMode", autoThresholdMode)
455 
456  histogramParameterChanged = False
457 
458  histogramBrushType = HISTOGRAM_BRUSH_TYPE_CIRCLE
459  if self.boxROIButton.checked:
460  histogramBrushType = HISTOGRAM_BRUSH_TYPE_BOX
461  elif self.circleROIButton.checked:
462  histogramBrushType = HISTOGRAM_BRUSH_TYPE_CIRCLE
463  elif self.drawROIButton.checked:
464  histogramBrushType = HISTOGRAM_BRUSH_TYPE_DRAW
465  elif self.lineROIButton.checked:
466  histogramBrushType = HISTOGRAM_BRUSH_TYPE_LINE
467 
468  if histogramBrushType != self.scriptedEffect.parameter(HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME):
469  self.scriptedEffect.setParameter(HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME, histogramBrushType)
470  histogramParameterChanged = True
471 
472  histogramSetModeLower = HISTOGRAM_SET_LOWER
473  if self.histogramLowerThresholdMinimumButton.checked:
474  histogramSetModeLower = HISTOGRAM_SET_MINIMUM
475  elif self.histogramLowerThresholdLowerButton.checked:
476  histogramSetModeLower = HISTOGRAM_SET_LOWER
477  elif self.histogramLowerThresholdAverageButton.checked:
478  histogramSetModeLower = HISTOGRAM_SET_AVERAGE
479  if histogramSetModeLower != self.scriptedEffect.parameter(HISTOGRAM_SET_LOWER_PARAMETER_NAME):
480  self.scriptedEffect.setParameter(HISTOGRAM_SET_LOWER_PARAMETER_NAME, histogramSetModeLower)
481  histogramParameterChanged = True
482 
483  histogramSetModeUpper = HISTOGRAM_SET_UPPER
484  if self.histogramUpperThresholdAverageButton.checked:
485  histogramSetModeUpper = HISTOGRAM_SET_AVERAGE
486  elif self.histogramUpperThresholdUpperButton.checked:
487  histogramSetModeUpper = HISTOGRAM_SET_UPPER
488  elif self.histogramUpperThresholdMaximumButton.checked:
489  histogramSetModeUpper = HISTOGRAM_SET_MAXIMUM
490  if histogramSetModeUpper != self.scriptedEffect.parameter(HISTOGRAM_SET_UPPER_PARAMETER_NAME):
491  self.scriptedEffect.setParameter(HISTOGRAM_SET_UPPER_PARAMETER_NAME, histogramSetModeUpper)
492  histogramParameterChanged = True
493 
494  if histogramParameterChanged:
495  self.updateHistogram()
496 
497  #
498  # Effect specific methods (the above ones are the API methods to override)
499  #
500  def onThresholdValuesChanged(self,min,max):
501  self.scriptedEffect.updateMRMLFromGUI()
502 
503  def onUseForPaint(self):
504  parameterSetNode = self.scriptedEffect.parameterSetNode()
505  parameterSetNode.MasterVolumeIntensityMaskOn()
506  parameterSetNode.SetMasterVolumeIntensityMaskRange(self.thresholdSlider.minimumValue, self.thresholdSlider.maximumValue)
507  # Switch to paint effect
508  self.scriptedEffect.selectEffect("Paint")
509 
511  self.autoThresholdMethodSelectorComboBox.currentIndex = (self.autoThresholdMethodSelectorComboBox.currentIndex - 1) \
514 
516  self.autoThresholdMethodSelectorComboBox.currentIndex = (self.autoThresholdMethodSelectorComboBox.currentIndex + 1) \
519 
521  self.updateMRMLFromGUI()
522  self.onAutoThreshold()
523  self.updateGUIFromMRML()
524 
525  def onAutoThreshold(self):
526  autoThresholdMethod = self.scriptedEffect.parameter("AutoThresholdMethod")
527  autoThresholdMode = self.scriptedEffect.parameter("AutoThresholdMode")
528  self.autoThreshold(autoThresholdMethod, autoThresholdMode)
529 
530  def autoThreshold(self, autoThresholdMethod, autoThresholdMode):
531  if autoThresholdMethod == METHOD_HUANG:
532  self.autoThresholdCalculator.SetMethodToHuang()
533  elif autoThresholdMethod == METHOD_INTERMODES:
534  self.autoThresholdCalculator.SetMethodToIntermodes()
535  elif autoThresholdMethod == METHOD_ISO_DATA:
536  self.autoThresholdCalculator.SetMethodToIsoData()
537  elif autoThresholdMethod == METHOD_KITTLER_ILLINGWORTH:
538  self.autoThresholdCalculator.SetMethodToKittlerIllingworth()
539  elif autoThresholdMethod == METHOD_LI:
540  self.autoThresholdCalculator.SetMethodToLi()
541  elif autoThresholdMethod == METHOD_MAXIMUM_ENTROPY:
542  self.autoThresholdCalculator.SetMethodToMaximumEntropy()
543  elif autoThresholdMethod == METHOD_MOMENTS:
544  self.autoThresholdCalculator.SetMethodToMoments()
545  elif autoThresholdMethod == METHOD_OTSU:
546  self.autoThresholdCalculator.SetMethodToOtsu()
547  elif autoThresholdMethod == METHOD_RENYI_ENTROPY:
548  self.autoThresholdCalculator.SetMethodToRenyiEntropy()
549  elif autoThresholdMethod == METHOD_SHANBHAG:
550  self.autoThresholdCalculator.SetMethodToShanbhag()
551  elif autoThresholdMethod == METHOD_TRIANGLE:
552  self.autoThresholdCalculator.SetMethodToTriangle()
553  elif autoThresholdMethod == METHOD_YEN:
554  self.autoThresholdCalculator.SetMethodToYen()
555  else:
556  logging.error("Unknown AutoThresholdMethod {0}".format(autoThresholdMethod))
557 
558  masterImageData = self.scriptedEffect.masterVolumeImageData()
559  self.autoThresholdCalculator.SetInputData(masterImageData)
560 
561  self.autoThresholdCalculator.Update()
562  computedThreshold = self.autoThresholdCalculator.GetThreshold()
563 
564  masterVolumeMin, masterVolumeMax = masterImageData.GetScalarRange()
565 
566  if autoThresholdMode == MODE_SET_UPPER:
567  self.scriptedEffect.setParameter("MaximumThreshold", computedThreshold)
568  elif autoThresholdMode == MODE_SET_LOWER:
569  self.scriptedEffect.setParameter("MinimumThreshold", computedThreshold)
570  elif autoThresholdMode == MODE_SET_MIN_UPPER:
571  self.scriptedEffect.setParameter("MinimumThreshold", masterVolumeMin)
572  self.scriptedEffect.setParameter("MaximumThreshold", computedThreshold)
573  elif autoThresholdMode == MODE_SET_LOWER_MAX:
574  self.scriptedEffect.setParameter("MinimumThreshold", computedThreshold)
575  self.scriptedEffect.setParameter("MaximumThreshold", masterVolumeMax)
576  else:
577  logging.error("Unknown AutoThresholdMode {0}".format(autoThresholdMode))
578 
579  def onApply(self):
580  if not self.scriptedEffect.confirmCurrentSegmentVisible():
581  return
582 
583  try:
584  # Get master volume image data
585  import vtkSegmentationCorePython as vtkSegmentationCore
586  masterImageData = self.scriptedEffect.masterVolumeImageData()
587  # Get modifier labelmap
588  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
589  originalImageToWorldMatrix = vtk.vtkMatrix4x4()
590  modifierLabelmap.GetImageToWorldMatrix(originalImageToWorldMatrix)
591  # Get parameters
592  min = self.scriptedEffect.doubleParameter("MinimumThreshold")
593  max = self.scriptedEffect.doubleParameter("MaximumThreshold")
594 
595  self.scriptedEffect.saveStateForUndo()
596 
597  # Perform thresholding
598  thresh = vtk.vtkImageThreshold()
599  thresh.SetInputData(masterImageData)
600  thresh.ThresholdBetween(min, max)
601  thresh.SetInValue(1)
602  thresh.SetOutValue(0)
603  thresh.SetOutputScalarType(modifierLabelmap.GetScalarType())
604  thresh.Update()
605  modifierLabelmap.DeepCopy(thresh.GetOutput())
606  except IndexError:
607  logging.error('apply: Failed to threshold master volume!')
608  pass
609 
610  # Apply changes
611  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
612 
613  # De-select effect
614  self.scriptedEffect.selectEffect("")
615 
617  for sliceWidget, pipeline in self.previewPipelines.items():
618  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
619  self.previewPipelines = {}
620 
622  if self.histogramPipeline is None:
623  return
624  self.histogramPipeline.removeActors()
625  self.histogramPipeline = None
626 
628  # Clear previous pipelines before setting up the new ones
629  self.clearPreviewDisplay()
630 
631  layoutManager = slicer.app.layoutManager()
632  if layoutManager is None:
633  return
634 
635  # Add a pipeline for each 2D slice view
636  for sliceViewName in layoutManager.sliceViewNames():
637  sliceWidget = layoutManager.sliceWidget(sliceViewName)
638  if not self.scriptedEffect.segmentationDisplayableInView(sliceWidget.mrmlSliceNode()):
639  continue
640  renderer = self.scriptedEffect.renderer(sliceWidget)
641  if renderer is None:
642  logging.error("setupPreviewDisplay: Failed to get renderer!")
643  continue
644 
645  # Create pipeline
646  pipeline = PreviewPipeline()
647  self.previewPipelines[sliceWidget] = pipeline
648 
649  # Add actor
650  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
651 
652  def preview(self):
653 
654  opacity = 0.5 + self.previewState / (2. * self.previewSteps)
655  min = self.scriptedEffect.doubleParameter("MinimumThreshold")
656  max = self.scriptedEffect.doubleParameter("MaximumThreshold")
657 
658  # Get color of edited segment
659  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
660  if not segmentationNode:
661  # scene was closed while preview was active
662  return
663  displayNode = segmentationNode.GetDisplayNode()
664  if displayNode is None:
665  logging.error("preview: Invalid segmentation display node!")
666  color = [0.5,0.5,0.5]
667  segmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
668 
669  # Make sure we keep the currently selected segment hidden (the user may have changed selection)
670  if segmentID != self.previewedSegmentID:
672 
673  r,g,b = segmentationNode.GetSegmentation().GetSegment(segmentID).GetColor()
674 
675  # Set values to pipelines
676  for sliceWidget in self.previewPipelines:
677  pipeline = self.previewPipelines[sliceWidget]
678  pipeline.lookupTable.SetTableValue(1, r, g, b, opacity)
679  layerLogic = self.getMasterVolumeLayerLogic(sliceWidget)
680  pipeline.thresholdFilter.SetInputConnection(layerLogic.GetReslice().GetOutputPort())
681  pipeline.thresholdFilter.ThresholdBetween(min, max)
682  pipeline.actor.VisibilityOn()
683  sliceWidget.sliceView().scheduleRender()
684 
685  self.previewState += self.previewStep
686  if self.previewState >= self.previewSteps:
687  self.previewStep = -1
688  if self.previewState <= 0:
689  self.previewStep = 1
690 
691  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
692  abortEvent = False
693 
694  masterImageData = self.scriptedEffect.masterVolumeImageData()
695  if masterImageData is None:
696  return abortEvent
697 
698  # Only allow for slice views
699  if viewWidget.className() != "qMRMLSliceWidget":
700  return abortEvent
701 
702  # Clicking in a view should remove all previous pipelines
703  if eventId == vtk.vtkCommand.LeftButtonPressEvent and not callerInteractor.GetShiftKey():
704  self.clearHistogramDisplay()
705 
706  if self.histogramPipeline is None:
707  self.createHistogramPipeline(viewWidget)
708 
709  xy = callerInteractor.GetEventPosition()
710  ras = self.xyToRas(xy, viewWidget)
711 
712  if eventId == vtk.vtkCommand.LeftButtonPressEvent and not callerInteractor.GetShiftKey():
713  self.histogramPipeline.state = HISTOGRAM_STATE_MOVING
714  self.histogramPipeline.addPoint(ras)
715  self.updateHistogram()
716  abortEvent = True
717  elif eventId == vtk.vtkCommand.LeftButtonReleaseEvent:
718  self.histogramPipeline.state = HISTOGRAM_STATE_PLACED
719  elif eventId == vtk.vtkCommand.MouseMoveEvent:
720  if self.histogramPipeline.state == HISTOGRAM_STATE_MOVING:
721  self.histogramPipeline.addPoint(ras)
722  self.updateHistogram()
723  return abortEvent
724 
725  def createHistogramPipeline(self, sliceWidget):
726  brushType = HISTOGRAM_BRUSH_TYPE_CIRCLE
727  if self.boxROIButton.checked:
728  brushType = HISTOGRAM_BRUSH_TYPE_BOX
729  elif self.drawROIButton.checked:
730  brushType = HISTOGRAM_BRUSH_TYPE_DRAW
731  elif self.lineROIButton.checked:
732  brushType = HISTOGRAM_BRUSH_TYPE_LINE
733  pipeline = HistogramPipeline(self, self.scriptedEffect, sliceWidget, brushType)
734  self.histogramPipeline = pipeline
735 
736  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
737  if self.histogramPipeline is not None:
738  self.histogramPipeline.updateBrushModel()
739 
740  def onHistogramMouseClick(self, pos, button):
741  self.selectionStartPosition = pos
742  self.selectionEndPosition = pos
743  if (button == qt.Qt.RightButton):
744  self.selectionStartPosition = None
745  self.selectionEndPosition = None
746  self.minMaxFunction.RemoveAllPoints()
747  self.averageFunction.RemoveAllPoints()
748  self.updateHistogram()
749 
750  def onHistogramMouseMove(self, pos, button):
751  self.selectionEndPosition = pos
752  if (button == qt.Qt.RightButton):
753  return
754  self.updateHistogram()
755 
756  def onHistogramMouseRelease(self, pos, button):
757  self.selectionEndPosition = pos
758  if (button == qt.Qt.RightButton):
759  return
760  self.updateHistogram()
761 
762  def getMasterVolumeLayerLogic(self, sliceWidget):
763  masterVolumeNode = self.scriptedEffect.parameterSetNode().GetMasterVolumeNode()
764  sliceLogic = sliceWidget.sliceLogic()
765 
766  backgroundLogic = sliceLogic.GetBackgroundLayer()
767  backgroundVolumeNode = backgroundLogic.GetVolumeNode()
768  if masterVolumeNode == backgroundVolumeNode:
769  return backgroundLogic
770 
771  foregroundLogic = sliceLogic.GetForegroundLayer()
772  foregroundVolumeNode = foregroundLogic.GetVolumeNode()
773  if masterVolumeNode == foregroundVolumeNode:
774  return foregroundLogic
775 
776  logging.warning("Master volume is not set as either the foreground or background")
777 
778  foregroundOpacity = 0.0
779  if foregroundVolumeNode:
780  compositeNode = sliceLogic.GetSliceCompositeNode()
781  foregroundOpacity = compositeNode.GetForegroundOpacity()
782 
783  if foregroundOpacity > 0.5:
784  return foregroundLogic
785 
786  return backgroundLogic
787 
788  def updateHistogram(self):
789  masterImageData = self.scriptedEffect.masterVolumeImageData()
790  if masterImageData is None or self.histogramPipeline is None:
791  self.histogramFunction.RemoveAllPoints()
792  return
793 
794  # Ensure that the brush is in the correct location
795  self.histogramPipeline.updateBrushModel()
796 
797  self.stencil.SetInputConnection(self.histogramPipeline.worldToSliceTransformer.GetOutputPort())
798 
799  self.histogramPipeline.worldToSliceTransformer.Update()
800  brushPolydata = self.histogramPipeline.worldToSliceTransformer.GetOutput()
801  brushBounds = brushPolydata.GetBounds()
802  brushExtent = [0, -1, 0, -1, 0, -1]
803  for i in range(3):
804  brushExtent[2*i] = vtk.vtkMath.Floor(brushBounds[2*i])
805  brushExtent[2*i+1] = vtk.vtkMath.Ceil(brushBounds[2*i+1])
806  if brushExtent[0] > brushExtent[1] or brushExtent[2] > brushExtent[3] or brushExtent[4] > brushExtent[5]:
807  self.histogramFunction.RemoveAllPoints()
808  return
809 
810  layerLogic = self.getMasterVolumeLayerLogic(self.histogramPipeline.sliceWidget)
811  self.reslice.SetInputConnection(layerLogic.GetReslice().GetInputConnection(0, 0))
812  self.reslice.SetResliceTransform(layerLogic.GetReslice().GetResliceTransform())
813  self.reslice.SetInterpolationMode(layerLogic.GetReslice().GetInterpolationMode())
814  self.reslice.SetOutputExtent(brushExtent)
815 
816  maxNumberOfBins = 1000
817  masterImageData = self.scriptedEffect.masterVolumeImageData()
818  scalarRange = masterImageData.GetScalarRange()
819  scalarType = masterImageData.GetScalarType()
820  if scalarType == vtk.VTK_FLOAT or scalarType == vtk.VTK_DOUBLE:
821  numberOfBins = maxNumberOfBins
822  else:
823  numberOfBins = int(scalarRange[1] - scalarRange[0]) + 1
824  if numberOfBins > maxNumberOfBins:
825  numberOfBins = maxNumberOfBins
826  binSpacing = (scalarRange[1] - scalarRange[0] + 1) / numberOfBins
827 
828  self.imageAccumulate.SetComponentExtent(0, numberOfBins - 1, 0, 0, 0, 0)
829  self.imageAccumulate.SetComponentSpacing(binSpacing, binSpacing, binSpacing)
830  self.imageAccumulate.SetComponentOrigin(scalarRange[0], scalarRange[0], scalarRange[0])
831 
832  self.imageAccumulate.Update()
833 
834  self.histogramFunction.RemoveAllPoints()
835  tableSize = self.imageAccumulate.GetOutput().GetPointData().GetScalars().GetNumberOfTuples()
836  for i in range(tableSize):
837  value = self.imageAccumulate.GetOutput().GetPointData().GetScalars().GetTuple1(i)
838  self.histogramFunction.AddPoint(binSpacing * i + scalarRange[0], value)
839  self.histogramFunction.AdjustRange(scalarRange)
840 
841  lower = self.imageAccumulate.GetMin()[0]
842  average = self.imageAccumulate.GetMean()[0]
843  upper = self.imageAccumulate.GetMax()[0]
844 
845  # If there is a selection, then set the threshold based on that
846  if self.selectionStartPosition is not None and self.selectionEndPosition is not None:
847 
848  # Clamp selection based on scalar range
849  startX = min(scalarRange[1], max(scalarRange[0], self.selectionStartPosition[0]))
850  endX = min(scalarRange[1], max(scalarRange[0], self.selectionEndPosition[0]))
851 
852  lower = min(startX, endX)
853  average = (startX + endX) / 2.0
854  upper = max(startX, endX)
855 
856  epsilon = 0.00001
857  self.minMaxFunction.RemoveAllPoints()
858  self.minMaxFunction.AddPoint(lower - epsilon, 0.0)
859  self.minMaxFunction.AddPoint(lower, 1.0)
860  self.minMaxFunction.AddPoint(lower + epsilon, 0.0)
861  self.minMaxFunction.AddPoint(upper - epsilon, 0.0)
862  self.minMaxFunction.AddPoint(upper, 1.0)
863  self.minMaxFunction.AddPoint(upper + epsilon, 0.0)
864  self.minMaxFunction.AdjustRange(scalarRange)
865 
866  self.averageFunction.RemoveAllPoints()
867  self.averageFunction.AddPoint(average - epsilon, 0.0)
868  self.averageFunction.AddPoint(average, 1.0)
869  self.averageFunction.AddPoint(average + epsilon, 0.0)
870  self.averageFunction.AdjustRange(scalarRange)
871 
872  minimumThreshold = lower
873  maximumThreshold = upper
874 
875  histogramSetModeLower = self.scriptedEffect.parameter(HISTOGRAM_SET_LOWER_PARAMETER_NAME)
876  if histogramSetModeLower == HISTOGRAM_SET_MINIMUM:
877  minimumThreshold = scalarRange[0]
878  elif histogramSetModeLower == HISTOGRAM_SET_LOWER:
879  minimumThreshold = lower
880  elif histogramSetModeLower == HISTOGRAM_SET_AVERAGE:
881  minimumThreshold = average
882 
883  histogramSetModeUpper = self.scriptedEffect.parameter(HISTOGRAM_SET_UPPER_PARAMETER_NAME)
884  if histogramSetModeUpper == HISTOGRAM_SET_AVERAGE:
885  maximumThreshold = average
886  elif histogramSetModeUpper == HISTOGRAM_SET_UPPER:
887  maximumThreshold = upper
888  elif histogramSetModeUpper == HISTOGRAM_SET_MAXIMUM:
889  maximumThreshold = scalarRange[1]
890 
891  self.scriptedEffect.setParameter("MinimumThreshold", minimumThreshold)
892  self.scriptedEffect.setParameter("MaximumThreshold", maximumThreshold)
893 
895  self.backgroundFunction.RemoveAllPoints()
896 
897  masterImageData = self.scriptedEffect.masterVolumeImageData()
898  if masterImageData is None:
899  return
900 
901  scalarRange = masterImageData.GetScalarRange()
902 
903  epsilon = 0.00001
904  low = self.scriptedEffect.doubleParameter("MinimumThreshold")
905  upper = self.scriptedEffect.doubleParameter("MaximumThreshold")
906  low = max(scalarRange[0] + epsilon, low)
907  upper = min(scalarRange[1] - epsilon, upper)
908 
909  self.backgroundFunction.AddRGBPoint(scalarRange[0], 1, 1, 1)
910  self.backgroundFunction.AddRGBPoint(low - epsilon, 1, 1, 1)
911  self.backgroundFunction.AddRGBPoint(low, self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2])
912  self.backgroundFunction.AddRGBPoint(upper, self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2])
913  self.backgroundFunction.AddRGBPoint(upper + epsilon, 1, 1, 1)
914  self.backgroundFunction.AddRGBPoint(scalarRange[1], 1, 1, 1)
915  self.backgroundFunction.SetAlpha(1.0)
916  self.backgroundFunction.Build()
917 
918 #
919 # PreviewPipeline
920 #
921 class PreviewPipeline(object):
922  """ Visualization objects and pipeline for each slice view for threshold preview
923  """
924 
925  def __init__(self):
926  self.lookupTable = vtk.vtkLookupTable()
927  self.lookupTable.SetRampToLinear()
928  self.lookupTable.SetNumberOfTableValues(2)
929  self.lookupTable.SetTableRange(0, 1)
930  self.lookupTable.SetTableValue(0, 0, 0, 0, 0)
931  self.colorMapper = vtk.vtkImageMapToRGBA()
932  self.colorMapper.SetOutputFormatToRGBA()
933  self.colorMapper.SetLookupTable(self.lookupTable)
934  self.thresholdFilter = vtk.vtkImageThreshold()
935  self.thresholdFilter.SetInValue(1)
936  self.thresholdFilter.SetOutValue(0)
937  self.thresholdFilter.SetOutputScalarTypeToUnsignedChar()
938 
939  # Feedback actor
940  self.mapper = vtk.vtkImageMapper()
941  self.dummyImage = vtk.vtkImageData()
942  self.dummyImage.AllocateScalars(vtk.VTK_UNSIGNED_INT, 1)
943  self.mapper.SetInputData(self.dummyImage)
944  self.actor = vtk.vtkActor2D()
945  self.actor.VisibilityOff()
946  self.actor.SetMapper(self.mapper)
947  self.mapper.SetColorWindow(255)
948  self.mapper.SetColorLevel(128)
949 
950  # Setup pipeline
951  self.colorMapper.SetInputConnection(self.thresholdFilter.GetOutputPort())
952  self.mapper.SetInputConnection(self.colorMapper.GetOutputPort())
953 
954 
958 class HistogramEventFilter(qt.QObject):
959  thresholdEffect = None
960  def setThresholdEffect(self, thresholdEffect):
961  self.thresholdEffect = thresholdEffect
962 
963  def eventFilter(self, object, event):
964  if self.thresholdEffect is None:
965  return
966 
967  if (event.type() == qt.QEvent.GraphicsSceneMousePress or
968  event.type() == qt.QEvent.GraphicsSceneMouseMove or
969  event.type() == qt.QEvent.GraphicsSceneMouseRelease):
970  transferFunction = object.transferFunction()
971  if transferFunction is None:
972  return
973 
974  representation = transferFunction.representation()
975  x = representation.mapXFromScene(event.pos().x())
976  y = representation.mapYFromScene(event.pos().y())
977  position = (x, y)
978 
979  if event.type() == qt.QEvent.GraphicsSceneMousePress:
980  self.thresholdEffect.onHistogramMouseClick(position, event.button())
981  elif event.type() == qt.QEvent.GraphicsSceneMouseMove:
982  self.thresholdEffect.onHistogramMouseMove(position, event.button())
983  elif event.type() == qt.QEvent.GraphicsSceneMouseRelease:
984  self.thresholdEffect.onHistogramMouseRelease(position, event.button())
985  return True
986  return False
987 
988 
989 class HistogramPipeline(object):
990 
991  def __init__(self, thresholdEffect, scriptedEffect, sliceWidget, brushMode):
992  self.thresholdEffect = thresholdEffect
993  self.scriptedEffect = scriptedEffect
994  self.sliceWidget = sliceWidget
995  self.brushMode = brushMode
996  self.state = HISTOGRAM_STATE_OFF
997 
998  self.point1 = None
999  self.point2 = None
1000 
1001  # Actor setup
1002  self.brushCylinderSource = vtk.vtkCylinderSource()
1003  self.brushCylinderSource.SetResolution(32)
1004 
1005  self.brushCubeSource = vtk.vtkCubeSource()
1006 
1007  self.brushLineSource = vtk.vtkLineSource()
1008  self.brushTubeSource = vtk.vtkTubeFilter()
1009  self.brushTubeSource.SetInputConnection(self.brushLineSource.GetOutputPort())
1010  self.brushTubeSource.SetNumberOfSides(50)
1011  self.brushTubeSource.SetCapping(True)
1012 
1013  self.brushToWorldOriginTransform = vtk.vtkTransform()
1014  self.brushToWorldOriginTransformer = vtk.vtkTransformPolyDataFilter()
1016  self.brushToWorldOriginTransformer.SetInputConnection(self.brushCylinderSource.GetOutputPort())
1017 
1018  self.normalFilter = vtk.vtkPolyDataNormals()
1019  self.normalFilter.AutoOrientNormalsOn()
1020  self.normalFilter.SetInputConnection(self.brushToWorldOriginTransformer.GetOutputPort())
1021 
1022  # Brush to RAS transform
1023  self.worldOriginToWorldTransform = vtk.vtkTransform()
1024  self.worldOriginToWorldTransformer = vtk.vtkTransformPolyDataFilter()
1026  self.worldOriginToWorldTransformer.SetInputConnection(self.normalFilter.GetOutputPort())
1027 
1028  # RAS to XY transform
1029  self.worldToSliceTransform = vtk.vtkTransform()
1030  self.worldToSliceTransformer = vtk.vtkTransformPolyDataFilter()
1031  self.worldToSliceTransformer.SetTransform(self.worldToSliceTransform)
1032  self.worldToSliceTransformer.SetInputConnection(self.worldOriginToWorldTransformer.GetOutputPort())
1033 
1034  # Cutting takes place in XY coordinates
1035  self.slicePlane = vtk.vtkPlane()
1036  self.slicePlane.SetNormal(0, 0, 1)
1037  self.slicePlane.SetOrigin(0, 0, 0)
1038  self.cutter = vtk.vtkCutter()
1039  self.cutter.SetCutFunction(self.slicePlane)
1040  self.cutter.SetInputConnection(self.worldToSliceTransformer.GetOutputPort())
1041 
1042  self.rasPoints = vtk.vtkPoints()
1043  lines = vtk.vtkCellArray()
1044  self.polyData = vtk.vtkPolyData()
1045  self.polyData.SetPoints(self.rasPoints)
1046  self.polyData.SetLines(lines)
1047 
1048  # Thin line
1049  self.thinRASPoints = vtk.vtkPoints()
1050  thinLines = vtk.vtkCellArray()
1051  self.thinPolyData = vtk.vtkPolyData()
1052  self.thinPolyData.SetPoints(self.rasPoints)
1053  self.thinPolyData.SetLines(thinLines)
1054 
1055  self.mapper = vtk.vtkPolyDataMapper2D()
1056  self.mapper.SetInputConnection(self.cutter.GetOutputPort())
1057 
1058  # Add actor
1059  self.actor = vtk.vtkActor2D()
1060  self.actor.SetMapper(self.mapper)
1061  actorProperty = self.actor.GetProperty()
1062  actorProperty.SetColor(1,1,0)
1063  actorProperty.SetLineWidth(2)
1064  renderer = self.scriptedEffect.renderer(sliceWidget)
1065  if renderer is None:
1066  logging.error("pipelineForWidget: Failed to get renderer!")
1067  return None
1068  self.scriptedEffect.addActor2D(sliceWidget, self.actor)
1069 
1070  self.thinActor = None
1071  if self.brushMode == HISTOGRAM_BRUSH_TYPE_DRAW:
1072  self.worldToSliceTransformer.SetInputData(self.polyData)
1073  self.mapper.SetInputConnection(self.worldToSliceTransformer.GetOutputPort())
1074 
1075  self.thinWorldToSliceTransformer = vtk.vtkTransformPolyDataFilter()
1076  self.thinWorldToSliceTransformer.SetInputData(self.thinPolyData)
1077  self.thinWorldToSliceTransformer.SetTransform(self.worldToSliceTransform)
1078 
1079  self.thinMapper = vtk.vtkPolyDataMapper2D()
1080  self.thinMapper.SetInputConnection(self.thinWorldToSliceTransformer.GetOutputPort())
1081 
1082  self.thinActor = vtk.vtkActor2D()
1083  self.thinActor.SetMapper(self.thinMapper)
1084  thinActorProperty = self.thinActor.GetProperty()
1085  thinActorProperty.SetColor(1,1,0)
1086  thinActorProperty.SetLineWidth(1)
1087  self.scriptedEffect.addActor2D(sliceWidget, self.thinActor)
1088  elif self.brushMode == HISTOGRAM_BRUSH_TYPE_LINE:
1089  self.worldToSliceTransformer.SetInputConnection(self.brushTubeSource.GetOutputPort())
1090 
1091  def removeActors(self):
1092  if self.actor is not None:
1093  self.scriptedEffect.removeActor2D(self.sliceWidget, self.actor)
1094  if self.thinActor is not None:
1095  self.scriptedEffect.removeActor2D(self.sliceWidget, self.thinActor)
1096 
1097  def setPoint1(self, ras):
1098  self.point1 = ras
1099  self.updateBrushModel()
1100 
1101  def setPoint2(self, ras):
1102  self.point2 = ras
1103  self.updateBrushModel()
1104 
1105  def addPoint(self, ras):
1106  if self.brushMode == HISTOGRAM_BRUSH_TYPE_DRAW:
1107  newPointIndex = self.rasPoints.InsertNextPoint(ras)
1108  previousPointIndex = newPointIndex - 1
1109  if (previousPointIndex >= 0):
1110  idList = vtk.vtkIdList()
1111  idList.InsertNextId(previousPointIndex)
1112  idList.InsertNextId(newPointIndex)
1113  self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
1114 
1115  thinLines = self.thinPolyData.GetLines()
1116  thinLines.Initialize()
1117  idList = vtk.vtkIdList()
1118  idList.InsertNextId(newPointIndex)
1119  idList.InsertNextId(0)
1120  self.thinPolyData.InsertNextCell(vtk.VTK_LINE, idList)
1121 
1122  else:
1123  if self.point1 is None:
1124  self.setPoint1(ras)
1125  self.setPoint2(ras)
1126 
1127  def updateBrushModel(self):
1128  if self.brushMode != HISTOGRAM_BRUSH_TYPE_DRAW and (self.point1 is None or self.point2 is None):
1129  return
1130 
1131  # Update slice cutting plane position and orientation
1132  sliceXyToRas = self.sliceWidget.sliceLogic().GetSliceNode().GetXYToRAS()
1133  rasToSliceXy = vtk.vtkMatrix4x4()
1134  vtk.vtkMatrix4x4.Invert(sliceXyToRas, rasToSliceXy)
1135  self.worldToSliceTransform.SetMatrix(rasToSliceXy)
1136 
1137  # brush is rotated to the slice widget plane
1138  brushToWorldOriginTransformMatrix = vtk.vtkMatrix4x4()
1139  brushToWorldOriginTransformMatrix.DeepCopy(self.sliceWidget.sliceLogic().GetSliceNode().GetSliceToRAS())
1140  brushToWorldOriginTransformMatrix.SetElement(0,3, 0)
1141  brushToWorldOriginTransformMatrix.SetElement(1,3, 0)
1142  brushToWorldOriginTransformMatrix.SetElement(2,3, 0)
1143 
1144  self.brushToWorldOriginTransform.Identity()
1145  self.brushToWorldOriginTransform.Concatenate(brushToWorldOriginTransformMatrix)
1146  self.brushToWorldOriginTransform.RotateX(90) # cylinder's long axis is the Y axis, we need to rotate it to Z axis
1147 
1148  sliceSpacingMm = self.scriptedEffect.sliceSpacing(self.sliceWidget)
1149 
1150  center = [0,0,0]
1151  if self.brushMode == HISTOGRAM_BRUSH_TYPE_CIRCLE:
1152  center = self.point1
1153 
1154  point1ToPoint2 = [0,0,0]
1155  vtk.vtkMath.Subtract(self.point1, self.point2, point1ToPoint2)
1156  radius = vtk.vtkMath.Normalize(point1ToPoint2)
1157 
1158  self.brushToWorldOriginTransformer.SetInputConnection(self.brushCylinderSource.GetOutputPort())
1159  self.brushCylinderSource.SetRadius(radius)
1160  self.brushCylinderSource.SetHeight(sliceSpacingMm)
1161 
1162  elif self.brushMode == HISTOGRAM_BRUSH_TYPE_BOX:
1163  self.brushToWorldOriginTransformer.SetInputConnection(self.brushCubeSource.GetOutputPort())
1164 
1165  length = [0,0,0]
1166  for i in range(3):
1167  center[i] = (self.point1[i] + self.point2[i]) / 2.0
1168  length[i] = abs(self.point1[i] - self.point2[i])
1169 
1170  xVector = [1,0,0,0]
1171  self.brushToWorldOriginTransform.MultiplyPoint(xVector, xVector)
1172  xLength = abs(vtk.vtkMath.Dot(xVector[:3], length))
1173  self.brushCubeSource.SetXLength(xLength)
1174 
1175  zVector = [0,0,1,0]
1176  self.brushToWorldOriginTransform.MultiplyPoint(zVector, zVector)
1177  zLength = abs(vtk.vtkMath.Dot(zVector[:3], length))
1178  self.brushCubeSource.SetZLength(zLength)
1179  self.brushCubeSource.SetYLength(sliceSpacingMm)
1180 
1181  elif self.brushMode == HISTOGRAM_BRUSH_TYPE_LINE:
1182  self.brushLineSource.SetPoint1(self.point1)
1183  self.brushLineSource.SetPoint2(self.point2)
1184  self.brushTubeSource.SetRadius(sliceSpacingMm)
1185 
1186  self.worldOriginToWorldTransform.Identity()
1187  self.worldOriginToWorldTransform.Translate(center)
1188 
1189  self.sliceWidget.sliceView().scheduleRender()
1190 
1191 HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME = "BrushType"
1192 
1193 HISTOGRAM_BRUSH_TYPE_BOX = 'BOX'
1194 HISTOGRAM_BRUSH_TYPE_CIRCLE = 'CIRCLE'
1195 HISTOGRAM_BRUSH_TYPE_DRAW = 'DRAW'
1196 HISTOGRAM_BRUSH_TYPE_LINE = 'LINE'
1197 
1198 HISTOGRAM_STATE_OFF = 'OFF'
1199 HISTOGRAM_STATE_MOVING = 'MOVING'
1200 HISTOGRAM_STATE_PLACED = 'PLACED'
1201 
1202 HISTOGRAM_SET_LOWER_PARAMETER_NAME = 'HistogramSetLower'
1203 HISTOGRAM_SET_UPPER_PARAMETER_NAME = 'HistogramSetUpper'
1204 
1205 HISTOGRAM_SET_MINIMUM = 'MINIMUM'
1206 HISTOGRAM_SET_LOWER = 'LOWER'
1207 HISTOGRAM_SET_AVERAGE = 'AVERAGE'
1208 HISTOGRAM_SET_UPPER = 'UPPER'
1209 HISTOGRAM_SET_MAXIMUM = 'MAXIMUM'
1210 
1211 
1212 
1213 METHOD_HUANG = 'HUANG'
1214 METHOD_INTERMODES = 'INTERMODES'
1215 METHOD_ISO_DATA = 'ISO_DATA'
1216 METHOD_KITTLER_ILLINGWORTH = 'KITTLER_ILLINGWORTH'
1217 METHOD_LI = 'LI'
1218 METHOD_MAXIMUM_ENTROPY = 'MAXIMUM_ENTROPY'
1219 METHOD_MOMENTS = 'MOMENTS'
1220 METHOD_OTSU = 'OTSU'
1221 METHOD_RENYI_ENTROPY = 'RENYI_ENTROPY'
1222 METHOD_SHANBHAG = 'SHANBHAG'
1223 METHOD_TRIANGLE = 'TRIANGLE'
1224 METHOD_YEN = 'YEN'
1225 
1226 MODE_SET_UPPER = 'SET_UPPER'
1227 MODE_SET_LOWER = 'SET_LOWER'
1228 MODE_SET_MIN_UPPER = 'SET_MIN_UPPER'
1229 MODE_SET_LOWER_MAX = 'SET_LOWER_MAX'
def processViewNodeEvents(self, callerViewNode, eventId, viewWidget)
def autoThreshold(self, autoThresholdMethod, autoThresholdMode)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)
def __init__(self, thresholdEffect, scriptedEffect, sliceWidget, brushMode)