Commit 333c4fd41e655127e2e6bc9ffd5b984809c2a112

Authored by tfmoraes
1 parent 46b08a73

ENH: Select a curve in clut raycasting widget to indicate in wich curve the wwwl…

… operation will be applied
invesalius/data/volume.py
@@ -75,7 +75,7 @@ class Volume(): @@ -75,7 +75,7 @@ class Volume():
75 self.opacity_transfer_func = None 75 self.opacity_transfer_func = None
76 self.ww = None 76 self.ww = None
77 self.wl = None 77 self.wl = None
78 - self.n = 0 78 + self.curve = 0
79 self.plane = None 79 self.plane = None
80 self.plane_on = False 80 self.plane_on = False
81 81
@@ -86,6 +86,8 @@ class Volume(): @@ -86,6 +86,8 @@ class Volume():
86 'Hide raycasting volume') 86 'Hide raycasting volume')
87 ps.Publisher().subscribe(self.OnUpdatePreset, 87 ps.Publisher().subscribe(self.OnUpdatePreset,
88 'Update raycasting preset') 88 'Update raycasting preset')
  89 + ps.Publisher().subscribe(self.OnSetCurve,
  90 + 'Set raycasting curve')
89 ps.Publisher().subscribe(self.OnSetWindowLevel, 91 ps.Publisher().subscribe(self.OnSetWindowLevel,
90 'Set raycasting wwwl') 92 'Set raycasting wwwl')
91 ps.Publisher().subscribe(self.Refresh, 93 ps.Publisher().subscribe(self.Refresh,
@@ -157,6 +159,8 @@ class Volume(): @@ -157,6 +159,8 @@ class Volume():
157 ps.Publisher.sendMessage('Change volume viewer background colour', colour) 159 ps.Publisher.sendMessage('Change volume viewer background colour', colour)
158 ps.Publisher.sendMessage('Change volume viewer gui colour', colour) 160 ps.Publisher.sendMessage('Change volume viewer gui colour', colour)
159 161
  162 + def OnSetCurve(self, pubsub_evt):
  163 + self.curve = pubsub_evt.data
160 164
161 def OnSetRelativeWindowLevel(self, pubsub_evt): 165 def OnSetRelativeWindowLevel(self, pubsub_evt):
162 diff_ww, diff_wl = pubsub_evt.data 166 diff_ww, diff_wl = pubsub_evt.data
@@ -170,13 +174,13 @@ class Volume(): @@ -170,13 +174,13 @@ class Volume():
170 174
171 def OnSetWindowLevel(self, pubsub_evt): 175 def OnSetWindowLevel(self, pubsub_evt):
172 ww, wl, n = pubsub_evt.data 176 ww, wl, n = pubsub_evt.data
173 - self.n = n 177 + self.curve = n
174 self.SetWWWL(ww,wl) 178 self.SetWWWL(ww,wl)
175 179
176 def SetWWWL(self, ww, wl): 180 def SetWWWL(self, ww, wl):
177 181
178 if self.config['advancedCLUT']: 182 if self.config['advancedCLUT']:
179 - curve = self.config['16bitClutCurves'][self.n] 183 + curve = self.config['16bitClutCurves'][self.curve]
180 184
181 p1 = curve[0] 185 p1 = curve[0]
182 p2 = curve[-1] 186 p2 = curve[-1]
invesalius/gui/default_viewers.py
@@ -26,7 +26,7 @@ import data.viewer_volume as volume_viewer @@ -26,7 +26,7 @@ import data.viewer_volume as volume_viewer
26 import widgets.slice_menu as slice_menu_ 26 import widgets.slice_menu as slice_menu_
27 27
28 from gui.widgets.clut_raycasting import CLUTRaycastingWidget, \ 28 from gui.widgets.clut_raycasting import CLUTRaycastingWidget, \
29 - EVT_CLUT_POINT_CHANGED 29 + EVT_CLUT_POINT_CHANGED, EVT_CLUT_CURVE_SELECTED
30 30
31 class Panel(wx.Panel): 31 class Panel(wx.Panel):
32 def __init__(self, parent): 32 def __init__(self, parent):
@@ -235,6 +235,7 @@ class VolumeInteraction(wx.Panel): @@ -235,6 +235,7 @@ class VolumeInteraction(wx.Panel):
235 235
236 def __bind_events_wx(self): 236 def __bind_events_wx(self):
237 self.clut_raycasting.Bind(EVT_CLUT_POINT_CHANGED, self.OnPointChanged) 237 self.clut_raycasting.Bind(EVT_CLUT_POINT_CHANGED, self.OnPointChanged)
  238 + self.clut_raycasting.Bind(EVT_CLUT_CURVE_SELECTED , self.OnCurveSelected)
238 #self.Bind(wx.EVT_SIZE, self.OnSize) 239 #self.Bind(wx.EVT_SIZE, self.OnSize)
239 #self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize) 240 #self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
240 241
@@ -242,6 +243,9 @@ class VolumeInteraction(wx.Panel): @@ -242,6 +243,9 @@ class VolumeInteraction(wx.Panel):
242 ps.Publisher.sendMessage('Set raycasting refresh', None) 243 ps.Publisher.sendMessage('Set raycasting refresh', None)
243 ps.Publisher().sendMessage('Render volume viewer', None) 244 ps.Publisher().sendMessage('Render volume viewer', None)
244 245
  246 + def OnCurveSelected(self, evt):
  247 + ps.Publisher.sendMessage('Set raycasting curve', evt.GetCurve())
  248 +
245 import wx.lib.platebtn as pbtn 249 import wx.lib.platebtn as pbtn
246 import wx.lib.buttons as btn 250 import wx.lib.buttons as btn
247 import wx.lib.pubsub as ps 251 import wx.lib.pubsub as ps
invesalius/gui/widgets/clut_raycasting.py
@@ -20,6 +20,7 @@ HISTOGRAM_FILL_COLOUR = (0.25, 0.25, 0.25) @@ -20,6 +20,7 @@ HISTOGRAM_FILL_COLOUR = (0.25, 0.25, 0.25)
20 BACKGROUND_TEXT_COLOUR_RGBA = (1, 0, 0, 0.5) 20 BACKGROUND_TEXT_COLOUR_RGBA = (1, 0, 0, 0.5)
21 GRADIENT_RGBA = 0.75 21 GRADIENT_RGBA = 0.75
22 RADIUS = 5 22 RADIUS = 5
  23 +SELECTION_SIZE = 10
23 24
24 class Node(object): 25 class Node(object):
25 """ 26 """
@@ -41,8 +42,13 @@ class Curve(object): @@ -41,8 +42,13 @@ class Curve(object):
41 def __init__(self): 42 def __init__(self):
42 self.wl = 0 43 self.wl = 0
43 self.ww = 0 44 self.ww = 0
  45 + self.wl_px = 0
44 self.nodes = [] 46 self.nodes = []
45 47
  48 + def CalculateWWWl(self):
  49 + self.ww = self.nodes[-1].graylevel - self.nodes[0].graylevel
  50 + self.wl = self.nodes[0].graylevel + self.ww / 2.0
  51 +
46 class CLUTRaycastingWidget(wx.Panel): 52 class CLUTRaycastingWidget(wx.Panel):
47 """ 53 """
48 This class represents the frame where images is showed 54 This class represents the frame where images is showed
@@ -65,6 +71,7 @@ class CLUTRaycastingWidget(wx.Panel): @@ -65,6 +71,7 @@ class CLUTRaycastingWidget(wx.Panel):
65 self.to_draw_points = 0 71 self.to_draw_points = 0
66 self.histogram_pixel_points = [[0,0]] 72 self.histogram_pixel_points = [[0,0]]
67 self.histogram_array = [100,100] 73 self.histogram_array = [100,100]
  74 + self.previous_wl = 0
68 self.CalculatePixelPoints() 75 self.CalculatePixelPoints()
69 self.dragged = False 76 self.dragged = False
70 self.point_dragged = None 77 self.point_dragged = None
@@ -104,18 +111,27 @@ class CLUTRaycastingWidget(wx.Panel): @@ -104,18 +111,27 @@ class CLUTRaycastingWidget(wx.Panel):
104 pass 111 pass
105 112
106 def OnClick(self, evt): 113 def OnClick(self, evt):
107 - point = self._has_clicked_in_a_point(evt.GetPositionTuple()) 114 + x, y = evt.GetPositionTuple()
  115 + point = self._has_clicked_in_a_point((x, y))
108 # A point has been selected. It can be dragged. 116 # A point has been selected. It can be dragged.
109 if point: 117 if point:
110 self.dragged = True 118 self.dragged = True
111 self.point_dragged = point 119 self.point_dragged = point
112 self.Refresh() 120 self.Refresh()
113 return 121 return
  122 + curve = self._has_clicked_in_selection_curve((x, y))
  123 + if curve is not None:
  124 + print "Selecionou a curva", curve
  125 + self.dragged = True
  126 + self.previous_wl = x
  127 + self.curve_dragged = curve
  128 + evt = CLUTEvent(myEVT_CLUT_CURVE_SELECTED, self.GetId(), curve)
  129 + self.GetEventHandler().ProcessEvent(evt)
  130 + return
114 else: 131 else:
115 - p = self._has_clicked_in_line(evt.GetPositionTuple()) 132 + p = self._has_clicked_in_line((x, y))
116 # The user clicked in the line. Insert a new point. 133 # The user clicked in the line. Insert a new point.
117 if p: 134 if p:
118 - x, y = evt.GetPositionTuple()  
119 n, p = p 135 n, p = p
120 self.points[n].insert(p, {'x': 0, 'y': 0}) 136 self.points[n].insert(p, {'x': 0, 'y': 0})
121 self.colours[n].insert(p, {'red': 0, 'green': 0, 'blue': 0}) 137 self.colours[n].insert(p, {'red': 0, 'green': 0, 'blue': 0})
@@ -131,7 +147,7 @@ class CLUTRaycastingWidget(wx.Panel): @@ -131,7 +147,7 @@ class CLUTRaycastingWidget(wx.Panel):
131 self.curves[n].nodes.insert(p, node) 147 self.curves[n].nodes.insert(p, node)
132 148
133 self.Refresh() 149 self.Refresh()
134 - nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId()) 150 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId(), n)
135 self.GetEventHandler().ProcessEvent(nevt) 151 self.GetEventHandler().ProcessEvent(nevt)
136 return 152 return
137 evt.Skip() 153 evt.Skip()
@@ -152,7 +168,7 @@ class CLUTRaycastingWidget(wx.Panel): @@ -152,7 +168,7 @@ class CLUTRaycastingWidget(wx.Panel):
152 print self.curves[i].nodes 168 print self.curves[i].nodes
153 self.curves[i].nodes[j].colour = (r, g, b) 169 self.curves[i].nodes[j].colour = (r, g, b)
154 self.Refresh() 170 self.Refresh()
155 - nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId()) 171 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId(), i)
156 self.GetEventHandler().ProcessEvent(nevt) 172 self.GetEventHandler().ProcessEvent(nevt)
157 return 173 return
158 evt.Skip() 174 evt.Skip()
@@ -167,7 +183,7 @@ class CLUTRaycastingWidget(wx.Panel): @@ -167,7 +183,7 @@ class CLUTRaycastingWidget(wx.Panel):
167 print "RightClick", i, j 183 print "RightClick", i, j
168 self.RemovePoint(i, j) 184 self.RemovePoint(i, j)
169 self.Refresh() 185 self.Refresh()
170 - nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId()) 186 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId(), i)
171 self.GetEventHandler().ProcessEvent(nevt) 187 self.GetEventHandler().ProcessEvent(nevt)
172 return 188 return
173 evt.Skip() 189 evt.Skip()
@@ -178,10 +194,13 @@ class CLUTRaycastingWidget(wx.Panel): @@ -178,10 +194,13 @@ class CLUTRaycastingWidget(wx.Panel):
178 been occurred in the preset points. 194 been occurred in the preset points.
179 """ 195 """
180 if self.to_render: 196 if self.to_render:
181 - evt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId()) 197 + evt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId(), 0)
182 self.GetEventHandler().ProcessEvent(evt) 198 self.GetEventHandler().ProcessEvent(evt)
183 self.dragged = False 199 self.dragged = False
  200 + self.curve_dragged = None
  201 + self.point_dragged = None
184 self.to_render = False 202 self.to_render = False
  203 + self.previous_wl = 0
185 204
186 def OnWheel(self, evt): 205 def OnWheel(self, evt):
187 """ 206 """
@@ -197,11 +216,11 @@ class CLUTRaycastingWidget(wx.Panel): @@ -197,11 +216,11 @@ class CLUTRaycastingWidget(wx.Panel):
197 216
198 def OnMotion(self, evt): 217 def OnMotion(self, evt):
199 # User dragging a point 218 # User dragging a point
200 - if self.dragged: 219 + x = evt.GetX()
  220 + y = evt.GetY()
  221 + if self.dragged and self.point_dragged:
201 self.to_render = True 222 self.to_render = True
202 i,j = self.point_dragged 223 i,j = self.point_dragged
203 - x = evt.GetX()  
204 - y = evt.GetY()  
205 224
206 width, height= self.GetVirtualSizeTuple() 225 width, height= self.GetVirtualSizeTuple()
207 226
@@ -234,9 +253,25 @@ class CLUTRaycastingWidget(wx.Panel): @@ -234,9 +253,25 @@ class CLUTRaycastingWidget(wx.Panel):
234 self.curves[i].nodes[j].y = y 253 self.curves[i].nodes[j].y = y
235 self.curves[i].nodes[j].graylevel = graylevel 254 self.curves[i].nodes[j].graylevel = graylevel
236 self.curves[i].nodes[j].opacity = opacity 255 self.curves[i].nodes[j].opacity = opacity
  256 + for curve in self.curves:
  257 + curve.CalculateWWWl()
  258 + curve.wl_px = (self.HounsfieldToPixel(curve.wl),
  259 + self.OpacityToPixel(0))
237 self.Refresh() 260 self.Refresh()
238 - evt = CLUTEvent(myEVT_CLUT_POINT , self.GetId()) 261 + evt = CLUTEvent(myEVT_CLUT_POINT , self.GetId(), i)
239 self.GetEventHandler().ProcessEvent(evt) 262 self.GetEventHandler().ProcessEvent(evt)
  263 +
  264 + elif self.dragged and self.curve_dragged is not None:
  265 + curve = self.curves[self.curve_dragged]
  266 + curve.wl = self.PixelToHounsfield(x)
  267 + curve.wl_px = x, self.OpacityToPixel(0)
  268 + for node in curve.nodes:
  269 + node.x += (x - self.previous_wl)
  270 + node.graylevel = self.PixelToHounsfield(node.x)
  271 + ps.Publisher().sendMessage('Set raycasting wwwl',
  272 + (curve.ww, curve.wl, self.curve_dragged))
  273 + self.previous_wl = x
  274 + self.Refresh()
240 else: 275 else:
241 evt.Skip() 276 evt.Skip()
242 277
@@ -257,10 +292,18 @@ class CLUTRaycastingWidget(wx.Panel): @@ -257,10 +292,18 @@ class CLUTRaycastingWidget(wx.Panel):
257 """ 292 """
258 for i, curve in enumerate(self.curves): 293 for i, curve in enumerate(self.curves):
259 for j, node in enumerate(curve.nodes): 294 for j, node in enumerate(curve.nodes):
260 - if self._calculate_distance(node, position) <= RADIUS: 295 + if self._calculate_distance((node.x, node.y), position) <= RADIUS:
261 return (i, j) 296 return (i, j)
262 return None 297 return None
263 298
  299 + def _has_clicked_in_selection_curve(self, position):
  300 + x, y = position
  301 + for i, curve in enumerate(self.curves):
  302 + if self._calculate_distance(curve.wl_px, position) <= RADIUS:
  303 + return i
  304 + return None
  305 +
  306 +
264 def _has_clicked_in_line(self, position): 307 def _has_clicked_in_line(self, position):
265 """ 308 """
266 Verify if was clicked in a line. If yes, it returns the insertion 309 Verify if was clicked in a line. If yes, it returns the insertion
@@ -278,7 +321,7 @@ class CLUTRaycastingWidget(wx.Panel): @@ -278,7 +321,7 @@ class CLUTRaycastingWidget(wx.Panel):
278 return None 321 return None
279 322
280 def _calculate_distance(self, p1, p2): 323 def _calculate_distance(self, p1, p2):
281 - return ((p1.x-p2[0])**2 + (p1.y-p2[1])**2) ** 0.5 324 + return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) ** 0.5
282 325
283 def RemovePoint(self, i, j): 326 def RemovePoint(self, i, j):
284 """ 327 """
@@ -413,12 +456,13 @@ class CLUTRaycastingWidget(wx.Panel): @@ -413,12 +456,13 @@ class CLUTRaycastingWidget(wx.Panel):
413 456
414 def _draw_selection_curve(self, ctx, height): 457 def _draw_selection_curve(self, ctx, height):
415 for curve in self.curves: 458 for curve in self.curves:
416 - x_center = (curve.nodes[0].x + curve.nodes[-1].x)/2.0  
417 - ctx.set_source_rgb(*LINE_COLOUR)  
418 - ctx.stroke()  
419 - ctx.rectangle(x_center-5, height, 10, 10) 459 + x_center, y_center = curve.wl_px
  460 + ctx.rectangle(x_center-SELECTION_SIZE/2.0, y_center, SELECTION_SIZE,
  461 + SELECTION_SIZE)
420 ctx.set_source_rgb(0,0,0) 462 ctx.set_source_rgb(0,0,0)
421 ctx.fill_preserve() 463 ctx.fill_preserve()
  464 + ctx.set_source_rgb(*LINE_COLOUR)
  465 + ctx.stroke()
422 466
423 def Render(self, dc): 467 def Render(self, dc):
424 ctx = wx.lib.wxcairo.ContextFromDC(dc) 468 ctx = wx.lib.wxcairo.ContextFromDC(dc)
@@ -488,6 +532,9 @@ class CLUTRaycastingWidget(wx.Panel): @@ -488,6 +532,9 @@ class CLUTRaycastingWidget(wx.Panel):
488 node.opacity = point['y'] 532 node.opacity = point['y']
489 node.colour = colour['red'], colour['green'], colour['blue'] 533 node.colour = colour['red'], colour['green'], colour['blue']
490 curve.nodes.append(node) 534 curve.nodes.append(node)
  535 + curve.CalculateWWWl()
  536 + curve.wl_px = (self.HounsfieldToPixel(curve.wl),
  537 + self.OpacityToPixel(0))
491 self.curves.append(curve) 538 self.curves.append(curve)
492 self._build_histogram() 539 self._build_histogram()
493 540
@@ -551,8 +598,11 @@ class CLUTRaycastingWidget(wx.Panel): @@ -551,8 +598,11 @@ class CLUTRaycastingWidget(wx.Panel):
551 self.histogram_array = h_array 598 self.histogram_array = h_array
552 599
553 class CLUTEvent(wx.PyCommandEvent): 600 class CLUTEvent(wx.PyCommandEvent):
554 - def __init__(self , evtType, id): 601 + def __init__(self , evtType, id, curve):
555 wx.PyCommandEvent.__init__(self, evtType, id) 602 wx.PyCommandEvent.__init__(self, evtType, id)
  603 + self.curve = curve
  604 + def GetCurve(self):
  605 + return self.curve
556 606
557 607
558 # Occurs when CLUT is sliding 608 # Occurs when CLUT is sliding
@@ -570,3 +620,7 @@ EVT_CLUT_POINT = wx.PyEventBinder(myEVT_CLUT_POINT, 1) @@ -570,3 +620,7 @@ EVT_CLUT_POINT = wx.PyEventBinder(myEVT_CLUT_POINT, 1)
570 # Occurs when a CLUT point was changed 620 # Occurs when a CLUT point was changed
571 myEVT_CLUT_POINT_CHANGED = wx.NewEventType() 621 myEVT_CLUT_POINT_CHANGED = wx.NewEventType()
572 EVT_CLUT_POINT_CHANGED = wx.PyEventBinder(myEVT_CLUT_POINT_CHANGED, 1) 622 EVT_CLUT_POINT_CHANGED = wx.PyEventBinder(myEVT_CLUT_POINT_CHANGED, 1)
  623 +
  624 +# Selected a curve
  625 +myEVT_CLUT_CURVE_SELECTED = wx.NewEventType()
  626 +EVT_CLUT_CURVE_SELECTED = wx.PyEventBinder(myEVT_CLUT_CURVE_SELECTED, 1)