Commit bc5cd630e6aa6813d60500c7740e63b6a34db836
1 parent
4efd8d3f
Exists in
master
and in
6 other branches
ADD: Raycasting window and level control by cursor
Showing
4 changed files
with
144 additions
and
38 deletions
Show diff stats
invesalius/constants.py
invesalius/data/viewer_volume.py
... | ... | @@ -23,8 +23,10 @@ import wx |
23 | 23 | import vtk |
24 | 24 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
25 | 25 | import wx.lib.pubsub as ps |
26 | + | |
26 | 27 | import constants as const |
27 | 28 | import project as prj |
29 | +import data.vtk_utils as vtku | |
28 | 30 | |
29 | 31 | class Viewer(wx.Panel): |
30 | 32 | def __init__(self, parent): |
... | ... | @@ -32,36 +34,57 @@ class Viewer(wx.Panel): |
32 | 34 | self.SetBackgroundColour(wx.Colour(0, 0, 0)) |
33 | 35 | |
34 | 36 | style = vtk.vtkInteractorStyleTrackballCamera() |
37 | + self.style = style | |
35 | 38 | |
36 | - iren = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) | |
37 | - iren.SetInteractorStyle(style) | |
39 | + interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) | |
40 | + interactor.SetInteractorStyle(style) | |
38 | 41 | |
39 | 42 | sizer = wx.BoxSizer(wx.VERTICAL) |
40 | - sizer.Add(iren, 1, wx.EXPAND) | |
43 | + sizer.Add(interactor, 1, wx.EXPAND) | |
41 | 44 | self.SetSizer(sizer) |
42 | 45 | self.Layout() |
43 | 46 | |
44 | - # It would be more correct (API-wise) to call iren.Initialize() and | |
45 | - # iren.Start() here, but Initialize() calls RenderWindow.Render(). | |
47 | + # It would be more correct (API-wise) to call interactor.Initialize() and | |
48 | + # interactor.Start() here, but Initialize() calls RenderWindow.Render(). | |
46 | 49 | # That Render() call will get through before we can setup the |
47 | 50 | # RenderWindow() to render via the wxWidgets-created context; this |
48 | 51 | # causes flashing on some platforms and downright breaks things on |
49 | 52 | # other platforms. Instead, we call widget.Enable(). This means |
50 | 53 | # that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE, |
51 | 54 | # that doesn't matter. |
52 | - iren.Enable(1) | |
55 | + interactor.Enable(1) | |
53 | 56 | |
54 | 57 | ren = vtk.vtkRenderer() |
55 | - iren.GetRenderWindow().AddRenderer(ren) | |
58 | + interactor.GetRenderWindow().AddRenderer(ren) | |
56 | 59 | |
57 | - self.iren = iren | |
60 | + self.interactor = interactor | |
58 | 61 | self.ren = ren |
59 | 62 | |
63 | + self.onclick = False | |
64 | + | |
60 | 65 | self.__bind_events() |
61 | 66 | self.__bind_events_wx() |
62 | 67 | |
63 | 68 | self.view_angle = None |
64 | 69 | |
70 | + def OnMove(self, obj, evt): | |
71 | + if self.onclick: | |
72 | + mouse_x, mouse_y = self.interactor.GetEventPosition() | |
73 | + diff_x = mouse_x - self.last_x | |
74 | + diff_y = mouse_y - self.last_y | |
75 | + self.last_x, self.last_y = mouse_x, mouse_y | |
76 | + ps.Publisher().sendMessage('Set raycasting relative window and level', | |
77 | + (diff_x, diff_y)) | |
78 | + self.interactor.Render() | |
79 | + | |
80 | + def OnClick(self, obj, evt): | |
81 | + self.onclick = True | |
82 | + mouse_x, mouse_y = self.interactor.GetEventPosition() | |
83 | + self.last_x, self.last_y = mouse_x, mouse_y | |
84 | + | |
85 | + def OnRelease(self, obj, evt): | |
86 | + self.onclick = False | |
87 | + | |
65 | 88 | def __bind_events(self): |
66 | 89 | ps.Publisher().subscribe(self.LoadActor, 'Load surface actor into viewer') |
67 | 90 | ps.Publisher().subscribe(self.UpdateRender, 'Render volume viewer') |
... | ... | @@ -73,25 +96,52 @@ class Viewer(wx.Panel): |
73 | 96 | 'Set Widget Interactor') |
74 | 97 | ps.Publisher().subscribe(self.OnSetViewAngle, |
75 | 98 | 'Set volume view angle') |
76 | - | |
99 | + ps.Publisher().subscribe(self.OnSetWindowLevelText, | |
100 | + 'Set volume window and level text') | |
101 | + ps.Publisher().subscribe(self.OnEnableBrightContrast, | |
102 | + 'Bright and contrast adjustment') | |
103 | + ps.Publisher().subscribe(self.OnDisableBrightContrast, | |
104 | + 'Set Editor Mode') | |
77 | 105 | |
78 | 106 | def __bind_events_wx(self): |
79 | 107 | #self.Bind(wx.EVT_SIZE, self.OnSize) |
80 | 108 | pass |
81 | - | |
109 | + | |
110 | + | |
111 | + def OnEnableBrightContrast(self, pubsub_evt): | |
112 | + style = self.style | |
113 | + style.AddObserver("MouseMoveEvent", self.OnMove) | |
114 | + style.AddObserver("LeftButtonPressEvent", self.OnClick) | |
115 | + style.AddObserver("LeftButtonReleaseEvent", self.OnRelease) | |
116 | + | |
117 | + | |
118 | + def OnDisableBrightContrast(self, pubsub_evt): | |
119 | + style = vtk.vtkInteractorStyleTrackballCamera() | |
120 | + self.interactor.SetInteractorStyle(style) | |
121 | + self.style = style | |
122 | + | |
123 | + | |
82 | 124 | def OnSize(self, evt): |
83 | - print "viewer_volume :: OnSize" | |
84 | 125 | self.UpdateRender() |
85 | 126 | self.Refresh() |
86 | - print dir(self.iren) | |
87 | - self.iren.UpdateWindowUI() | |
88 | - self.iren.Update() | |
127 | + self.interactor.UpdateWindowUI() | |
128 | + self.interactor.Update() | |
89 | 129 | evt.Skip() |
90 | 130 | |
131 | + def OnSetWindowLevelText(self, pubsub_evt): | |
132 | + ww, wl = pubsub_evt.data | |
133 | + self.text.SetValue("WL: %d WW: %d"%(wl, ww)) | |
134 | + | |
91 | 135 | def LoadVolume(self, pubsub_evt): |
92 | - volume, colour = pubsub_evt.data | |
136 | + volume = pubsub_evt.data[0] | |
93 | 137 | self.light = self.ren.GetLights().GetNextItem() |
138 | + | |
139 | + text = vtku.Text() | |
140 | + self.text = text | |
141 | + | |
94 | 142 | self.ren.AddVolume(volume) |
143 | + self.ren.AddActor(text.actor) | |
144 | + | |
95 | 145 | if not (self.view_angle): |
96 | 146 | self.SetViewAngle(const.VOL_FRONT) |
97 | 147 | else: |
... | ... | @@ -117,7 +167,7 @@ class Viewer(wx.Panel): |
117 | 167 | ren.ResetCamera() |
118 | 168 | ren.ResetCameraClippingRange() |
119 | 169 | |
120 | - self.iren.Render() | |
170 | + self.interactor.Render() | |
121 | 171 | |
122 | 172 | def OnSetViewAngle(self, evt_pubsub): |
123 | 173 | view = evt_pubsub.data |
... | ... | @@ -139,21 +189,21 @@ class Viewer(wx.Panel): |
139 | 189 | |
140 | 190 | self.ren.ResetCameraClippingRange() |
141 | 191 | self.ren.ResetCamera() |
142 | - self.iren.Render() | |
192 | + self.interactor.Render() | |
143 | 193 | |
144 | 194 | def UpdateRender(self, evt_pubsub=None): |
145 | - self.iren.Render() | |
195 | + self.interactor.Render() | |
146 | 196 | |
147 | 197 | def CreatePlanes(self): |
148 | 198 | |
149 | 199 | imagedata = self.imagedata |
150 | 200 | ren = self.ren |
151 | - iren = self.iren | |
201 | + interactor = self.interactor | |
152 | 202 | |
153 | 203 | import ivVolumeWidgets as vw |
154 | 204 | axial_plane = vw.Plane() |
155 | 205 | axial_plane.SetRender(ren) |
156 | - axial_plane.SetInteractor(iren) | |
206 | + axial_plane.SetInteractor(interactor) | |
157 | 207 | axial_plane.SetOrientation(vw.AXIAL) |
158 | 208 | axial_plane.SetInput(imagedata) |
159 | 209 | axial_plane.Show() |
... | ... | @@ -161,7 +211,7 @@ class Viewer(wx.Panel): |
161 | 211 | |
162 | 212 | coronal_plane = vw.Plane() |
163 | 213 | coronal_plane.SetRender(ren) |
164 | - coronal_plane.SetInteractor(iren) | |
214 | + coronal_plane.SetInteractor(interactor) | |
165 | 215 | coronal_plane.SetOrientation(vw.CORONAL) |
166 | 216 | coronal_plane.SetInput(imagedata) |
167 | 217 | coronal_plane.Show() |
... | ... | @@ -169,14 +219,14 @@ class Viewer(wx.Panel): |
169 | 219 | |
170 | 220 | sagital_plane = vw.Plane() |
171 | 221 | sagital_plane.SetRender(ren) |
172 | - sagital_plane.SetInteractor(iren) | |
222 | + sagital_plane.SetInteractor(interactor) | |
173 | 223 | sagital_plane.SetOrientation(vw.SAGITAL) |
174 | 224 | sagital_plane.SetInput(imagedata) |
175 | 225 | sagital_plane.Show() |
176 | 226 | sagital_plane.Update() |
177 | 227 | |
178 | 228 | def SetWidgetInteractor(self, evt_pubsub=None): |
179 | - evt_pubsub.data.SetInteractor(self.iren._Iren) | |
229 | + evt_pubsub.data.SetInteractor(self.interactor._Iren) | |
180 | 230 | |
181 | 231 | def AppendActor(self, evt_pubsub=None): |
182 | 232 | self.ren.AddActor(evt_pubsub.data) | ... | ... |
invesalius/data/volume.py
... | ... | @@ -72,6 +72,9 @@ class Volume(): |
72 | 72 | self.exist = None |
73 | 73 | self.color_transfer = None |
74 | 74 | self.opacity_transfer_func = None |
75 | + self.ww = None | |
76 | + self.wl = None | |
77 | + self.n = 0 | |
75 | 78 | |
76 | 79 | self.__bind_events() |
77 | 80 | |
... | ... | @@ -83,10 +86,12 @@ class Volume(): |
83 | 86 | 'Hide raycasting volume') |
84 | 87 | ps.Publisher().subscribe(self.SetRaycastPreset, |
85 | 88 | 'Set raycasting preset') |
86 | - ps.Publisher().subscribe(self.SetWWWL, | |
89 | + ps.Publisher().subscribe(self.OnSetWindowLevel, | |
87 | 90 | 'Set raycasting wwwl') |
88 | 91 | ps.Publisher().subscribe(self.Refresh, |
89 | 92 | 'Set raycasting refresh') |
93 | + ps.Publisher().subscribe(self.OnSetRelativeWindowLevel, | |
94 | + 'Set raycasting relative window and level') | |
90 | 95 | |
91 | 96 | def OnLoadVolume(self, pubsub_evt): |
92 | 97 | label = pubsub_evt.data |
... | ... | @@ -95,7 +100,6 @@ class Volume(): |
95 | 100 | |
96 | 101 | def LoadConfig(self): |
97 | 102 | self.config = Project().raycasting_preset |
98 | - #print path | |
99 | 103 | |
100 | 104 | def OnHideVolume(self, pubsub_evt): |
101 | 105 | self.volume.SetVisibility(0) |
... | ... | @@ -126,11 +130,24 @@ class Volume(): |
126 | 130 | self.Create8bColorTable(self.scale) |
127 | 131 | self.Create8bOpacityTable(self.scale) |
128 | 132 | |
129 | - def SetWWWL(self, pubsub_evt): | |
133 | + def OnSetRelativeWindowLevel(self, pubsub_evt): | |
134 | + diff_ww, diff_wl = pubsub_evt.data | |
135 | + ww = self.ww + diff_ww | |
136 | + wl = self.wl + diff_wl | |
137 | + ps.Publisher().sendMessage('Set volume window and level text', | |
138 | + (ww, wl)) | |
139 | + self.SetWWWL(ww, wl) | |
140 | + self.ww = ww | |
141 | + self.wl = wl | |
142 | + | |
143 | + def OnSetWindowLevel(self, pubsub_evt): | |
130 | 144 | ww, wl, n = pubsub_evt.data |
131 | - print "Setting ww, wl", ww, wl | |
145 | + self.SetWWWL(ww,wl,n) | |
146 | + | |
147 | + def SetWWWL(self, ww, wl): | |
148 | + | |
132 | 149 | if self.config['advancedCLUT']: |
133 | - curve = self.config['16bitClutCurves'][n] | |
150 | + curve = self.config['16bitClutCurves'][self.n] | |
134 | 151 | |
135 | 152 | p1 = curve[0] |
136 | 153 | p2 = curve[-1] |
... | ... | @@ -155,7 +172,7 @@ class Volume(): |
155 | 172 | self.config['ww'] = ww |
156 | 173 | |
157 | 174 | self.__config_preset() |
158 | - ps.Publisher().sendMessage('Render volume viewer', None) | |
175 | + #ps.Publisher().sendMessage('Render volume viewer', None) | |
159 | 176 | |
160 | 177 | def Refresh(self, pubsub_evt): |
161 | 178 | self.__config_preset() |
... | ... | @@ -167,7 +184,6 @@ class Volume(): |
167 | 184 | else: |
168 | 185 | color_transfer = vtk.vtkColorTransferFunction() |
169 | 186 | color_transfer.RemoveAllPoints() |
170 | - print self.config | |
171 | 187 | curve_table = self.config['16bitClutCurves'] |
172 | 188 | color_table = self.config['16bitClutColors'] |
173 | 189 | colors = [] |
... | ... | @@ -191,12 +207,10 @@ class Volume(): |
191 | 207 | color_transfer = vtk.vtkColorTransferFunction() |
192 | 208 | color_transfer.RemoveAllPoints() |
193 | 209 | color_preset = self.config['CLUT'] |
194 | - print ">>>", color_preset | |
195 | 210 | if color_preset != "No CLUT": |
196 | 211 | p = plistlib.readPlist( |
197 | 212 | os.path.join(const.RAYCASTING_PRESETS_DIRECTORY, |
198 | 213 | 'color_list', color_preset + '.plist')) |
199 | - print "nome clut", p | |
200 | 214 | r = p['Red'] |
201 | 215 | g = p['Green'] |
202 | 216 | b = p['Blue'] |
... | ... | @@ -205,7 +219,6 @@ class Volume(): |
205 | 219 | wl = self.TranslateScale(scale, self.config['wl']) |
206 | 220 | inc = ww / 254.0 |
207 | 221 | for i,rgb in enumerate(colors): |
208 | - print i,inc, ww, wl - ww/2 + i * inc, rgb | |
209 | 222 | color_transfer.AddRGBPoint((wl - ww/2) + (i * inc), *[i/255.0 for i in rgb]) |
210 | 223 | self.color_transfer = color_transfer |
211 | 224 | return color_transfer |
... | ... | @@ -221,6 +234,8 @@ class Volume(): |
221 | 234 | |
222 | 235 | ww = self.config['ww'] |
223 | 236 | wl = self.config['wl'] |
237 | + self.ww = ww | |
238 | + self.wl = wl | |
224 | 239 | |
225 | 240 | l1 = wl - ww/2.0 |
226 | 241 | l2 = wl + ww/2.0 |
... | ... | @@ -255,16 +270,12 @@ class Volume(): |
255 | 270 | ww = self.config['ww'] |
256 | 271 | wl = self.TranslateScale(scale, self.config['wl']) |
257 | 272 | |
258 | - print ww, wl | |
259 | - | |
260 | 273 | l1 = wl - ww/2.0 |
261 | 274 | l2 = wl + ww/2.0 |
262 | 275 | |
263 | 276 | opacity_transfer_func.RemoveAllPoints() |
264 | 277 | opacity_transfer_func.AddSegment(0, 0, 2**16-1, 0) |
265 | 278 | |
266 | - print "l1, l2", l1, l2 | |
267 | - | |
268 | 279 | k1 = 0.0 |
269 | 280 | k2 = 1.0 |
270 | 281 | ... | ... |
invesalius/data/vtk_utils.py
1 | 1 | import wx.lib.pubsub as ps |
2 | +import vtk | |
3 | + | |
4 | +import constants as const | |
2 | 5 | |
3 | 6 | # If you are frightened by the code bellow, or think it must have been result of |
4 | 7 | # an identation error, lookup at: |
... | ... | @@ -47,4 +50,41 @@ def ShowProgress(number_of_filters = 1): |
47 | 50 | (progress[0], label)) |
48 | 51 | return progress[0] |
49 | 52 | |
50 | - return UpdateProgress | |
51 | 53 | \ No newline at end of file |
54 | + return UpdateProgress | |
55 | + | |
56 | +class Text(object): | |
57 | + def __init__(self): | |
58 | + | |
59 | + property = vtk.vtkTextProperty() | |
60 | + property.SetFontSize(const.TEXT_SIZE) | |
61 | + property.SetFontFamilyToArial() | |
62 | + property.BoldOff() | |
63 | + property.ItalicOff() | |
64 | + property.ShadowOn() | |
65 | + property.SetJustificationToLeft() | |
66 | + property.SetVerticalJustificationToTop() | |
67 | + property.SetColor(const.TEXT_COLOUR) | |
68 | + self.property = property | |
69 | + | |
70 | + mapper = vtk.vtkTextMapper() | |
71 | + mapper.SetTextProperty(property) | |
72 | + self.mapper = mapper | |
73 | + | |
74 | + x, y = const.TEXT_POSITION | |
75 | + actor = vtk.vtkActor2D() | |
76 | + actor.SetMapper(mapper) | |
77 | + actor.GetPositionCoordinate().SetCoordinateSystemToNormalizedDisplay() | |
78 | + actor.GetPositionCoordinate().SetValue(x,y) | |
79 | + self.actor = actor | |
80 | + | |
81 | + def SetValue(self, value): | |
82 | + self.mapper.SetInput(str(value)) | |
83 | + | |
84 | + def SetPosition(self, position): | |
85 | + self.actor.SetPositionCoordinate().SetValue(position) | |
86 | + | |
87 | + def Show(self, value=1): | |
88 | + if value: | |
89 | + self.actor.VisibilityOn() | |
90 | + else: | |
91 | + self.actor.VisibilityOff() | ... | ... |