Slicer  4.10
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 os
2 import vtk, qt, ctk, slicer
3 import logging
4 from SegmentEditorEffects import *
5 import vtkITK
6 
8  """ LevelTracingEffect is a LabelEffect implementing level tracing fill
9  using intensity-based isolines
10  """
11 
12  def __init__(self, scriptedEffect):
13  scriptedEffect.name = 'Level tracing'
14  AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
15 
16  # Effect-specific members
18  self.lastXY = None
19 
20  def clone(self):
21  import qSlicerSegmentationsEditorEffectsPythonQt as effects
22  clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
23  clonedEffect.setPythonSource(__file__.replace('\\','/'))
24  return clonedEffect
25 
26  def icon(self):
27  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/LevelTracing.png')
28  if os.path.exists(iconPath):
29  return qt.QIcon(iconPath)
30  return qt.QIcon()
31 
32  def helpText(self):
33  return """<html>Add uniform intensity region to selected segment<br>.
34 <p><ul style="margin: 0">
35 <li><b>Mouse move:</b> current background voxel is used to find a closed path that
36 follows the same intensity value back to the starting point within the current slice.</li>
37 <li><b>Left-click:</b> add the previewed region to the current segment.</li>
38 </ul><p></html>"""
39 
40  def deactivate(self):
41  # Clear draw pipelines
42  for sliceWidget, pipeline in self.levelTracingPipelines.iteritems():
43  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
44  self.levelTracingPipelines = {}
45  self.lastXY = None
46 
47  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
48  abortEvent = False
49 
50  # Only allow for slice views
51  if viewWidget.className() != "qMRMLSliceWidget":
52  return abortEvent
53  # Get draw pipeline for current slice
54  pipeline = self.pipelineForWidget(viewWidget)
55  if pipeline is None:
56  return abortEvent
57 
58  if eventId == vtk.vtkCommand.LeftButtonPressEvent:
59  self.scriptedEffect.saveStateForUndo()
60 
61  # Get modifier labelmap
62  import vtkSegmentationCorePython as vtkSegmentationCore
63  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
64 
65  # Apply poly data on modifier labelmap
66  pipeline.appendPolyMask(modifierLabelmap)
67  # TODO: it would be nice to reduce extent
68  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
69  abortEvent = True
70  elif eventId == vtk.vtkCommand.MouseMoveEvent:
71  if pipeline.actionState == '':
72  xy = callerInteractor.GetEventPosition()
73  pipeline.preview(xy)
74  abortEvent = True
75  self.lastXY = xy
76  elif eventId == vtk.vtkCommand.RightButtonPressEvent or eventId == vtk.vtkCommand.MiddleButtonPressEvent:
77  pipeline.actionState = 'interacting'
78  elif eventId == vtk.vtkCommand.RightButtonReleaseEvent or eventId == vtk.vtkCommand.MiddleButtonReleaseEvent:
79  pipeline.actionState = ''
80  elif eventId == vtk.vtkCommand.EnterEvent:
81  pipeline.actor.VisibilityOn()
82  elif eventId == vtk.vtkCommand.LeaveEvent:
83  pipeline.actor.VisibilityOff()
84  self.lastXY = None
85 
86  return abortEvent
87 
88  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
89  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
90  # Get draw pipeline for current slice
91  pipeline = self.pipelineForWidget(viewWidget)
92  if pipeline is None:
93  logging.error('processViewNodeEvents: Invalid pipeline')
94  return
95 
96  # Update the preview to the new slice
97  if pipeline.actionState == '' and self.lastXY:
98  pipeline.preview(self.lastXY)
99 
100  def pipelineForWidget(self, sliceWidget):
101  if sliceWidget in self.levelTracingPipelines:
102  return self.levelTracingPipelines[sliceWidget]
103 
104  # Create pipeline if does not yet exist
105  pipeline = LevelTracingPipeline(self, sliceWidget)
106 
107  # Add actor
108  renderer = self.scriptedEffect.renderer(sliceWidget)
109  if renderer is None:
110  logging.error("setupPreviewDisplay: Failed to get renderer!")
111  return None
112  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
113 
114  self.levelTracingPipelines[sliceWidget] = pipeline
115  return pipeline
116 
117 #
118 # LevelTracingPipeline
119 #
121  """ Visualization objects and pipeline for each slice view for level tracing
122  """
123  def __init__(self, effect, sliceWidget):
124  self.effect = effect
125  self.sliceWidget = sliceWidget
126  self.actionState = ''
127 
128  self.xyPoints = vtk.vtkPoints()
129  self.rasPoints = vtk.vtkPoints()
130  self.polyData = vtk.vtkPolyData()
131 
132  self.tracingFilter = vtkITK.vtkITKLevelTracingImageFilter()
133  self.ijkToXY = vtk.vtkGeneralTransform()
134 
135  self.mapper = vtk.vtkPolyDataMapper2D()
136  self.actor = vtk.vtkActor2D()
137  actorProperty = self.actor.GetProperty()
138  actorProperty.SetColor( 107/255., 190/255., 99/255. )
139  actorProperty.SetLineWidth( 1 )
140  self.mapper.SetInputData(self.polyData)
141  self.actor.SetMapper(self.mapper)
142  actorProperty = self.actor.GetProperty()
143  actorProperty.SetColor(1,1,0)
144  actorProperty.SetLineWidth(1)
145 
146  def preview(self,xy):
147  # Calculate the current level trace view if the mouse is inside the volume extent
148 
149  # Get master volume image data
150  import vtkSegmentationCorePython as vtkSegmentationCore
151  masterImageData = self.effect.scriptedEffect.masterVolumeImageData()
152 
153  self.xyPoints.Reset()
154  ijk = self.effect.xyToIjk(xy, self.sliceWidget, masterImageData)
155  dimensions = masterImageData.GetDimensions()
156 
157  for index in xrange(3):
158  # TracingFilter crashes if it receives a seed point at the edge of the image,
159  # so only accept the point if it is inside the image and is at least one pixel away from the edge
160  if ijk[index] < 1 or ijk[index] >= dimensions[index]-1:
161  return
162  self.tracingFilter.SetInputData(masterImageData)
163  self.tracingFilter.SetSeed(ijk)
164 
165  # Select the plane corresponding to current slice orientation
166  # for the input volume
167  sliceNode = self.effect.scriptedEffect.viewNode(self.sliceWidget)
168  offset = max(sliceNode.GetDimensions())
169  i0,j0,k0 = self.effect.xyToIjk((0,0), self.sliceWidget, masterImageData)
170  i1,j1,k1 = self.effect.xyToIjk((offset,offset), self.sliceWidget, masterImageData)
171  if i0 == i1:
172  self.tracingFilter.SetPlaneToJK()
173  if j0 == j1:
174  self.tracingFilter.SetPlaneToIK()
175  if k0 == k1:
176  self.tracingFilter.SetPlaneToIJ()
177 
178  self.tracingFilter.Update()
179  polyData = self.tracingFilter.GetOutput()
180 
181  # Get master volume IJK to slice XY transform
182  xyToRas = sliceNode.GetXYToRAS()
183  rasToIjk = vtk.vtkMatrix4x4()
184  masterImageData.GetImageToWorldMatrix(rasToIjk)
185  rasToIjk.Invert()
186  xyToIjk = vtk.vtkGeneralTransform()
187  xyToIjk.PostMultiply()
188  xyToIjk.Concatenate(xyToRas)
189  xyToIjk.Concatenate(rasToIjk)
190  ijkToXy = xyToIjk.GetInverse()
191  ijkToXy.TransformPoints(polyData.GetPoints(), self.xyPoints)
192 
193  self.polyData.DeepCopy(polyData)
194  self.polyData.GetPoints().DeepCopy(self.xyPoints)
195  self.sliceWidget.sliceView().scheduleRender()
196 
197  def appendPolyMask(self, modifierLabelmap):
198  lines = self.polyData.GetLines()
199  if lines.GetNumberOfCells() == 0:
200  return
201 
202  # Apply poly data on modifier labelmap
203  self.effect.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)