Commit bc5cd630e6aa6813d60500c7740e63b6a34db836

Authored by tatiana
1 parent 4efd8d3f

ADD: Raycasting window and level control by cursor

invesalius/constants.py
@@ -2,6 +2,11 @@ import os.path @@ -2,6 +2,11 @@ import os.path
2 import wx 2 import wx
3 from project import Project 3 from project import Project
4 4
  5 +# VTK text
  6 +TEXT_SIZE = 12
  7 +TEXT_COLOUR = (1,1,1)
  8 +TEXT_POSITION = (0.03, 0.97)
  9 +
5 # Slice orientation 10 # Slice orientation
6 AXIAL = 0 11 AXIAL = 0
7 CORONAL = 1 12 CORONAL = 1
invesalius/data/viewer_volume.py
@@ -23,8 +23,10 @@ import wx @@ -23,8 +23,10 @@ import wx
23 import vtk 23 import vtk
24 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor 24 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
25 import wx.lib.pubsub as ps 25 import wx.lib.pubsub as ps
  26 +
26 import constants as const 27 import constants as const
27 import project as prj 28 import project as prj
  29 +import data.vtk_utils as vtku
28 30
29 class Viewer(wx.Panel): 31 class Viewer(wx.Panel):
30 def __init__(self, parent): 32 def __init__(self, parent):
@@ -32,36 +34,57 @@ class Viewer(wx.Panel): @@ -32,36 +34,57 @@ class Viewer(wx.Panel):
32 self.SetBackgroundColour(wx.Colour(0, 0, 0)) 34 self.SetBackgroundColour(wx.Colour(0, 0, 0))
33 35
34 style = vtk.vtkInteractorStyleTrackballCamera() 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 sizer = wx.BoxSizer(wx.VERTICAL) 42 sizer = wx.BoxSizer(wx.VERTICAL)
40 - sizer.Add(iren, 1, wx.EXPAND) 43 + sizer.Add(interactor, 1, wx.EXPAND)
41 self.SetSizer(sizer) 44 self.SetSizer(sizer)
42 self.Layout() 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 # That Render() call will get through before we can setup the 49 # That Render() call will get through before we can setup the
47 # RenderWindow() to render via the wxWidgets-created context; this 50 # RenderWindow() to render via the wxWidgets-created context; this
48 # causes flashing on some platforms and downright breaks things on 51 # causes flashing on some platforms and downright breaks things on
49 # other platforms. Instead, we call widget.Enable(). This means 52 # other platforms. Instead, we call widget.Enable(). This means
50 # that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE, 53 # that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE,
51 # that doesn't matter. 54 # that doesn't matter.
52 - iren.Enable(1) 55 + interactor.Enable(1)
53 56
54 ren = vtk.vtkRenderer() 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 self.ren = ren 61 self.ren = ren
59 62
  63 + self.onclick = False
  64 +
60 self.__bind_events() 65 self.__bind_events()
61 self.__bind_events_wx() 66 self.__bind_events_wx()
62 67
63 self.view_angle = None 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 def __bind_events(self): 88 def __bind_events(self):
66 ps.Publisher().subscribe(self.LoadActor, 'Load surface actor into viewer') 89 ps.Publisher().subscribe(self.LoadActor, 'Load surface actor into viewer')
67 ps.Publisher().subscribe(self.UpdateRender, 'Render volume viewer') 90 ps.Publisher().subscribe(self.UpdateRender, 'Render volume viewer')
@@ -73,25 +96,52 @@ class Viewer(wx.Panel): @@ -73,25 +96,52 @@ class Viewer(wx.Panel):
73 'Set Widget Interactor') 96 'Set Widget Interactor')
74 ps.Publisher().subscribe(self.OnSetViewAngle, 97 ps.Publisher().subscribe(self.OnSetViewAngle,
75 'Set volume view angle') 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 def __bind_events_wx(self): 106 def __bind_events_wx(self):
79 #self.Bind(wx.EVT_SIZE, self.OnSize) 107 #self.Bind(wx.EVT_SIZE, self.OnSize)
80 pass 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 def OnSize(self, evt): 124 def OnSize(self, evt):
83 - print "viewer_volume :: OnSize"  
84 self.UpdateRender() 125 self.UpdateRender()
85 self.Refresh() 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 evt.Skip() 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 def LoadVolume(self, pubsub_evt): 135 def LoadVolume(self, pubsub_evt):
92 - volume, colour = pubsub_evt.data 136 + volume = pubsub_evt.data[0]
93 self.light = self.ren.GetLights().GetNextItem() 137 self.light = self.ren.GetLights().GetNextItem()
  138 +
  139 + text = vtku.Text()
  140 + self.text = text
  141 +
94 self.ren.AddVolume(volume) 142 self.ren.AddVolume(volume)
  143 + self.ren.AddActor(text.actor)
  144 +
95 if not (self.view_angle): 145 if not (self.view_angle):
96 self.SetViewAngle(const.VOL_FRONT) 146 self.SetViewAngle(const.VOL_FRONT)
97 else: 147 else:
@@ -117,7 +167,7 @@ class Viewer(wx.Panel): @@ -117,7 +167,7 @@ class Viewer(wx.Panel):
117 ren.ResetCamera() 167 ren.ResetCamera()
118 ren.ResetCameraClippingRange() 168 ren.ResetCameraClippingRange()
119 169
120 - self.iren.Render() 170 + self.interactor.Render()
121 171
122 def OnSetViewAngle(self, evt_pubsub): 172 def OnSetViewAngle(self, evt_pubsub):
123 view = evt_pubsub.data 173 view = evt_pubsub.data
@@ -139,21 +189,21 @@ class Viewer(wx.Panel): @@ -139,21 +189,21 @@ class Viewer(wx.Panel):
139 189
140 self.ren.ResetCameraClippingRange() 190 self.ren.ResetCameraClippingRange()
141 self.ren.ResetCamera() 191 self.ren.ResetCamera()
142 - self.iren.Render() 192 + self.interactor.Render()
143 193
144 def UpdateRender(self, evt_pubsub=None): 194 def UpdateRender(self, evt_pubsub=None):
145 - self.iren.Render() 195 + self.interactor.Render()
146 196
147 def CreatePlanes(self): 197 def CreatePlanes(self):
148 198
149 imagedata = self.imagedata 199 imagedata = self.imagedata
150 ren = self.ren 200 ren = self.ren
151 - iren = self.iren 201 + interactor = self.interactor
152 202
153 import ivVolumeWidgets as vw 203 import ivVolumeWidgets as vw
154 axial_plane = vw.Plane() 204 axial_plane = vw.Plane()
155 axial_plane.SetRender(ren) 205 axial_plane.SetRender(ren)
156 - axial_plane.SetInteractor(iren) 206 + axial_plane.SetInteractor(interactor)
157 axial_plane.SetOrientation(vw.AXIAL) 207 axial_plane.SetOrientation(vw.AXIAL)
158 axial_plane.SetInput(imagedata) 208 axial_plane.SetInput(imagedata)
159 axial_plane.Show() 209 axial_plane.Show()
@@ -161,7 +211,7 @@ class Viewer(wx.Panel): @@ -161,7 +211,7 @@ class Viewer(wx.Panel):
161 211
162 coronal_plane = vw.Plane() 212 coronal_plane = vw.Plane()
163 coronal_plane.SetRender(ren) 213 coronal_plane.SetRender(ren)
164 - coronal_plane.SetInteractor(iren) 214 + coronal_plane.SetInteractor(interactor)
165 coronal_plane.SetOrientation(vw.CORONAL) 215 coronal_plane.SetOrientation(vw.CORONAL)
166 coronal_plane.SetInput(imagedata) 216 coronal_plane.SetInput(imagedata)
167 coronal_plane.Show() 217 coronal_plane.Show()
@@ -169,14 +219,14 @@ class Viewer(wx.Panel): @@ -169,14 +219,14 @@ class Viewer(wx.Panel):
169 219
170 sagital_plane = vw.Plane() 220 sagital_plane = vw.Plane()
171 sagital_plane.SetRender(ren) 221 sagital_plane.SetRender(ren)
172 - sagital_plane.SetInteractor(iren) 222 + sagital_plane.SetInteractor(interactor)
173 sagital_plane.SetOrientation(vw.SAGITAL) 223 sagital_plane.SetOrientation(vw.SAGITAL)
174 sagital_plane.SetInput(imagedata) 224 sagital_plane.SetInput(imagedata)
175 sagital_plane.Show() 225 sagital_plane.Show()
176 sagital_plane.Update() 226 sagital_plane.Update()
177 227
178 def SetWidgetInteractor(self, evt_pubsub=None): 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 def AppendActor(self, evt_pubsub=None): 231 def AppendActor(self, evt_pubsub=None):
182 self.ren.AddActor(evt_pubsub.data) 232 self.ren.AddActor(evt_pubsub.data)
invesalius/data/volume.py
@@ -72,6 +72,9 @@ class Volume(): @@ -72,6 +72,9 @@ class Volume():
72 self.exist = None 72 self.exist = None
73 self.color_transfer = None 73 self.color_transfer = None
74 self.opacity_transfer_func = None 74 self.opacity_transfer_func = None
  75 + self.ww = None
  76 + self.wl = None
  77 + self.n = 0
75 78
76 self.__bind_events() 79 self.__bind_events()
77 80
@@ -83,10 +86,12 @@ class Volume(): @@ -83,10 +86,12 @@ class Volume():
83 'Hide raycasting volume') 86 'Hide raycasting volume')
84 ps.Publisher().subscribe(self.SetRaycastPreset, 87 ps.Publisher().subscribe(self.SetRaycastPreset,
85 'Set raycasting preset') 88 'Set raycasting preset')
86 - ps.Publisher().subscribe(self.SetWWWL, 89 + ps.Publisher().subscribe(self.OnSetWindowLevel,
87 'Set raycasting wwwl') 90 'Set raycasting wwwl')
88 ps.Publisher().subscribe(self.Refresh, 91 ps.Publisher().subscribe(self.Refresh,
89 'Set raycasting refresh') 92 'Set raycasting refresh')
  93 + ps.Publisher().subscribe(self.OnSetRelativeWindowLevel,
  94 + 'Set raycasting relative window and level')
90 95
91 def OnLoadVolume(self, pubsub_evt): 96 def OnLoadVolume(self, pubsub_evt):
92 label = pubsub_evt.data 97 label = pubsub_evt.data
@@ -95,7 +100,6 @@ class Volume(): @@ -95,7 +100,6 @@ class Volume():
95 100
96 def LoadConfig(self): 101 def LoadConfig(self):
97 self.config = Project().raycasting_preset 102 self.config = Project().raycasting_preset
98 - #print path  
99 103
100 def OnHideVolume(self, pubsub_evt): 104 def OnHideVolume(self, pubsub_evt):
101 self.volume.SetVisibility(0) 105 self.volume.SetVisibility(0)
@@ -126,11 +130,24 @@ class Volume(): @@ -126,11 +130,24 @@ class Volume():
126 self.Create8bColorTable(self.scale) 130 self.Create8bColorTable(self.scale)
127 self.Create8bOpacityTable(self.scale) 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 ww, wl, n = pubsub_evt.data 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 if self.config['advancedCLUT']: 149 if self.config['advancedCLUT']:
133 - curve = self.config['16bitClutCurves'][n] 150 + curve = self.config['16bitClutCurves'][self.n]
134 151
135 p1 = curve[0] 152 p1 = curve[0]
136 p2 = curve[-1] 153 p2 = curve[-1]
@@ -155,7 +172,7 @@ class Volume(): @@ -155,7 +172,7 @@ class Volume():
155 self.config['ww'] = ww 172 self.config['ww'] = ww
156 173
157 self.__config_preset() 174 self.__config_preset()
158 - ps.Publisher().sendMessage('Render volume viewer', None) 175 + #ps.Publisher().sendMessage('Render volume viewer', None)
159 176
160 def Refresh(self, pubsub_evt): 177 def Refresh(self, pubsub_evt):
161 self.__config_preset() 178 self.__config_preset()
@@ -167,7 +184,6 @@ class Volume(): @@ -167,7 +184,6 @@ class Volume():
167 else: 184 else:
168 color_transfer = vtk.vtkColorTransferFunction() 185 color_transfer = vtk.vtkColorTransferFunction()
169 color_transfer.RemoveAllPoints() 186 color_transfer.RemoveAllPoints()
170 - print self.config  
171 curve_table = self.config['16bitClutCurves'] 187 curve_table = self.config['16bitClutCurves']
172 color_table = self.config['16bitClutColors'] 188 color_table = self.config['16bitClutColors']
173 colors = [] 189 colors = []
@@ -191,12 +207,10 @@ class Volume(): @@ -191,12 +207,10 @@ class Volume():
191 color_transfer = vtk.vtkColorTransferFunction() 207 color_transfer = vtk.vtkColorTransferFunction()
192 color_transfer.RemoveAllPoints() 208 color_transfer.RemoveAllPoints()
193 color_preset = self.config['CLUT'] 209 color_preset = self.config['CLUT']
194 - print ">>>", color_preset  
195 if color_preset != "No CLUT": 210 if color_preset != "No CLUT":
196 p = plistlib.readPlist( 211 p = plistlib.readPlist(
197 os.path.join(const.RAYCASTING_PRESETS_DIRECTORY, 212 os.path.join(const.RAYCASTING_PRESETS_DIRECTORY,
198 'color_list', color_preset + '.plist')) 213 'color_list', color_preset + '.plist'))
199 - print "nome clut", p  
200 r = p['Red'] 214 r = p['Red']
201 g = p['Green'] 215 g = p['Green']
202 b = p['Blue'] 216 b = p['Blue']
@@ -205,7 +219,6 @@ class Volume(): @@ -205,7 +219,6 @@ class Volume():
205 wl = self.TranslateScale(scale, self.config['wl']) 219 wl = self.TranslateScale(scale, self.config['wl'])
206 inc = ww / 254.0 220 inc = ww / 254.0
207 for i,rgb in enumerate(colors): 221 for i,rgb in enumerate(colors):
208 - print i,inc, ww, wl - ww/2 + i * inc, rgb  
209 color_transfer.AddRGBPoint((wl - ww/2) + (i * inc), *[i/255.0 for i in rgb]) 222 color_transfer.AddRGBPoint((wl - ww/2) + (i * inc), *[i/255.0 for i in rgb])
210 self.color_transfer = color_transfer 223 self.color_transfer = color_transfer
211 return color_transfer 224 return color_transfer
@@ -221,6 +234,8 @@ class Volume(): @@ -221,6 +234,8 @@ class Volume():
221 234
222 ww = self.config['ww'] 235 ww = self.config['ww']
223 wl = self.config['wl'] 236 wl = self.config['wl']
  237 + self.ww = ww
  238 + self.wl = wl
224 239
225 l1 = wl - ww/2.0 240 l1 = wl - ww/2.0
226 l2 = wl + ww/2.0 241 l2 = wl + ww/2.0
@@ -255,16 +270,12 @@ class Volume(): @@ -255,16 +270,12 @@ class Volume():
255 ww = self.config['ww'] 270 ww = self.config['ww']
256 wl = self.TranslateScale(scale, self.config['wl']) 271 wl = self.TranslateScale(scale, self.config['wl'])
257 272
258 - print ww, wl  
259 -  
260 l1 = wl - ww/2.0 273 l1 = wl - ww/2.0
261 l2 = wl + ww/2.0 274 l2 = wl + ww/2.0
262 275
263 opacity_transfer_func.RemoveAllPoints() 276 opacity_transfer_func.RemoveAllPoints()
264 opacity_transfer_func.AddSegment(0, 0, 2**16-1, 0) 277 opacity_transfer_func.AddSegment(0, 0, 2**16-1, 0)
265 278
266 - print "l1, l2", l1, l2  
267 -  
268 k1 = 0.0 279 k1 = 0.0
269 k2 = 1.0 280 k2 = 1.0
270 281
invesalius/data/vtk_utils.py
1 import wx.lib.pubsub as ps 1 import wx.lib.pubsub as ps
  2 +import vtk
  3 +
  4 +import constants as const
2 5
3 # If you are frightened by the code bellow, or think it must have been result of 6 # If you are frightened by the code bellow, or think it must have been result of
4 # an identation error, lookup at: 7 # an identation error, lookup at:
@@ -47,4 +50,41 @@ def ShowProgress(number_of_filters = 1): @@ -47,4 +50,41 @@ def ShowProgress(number_of_filters = 1):
47 (progress[0], label)) 50 (progress[0], label))
48 return progress[0] 51 return progress[0]
49 52
50 - return UpdateProgress  
51 \ No newline at end of file 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()