Slicer 5.9
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
AbstractScriptedSegmentEditorAutoCompleteEffect.py
Go to the documentation of this file.
1import logging
2
3import ctk
4import qt
5import vtk
6
7import slicer
8from slicer.i18n import tr as _
9
10from .AbstractScriptedSegmentEditorEffect import *
11
12__all__ = ["AbstractScriptedSegmentEditorAutoCompleteEffect"]
13
14
15#
16# Abstract class of python scripted segment editor auto-complete effects
17#
18# Auto-complete effects are a subtype of general effects that allow preview
19# and refinement of segmentation results before accepting them.
20#
21
22
24 """AutoCompleteEffect is an effect that can create a full segmentation
25 from a partial segmentation (not all slices are segmented or only
26 part of the target structures are painted).
27 """
28
29 def __init__(self, scriptedEffect):
30 # Indicates that effect does not operate on one segment, but the whole segmentation.
31 # This means that while this effect is active, no segment can be selected
32 scriptedEffect.perSegment = False
33 AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
34
35 # Number of segments required when editable area is not specified
37 # Number of segments required when editable area is specified
41
42 # Stores merged labelmap image geometry (voxel data is not allocated)
45 self.selectedSegmentModifiedTimes = {} # map from segment ID to ModifiedTime
48
49 # Observation for auto-update
52
53 # Wait this much after the last modified event before starting aut-update:
54 autoUpdateDelaySec = 1.0
55 self.delayedAutoUpdateTimer = qt.QTimer()
56 self.delayedAutoUpdateTimer.setSingleShot(True)
57 self.delayedAutoUpdateTimer.interval = autoUpdateDelaySec * 1000
58 self.delayedAutoUpdateTimer.connect("timeout()", self.onPreview)
59
60 self.extentGrowthRatio = 0.1 # extent of seed region will be grown outside by this much
62
64
65 def __del__(self):
66 self.delayedAutoUpdateTimer.stop()
67 self.observeSegmentation(False)
68
69 def cleanup(self):
70 # Disconnect the timer signal to allow proper garbage collection.
71 #
72 # This prevents lingering signal/slot connections from keeping
73 # the object alive. For more details, see the parent class
74 # cleanup() docstring or the following issue:
75 # https://github.com/Slicer/Slicer/issues/7392
76 self.delayedAutoUpdateTimer.disconnect("timeout()", self.onPreview)
77
78 @staticmethod
79 def isBackgroundLabelmap(labelmapOrientedImageData, label=None):
80 if labelmapOrientedImageData is None:
81 return False
82 # If five or more corner voxels of the image contain non-zero, then it is background
83 extent = labelmapOrientedImageData.GetExtent()
84 if extent[0] > extent[1] or extent[2] > extent[3] or extent[4] > extent[5]:
85 return False
86 numberOfFilledCorners = 0
87 for i in [0, 1]:
88 for j in [2, 3]:
89 for k in [4, 5]:
90 voxelValue = labelmapOrientedImageData.GetScalarComponentAsFloat(extent[i], extent[j], extent[k], 0)
91 if label is None:
92 if voxelValue > 0:
93 numberOfFilledCorners += 1
94 else:
95 if voxelValue == label:
96 numberOfFilledCorners += 1
97 if numberOfFilledCorners > 4:
98 return True
99 return False
100
102 self.autoUpdateCheckBox = qt.QCheckBox(_("Auto-update"))
103 self.autoUpdateCheckBox.setToolTip(_("Auto-update results preview when input segments change."))
104 self.autoUpdateCheckBox.setChecked(True)
105 self.autoUpdateCheckBox.setEnabled(False)
106
107 self.previewButton = qt.QPushButton(_("Initialize"))
108 self.previewButton.objectName = self.__class__.__name__ + "Preview"
109 self.previewButton.setToolTip(_("Preview complete segmentation"))
110 # qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
111 # fails on some systems, therefore set the policies using separate method calls
112 qSize = qt.QSizePolicy()
113 qSize.setHorizontalPolicy(qt.QSizePolicy.Expanding)
114 self.previewButton.setSizePolicy(qSize)
115
116 previewFrame = qt.QHBoxLayout()
117 previewFrame.addWidget(self.autoUpdateCheckBox)
118 previewFrame.addWidget(self.previewButton)
119 self.scriptedEffect.addLabeledOptionsWidget(_("Preview:"), previewFrame)
120
121 self.previewOpacitySlider = ctk.ctkSliderWidget()
122 self.previewOpacitySlider.setToolTip(_("Adjust visibility of results preview."))
123 self.previewOpacitySlider.minimum = 0
124 self.previewOpacitySlider.maximum = 1.0
125 self.previewOpacitySlider.value = 0.0
126 self.previewOpacitySlider.singleStep = 0.05
127 self.previewOpacitySlider.pageStep = 0.1
128 self.previewOpacitySlider.spinBoxVisible = False
129
130 self.previewShow3DButton = qt.QPushButton(_("Show 3D"))
131 self.previewShow3DButton.setToolTip(_("Preview results in 3D."))
132 self.previewShow3DButton.setCheckable(True)
133
134 displayFrame = qt.QHBoxLayout()
135 displayFrame.addWidget(qt.QLabel(_("inputs")))
136 displayFrame.addWidget(self.previewOpacitySlider)
137 displayFrame.addWidget(qt.QLabel(_("results")))
138 displayFrame.addWidget(self.previewShow3DButton)
139 self.scriptedEffect.addLabeledOptionsWidget(_("Display:"), displayFrame)
140
141 self.cancelButton = qt.QPushButton(_("Cancel"))
142 self.cancelButton.objectName = self.__class__.__name__ + "Cancel"
143 self.cancelButton.setToolTip(_("Clear preview and cancel auto-complete"))
144
145 self.applyButton = qt.QPushButton(_("Apply"))
146 self.applyButton.objectName = self.__class__.__name__ + "Apply"
147 self.applyButton.setToolTip(_("Replace segments by previewed result"))
148
149 finishFrame = qt.QHBoxLayout()
150 finishFrame.addWidget(self.cancelButton)
151 finishFrame.addWidget(self.applyButton)
152 self.scriptedEffect.addOptionsWidget(finishFrame)
153
154 self.previewButton.connect("clicked()", self.onPreview)
155 self.cancelButton.connect("clicked()", self.onCancel)
156 self.applyButton.connect("clicked()", self.onApply)
157 self.previewOpacitySlider.connect("valueChanged(double)", self.updateMRMLFromGUI)
158 self.previewShow3DButton.connect("toggled(bool)", self.updateMRMLFromGUI)
159 self.autoUpdateCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUI)
160
161 def createCursor(self, widget):
162 # Turn off effect-specific cursor for this effect
163 return slicer.util.mainWindow().cursor
164
166 self.scriptedEffect.setParameterDefault("AutoUpdate", "1")
167
168 def onSegmentationModified(self, caller, event):
169 if not self.autoUpdateCheckBox.isChecked():
170 # just in case a queued request comes through
171 return
172
173 import vtkSegmentationCorePython as vtkSegmentationCore
174
175 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
176 segmentation = segmentationNode.GetSegmentation()
177
178 updateNeeded = False
179 for segmentIndex in range(self.selectedSegmentIds.GetNumberOfValues()):
180 segmentID = self.selectedSegmentIds.GetValue(segmentIndex)
181 segment = segmentation.GetSegment(segmentID)
182 if not segment:
183 # selected segment was deleted, cancel segmentation
184 logging.debug("Segmentation operation is cancelled because an input segment was deleted")
185 self.onCancel()
186 slicer.util.showStatusMessage(_("Segmentation operation is cancelled because an input segment was deleted."), 3000)
187 return
188 segmentLabelmap = segment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
189 if segmentID in self.selectedSegmentModifiedTimes \
190 and segmentLabelmap and segmentLabelmap.GetMTime() == self.selectedSegmentModifiedTimes[segmentID]:
191 # this segment has not changed since last update
192 continue
193 if segmentLabelmap:
194 self.selectedSegmentModifiedTimes[segmentID] = segmentLabelmap.GetMTime()
195 elif segmentID in self.selectedSegmentModifiedTimes:
196 self.selectedSegmentModifiedTimes.pop(segmentID)
197 updateNeeded = True
198 # continue so that all segment modified times are updated
199
200 if not updateNeeded:
201 return
202
203 logging.debug("Segmentation update requested")
204 # There could be multiple update events for a single paint operation (e.g., one segment overwrites the other)
205 # therefore don't update directly, just set up/reset a timer that will perform the update when it elapses.
207 self.delayedAutoUpdateTimer.start()
208
209 def observeSegmentation(self, observationEnabled):
210 import vtkSegmentationCorePython as vtkSegmentationCore
211
212 parameterSetNode = self.scriptedEffect.parameterSetNode()
213 segmentationNode = None
214 if parameterSetNode:
215 segmentationNode = parameterSetNode.GetSegmentationNode()
216
217 segmentation = None
218 if segmentationNode:
219 segmentation = segmentationNode.GetSegmentation()
220
221 if observationEnabled and self.observedSegmentation == segmentation:
222 return
223 if not observationEnabled and not self.observedSegmentation:
224 return
225 # Need to update the observer
226 # Remove old observer
227 if self.observedSegmentation:
228 for tag in self.segmentationNodeObserverTags:
229 self.observedSegmentation.RemoveObserver(tag)
231 self.observedSegmentation = None
232 # Add new observer
233 if observationEnabled and segmentation is not None:
234 self.observedSegmentation = segmentation
235 observedEvents = [
236 vtkSegmentationCore.vtkSegmentation.SegmentAdded,
237 vtkSegmentationCore.vtkSegmentation.SegmentRemoved,
238 vtkSegmentationCore.vtkSegmentation.SegmentModified,
239 vtkSegmentationCore.vtkSegmentation.SourceRepresentationModified]
240 for eventId in observedEvents:
242
243 def getPreviewNode(self):
244 previewNode = self.scriptedEffect.nodeReference(ResultPreviewNodeReferenceRole)
245 if (previewNode
246 and self.scriptedEffect.parameterDefined("SegmentationResultPreviewOwnerEffect")
247 and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name):
248 # another effect owns this preview node
249 return None
250 return previewNode
251
253 previewNode = self.getPreviewNode()
254
255 self.cancelButton.setEnabled(previewNode is not None)
256 self.applyButton.setEnabled(previewNode is not None)
257
258 self.previewOpacitySlider.setEnabled(previewNode is not None)
259 if previewNode:
260 wasBlocked = self.previewOpacitySlider.blockSignals(True)
261 self.previewOpacitySlider.value = self.getPreviewOpacity()
262 self.previewOpacitySlider.blockSignals(wasBlocked)
263 self.previewButton.text = _("Update")
264 self.previewShow3DButton.setEnabled(True)
265 self.previewShow3DButton.setChecked(self.getPreviewShow3D())
266 self.autoUpdateCheckBox.setEnabled(True)
267 self.observeSegmentation(self.autoUpdateCheckBox.isChecked())
268 else:
269 self.previewButton.text = _("Initialize")
270 self.autoUpdateCheckBox.setEnabled(False)
271 self.previewShow3DButton.setEnabled(False)
272 self.delayedAutoUpdateTimer.stop()
273 self.observeSegmentation(False)
274
275 autoUpdate = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("AutoUpdate") == 0 else qt.Qt.Checked
276 wasBlocked = self.autoUpdateCheckBox.blockSignals(True)
277 self.autoUpdateCheckBox.setCheckState(autoUpdate)
278 self.autoUpdateCheckBox.blockSignals(wasBlocked)
279
281 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
282 previewNode = self.getPreviewNode()
283 if previewNode:
285 self.setPreviewShow3D(self.previewShow3DButton.checked)
286
287 autoUpdate = 1 if self.autoUpdateCheckBox.isChecked() else 0
288 self.scriptedEffect.setParameter("AutoUpdate", autoUpdate)
289
290 def onPreview(self):
292 return
294
295 slicer.util.showStatusMessage(_("Running {effectName} auto-complete...").format(effectName=self.scriptedEffect.name), 2000)
296 try:
297 with slicer.util.tryWithErrorDisplay(_("Segmentation operation failed:"), waitCursor=True):
298 self.preview()
299 finally:
301
302 def reset(self):
303 self.delayedAutoUpdateTimer.stop()
304 self.observeSegmentation(False)
305 previewNode = self.scriptedEffect.nodeReference(ResultPreviewNodeReferenceRole)
306 if previewNode:
307 self.scriptedEffect.setCommonNodeReference(ResultPreviewNodeReferenceRole, None)
308 slicer.mrmlScene.RemoveNode(previewNode)
309 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", "")
310 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
311 segmentationNode.GetDisplayNode().SetOpacity(1.0)
313 self.selectedSegmentIds = None
315 self.clippedMasterImageData = None
316 self.clippedMaskImageData = None
317 self.updateGUIFromMRML()
318
319 def onCancel(self):
320 self.reset()
321
322 def onApply(self):
323 self.delayedAutoUpdateTimer.stop()
324 self.observeSegmentation(False)
325
326 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
327 segmentationDisplayNode = segmentationNode.GetDisplayNode()
328 previewNode = self.getPreviewNode()
329
330 self.scriptedEffect.saveStateForUndo()
331
332 previewContainsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
333 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
334
335 # Move segments from preview into current segmentation
336 segmentIDs = vtk.vtkStringArray()
337 previewNode.GetSegmentation().GetSegmentIDs(segmentIDs)
338 for index in range(segmentIDs.GetNumberOfValues()):
339 segmentID = segmentIDs.GetValue(index)
340 previewSegmentLabelmap = slicer.vtkOrientedImageData()
341 previewNode.GetBinaryLabelmapRepresentation(segmentID, previewSegmentLabelmap)
342 self.scriptedEffect.modifySegmentByLabelmap(segmentationNode, segmentID, previewSegmentLabelmap,
343 slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
344 if segmentationDisplayNode is not None and self.isBackgroundLabelmap(previewSegmentLabelmap):
345 # Automatically hide result segments that are background (all eight corners are non-zero)
346 segmentationDisplayNode.SetSegmentVisibility(segmentID, False)
347 previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage
348
349 if previewContainsClosedSurfaceRepresentation:
350 segmentationNode.CreateClosedSurfaceRepresentation()
351
352 self.reset()
353
354 def setPreviewOpacity(self, opacity):
355 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
356 segmentationNode.GetDisplayNode().SetOpacity(1.0 - opacity)
357 previewNode = self.getPreviewNode()
358 if previewNode:
359 previewNode.GetDisplayNode().SetOpacity(opacity)
360 previewNode.GetDisplayNode().SetOpacity3D(opacity)
361
362 # Make sure the GUI is up-to-date
363 wasBlocked = self.previewOpacitySlider.blockSignals(True)
364 self.previewOpacitySlider.value = opacity
365 self.previewOpacitySlider.blockSignals(wasBlocked)
366
368 previewNode = self.getPreviewNode()
369 return previewNode.GetDisplayNode().GetOpacity() if previewNode else 0.6 # default opacity for preview
370
371 def setPreviewShow3D(self, show):
372 previewNode = self.getPreviewNode()
373 if previewNode:
374 if show:
375 previewNode.CreateClosedSurfaceRepresentation()
376 else:
377 previewNode.RemoveClosedSurfaceRepresentation()
378
379 # Make sure the GUI is up-to-date
380 wasBlocked = self.previewShow3DButton.blockSignals(True)
381 self.previewShow3DButton.checked = show
382 self.previewShow3DButton.blockSignals(wasBlocked)
383
385 previewNode = self.getPreviewNode()
386 if not previewNode:
387 return False
388 containsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
389 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
390 return containsClosedSurfaceRepresentation
391
393 if self.getPreviewNode() is None:
394 return True
395 if self.mergedLabelmapGeometryImage is None:
396 return True
397 if self.selectedSegmentIds is None:
398 return True
399
400 import vtkSegmentationCorePython as vtkSegmentationCore
401
402 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
403
404 # The effective extent for the current input segments
405 effectiveGeometryImage = slicer.vtkOrientedImageData()
406 effectiveGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
407 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
408 if effectiveGeometryString is None:
409 return True
410 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(effectiveGeometryString, effectiveGeometryImage)
411
412 masterImageData = self.scriptedEffect.sourceVolumeImageData()
413 masterImageExtent = masterImageData.GetExtent()
414
415 # The effective extent of the selected segments
416 effectiveLabelExtent = effectiveGeometryImage.GetExtent()
417 # Current extent used for auto-complete preview
418 currentLabelExtent = self.mergedLabelmapGeometryImage.GetExtent()
419
420 # Determine if the current merged labelmap extent has less than a 3 voxel margin around the effective segment extent
421 # (limited by the source image extent)
422 return ((masterImageExtent[0] != currentLabelExtent[0] and currentLabelExtent[0] > effectiveLabelExtent[0] - self.minimumExtentMargin) or
423 (masterImageExtent[1] != currentLabelExtent[1] and currentLabelExtent[1] < effectiveLabelExtent[1] + self.minimumExtentMargin) or
424 (masterImageExtent[2] != currentLabelExtent[2] and currentLabelExtent[2] > effectiveLabelExtent[2] - self.minimumExtentMargin) or
425 (masterImageExtent[3] != currentLabelExtent[3] and currentLabelExtent[3] < effectiveLabelExtent[3] + self.minimumExtentMargin) or
426 (masterImageExtent[4] != currentLabelExtent[4] and currentLabelExtent[4] > effectiveLabelExtent[4] - self.minimumExtentMargin) or
427 (masterImageExtent[5] != currentLabelExtent[5] and currentLabelExtent[5] < effectiveLabelExtent[5] + self.minimumExtentMargin))
428
429 def preview(self):
430 # Get source volume image data
431 import vtkSegmentationCorePython as vtkSegmentationCore
432
433 # Get segmentation
434 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
435
436 previewNode = self.getPreviewNode()
437 previewOpacity = self.getPreviewOpacity()
438 previewShow3D = self.getPreviewShow3D()
439
440 # If the selectedSegmentIds have been specified, then they shouldn't be overwritten here
441 currentSelectedSegmentIds = self.selectedSegmentIds
442
443 if self.effectiveExtentChanged():
444 self.reset()
445
446 # Restore the selectedSegmentIds
447 self.selectedSegmentIds = currentSelectedSegmentIds
448 if self.selectedSegmentIds is None:
449 self.selectedSegmentIds = vtk.vtkStringArray()
450 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
451
453 editableAreaSpecified = (
454 self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
455 or self.scriptedEffect.parameterSetNode().GetMaskMode() != slicer.vtkMRMLSegmentationNode.EditAllowedEverywhere)
456 if editableAreaSpecified and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegmentsWithEditableArea:
457 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments are required when editable area is defined")
458 raise RuntimeError(
459 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
460 minimumNumberOfSegments=self.minimumNumberOfSegmentsWithEditableArea))
461 elif (not editableAreaSpecified) and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
462 logging.error(f"Auto-complete operation skipped: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments or setting of editable area is required")
463 raise RuntimeError(
464 _("Minimum {minimumNumberOfSegments} visible segments (or specification of editable area or intensity range) is required.").format(
465 minimumNumberOfSegments=self.minimumNumberOfSegments))
466 elif self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
467 # Same number of input segments required regardless of editable area
468 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegments} visible segments are required")
469 self.selectedSegmentIds = None
470 raise RuntimeError(
471 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
472 minimumNumberOfSegments=self.minimumNumberOfSegments))
473
474 # Compute merged labelmap extent (effective extent slightly expanded)
475 if not self.mergedLabelmapGeometryImage:
476 self.mergedLabelmapGeometryImage = slicer.vtkOrientedImageData()
477 commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
478 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
479 if not commonGeometryString:
480 logging.info("Auto-complete operation skipped: all visible segments are empty")
481 return
482 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, self.mergedLabelmapGeometryImage)
483
484 masterImageData = self.scriptedEffect.sourceVolumeImageData()
485 masterImageExtent = masterImageData.GetExtent()
486 labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent()
487 # Margin size is relative to combined seed region size, but minimum of 3 voxels
488 print(f"self.extentGrowthRatio = {self.extentGrowthRatio}")
489 margin = [
490 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[1] - labelsEffectiveExtent[0]))),
491 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[3] - labelsEffectiveExtent[2]))),
492 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[5] - labelsEffectiveExtent[4])))]
493 labelsExpandedExtent = [
494 max(masterImageExtent[0], labelsEffectiveExtent[0] - margin[0]),
495 min(masterImageExtent[1], labelsEffectiveExtent[1] + margin[0]),
496 max(masterImageExtent[2], labelsEffectiveExtent[2] - margin[1]),
497 min(masterImageExtent[3], labelsEffectiveExtent[3] + margin[1]),
498 max(masterImageExtent[4], labelsEffectiveExtent[4] - margin[2]),
499 min(masterImageExtent[5], labelsEffectiveExtent[5] + margin[2])]
500 print("masterImageExtent = " + repr(masterImageExtent))
501 print("labelsEffectiveExtent = " + repr(labelsEffectiveExtent))
502 print("labelsExpandedExtent = " + repr(labelsExpandedExtent))
503 self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent)
504
505 # Create and setup preview node
506 previewNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
507 previewNode.CreateDefaultDisplayNodes()
508 previewNode.GetDisplayNode().SetVisibility2DOutline(False)
509 if segmentationNode.GetParentTransformNode():
510 previewNode.SetAndObserveTransformNodeID(segmentationNode.GetParentTransformNode().GetID())
511 self.scriptedEffect.setCommonNodeReference(ResultPreviewNodeReferenceRole, previewNode)
512 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name)
513 self.setPreviewOpacity(0.6)
514
515 # Disable smoothing for closed surface generation to make it fast
516 previewNode.GetSegmentation().SetConversionParameter(
517 slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule.GetSmoothingFactorParameterName(),
518 "-0.5")
519
520 inputContainsClosedSurfaceRepresentation = segmentationNode.GetSegmentation().ContainsRepresentation(
521 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
522
523 self.setPreviewShow3D(inputContainsClosedSurfaceRepresentation)
524
526 self.clippedMasterImageData = slicer.vtkOrientedImageData()
527 masterImageClipper = vtk.vtkImageConstantPad()
528 masterImageClipper.SetInputData(masterImageData)
529 masterImageClipper.SetOutputWholeExtent(self.mergedLabelmapGeometryImage.GetExtent())
530 masterImageClipper.Update()
531 self.clippedMasterImageData.ShallowCopy(masterImageClipper.GetOutput())
533
534 self.clippedMaskImageData = None
536 self.clippedMaskImageData = slicer.vtkOrientedImageData()
537 intensityBasedMasking = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
538 maskSegmentID = self.scriptedEffect.parameterSetNode().GetMaskSegmentID() if self.scriptedEffect.parameterSetNode().GetMaskSegmentID() else ""
539 intensityRange = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMaskRange() if intensityBasedMasking else None
540 success = segmentationNode.GenerateEditMask(self.clippedMaskImageData,
541 self.scriptedEffect.parameterSetNode().GetMaskMode(),
542 self.clippedMasterImageData, # reference geometry
543 "", # edited segment ID
544 maskSegmentID,
545 self.clippedMasterImageData if intensityBasedMasking else None,
546 intensityRange)
547 if not success:
548 logging.error("Failed to create edit mask")
549 self.clippedMaskImageData = None
550
551 previewNode.SetName(segmentationNode.GetName() + " preview")
552 previewNode.RemoveClosedSurfaceRepresentation() # Force the closed surface representation to update
553 # TODO: This will no longer be required when we can use the segment editor to set multiple segments
554 # as the closed surfaces will be converted as necessary by the segmentation logic.
555
556 mergedImage = slicer.vtkOrientedImageData()
557 segmentationNode.GenerateMergedLabelmapForAllSegments(
558 mergedImage,
559 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds)
560
561 outputLabelmap = slicer.vtkOrientedImageData()
562 self.computePreviewLabelmap(mergedImage, outputLabelmap)
563
564 if previewNode.GetSegmentation().GetNumberOfSegments() != self.selectedSegmentIds.GetNumberOfValues():
565 # first update (or number of segments changed), need a full reinitialization
566 previewNode.GetSegmentation().RemoveAllSegments()
567
568 for index in range(self.selectedSegmentIds.GetNumberOfValues()):
569 segmentID = self.selectedSegmentIds.GetValue(index)
570
571 previewSegment = previewNode.GetSegmentation().GetSegment(segmentID)
572 if not previewSegment:
573 inputSegment = segmentationNode.GetSegmentation().GetSegment(segmentID)
574
575 previewSegment = vtkSegmentationCore.vtkSegment()
576 previewSegment.SetName(inputSegment.GetName())
577 previewSegment.SetColor(inputSegment.GetColor())
578 previewNode.GetSegmentation().AddSegment(previewSegment, segmentID)
579
580 labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0)
581 previewSegment.AddRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName(), outputLabelmap)
582 previewSegment.SetLabelValue(labelValue)
583
584 # Automatically hide result segments that are background (all eight corners are non-zero)
585 previewNode.GetDisplayNode().SetSegmentVisibility3D(segmentID, not self.isBackgroundLabelmap(outputLabelmap, labelValue))
586
587 # If the preview was reset, we need to restore the visibility options
588 self.setPreviewOpacity(previewOpacity)
589 self.setPreviewShow3D(previewShow3D)
590
591 self.updateGUIFromMRML()
592
593
594ResultPreviewNodeReferenceRole = "SegmentationResultPreview"