Slicer 5.11
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
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 if not self.scriptedEffect:
211 # This can be the case if the Python interpreter has already begun its shutdown process
212 # when observeSegmentation is called from __del__ method.
213 return
214
215 import vtkSegmentationCorePython as vtkSegmentationCore
216
217 parameterSetNode = self.scriptedEffect.parameterSetNode()
218 segmentationNode = None
219 if parameterSetNode:
220 segmentationNode = parameterSetNode.GetSegmentationNode()
221
222 segmentation = None
223 if segmentationNode:
224 segmentation = segmentationNode.GetSegmentation()
225
226 if observationEnabled and self.observedSegmentation == segmentation:
227 return
228 if not observationEnabled and not self.observedSegmentation:
229 return
230 # Need to update the observer
231 # Remove old observer
232 if self.observedSegmentation:
233 for tag in self.segmentationNodeObserverTags:
234 self.observedSegmentation.RemoveObserver(tag)
236 self.observedSegmentation = None
237 # Add new observer
238 if observationEnabled and segmentation is not None:
239 self.observedSegmentation = segmentation
240 observedEvents = [
241 vtkSegmentationCore.vtkSegmentation.SegmentAdded,
242 vtkSegmentationCore.vtkSegmentation.SegmentRemoved,
243 vtkSegmentationCore.vtkSegmentation.SegmentModified,
244 vtkSegmentationCore.vtkSegmentation.SourceRepresentationModified]
245 for eventId in observedEvents:
247
248 def getPreviewNode(self):
249 previewNode = self.scriptedEffect.nodeReference(ResultPreviewNodeReferenceRole)
250 if (previewNode
251 and self.scriptedEffect.parameterDefined("SegmentationResultPreviewOwnerEffect")
252 and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name):
253 # another effect owns this preview node
254 return None
255 return previewNode
256
258 previewNode = self.getPreviewNode()
259
260 self.cancelButton.setEnabled(previewNode is not None)
261 self.applyButton.setEnabled(previewNode is not None)
262
263 self.previewOpacitySlider.setEnabled(previewNode is not None)
264 if previewNode:
265 wasBlocked = self.previewOpacitySlider.blockSignals(True)
266 self.previewOpacitySlider.value = self.getPreviewOpacity()
267 self.previewOpacitySlider.blockSignals(wasBlocked)
268 self.previewButton.text = _("Update")
269 self.previewShow3DButton.setEnabled(True)
270 self.previewShow3DButton.setChecked(self.getPreviewShow3D())
271 self.autoUpdateCheckBox.setEnabled(True)
272 self.observeSegmentation(self.autoUpdateCheckBox.isChecked())
273 else:
274 self.previewButton.text = _("Initialize")
275 self.autoUpdateCheckBox.setEnabled(False)
276 self.previewShow3DButton.setEnabled(False)
277 self.delayedAutoUpdateTimer.stop()
278 self.observeSegmentation(False)
279
280 autoUpdate = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("AutoUpdate") == 0 else qt.Qt.Checked
281 wasBlocked = self.autoUpdateCheckBox.blockSignals(True)
282 self.autoUpdateCheckBox.setCheckState(autoUpdate)
283 self.autoUpdateCheckBox.blockSignals(wasBlocked)
284
286 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
287 previewNode = self.getPreviewNode()
288 if previewNode:
290 self.setPreviewShow3D(self.previewShow3DButton.checked)
291
292 autoUpdate = 1 if self.autoUpdateCheckBox.isChecked() else 0
293 self.scriptedEffect.setParameter("AutoUpdate", autoUpdate)
294
295 def onPreview(self):
297 return
299
300 slicer.util.showStatusMessage(_("Running {effectName} auto-complete...").format(effectName=self.scriptedEffect.name), 2000)
301 try:
302 with slicer.util.tryWithErrorDisplay(_("Segmentation operation failed:"), waitCursor=True):
303 self.preview()
304 finally:
306
307 def reset(self):
308 self.delayedAutoUpdateTimer.stop()
309 self.observeSegmentation(False)
310 previewNode = self.scriptedEffect.nodeReference(ResultPreviewNodeReferenceRole)
311 if previewNode:
312 self.scriptedEffect.setCommonNodeReference(ResultPreviewNodeReferenceRole, None)
313 slicer.mrmlScene.RemoveNode(previewNode)
314 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", "")
315 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
316 segmentationNode.GetDisplayNode().SetOpacity(1.0)
318 self.selectedSegmentIds = None
320 self.clippedMasterImageData = None
321 self.clippedMaskImageData = None
322 self.updateGUIFromMRML()
323
324 def onCancel(self):
325 self.reset()
326
327 def onApply(self):
328 self.delayedAutoUpdateTimer.stop()
329 self.observeSegmentation(False)
330
331 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
332 segmentationDisplayNode = segmentationNode.GetDisplayNode()
333 previewNode = self.getPreviewNode()
334
335 self.scriptedEffect.saveStateForUndo()
336
337 previewContainsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
338 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
339
340 # Move segments from preview into current segmentation
341 segmentIDs = vtk.vtkStringArray()
342 previewNode.GetSegmentation().GetSegmentIDs(segmentIDs)
343 for index in range(segmentIDs.GetNumberOfValues()):
344 segmentID = segmentIDs.GetValue(index)
345 previewSegmentLabelmap = slicer.vtkOrientedImageData()
346 previewNode.GetBinaryLabelmapRepresentation(segmentID, previewSegmentLabelmap)
347 self.scriptedEffect.modifySegmentByLabelmap(segmentationNode, segmentID, previewSegmentLabelmap,
348 slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
349 if segmentationDisplayNode is not None and self.isBackgroundLabelmap(previewSegmentLabelmap):
350 # Automatically hide result segments that are background (all eight corners are non-zero)
351 segmentationDisplayNode.SetSegmentVisibility(segmentID, False)
352 previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage
353
354 if previewContainsClosedSurfaceRepresentation:
355 segmentationNode.CreateClosedSurfaceRepresentation()
356
357 self.reset()
358
359 def setPreviewOpacity(self, opacity):
360 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
361 segmentationNode.GetDisplayNode().SetOpacity(1.0 - opacity)
362 previewNode = self.getPreviewNode()
363 if previewNode:
364 previewNode.GetDisplayNode().SetOpacity(opacity)
365 previewNode.GetDisplayNode().SetOpacity3D(opacity)
366
367 # Make sure the GUI is up-to-date
368 wasBlocked = self.previewOpacitySlider.blockSignals(True)
369 self.previewOpacitySlider.value = opacity
370 self.previewOpacitySlider.blockSignals(wasBlocked)
371
373 previewNode = self.getPreviewNode()
374 return previewNode.GetDisplayNode().GetOpacity() if previewNode else 0.6 # default opacity for preview
375
376 def setPreviewShow3D(self, show):
377 previewNode = self.getPreviewNode()
378 if previewNode:
379 if show:
380 previewNode.CreateClosedSurfaceRepresentation()
381 else:
382 previewNode.RemoveClosedSurfaceRepresentation()
383
384 # Make sure the GUI is up-to-date
385 wasBlocked = self.previewShow3DButton.blockSignals(True)
386 self.previewShow3DButton.checked = show
387 self.previewShow3DButton.blockSignals(wasBlocked)
388
390 previewNode = self.getPreviewNode()
391 if not previewNode:
392 return False
393 containsClosedSurfaceRepresentation = previewNode.GetSegmentation().ContainsRepresentation(
394 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
395 return containsClosedSurfaceRepresentation
396
398 if self.getPreviewNode() is None:
399 return True
400 if self.mergedLabelmapGeometryImage is None:
401 return True
402 if self.selectedSegmentIds is None:
403 return True
404
405 import vtkSegmentationCorePython as vtkSegmentationCore
406
407 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
408
409 # The effective extent for the current input segments
410 effectiveGeometryImage = slicer.vtkOrientedImageData()
411 effectiveGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
412 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
413 if not effectiveGeometryString:
414 return True
415 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(effectiveGeometryString, effectiveGeometryImage)
416
417 masterImageData = self.scriptedEffect.sourceVolumeImageData()
418 masterImageExtent = masterImageData.GetExtent()
419
420 # The effective extent of the selected segments
421 effectiveLabelExtent = effectiveGeometryImage.GetExtent()
422 # Current extent used for auto-complete preview
423 currentLabelExtent = self.mergedLabelmapGeometryImage.GetExtent()
424
425 # Determine if the current merged labelmap extent has less than a 3 voxel margin around the effective segment extent
426 # (limited by the source image extent)
427 return ((masterImageExtent[0] != currentLabelExtent[0] and currentLabelExtent[0] > effectiveLabelExtent[0] - self.minimumExtentMargin) or
428 (masterImageExtent[1] != currentLabelExtent[1] and currentLabelExtent[1] < effectiveLabelExtent[1] + self.minimumExtentMargin) or
429 (masterImageExtent[2] != currentLabelExtent[2] and currentLabelExtent[2] > effectiveLabelExtent[2] - self.minimumExtentMargin) or
430 (masterImageExtent[3] != currentLabelExtent[3] and currentLabelExtent[3] < effectiveLabelExtent[3] + self.minimumExtentMargin) or
431 (masterImageExtent[4] != currentLabelExtent[4] and currentLabelExtent[4] > effectiveLabelExtent[4] - self.minimumExtentMargin) or
432 (masterImageExtent[5] != currentLabelExtent[5] and currentLabelExtent[5] < effectiveLabelExtent[5] + self.minimumExtentMargin))
433
434 def preview(self):
435 # Get source volume image data
436 import vtkSegmentationCorePython as vtkSegmentationCore
437
438 # Get segmentation
439 segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
440
441 previewNode = self.getPreviewNode()
442 previewOpacity = self.getPreviewOpacity()
443 previewShow3D = self.getPreviewShow3D()
444
445 # If the selectedSegmentIds have been specified, then they shouldn't be overwritten here
446 currentSelectedSegmentIds = self.selectedSegmentIds
447
448 if self.effectiveExtentChanged():
449 self.reset()
450
451 # Restore the selectedSegmentIds
452 self.selectedSegmentIds = currentSelectedSegmentIds
453 if self.selectedSegmentIds is None:
454 self.selectedSegmentIds = vtk.vtkStringArray()
455 segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
456
458 editableAreaSpecified = (
459 self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
460 or self.scriptedEffect.parameterSetNode().GetMaskMode() != slicer.vtkMRMLSegmentationNode.EditAllowedEverywhere)
461 if editableAreaSpecified and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegmentsWithEditableArea:
462 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments are required when editable area is defined")
463 raise RuntimeError(
464 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
465 minimumNumberOfSegments=self.minimumNumberOfSegmentsWithEditableArea))
466 elif (not editableAreaSpecified) and self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
467 logging.error(f"Auto-complete operation skipped: at least {self.minimumNumberOfSegmentsWithEditableArea} visible segments or setting of editable area is required")
468 raise RuntimeError(
469 _("Minimum {minimumNumberOfSegments} visible segments (or specification of editable area or intensity range) is required.").format(
470 minimumNumberOfSegments=self.minimumNumberOfSegments))
471 elif self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
472 # Same number of input segments required regardless of editable area
473 logging.error(f"Auto-complete operation failed: at least {self.minimumNumberOfSegments} visible segments are required")
474 self.selectedSegmentIds = None
475 raise RuntimeError(
476 _("Minimum {minimumNumberOfSegments} visible segments are required.").format(
477 minimumNumberOfSegments=self.minimumNumberOfSegments))
478
479 # Compute merged labelmap extent (effective extent slightly expanded)
480 if not self.mergedLabelmapGeometryImage:
481 self.mergedLabelmapGeometryImage = slicer.vtkOrientedImageData()
482 commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
483 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
484 if not commonGeometryString:
485 logging.info("Auto-complete operation skipped: all visible segments are empty")
486 return
487 vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, self.mergedLabelmapGeometryImage)
488
489 masterImageData = self.scriptedEffect.sourceVolumeImageData()
490 masterImageExtent = masterImageData.GetExtent()
491 labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent()
492 # Margin size is relative to combined seed region size, but minimum of 3 voxels
493 print(f"self.extentGrowthRatio = {self.extentGrowthRatio}")
494 margin = [
495 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[1] - labelsEffectiveExtent[0]))),
496 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[3] - labelsEffectiveExtent[2]))),
497 int(max(3, self.extentGrowthRatio * (labelsEffectiveExtent[5] - labelsEffectiveExtent[4])))]
498 labelsExpandedExtent = [
499 max(masterImageExtent[0], labelsEffectiveExtent[0] - margin[0]),
500 min(masterImageExtent[1], labelsEffectiveExtent[1] + margin[0]),
501 max(masterImageExtent[2], labelsEffectiveExtent[2] - margin[1]),
502 min(masterImageExtent[3], labelsEffectiveExtent[3] + margin[1]),
503 max(masterImageExtent[4], labelsEffectiveExtent[4] - margin[2]),
504 min(masterImageExtent[5], labelsEffectiveExtent[5] + margin[2])]
505 print("masterImageExtent = " + repr(masterImageExtent))
506 print("labelsEffectiveExtent = " + repr(labelsEffectiveExtent))
507 print("labelsExpandedExtent = " + repr(labelsExpandedExtent))
508 self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent)
509
510 # Create and setup preview node
511 previewNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
512 previewNode.CreateDefaultDisplayNodes()
513 previewNode.GetDisplayNode().SetVisibility2DOutline(False)
514 if segmentationNode.GetParentTransformNode():
515 previewNode.SetAndObserveTransformNodeID(segmentationNode.GetParentTransformNode().GetID())
516 self.scriptedEffect.setCommonNodeReference(ResultPreviewNodeReferenceRole, previewNode)
517 self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name)
518 self.setPreviewOpacity(0.6)
519
520 # Disable smoothing for closed surface generation to make it fast
521 previewNode.GetSegmentation().SetConversionParameter(
522 slicer.vtkBinaryLabelmapToClosedSurfaceConversionRule.GetSmoothingFactorParameterName(),
523 "-0.5")
524
525 inputContainsClosedSurfaceRepresentation = segmentationNode.GetSegmentation().ContainsRepresentation(
526 slicer.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
527
528 self.setPreviewShow3D(inputContainsClosedSurfaceRepresentation)
529
531 self.clippedMasterImageData = slicer.vtkOrientedImageData()
532 masterImageClipper = vtk.vtkImageConstantPad()
533 masterImageClipper.SetInputData(masterImageData)
534 masterImageClipper.SetOutputWholeExtent(self.mergedLabelmapGeometryImage.GetExtent())
535 masterImageClipper.Update()
536 self.clippedMasterImageData.ShallowCopy(masterImageClipper.GetOutput())
538
539 self.clippedMaskImageData = None
541 self.clippedMaskImageData = slicer.vtkOrientedImageData()
542 intensityBasedMasking = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMask()
543 maskSegmentID = self.scriptedEffect.parameterSetNode().GetMaskSegmentID() if self.scriptedEffect.parameterSetNode().GetMaskSegmentID() else ""
544 intensityRange = self.scriptedEffect.parameterSetNode().GetSourceVolumeIntensityMaskRange() if intensityBasedMasking else None
545 success = segmentationNode.GenerateEditMask(self.clippedMaskImageData,
546 self.scriptedEffect.parameterSetNode().GetMaskMode(),
547 self.clippedMasterImageData, # reference geometry
548 "", # edited segment ID
549 maskSegmentID,
550 self.clippedMasterImageData if intensityBasedMasking else None,
551 intensityRange)
552 if not success:
553 logging.error("Failed to create edit mask")
554 self.clippedMaskImageData = None
555
556 previewNode.SetName(segmentationNode.GetName() + " preview")
557 previewNode.RemoveClosedSurfaceRepresentation() # Force the closed surface representation to update
558 # TODO: This will no longer be required when we can use the segment editor to set multiple segments
559 # as the closed surfaces will be converted as necessary by the segmentation logic.
560
561 mergedImage = slicer.vtkOrientedImageData()
562 segmentationNode.GenerateMergedLabelmapForAllSegments(
563 mergedImage,
564 vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds)
565
566 outputLabelmap = slicer.vtkOrientedImageData()
567 self.computePreviewLabelmap(mergedImage, outputLabelmap)
568
569 if previewNode.GetSegmentation().GetNumberOfSegments() != self.selectedSegmentIds.GetNumberOfValues():
570 # first update (or number of segments changed), need a full reinitialization
571 previewNode.GetSegmentation().RemoveAllSegments()
572
573 for index in range(self.selectedSegmentIds.GetNumberOfValues()):
574 segmentID = self.selectedSegmentIds.GetValue(index)
575
576 previewSegment = previewNode.GetSegmentation().GetSegment(segmentID)
577 if not previewSegment:
578 inputSegment = segmentationNode.GetSegmentation().GetSegment(segmentID)
579
580 previewSegment = vtkSegmentationCore.vtkSegment()
581 previewSegment.SetName(inputSegment.GetName())
582 previewSegment.SetColor(inputSegment.GetColor())
583 previewNode.GetSegmentation().AddSegment(previewSegment, segmentID)
584
585 labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0)
586 previewSegment.AddRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName(), outputLabelmap)
587 previewSegment.SetLabelValue(labelValue)
588
589 # Automatically hide result segments that are background (all eight corners are non-zero)
590 previewNode.GetDisplayNode().SetSegmentVisibility3D(segmentID, not self.isBackgroundLabelmap(outputLabelmap, labelValue))
591
592 # If the preview was reset, we need to restore the visibility options
593 self.setPreviewOpacity(previewOpacity)
594 self.setPreviewShow3D(previewShow3D)
595
596 self.updateGUIFromMRML()
597
598
599ResultPreviewNodeReferenceRole = "SegmentationResultPreview"