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