Slicer 5.9
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 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"