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