Slicer 5.9
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
SegmentEditorLevelTracingEffect.py
Go to the documentation of this file.
1import logging
2import os
3
4import qt
5import vtk
6import vtkITK
7
8import slicer
9from slicer.i18n import tr as _
10
11from SegmentEditorEffects import *
12
13
15 """LevelTracingEffect is a LabelEffect implementing level tracing fill
16 using intensity-based isolines
17 """
18
19 def __init__(self, scriptedEffect):
20 scriptedEffect.name = "Level tracing" # no tr (don't translate it because modules find effects by name)
21 scriptedEffect.title = _("Level tracing")
22 AbstractScriptedSegmentEditorLabelEffect.__init__(self, scriptedEffect)
23
24 # Effect-specific members
26 self.lastXY = None
27
28 def clone(self):
29 import qSlicerSegmentationsEditorEffectsPythonQt as effects
30
31 clonedEffect = effects.qSlicerSegmentEditorScriptedLabelEffect(None)
32 clonedEffect.setPythonSource(__file__.replace("\\", "/"))
33 return clonedEffect
34
35 def icon(self):
36 iconPath = os.path.join(os.path.dirname(__file__), "Resources/Icons/LevelTracing.png")
37 if os.path.exists(iconPath):
38 return qt.QIcon(iconPath)
39 return qt.QIcon()
40
41 def helpText(self):
42 return "<html>" + _("""Add uniform intensity region to selected segment<br>.
43 <p><ul style="margin: 0">
44 <li><b>Mouse move:</b> current background voxel is used to find a closed path that
45 follows the same intensity value back to the starting point within the current slice.
46 <li><b>Left-click:</b> add the previewed region to the current segment.
47 </ul><p>""")
48
49 def setupOptionsFrame(self):
50 self.sliceRotatedErrorLabel = qt.QLabel()
51 # This widget displays an error message if the slice view is not aligned
52 # with the segmentation's axes.
53 self.scriptedEffect.addOptionsWidget(self.sliceRotatedErrorLabel)
54
55 def activate(self):
56 self.sliceRotatedErrorLabel.text = ""
57
58 def deactivate(self):
59 # Clear draw pipelines
60 for sliceWidget, pipeline in self.levelTracingPipelines.items():
61 self.scriptedEffect.removeActor2D(sliceWidget, pipeline.actor)
62 self.levelTracingPipelines = {}
63 self.lastXY = None
64
65 def processInteractionEvents(self, callerInteractor, eventId, viewWidget):
66 abortEvent = False
67
68 # Only allow for slice views
69 if viewWidget.className() != "qMRMLSliceWidget":
70 return abortEvent
71 # Get draw pipeline for current slice
72 pipeline = self.pipelineForWidget(viewWidget)
73 if pipeline is None:
74 return abortEvent
75
76 anyModifierKeyPressed = callerInteractor.GetShiftKey() or callerInteractor.GetControlKey() or callerInteractor.GetAltKey()
77
78 if eventId == vtk.vtkCommand.LeftButtonPressEvent and not anyModifierKeyPressed:
79 abortEvent = True
80
81 # Make sure the user wants to do the operation, even if the segment is not visible
82 if not self.scriptedEffect.confirmCurrentSegmentVisible():
83 self.sliceRotatedErrorLabel.text = ""
84 pipeline.actor.VisibilityOff()
85 self.lastXY = None
86 pipeline.sliceWidget.sliceView().scheduleRender()
87 return abortEvent
88
89 self.scriptedEffect.saveStateForUndo()
90
91 # Get modifier labelmap
92 modifierLabelmap = self.scriptedEffect.defaultModifierLabelmap()
93
94 # Apply poly data on modifier labelmap
95 pipeline.appendPolyMask(modifierLabelmap)
96 # TODO: it would be nice to reduce extent
97 self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
98
99 elif eventId == vtk.vtkCommand.MouseMoveEvent:
100 if pipeline.actionState == "":
101 xy = callerInteractor.GetEventPosition()
102 if pipeline.preview(xy):
103 self.sliceRotatedErrorLabel.text = ""
104 else:
105 self.sliceRotatedErrorLabel.text = (
106 '<b><font color="red">'
107 + _("Slice view is not aligned with segmentation axis.<br>To use this effect, click the 'Slice views orientation' warning button.")
108 + "</font></b>")
109 abortEvent = True
110 self.lastXY = xy
111 elif eventId == vtk.vtkCommand.EnterEvent:
112 self.sliceRotatedErrorLabel.text = ""
113 pipeline.actor.VisibilityOn()
114 elif eventId == vtk.vtkCommand.LeaveEvent:
115 self.sliceRotatedErrorLabel.text = ""
116 pipeline.actor.VisibilityOff()
117 self.lastXY = None
118
119 return abortEvent
120
121 def processViewNodeEvents(self, callerViewNode, eventId, viewWidget):
122 if callerViewNode and callerViewNode.IsA("vtkMRMLSliceNode"):
123 # Get draw pipeline for current slice
124 pipeline = self.pipelineForWidget(viewWidget)
125 if pipeline is None:
126 logging.error("processViewNodeEvents: Invalid pipeline")
127 return
128
129 # Update the preview to the new slice
130 if pipeline.actionState == "" and self.lastXY:
131 pipeline.preview(self.lastXY)
132
133 def pipelineForWidget(self, sliceWidget):
134 if sliceWidget in self.levelTracingPipelines:
135 return self.levelTracingPipelines[sliceWidget]
136
137 # Create pipeline if does not yet exist
138 pipeline = LevelTracingPipeline(self, sliceWidget)
139
140 # Add actor
141 renderer = self.scriptedEffect.renderer(sliceWidget)
142 if renderer is None:
143 logging.error("setupPreviewDisplay: Failed to get renderer!")
144 return None
145 self.scriptedEffect.addActor2D(sliceWidget, pipeline.actor)
146
147 self.levelTracingPipelines[sliceWidget] = pipeline
148 return pipeline
149
150
151#
152# LevelTracingPipeline
153#
155 """Visualization objects and pipeline for each slice view for level tracing"""
156
157 def __init__(self, effect, sliceWidget):
158 self.effect = effect
159 self.sliceWidget = sliceWidget
160 self.actionState = ""
161
162 self.xyPoints = vtk.vtkPoints()
163 self.rasPoints = vtk.vtkPoints()
164 self.polyData = vtk.vtkPolyData()
165
166 self.tracingFilter = vtkITK.vtkITKLevelTracingImageFilter()
167 self.ijkToXY = vtk.vtkGeneralTransform()
168
169 self.mapper = vtk.vtkPolyDataMapper2D()
170 self.actor = vtk.vtkActor2D()
171 actorProperty = self.actor.GetProperty()
172 actorProperty.SetColor(107 / 255.0, 190 / 255.0, 99 / 255.0)
173 actorProperty.SetLineWidth(1)
174 self.mapper.SetInputData(self.polyData)
175 self.actor.SetMapper(self.mapper)
176 actorProperty = self.actor.GetProperty()
177 actorProperty.SetColor(1, 1, 0)
178 actorProperty.SetLineWidth(1)
179
180 def preview(self, xy):
181 """Calculate the current level trace view if the mouse is inside the volume extent
182 Returns False if slice views are rotated.
183 """
184
185 # Get source volume image data
186 sourceImageData = self.effect.scriptedEffect.sourceVolumeImageData()
187
188 segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
189 parentTransformNode = None
190 if segmentationNode:
191 parentTransformNode = segmentationNode.GetParentTransformNode()
192
193 self.xyPoints.Reset()
194 ijk = self.effect.xyToIjk(xy, self.sliceWidget, sourceImageData, parentTransformNode)
195 dimensions = sourceImageData.GetDimensions()
196
197 self.tracingFilter.SetInputData(sourceImageData)
198 self.tracingFilter.SetSeed(ijk)
199
200 # Select the plane corresponding to current slice orientation
201 # for the input volume
202 sliceNode = self.effect.scriptedEffect.viewNode(self.sliceWidget)
203 offset = max(sliceNode.GetDimensions())
204
205 i0, j0, k0 = self.effect.xyToIjk((0, 0), self.sliceWidget, sourceImageData, parentTransformNode)
206 i1, j1, k1 = self.effect.xyToIjk((offset, offset), self.sliceWidget, sourceImageData, parentTransformNode)
207 if i0 == i1:
208 self.tracingFilter.SetPlaneToJK()
209 elif j0 == j1:
210 self.tracingFilter.SetPlaneToIK()
211 elif k0 == k1:
212 self.tracingFilter.SetPlaneToIJ()
213 else:
214 self.polyData.Reset()
215 self.sliceWidget.sliceView().scheduleRender()
216 return False
217
218 self.tracingFilter.Update()
219 polyData = self.tracingFilter.GetOutput()
220
221 # Get source volume IJK to slice XY transform
222 xyToRas = sliceNode.GetXYToRAS()
223 rasToIjk = vtk.vtkMatrix4x4()
224 sourceImageData.GetImageToWorldMatrix(rasToIjk)
225 rasToIjk.Invert()
226 xyToIjk = vtk.vtkGeneralTransform()
227 xyToIjk.PostMultiply()
228 xyToIjk.Concatenate(xyToRas)
229 if parentTransformNode:
230 worldToSegmentation = vtk.vtkMatrix4x4()
231 parentTransformNode.GetMatrixTransformFromWorld(worldToSegmentation)
232 xyToIjk.Concatenate(worldToSegmentation)
233 xyToIjk.Concatenate(rasToIjk)
234 ijkToXy = xyToIjk.GetInverse()
235 if polyData.GetPoints():
236 ijkToXy.TransformPoints(polyData.GetPoints(), self.xyPoints)
237 self.polyData.DeepCopy(polyData)
238 self.polyData.GetPoints().DeepCopy(self.xyPoints)
239 else:
240 self.polyData.Reset()
241 self.sliceWidget.sliceView().scheduleRender()
242 return True
243
244 def appendPolyMask(self, modifierLabelmap):
245 lines = self.polyData.GetLines()
246 if lines.GetNumberOfCells() == 0:
247 return
248
249 # Apply poly data on modifier labelmap
250 segmentationNode = self.effect.scriptedEffect.parameterSetNode().GetSegmentationNode()
251 self.effect.scriptedEffect.appendPolyMask(modifierLabelmap, self.polyData, self.sliceWidget, segmentationNode)