Slicer  4.11
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
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.items():
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  # Make sure the user wants to do the operation, even if the segment is not visible
60  if not self.scriptedEffect.confirmCurrentSegmentVisible():
61  return abortEvent
62 
63  self.scriptedEffect.saveStateForUndo()
64 
65  # Get modifier labelmap
66  import vtkSegmentationCorePython as vtkSegmentationCore
67  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
68 
69  # Apply poly data on modifier labelmap
70  pipeline.appendPolyMask(modifierLabelmap)
71  # TODO: it would be nice to reduce extent
72  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
73  abortEvent = True
74  elif eventId == vtk.vtkCommand.MouseMoveEvent:
75  if pipeline.actionState == '':
76  xy = callerInteractor.GetEventPosition()
77  pipeline.preview(xy)
78  abortEvent = True
79  self.lastXY = xy
80  elif eventId == vtk.vtkCommand.RightButtonPressEvent or eventId == vtk.vtkCommand.MiddleButtonPressEvent:
81  pipeline.actionState = 'interacting'
82  elif eventId == vtk.vtkCommand.RightButtonReleaseEvent or eventId == vtk.vtkCommand.MiddleButtonReleaseEvent:
83  pipeline.actionState = ''
84  elif eventId == vtk.vtkCommand.EnterEvent:
85  pipeline.actor.VisibilityOn()
86  elif eventId == vtk.vtkCommand.LeaveEvent:
87  pipeline.actor.VisibilityOff()
88  self.lastXY = None
89 
90  return abortEvent
91 
92  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
93  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
94  # Get draw pipeline for current slice
95  pipeline = self.pipelineForWidget(viewWidget)
96  if pipeline is None:
97  logging.error('processViewNodeEvents: Invalid pipeline')
98  return
99 
100  # Update the preview to the new slice
101  if pipeline.actionState == '' and self.lastXY:
102  pipeline.preview(self.lastXY)
103 
104  def pipelineForWidget(self, sliceWidget):
105  if sliceWidget in self.levelTracingPipelines:
106  return self.levelTracingPipelines[sliceWidget]
107 
108  # Create pipeline if does not yet exist
109  pipeline = LevelTracingPipeline(self, sliceWidget)
110 
111  # Add actor
112  renderer = self.scriptedEffect.renderer(sliceWidget)
113  if renderer is None:
114  logging.error("setupPreviewDisplay: Failed to get renderer!")
115  return None
116  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
117 
118  self.levelTracingPipelines[sliceWidget] = pipeline
119  return pipeline
120 
121 #
122 # LevelTracingPipeline
123 #
124 class LevelTracingPipeline(object):
125  """ Visualization objects and pipeline for each slice view for level tracing
126  """
127  def __init__(self, effect, sliceWidget):
128  self.effect = effect
129  self.sliceWidget = sliceWidget
130  self.actionState = ''
131 
132  self.xyPoints = vtk.vtkPoints()
133  self.rasPoints = vtk.vtkPoints()
134  self.polyData = vtk.vtkPolyData()
135 
136  self.tracingFilter = vtkITK.vtkITKLevelTracingImageFilter()
137  self.ijkToXY = vtk.vtkGeneralTransform()
138 
139  self.mapper = vtk.vtkPolyDataMapper2D()
140  self.actor = vtk.vtkActor2D()
141  actorProperty = self.actor.GetProperty()
142  actorProperty.SetColor( 107/255., 190/255., 99/255. )
143  actorProperty.SetLineWidth( 1 )
144  self.mapper.SetInputData(self.polyData)
145  self.actor.SetMapper(self.mapper)
146  actorProperty = self.actor.GetProperty()
147  actorProperty.SetColor(1,1,0)
148  actorProperty.SetLineWidth(1)
149 
150  def preview(self,xy):
151  # Calculate the current level trace view if the mouse is inside the volume extent
152 
153  # Get master volume image data
154  import vtkSegmentationCorePython as vtkSegmentationCore
155  masterImageData = self.effect.scriptedEffect.masterVolumeImageData()
156 
157  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
158  parentTransformNode = None
159  if segmentationNode:
160  parentTransformNode = segmentationNode.GetParentTransformNode()
161 
162  self.xyPoints.Reset()
163  ijk = self.effect.xyToIjk(xy, self.sliceWidget, masterImageData, parentTransformNode)
164  dimensions = masterImageData.GetDimensions()
165 
166  for index in range(3):
167  # TracingFilter crashes if it receives a seed point at the edge of the image,
168  # so only accept the point if it is inside the image and is at least one pixel away from the edge
169  if ijk[index] < 1 or ijk[index] >= dimensions[index]-1:
170  return
171  self.tracingFilter.SetInputData(masterImageData)
172  self.tracingFilter.SetSeed(ijk)
173 
174  # Select the plane corresponding to current slice orientation
175  # for the input volume
176  sliceNode = self.effect.scriptedEffect.viewNode(self.sliceWidget)
177  offset = max(sliceNode.GetDimensions())
178 
179  i0,j0,k0 = self.effect.xyToIjk((0,0), self.sliceWidget, masterImageData, parentTransformNode)
180  i1,j1,k1 = self.effect.xyToIjk((offset,offset), self.sliceWidget, masterImageData, parentTransformNode)
181  if i0 == i1:
182  self.tracingFilter.SetPlaneToJK()
183  if j0 == j1:
184  self.tracingFilter.SetPlaneToIK()
185  if k0 == k1:
186  self.tracingFilter.SetPlaneToIJ()
187 
188  self.tracingFilter.Update()
189  polyData = self.tracingFilter.GetOutput()
190 
191  # Get master volume IJK to slice XY transform
192  xyToRas = sliceNode.GetXYToRAS()
193  rasToIjk = vtk.vtkMatrix4x4()
194  masterImageData.GetImageToWorldMatrix(rasToIjk)
195  rasToIjk.Invert()
196  xyToIjk = vtk.vtkGeneralTransform()
197  xyToIjk.PostMultiply()
198  xyToIjk.Concatenate(xyToRas)
199  if parentTransformNode:
200  worldToSegmentation = vtk.vtkMatrix4x4()
201  parentTransformNode.GetMatrixTransformFromWorld(worldToSegmentation)
202  xyToIjk.Concatenate(worldToSegmentation)
203  xyToIjk.Concatenate(rasToIjk)
204  ijkToXy = xyToIjk.GetInverse()
205  ijkToXy.TransformPoints(polyData.GetPoints(), self.xyPoints)
206 
207  self.polyData.DeepCopy(polyData)
208  self.polyData.GetPoints().DeepCopy(self.xyPoints)
209  self.sliceWidget.sliceView().scheduleRender()
210 
211  def appendPolyMask(self, modifierLabelmap):
212  lines = self.polyData.GetLines()
213  if lines.GetNumberOfCells() == 0:
214  return
215 
216  # Apply poly data on modifier labelmap
217  segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
218  self.effect.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)