Slicer  5.1
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorLevelTracingEffect.py
Go to the documentation of this file.
1 import logging
2 import os
3 
4 import qt
5 import vtk
6 import vtkITK
7 
8 import slicer
9 
10 from SegmentEditorEffects import *
11 
12 
14  """ LevelTracingEffect is a LabelEffect implementing level tracing fill
15  using intensity-based isolines
16  """
17 
18  def __init__(self, scriptedEffect):
19  scriptedEffect.name = 'Level tracing'
20  AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
21 
22  # Effect-specific members
24  self.lastXY = None
25 
26  def clone(self):
27  import qSlicerSegmentationsEditorEffectsPythonQt as effects
28  clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
29  clonedEffect.setPythonSource(__file__.replace('\\', '/'))
30  return clonedEffect
31 
32  def icon(self):
33  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/LevelTracing.png')
34  if os.path.exists(iconPath):
35  return qt.QIcon(iconPath)
36  return qt.QIcon()
37 
38  def helpText(self):
39  return """<html>Add uniform intensity region to selected segment<br>.
40 <p><ul style="margin: 0">
41 <li><b>Mouse move:</b> current background voxel is used to find a closed path that
42 follows the same intensity value back to the starting point within the current slice.</li>
43 <li><b>Left-click:</b> add the previewed region to the current segment.</li>
44 </ul><p></html>"""
45 
46  def setupOptionsFrame(self):
47  self.sliceRotatedErrorLabel = qt.QLabel()
48  # This widget displays an error message if the slice view is not aligned
49  # with the segmentation's axes.
50  self.scriptedEffect.addOptionsWidget(self.sliceRotatedErrorLabel)
51 
52  def activate(self):
53  self.sliceRotatedErrorLabel.text = ""
54 
55  def deactivate(self):
56  # Clear draw pipelines
57  for sliceWidget, pipeline in self.levelTracingPipelines.items():
58  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
59  self.levelTracingPipelines = {}
60  self.lastXY = None
61 
62  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
63  abortEvent = False
64 
65  # Only allow for slice views
66  if viewWidget.className() != "qMRMLSliceWidget":
67  return abortEvent
68  # Get draw pipeline for current slice
69  pipeline = self.pipelineForWidget(viewWidget)
70  if pipeline is None:
71  return abortEvent
72 
73  anyModifierKeyPressed = callerInteractor.GetShiftKey() or callerInteractor.GetControlKey() or callerInteractor.GetAltKey()
74 
75  if eventId == vtk.vtkCommand.LeftButtonPressEvent and not anyModifierKeyPressed:
76  # Make sure the user wants to do the operation, even if the segment is not visible
77  if not self.scriptedEffect.confirmCurrentSegmentVisible():
78  return abortEvent
79 
80  self.scriptedEffect.saveStateForUndo()
81 
82  # Get modifier labelmap
83  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
84 
85  # Apply poly data on modifier labelmap
86  pipeline.appendPolyMask(modifierLabelmap)
87  # TODO: it would be nice to reduce extent
88  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
89  abortEvent = True
90  elif eventId == vtk.vtkCommand.MouseMoveEvent:
91  if pipeline.actionState == '':
92  xy = callerInteractor.GetEventPosition()
93  if pipeline.preview(xy):
94  self.sliceRotatedErrorLabel.text = ""
95  else:
96  self.sliceRotatedErrorLabel.text = ("<b><font color=\"red\">"
97  + "Slice view is not aligned with segmentation axis.<br>To use this effect, click the 'Slice views orientation' warning button."
98  + "</font></b>")
99  abortEvent = True
100  self.lastXY = xy
101  elif eventId == vtk.vtkCommand.EnterEvent:
102  self.sliceRotatedErrorLabel.text = ""
103  pipeline.actor.VisibilityOn()
104  elif eventId == vtk.vtkCommand.LeaveEvent:
105  self.sliceRotatedErrorLabel.text = ""
106  pipeline.actor.VisibilityOff()
107  self.lastXY = None
108 
109  return abortEvent
110 
111  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
112  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
113  # Get draw pipeline for current slice
114  pipeline = self.pipelineForWidget(viewWidget)
115  if pipeline is None:
116  logging.error('processViewNodeEvents: Invalid pipeline')
117  return
118 
119  # Update the preview to the new slice
120  if pipeline.actionState == '' and self.lastXY:
121  pipeline.preview(self.lastXY)
122 
123  def pipelineForWidget(self, sliceWidget):
124  if sliceWidget in self.levelTracingPipelines:
125  return self.levelTracingPipelines[sliceWidget]
126 
127  # Create pipeline if does not yet exist
128  pipeline = LevelTracingPipeline(self, sliceWidget)
129 
130  # Add actor
131  renderer = self.scriptedEffect.renderer(sliceWidget)
132  if renderer is None:
133  logging.error("setupPreviewDisplay: Failed to get renderer!")
134  return None
135  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
136 
137  self.levelTracingPipelines[sliceWidget] = pipeline
138  return pipeline
139 
140 
141 #
142 # LevelTracingPipeline
143 #
145  """ Visualization objects and pipeline for each slice view for level tracing
146  """
147 
148  def __init__(self, effect, sliceWidget):
149  self.effect = effect
150  self.sliceWidget = sliceWidget
151  self.actionState = ''
152 
153  self.xyPoints = vtk.vtkPoints()
154  self.rasPoints = vtk.vtkPoints()
155  self.polyData = vtk.vtkPolyData()
156 
157  self.tracingFilter = vtkITK.vtkITKLevelTracingImageFilter()
158  self.ijkToXY = vtk.vtkGeneralTransform()
159 
160  self.mapper = vtk.vtkPolyDataMapper2D()
161  self.actor = vtk.vtkActor2D()
162  actorProperty = self.actor.GetProperty()
163  actorProperty.SetColor(107 / 255., 190 / 255., 99 / 255.)
164  actorProperty.SetLineWidth(1)
165  self.mapper.SetInputData(self.polyData)
166  self.actor.SetMapper(self.mapper)
167  actorProperty = self.actor.GetProperty()
168  actorProperty.SetColor(1, 1, 0)
169  actorProperty.SetLineWidth(1)
170 
171  def preview(self, xy):
172  """Calculate the current level trace view if the mouse is inside the volume extent
173  Returns False if slice views are rotated.
174  """
175 
176  # Get master volume image data
177  masterImageData = self.effect.scriptedEffect.masterVolumeImageData()
178 
179  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
180  parentTransformNode = None
181  if segmentationNode:
182  parentTransformNode = segmentationNode.GetParentTransformNode()
183 
184  self.xyPoints.Reset()
185  ijk = self.effect.xyToIjk(xy, self.sliceWidget, masterImageData, parentTransformNode)
186  dimensions = masterImageData.GetDimensions()
187 
188  self.tracingFilter.SetInputData(masterImageData)
189  self.tracingFilter.SetSeed(ijk)
190 
191  # Select the plane corresponding to current slice orientation
192  # for the input volume
193  sliceNode = self.effect.scriptedEffect.viewNode(self.sliceWidget)
194  offset = max(sliceNode.GetDimensions())
195 
196  i0, j0, k0 = self.effect.xyToIjk((0, 0), self.sliceWidget, masterImageData, parentTransformNode)
197  i1, j1, k1 = self.effect.xyToIjk((offset, offset), self.sliceWidget, masterImageData, parentTransformNode)
198  if i0 == i1:
199  self.tracingFilter.SetPlaneToJK()
200  elif j0 == j1:
201  self.tracingFilter.SetPlaneToIK()
202  elif k0 == k1:
203  self.tracingFilter.SetPlaneToIJ()
204  else:
205  self.polyData.Reset()
206  self.sliceWidget.sliceView().scheduleRender()
207  return False
208 
209  self.tracingFilter.Update()
210  polyData = self.tracingFilter.GetOutput()
211 
212  # Get master volume IJK to slice XY transform
213  xyToRas = sliceNode.GetXYToRAS()
214  rasToIjk = vtk.vtkMatrix4x4()
215  masterImageData.GetImageToWorldMatrix(rasToIjk)
216  rasToIjk.Invert()
217  xyToIjk = vtk.vtkGeneralTransform()
218  xyToIjk.PostMultiply()
219  xyToIjk.Concatenate(xyToRas)
220  if parentTransformNode:
221  worldToSegmentation = vtk.vtkMatrix4x4()
222  parentTransformNode.GetMatrixTransformFromWorld(worldToSegmentation)
223  xyToIjk.Concatenate(worldToSegmentation)
224  xyToIjk.Concatenate(rasToIjk)
225  ijkToXy = xyToIjk.GetInverse()
226  if polyData.GetPoints():
227  ijkToXy.TransformPoints(polyData.GetPoints(), self.xyPoints)
228  self.polyData.DeepCopy(polyData)
229  self.polyData.GetPoints().DeepCopy(self.xyPoints)
230  else:
231  self.polyData.Reset()
232  self.sliceWidget.sliceView().scheduleRender()
233  return True
234 
235  def appendPolyMask(self, modifierLabelmap):
236  lines = self.polyData.GetLines()
237  if lines.GetNumberOfCells() == 0:
238  return
239 
240  # Apply poly data on modifier labelmap
241  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
242  self.effect.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)