Slicer  4.8
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.
1 import os
2 import vtk, qt, ctk, slicer, logging
3 from AbstractScriptedSegmentEditorEffect import *
4 
5 __all__ = ['AbstractScriptedSegmentEditorAutoCompleteEffect']
6 
7 #
8 # Abstract class of python scripted segment editor auto-complete effects
9 #
10 # Auto-complete effects are a subtype of general effects that allow preview
11 # and refinement of segmentation results before accepting them.
12 #
13 
15  """ AutoCompleteEffect is an effect that can create a full segmentation
16  from a partial segmentation (not all slices are segmented or only
17  part of the target structures are painted).
18  """
19 
20  def __init__(self, scriptedEffect):
21  # Indicates that effect does not operate on one segment, but the whole segmentation.
22  # This means that while this effect is active, no segment can be selected
23  scriptedEffect.perSegment = False
24  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
25 
28 
29  # Stores merged labelmap image geometry (voxel data is not allocated)
31  self.selectedSegmentIds = None
32  self.selectedSegmentModifiedTimes = {} # map from segment ID to ModifiedTime
34 
35  # Observation for auto-update
38 
39  # Wait this much after the last modified event before starting aut-update:
40  autoUpdateDelaySec = 1.0
41  self.delayedAutoUpdateTimer = qt.QTimer()
42  self.delayedAutoUpdateTimer.setSingleShot(True)
43  self.delayedAutoUpdateTimer.interval = autoUpdateDelaySec * 1000
44  self.delayedAutoUpdateTimer.connect('timeout()', self.onPreview)
45 
46 
47  def __del__(self, scriptedEffect):
48  super(SegmentEditorAutoCompleteEffect,self).__del__()
49  self.delayedAutoUpdateTimer.stop()
50  self.observeSegmentation(False)
51 
52  def setupOptionsFrame(self):
53  self.autoUpdateCheckBox = qt.QCheckBox("Auto-update")
54  self.autoUpdateCheckBox.setToolTip("Auto-update results preview when input segments change.")
55  self.autoUpdateCheckBox.setChecked(True)
56  self.autoUpdateCheckBox.setEnabled(False)
57 
58  self.previewButton = qt.QPushButton("Initialize")
59  self.previewButton.objectName = self.__class__.__name__ + 'Preview'
60  self.previewButton.setToolTip("Preview complete segmentation")
61  # qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
62  # fails on some systems, therefore set the policies using separate method calls
63  qSize = qt.QSizePolicy()
64  qSize.setHorizontalPolicy(qt.QSizePolicy.Expanding)
65  self.previewButton.setSizePolicy(qSize)
66 
67  previewFrame = qt.QHBoxLayout()
68  previewFrame.addWidget(self.autoUpdateCheckBox)
69  previewFrame.addWidget(self.previewButton)
70  self.scriptedEffect.addLabeledOptionsWidget("Preview:", previewFrame)
71 
72  self.previewOpacitySlider = ctk.ctkSliderWidget()
73  self.previewOpacitySlider.setToolTip("Adjust visibility of results preview.")
74  self.previewOpacitySlider.minimum = 0
75  self.previewOpacitySlider.maximum = 1.0
76  self.previewOpacitySlider.value = 0.0
77  self.previewOpacitySlider.singleStep = 0.05
78  self.previewOpacitySlider.pageStep = 0.1
79  self.previewOpacitySlider.spinBoxVisible = False
80 
81  displayFrame = qt.QHBoxLayout()
82  displayFrame.addWidget(qt.QLabel("inputs"))
83  displayFrame.addWidget(self.previewOpacitySlider)
84  displayFrame.addWidget(qt.QLabel("results"))
85  self.scriptedEffect.addLabeledOptionsWidget("Display:", displayFrame)
86 
87  self.cancelButton = qt.QPushButton("Cancel")
88  self.cancelButton.objectName = self.__class__.__name__ + 'Cancel'
89  self.cancelButton.setToolTip("Clear preview and cancel auto-complete")
90 
91  self.applyButton = qt.QPushButton("Apply")
92  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
93  self.applyButton.setToolTip("Replace segments by previewed result")
94 
95  finishFrame = qt.QHBoxLayout()
96  finishFrame.addWidget(self.cancelButton)
97  finishFrame.addWidget(self.applyButton)
98  self.scriptedEffect.addOptionsWidget(finishFrame)
99 
100  self.previewButton.connect('clicked()', self.onPreview)
101  self.cancelButton.connect('clicked()', self.onCancel)
102  self.applyButton.connect('clicked()', self.onApply)
103  self.previewOpacitySlider.connect("valueChanged(double)", self.updateMRMLFromGUI)
104  self.autoUpdateCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUI)
105 
106  def createCursor(self, widget):
107  # Turn off effect-specific cursor for this effect
108  return slicer.util.mainWindow().cursor
109 
110  def setMRMLDefaults(self):
111  self.scriptedEffect.setParameterDefault("AutoUpdate", "1")
112 
113  def onSegmentationModified(self, caller, event):
114  if not self.autoUpdateCheckBox.isChecked():
115  # just in case a queued request comes through
116  return
117 
118  import vtkSegmentationCorePython as vtkSegmentationCore
119  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
120  segmentation = segmentationNode.GetSegmentation()
121 
122  updateNeeded = False
123  for segmentIndex in range(self.selectedSegmentIds.GetNumberOfValues()):
124  segmentID = self.selectedSegmentIds.GetValue(segmentIndex)
125  segment = segmentation.GetSegment(segmentID)
126  if not segment:
127  # selected segment was deleted, cancel segmentation
128  logging.debug("Segmentation cancelled because an input segment was deleted")
129  self.onCancel()
130  return
131  segmentLabelmap = segment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
132  if self.selectedSegmentModifiedTimes.has_key(segmentID) \
133  and segmentLabelmap.GetMTime() == self.selectedSegmentModifiedTimes[segmentID]:
134  # this segment has not changed since last update
135  continue
136  self.selectedSegmentModifiedTimes[segmentID] = segmentLabelmap.GetMTime()
137  updateNeeded = True
138  # continue so that all segment modified times are updated
139 
140  if not updateNeeded:
141  return
142 
143  logging.debug("Segmentation update requested")
144  # There could be multiple update events for a single paint operation (e.g., one segment overwrites the other)
145  # therefore don't update directly, just set up/reset a timer that will perform the update when it elapses.
146  self.delayedAutoUpdateTimer.start()
147 
148  def observeSegmentation(self, observationEnabled):
149  import vtkSegmentationCorePython as vtkSegmentationCore
150  segmentation = self.scriptedEffect.parameterSetNode().GetSegmentationNode().GetSegmentation()
151  if observationEnabled and self.observedSegmentation == segmentation:
152  return
153  if not observationEnabled and not self.observedSegmentation:
154  return
155  # Need to update the observer
156  # Remove old observer
157  if self.observedSegmentation:
158  for tag in self.segmentationNodeObserverTags:
159  self.observedSegmentation.RemoveObserver(tag)
161  self.observedSegmentation = None
162  # Add new observer
163  if observationEnabled and segmentation is not None:
164  self.observedSegmentation = segmentation
165  observedEvents = [
166  vtkSegmentationCore.vtkSegmentation.SegmentAdded,
167  vtkSegmentationCore.vtkSegmentation.SegmentRemoved,
168  vtkSegmentationCore.vtkSegmentation.SegmentModified,
169  vtkSegmentationCore.vtkSegmentation.MasterRepresentationModified ]
170  for eventId in observedEvents:
171  self.segmentationNodeObserverTags.append(self.observedSegmentation.AddObserver(eventId, self.onSegmentationModified))
172 
173  def getPreviewNode(self):
174  previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
175  if previewNode and self.scriptedEffect.parameter("SegmentationResultPreviewOwnerEffect") != self.scriptedEffect.name:
176  # another effect owns this preview node
177  return None
178  return previewNode
179 
180  def updateGUIFromMRML(self):
181 
182  previewNode = self.getPreviewNode()
183 
184  self.cancelButton.setEnabled(previewNode is not None)
185  self.applyButton.setEnabled(previewNode is not None)
186 
187  self.previewOpacitySlider.setEnabled(previewNode is not None)
188  if previewNode:
189  wasBlocked = self.previewOpacitySlider.blockSignals(True)
190  self.previewOpacitySlider.value = self.getPreviewOpacity()
191  self.previewOpacitySlider.blockSignals(wasBlocked)
192  self.previewButton.text = "Update"
193  self.autoUpdateCheckBox.setEnabled(True)
194  self.observeSegmentation(self.autoUpdateCheckBox.isChecked())
195  else:
196  self.previewButton.text = "Initialize"
197  self.autoUpdateCheckBox.setEnabled(False)
198  self.delayedAutoUpdateTimer.stop()
199  self.observeSegmentation(False)
200 
201  autoUpdate = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("AutoUpdate") == 0 else qt.Qt.Checked
202  wasBlocked = self.autoUpdateCheckBox.blockSignals(True)
203  self.autoUpdateCheckBox.setCheckState(autoUpdate)
204  self.autoUpdateCheckBox.blockSignals(wasBlocked)
205 
206  def updateMRMLFromGUI(self):
207  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
208  previewNode = self.getPreviewNode()
209  if previewNode:
210  self.setPreviewOpacity(self.previewOpacitySlider.value)
211 
212  autoUpdate = 1 if self.autoUpdateCheckBox.isChecked() else 0
213  self.scriptedEffect.setParameter("AutoUpdate", autoUpdate)
214 
215  def onPreview(self):
216  slicer.util.showStatusMessage("Running {0} auto-complete...".format(self.scriptedEffect.name), 2000)
217  try:
218  # This can be a long operation - indicate it to the user
219  qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
220  self.preview()
221  finally:
222  qt.QApplication.restoreOverrideCursor()
223 
224  def reset(self):
225  self.delayedAutoUpdateTimer.stop()
226  self.observeSegmentation(False)
227  previewNode = self.scriptedEffect.parameterSetNode().GetNodeReference(ResultPreviewNodeReferenceRole)
228  if previewNode:
229  self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, None)
230  slicer.mrmlScene.RemoveNode(previewNode)
231  self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", "")
232  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
233  segmentationNode.GetDisplayNode().SetOpacity(1.0)
234  self.mergedLabelmapGeometryImage = None
235  self.selectedSegmentIds = None
237  self.clippedMasterImageData = None
238  self.updateGUIFromMRML()
239 
240  def onCancel(self):
241  self.reset()
242 
243  def onApply(self):
244  self.delayedAutoUpdateTimer.stop()
245  self.observeSegmentation(False)
246 
247  import vtkSegmentationCorePython as vtkSegmentationCore
248  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
249  previewNode = self.getPreviewNode()
250 
251  self.scriptedEffect.saveStateForUndo()
252 
253  # Move segments from preview into current segmentation
254  segmentIDs = vtk.vtkStringArray()
255  previewNode.GetSegmentation().GetSegmentIDs(segmentIDs)
256  for index in xrange(segmentIDs.GetNumberOfValues()):
257  segmentID = segmentIDs.GetValue(index)
258  previewSegment = previewNode.GetSegmentation().GetSegment(segmentID)
259  previewSegmentLabelmap = previewSegment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
260  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(previewSegmentLabelmap, segmentationNode, segmentID)
261  previewNode.GetSegmentation().RemoveSegment(segmentID) # delete now to limit memory usage
262 
263  self.reset()
264 
265  def setPreviewOpacity(self, opacity):
266  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
267  segmentationNode.GetDisplayNode().SetOpacity(1.0-opacity)
268  previewNode = self.getPreviewNode()
269  if previewNode:
270  previewNode.GetDisplayNode().SetOpacity(opacity)
271 
272  # Make sure the GUI is up-to-date
273  wasBlocked = self.previewOpacitySlider.blockSignals(True)
274  self.previewOpacitySlider.value = opacity
275  self.previewOpacitySlider.blockSignals(wasBlocked)
276 
277  def getPreviewOpacity(self):
278  previewNode = self.getPreviewNode()
279  return previewNode.GetDisplayNode().GetOpacity() if previewNode else 0.6 # default opacity for preview
280 
281  def preview(self):
282  # Get master volume image data
283  import vtkSegmentationCorePython as vtkSegmentationCore
284  masterImageData = self.scriptedEffect.masterVolumeImageData()
285 
286  # Get segmentation
287  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
288 
289  previewNode = self.getPreviewNode()
290  if not previewNode or not self.mergedLabelmapGeometryImage \
292  self.reset()
293  # Compute merged labelmap extent (effective extent slightly expanded)
294  self.selectedSegmentIds = vtk.vtkStringArray()
295  segmentationNode.GetDisplayNode().GetVisibleSegmentIDs(self.selectedSegmentIds)
296  if self.selectedSegmentIds.GetNumberOfValues() < self.minimumNumberOfSegments:
297  logging.error("Auto-complete operation skipped: at least {0} visible segments are required".format(self.minimumNumberOfSegments))
298  return
299  if not self.mergedLabelmapGeometryImage:
300  self.mergedLabelmapGeometryImage = vtkSegmentationCore.vtkOrientedImageData()
301  commonGeometryString = segmentationNode.GetSegmentation().DetermineCommonLabelmapGeometry(
302  vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.selectedSegmentIds)
303  if not commonGeometryString:
304  logging.info("Auto-complete operation skipped: all visible segments are empty")
305  return
306  vtkSegmentationCore.vtkSegmentationConverter.DeserializeImageGeometry(commonGeometryString, self.mergedLabelmapGeometryImage)
307 
308  masterImageExtent = masterImageData.GetExtent()
309  labelsEffectiveExtent = self.mergedLabelmapGeometryImage.GetExtent()
310  margin = [17, 17, 17]
311  labelsExpandedExtent = [
312  max(masterImageExtent[0], labelsEffectiveExtent[0]-margin[0]),
313  min(masterImageExtent[1], labelsEffectiveExtent[1]+margin[0]),
314  max(masterImageExtent[2], labelsEffectiveExtent[2]-margin[1]),
315  min(masterImageExtent[3], labelsEffectiveExtent[3]+margin[1]),
316  max(masterImageExtent[4], labelsEffectiveExtent[4]-margin[2]),
317  min(masterImageExtent[5], labelsEffectiveExtent[5]+margin[2]) ]
318  self.mergedLabelmapGeometryImage.SetExtent(labelsExpandedExtent)
319 
320  previewNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSegmentationNode')
321  previewNode.UnRegister(None)
322  previewNode = slicer.mrmlScene.AddNode(previewNode)
323  previewNode.CreateDefaultDisplayNodes()
324  previewNode.GetDisplayNode().SetVisibility2DOutline(False)
325  if segmentationNode.GetParentTransformNode():
326  previewNode.SetAndObserveTransformNodeID(segmentationNode.GetParentTransformNode().GetID())
327  self.scriptedEffect.parameterSetNode().SetNodeReferenceID(ResultPreviewNodeReferenceRole, previewNode.GetID())
328  self.scriptedEffect.setCommonParameter("SegmentationResultPreviewOwnerEffect", self.scriptedEffect.name)
329  self.setPreviewOpacity(0.6)
330 
332  self.clippedMasterImageData = vtkSegmentationCore.vtkOrientedImageData()
333  masterImageClipper = vtk.vtkImageConstantPad()
334  masterImageClipper.SetInputData(masterImageData)
335  masterImageClipper.SetOutputWholeExtent(self.mergedLabelmapGeometryImage.GetExtent())
336  masterImageClipper.Update()
337  self.clippedMasterImageData.ShallowCopy(masterImageClipper.GetOutput())
338  self.clippedMasterImageData.CopyDirections(self.mergedLabelmapGeometryImage)
339 
340  previewNode.SetName(segmentationNode.GetName()+" preview")
341 
342  mergedImage = vtkSegmentationCore.vtkOrientedImageData()
343  segmentationNode.GenerateMergedLabelmapForAllSegments(mergedImage,
344  vtkSegmentationCore.vtkSegmentation.EXTENT_UNION_OF_EFFECTIVE_SEGMENTS, self.mergedLabelmapGeometryImage, self.selectedSegmentIds)
345 
346  outputLabelmap = vtkSegmentationCore.vtkOrientedImageData()
347 
348  self.computePreviewLabelmap(mergedImage, outputLabelmap)
349 
350  # Write output segmentation results in segments
351  for index in xrange(self.selectedSegmentIds.GetNumberOfValues()):
352  segmentID = self.selectedSegmentIds.GetValue(index)
353  segment = segmentationNode.GetSegmentation().GetSegment(segmentID)
354  # Disable save with scene?
355 
356  # Get only the label of the current segment from the output image
357  thresh = vtk.vtkImageThreshold()
358  thresh.ReplaceInOn()
359  thresh.ReplaceOutOn()
360  thresh.SetInValue(1)
361  thresh.SetOutValue(0)
362  labelValue = index + 1 # n-th segment label value = n + 1 (background label value is 0)
363  thresh.ThresholdBetween(labelValue, labelValue);
364  thresh.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
365  thresh.SetInputData(outputLabelmap)
366  thresh.Update()
367 
368  # Write label to segment
369  newSegmentLabelmap = vtkSegmentationCore.vtkOrientedImageData()
370  newSegmentLabelmap.ShallowCopy(thresh.GetOutput())
371  newSegmentLabelmap.CopyDirections(mergedImage)
372  newSegment = previewNode.GetSegmentation().GetSegment(segmentID)
373  if not newSegment:
374  newSegment = vtkSegmentationCore.vtkSegment()
375  newSegment.SetName(segment.GetName())
376  color = segmentationNode.GetSegmentation().GetSegment(segmentID).GetColor()
377  newSegment.SetColor(color)
378  previewNode.GetSegmentation().AddSegment(newSegment, segmentID)
379  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(newSegmentLabelmap, previewNode, segmentID)
380 
381  self.updateGUIFromMRML()
382 
383 ResultPreviewNodeReferenceRole = "SegmentationResultPreview"