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