Slicer  5.2
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
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)