Slicer 5.4
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
SegmentEditorThresholdEffect.py
Go to the documentation of this file.
1import logging
2import os
3
4import ctk
5import vtk
6import qt
7
8import slicer
9from SegmentEditorEffects import *
10
11
12class SegmentEditorThresholdEffect(AbstractScriptedSegmentEditorEffect):
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
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.previewpreview)
38
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 source 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
89 self.timer.start(200)
90
91 def deactivate(self):
93
94 # Clear preview pipeline and stop timer
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
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
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()
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)
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.updateMRMLFromGUIupdateMRMLFromGUI)
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.updateMRMLFromGUIupdateMRMLFromGUI)
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.updateMRMLFromGUIupdateMRMLFromGUI)
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
319 self.histogramLowerThresholdMinimumButton.setText("Min")
320 self.histogramLowerThresholdMinimumButton.setToolTip("Minimum")
321 self.histogramLowerThresholdMinimumButton.setCheckable(True)
323 lowerHistogramLayout.addWidget(self.histogramLowerThresholdMinimumButton)
325
327 self.histogramLowerThresholdLowerButton.setText("Lower")
328 self.histogramLowerThresholdLowerButton.setCheckable(True)
330 lowerHistogramLayout.addWidget(self.histogramLowerThresholdLowerButton)
332
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
352 self.histogramUpperThresholdAverageButton.setText("Mean")
353 self.histogramUpperThresholdAverageButton.setCheckable(True)
355 upperHistogramLayout.addWidget(self.histogramUpperThresholdAverageButton)
357
359 self.histogramUpperThresholdUpperButton.setText("Upper")
360 self.histogramUpperThresholdUpperButton.setCheckable(True)
362 upperHistogramLayout.addWidget(self.histogramUpperThresholdUpperButton)
364
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.onUseForPaintonUseForPaint)
389 self.thresholdSlider.connect('valuesChanged(double,double)', self.onThresholdValuesChangedonThresholdValuesChanged)
395 self.applyButton.connect('clicked()', self.onApplyonApply)
396
398 # Set scalar range of source volume image data to threshold slider
399 masterImageData = self.scriptedEffect.sourceVolumeImageData()
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):
411
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
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
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
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
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.SourceVolumeIntensityMaskOn()
518 parameterSetNode.SetSourceVolumeIntensityMaskRange(self.thresholdSlider.minimumValue, self.thresholdSlider.maximumValue)
519 # Switch to paint effect
520 self.scriptedEffect.selectEffect("Paint")
521
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.sourceVolumeImageData()
571 self.autoThresholdCalculator.SetInputData(masterImageData)
572
573 self.autoThresholdCalculator.Update()
574 computedThreshold = self.autoThresholdCalculator.GetThreshold()
575
576 sourceVolumeMin, sourceVolumeMax = 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", sourceVolumeMin)
584 self.scriptedEffect.setParameter("MaximumThreshold", computedThreshold)
585 elif autoThresholdMode == MODE_SET_LOWER_MAX:
586 self.scriptedEffect.setParameter("MinimumThreshold", computedThreshold)
587 self.scriptedEffect.setParameter("MaximumThreshold", sourceVolumeMax)
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 source volume image data
597 masterImageData = self.scriptedEffect.sourceVolumeImageData()
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 source 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
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.getSourceVolumeLayerLogic(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.sourceVolumeImageData()
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:
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 getSourceVolumeLayerLogic(self, sliceWidget):
778 sourceVolumeNode = self.scriptedEffect.parameterSetNode().GetSourceVolumeNode()
779 sliceLogic = sliceWidget.sliceLogic()
780
781 backgroundLogic = sliceLogic.GetBackgroundLayer()
782 backgroundVolumeNode = backgroundLogic.GetVolumeNode()
783 if sourceVolumeNode == backgroundVolumeNode:
784 return backgroundLogic
785
786 foregroundLogic = sliceLogic.GetForegroundLayer()
787 foregroundVolumeNode = foregroundLogic.GetVolumeNode()
788 if sourceVolumeNode == foregroundVolumeNode:
789 return foregroundLogic
790
791 logging.warning("Source 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
804 masterImageData = self.scriptedEffect.sourceVolumeImageData()
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.getSourceVolumeLayerLogic(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.sourceVolumeImageData()
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.sourceVolumeImageData()
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
975class 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)
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
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
1210HISTOGRAM_BRUSH_TYPE_PARAMETER_NAME = "BrushType"
1211
1212HISTOGRAM_BRUSH_TYPE_BOX = 'BOX'
1213HISTOGRAM_BRUSH_TYPE_CIRCLE = 'CIRCLE'
1214HISTOGRAM_BRUSH_TYPE_DRAW = 'DRAW'
1215HISTOGRAM_BRUSH_TYPE_LINE = 'LINE'
1216
1217HISTOGRAM_STATE_OFF = 'OFF'
1218HISTOGRAM_STATE_MOVING = 'MOVING'
1219HISTOGRAM_STATE_PLACED = 'PLACED'
1220
1221HISTOGRAM_SET_LOWER_PARAMETER_NAME = 'HistogramSetLower'
1222HISTOGRAM_SET_UPPER_PARAMETER_NAME = 'HistogramSetUpper'
1223
1224HISTOGRAM_SET_MINIMUM = 'MINIMUM'
1225HISTOGRAM_SET_LOWER = 'LOWER'
1226HISTOGRAM_SET_AVERAGE = 'AVERAGE'
1227HISTOGRAM_SET_UPPER = 'UPPER'
1228HISTOGRAM_SET_MAXIMUM = 'MAXIMUM'
1229
1230
1231
1232METHOD_HUANG = 'HUANG'
1233METHOD_INTERMODES = 'INTERMODES'
1234METHOD_ISO_DATA = 'ISO_DATA'
1235METHOD_KITTLER_ILLINGWORTH = 'KITTLER_ILLINGWORTH'
1236METHOD_LI = 'LI'
1237METHOD_MAXIMUM_ENTROPY = 'MAXIMUM_ENTROPY'
1238METHOD_MOMENTS = 'MOMENTS'
1239METHOD_OTSU = 'OTSU'
1240METHOD_RENYI_ENTROPY = 'RENYI_ENTROPY'
1241METHOD_SHANBHAG = 'SHANBHAG'
1242METHOD_TRIANGLE = 'TRIANGLE'
1243METHOD_YEN = 'YEN'
1244
1245MODE_SET_UPPER = 'SET_UPPER'
1246MODE_SET_LOWER = 'SET_LOWER'
1247MODE_SET_MIN_UPPER = 'SET_MIN_UPPER'
1248MODE_SET_LOWER_MAX = 'SET_LOWER_MAX'
__init__(self, thresholdEffect, scriptedEffect, sliceWidget, brushMode)
processInteractionEvents(self, callerInteractor, eventId, viewWidget)