Slicer  4.11
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorDrawEffect.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 
7  """ DrawEffect is a LabelEffect implementing the interactive draw
8  tool in the segment editor
9  """
10 
11  def __init__(self, scriptedEffect):
12  scriptedEffect.name = 'Draw'
13  self.drawPipelines = {}
14  AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
15 
16  def clone(self):
17  import qSlicerSegmentationsEditorEffectsPythonQt as effects
18  clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
19  clonedEffect.setPythonSource(__file__.replace('\\','/'))
20  return clonedEffect
21 
22  def icon(self):
23  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Draw.png')
24  if os.path.exists(iconPath):
25  return qt.QIcon(iconPath)
26  return qt.QIcon()
27 
28  def helpText(self):
29  return """<html>Draw segment outline in slice viewers<br>.
30 <p><ul style="margin: 0">
31 <li><b>Left-click:</b> add point.</li>
32 <li><b>Left-button drag-and-drop:</b> add multiple points.</li>
33 <li><b>x:</b> delete last point.</li>
34 <li><b>Right-click</b> or <b>a</b> or <b>enter:</b> apply outline.</li>
35 </ul><p></html>"""
36 
37  def deactivate(self):
38  # Clear draw pipelines
39  for sliceWidget, pipeline in self.drawPipelines.items():
40  self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
41  self.drawPipelines = {}
42 
43  def setupOptionsFrame(self):
44  pass
45 
46  def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
47  abortEvent = False
48 
49  # Only allow for slice views
50  if viewWidget.className() != "qMRMLSliceWidget":
51  return abortEvent
52  # Get draw pipeline for current slice
53  pipeline = self.pipelineForWidget(viewWidget)
54  if pipeline is None:
55  return abortEvent
56 
57  if eventId == vtk.vtkCommand.LeftButtonPressEvent:
58  # Make sure the user wants to do the operation, even if the segment is not visible
59  confirmedEditingAllowed = self.scriptedEffect.confirmCurrentSegmentVisible()
60  if confirmedEditingAllowed == self.scriptedEffect.NotConfirmed or confirmedEditingAllowed == self.scriptedEffect.ConfirmedWithDialog:
61  # If user had to move the mouse to click on the popup, so we cannot continue with painting
62  # from the current mouse position. User will need to click again.
63  # The dialog is not displayed again for the same segment.
64  return abortEvent
65  pipeline.actionState = "drawing"
66  self.scriptedEffect.cursorOff(viewWidget)
67  xy = callerInteractor.GetEventPosition()
68  ras = self.xyToRas(xy, viewWidget)
69  pipeline.addPoint(ras)
70  abortEvent = True
71  elif eventId == vtk.vtkCommand.LeftButtonReleaseEvent:
72  pipeline.actionState = ""
73  self.scriptedEffect.cursorOn(viewWidget)
74  elif eventId == vtk.vtkCommand.RightButtonPressEvent:
75  sliceNode = viewWidget.sliceLogic().GetSliceNode()
76  pipeline.lastInsertSliceNodeMTime = sliceNode.GetMTime()
77  elif eventId == vtk.vtkCommand.RightButtonReleaseEvent:
78  sliceNode = viewWidget.sliceLogic().GetSliceNode()
79  if abs(pipeline.lastInsertSliceNodeMTime - sliceNode.GetMTime()) < 2:
80  pipeline.apply()
81  pipeline.actionState = None
82  elif eventId == vtk.vtkCommand.MouseMoveEvent:
83  if pipeline.actionState == "drawing":
84  xy = callerInteractor.GetEventPosition()
85  ras = self.xyToRas(xy, viewWidget)
86  pipeline.addPoint(ras)
87  abortEvent = True
88  elif eventId == vtk.vtkCommand.KeyPressEvent:
89  key = callerInteractor.GetKeySym()
90  if key == 'a' or key == 'Return':
91  pipeline.apply()
92  abortEvent = True
93  if key == 'x':
94  pipeline.deleteLastPoint()
95  abortEvent = True
96  else:
97  pass
98 
99  pipeline.positionActors()
100  return abortEvent
101 
102  def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
103  if callerViewNode and callerViewNode.IsA('vtkMRMLSliceNode'):
104  # Get draw pipeline for current slice
105  pipeline = self.pipelineForWidget(viewWidget)
106  if pipeline is None:
107  logging.error('processViewNodeEvents: Invalid pipeline')
108  return
109 
110  # Make sure all points are on the current slice plane.
111  # If the SliceToRAS has been modified, then we're on a different plane
112  sliceLogic = viewWidget.sliceLogic()
113  lineMode = "solid"
114  currentSlice = sliceLogic.GetSliceOffset()
115  if pipeline.activeSlice:
116  offset = abs(currentSlice - pipeline.activeSlice)
117  if offset > 0.01:
118  lineMode = "dashed"
119  pipeline.setLineMode(lineMode)
120  pipeline.positionActors()
121 
122  def pipelineForWidget(self, sliceWidget):
123  if sliceWidget in self.drawPipelines:
124  return self.drawPipelines[sliceWidget]
125 
126  # Create pipeline if does not yet exist
127  pipeline = DrawPipeline(self.scriptedEffect, sliceWidget)
128 
129  # Add actor
130  renderer = self.scriptedEffect.renderer(sliceWidget)
131  if renderer is None:
132  logging.error("pipelineForWidget: Failed to get renderer!")
133  return None
134  self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
135 
136  self.drawPipelines[sliceWidget] = pipeline
137  return pipeline
138 
139 #
140 # DrawPipeline
141 #
142 class DrawPipeline(object):
143  """ Visualization objects and pipeline for each slice view for drawing
144  """
145  def __init__(self, scriptedEffect, sliceWidget):
146  self.scriptedEffect = scriptedEffect
147  self.sliceWidget = sliceWidget
148  self.activeSlice = None
150  self.actionState = None
151 
152  self.xyPoints = vtk.vtkPoints()
153  self.rasPoints = vtk.vtkPoints()
154  self.polyData = self.createPolyData()
155 
156  self.mapper = vtk.vtkPolyDataMapper2D()
157  self.actor = vtk.vtkActor2D()
158  self.mapper.SetInputData(self.polyData)
159  self.actor.SetMapper(self.mapper)
160  actorProperty = self.actor.GetProperty()
161  actorProperty.SetColor(1,1,0)
162  actorProperty.SetLineWidth(1)
163 
164  def createPolyData(self):
165  # Make an empty single-polyline polydata
166  polyData = vtk.vtkPolyData()
167  polyData.SetPoints(self.xyPoints)
168  lines = vtk.vtkCellArray()
169  polyData.SetLines(lines)
170  return polyData
171 
172  def addPoint(self,ras):
173  # Add a world space point to the current outline
174 
175  # Store active slice when first point is added
176  sliceLogic = self.sliceWidget.sliceLogic()
177  currentSlice = sliceLogic.GetSliceOffset()
178  if not self.activeSlice:
179  self.activeSlice = currentSlice
180  self.setLineMode("solid")
181 
182  # Don't allow adding points on except on the active slice
183  # (where first point was laid down)
184  if self.activeSlice != currentSlice: return
185 
186  # Keep track of node state (in case of pan/zoom)
187  sliceNode = sliceLogic.GetSliceNode()
188  self.lastInsertSliceNodeMTime = sliceNode.GetMTime()
189 
190  p = self.rasPoints.InsertNextPoint(ras)
191  if p > 0:
192  idList = vtk.vtkIdList()
193  idList.InsertNextId(p-1)
194  idList.InsertNextId(p)
195  self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
196 
197  def setLineMode(self,mode="solid"):
198  actorProperty = self.actor.GetProperty()
199  if mode == "solid":
200  actorProperty.SetLineStipplePattern(0xffff)
201  elif mode == "dashed":
202  actorProperty.SetLineStipplePattern(0xff00)
203 
204  def positionActors(self):
205  # Update draw feedback to follow slice node
206  sliceLogic = self.sliceWidget.sliceLogic()
207  sliceNode = sliceLogic.GetSliceNode()
208  rasToXY = vtk.vtkTransform()
209  rasToXY.SetMatrix(sliceNode.GetXYToRAS())
210  rasToXY.Inverse()
211  self.xyPoints.Reset()
212  rasToXY.TransformPoints(self.rasPoints, self.xyPoints)
213  self.polyData.Modified()
214  self.sliceWidget.sliceView().scheduleRender()
215 
216  def apply(self):
217  lines = self.polyData.GetLines()
218  lineExists = lines.GetNumberOfCells() > 0
219  if lineExists:
220  # Close the polyline back to the first point
221  idList = vtk.vtkIdList()
222  idList.InsertNextId(self.polyData.GetNumberOfPoints()-1)
223  idList.InsertNextId(0)
224  self.polyData.InsertNextCell(vtk.VTK_LINE, idList)
225 
226  # Get modifier labelmap
227  import vtkSegmentationCorePython as vtkSegmentationCore
228  modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
229 
230  # Apply poly data on modifier labelmap
231  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
232  self.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)
233 
234  self.resetPolyData()
235  if lineExists:
236  self.scriptedEffect.saveStateForUndo()
237  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
238 
239  def resetPolyData(self):
240  # Return the polyline to initial state with no points
241  lines = self.polyData.GetLines()
242  lines.Initialize()
243  self.xyPoints.Reset()
244  self.rasPoints.Reset()
245  self.activeSlice = None
246 
247  def deleteLastPoint(self):
248  # Unwind through addPoint list back to empty polydata
249  pcount = self.rasPoints.GetNumberOfPoints()
250  if pcount <= 0:
251  return
252 
253  pcount = pcount - 1
254  self.rasPoints.SetNumberOfPoints(pcount)
255 
256  cellCount = self.polyData.GetNumberOfCells()
257  if cellCount > 0:
258  self.polyData.DeleteCell(cellCount - 1)
259  self.polyData.RemoveDeletedCells()
260 
261  self.positionActors()
def __init__(self, scriptedEffect, sliceWidget)
def processViewNodeEvents(self, callerViewNode, eventId, viewWidget)
def processInteractionEvents(self, callerInteractor, eventId, viewWidget)