Commit d7c4240ebed155ff41ce8a4a790f8a6c0e37b375

Authored by tfmoraes
1 parent 15b8a469

STYLE: Reestructured clut raycasting widget class. Created a class to represents…

… the curves and class to represent the points from preset
Showing 1 changed file with 159 additions and 103 deletions   Show diff stats
invesalius/gui/widgets/clut_raycasting.py
... ... @@ -21,6 +21,28 @@ BACKGROUND_TEXT_COLOUR_RGBA = (1, 0, 0, 0.5)
21 21 GRADIENT_RGBA = 0.75
22 22 RADIUS = 5
23 23  
  24 +class Node(object):
  25 + """
  26 + Represents the points in the raycasting preset. Contains its colour,
  27 + graylevel (hounsfield scale), opacity, x and y position in the widget.
  28 + """
  29 + def __init__(self):
  30 + self.colour = None
  31 + self.x = 0
  32 + self.y = 0
  33 + self.graylevel = 0
  34 + self.opacity = 0
  35 +
  36 +class Curve(object):
  37 + """
  38 + Represents the curves in the raycasting preset. It contains the point nodes from
  39 + the curve and its window width & level.
  40 + """
  41 + def __init__(self):
  42 + self.wl = 0
  43 + self.ww = 0
  44 + self.nodes = []
  45 +
24 46 class CLUTRaycastingWidget(wx.Panel):
25 47 """
26 48 This class represents the frame where images is showed
... ... @@ -35,6 +57,7 @@ class CLUTRaycastingWidget(wx.Panel):
35 57 super(CLUTRaycastingWidget, self).__init__(parent, id)
36 58 self.points = []
37 59 self.colours = []
  60 + self.curves = []
38 61 self.init = -1024
39 62 self.end = 2000
40 63 self.padding = 5
... ... @@ -42,7 +65,7 @@ class CLUTRaycastingWidget(wx.Panel):
42 65 self.to_draw_points = 0
43 66 self.histogram_pixel_points = [[0,0]]
44 67 self.histogram_array = [100,100]
45   - self.CreatePixelArray()
  68 + self.CalculatePixelPoints()
46 69 self.dragged = False
47 70 self.point_dragged = None
48 71 self.__bind_events_wx()
... ... @@ -55,7 +78,7 @@ class CLUTRaycastingWidget(wx.Panel):
55 78 """
56 79 self.init, self.end = range
57 80 print "Range", range
58   - self.CreatePixelArray()
  81 + self.CalculatePixelPoints()
59 82  
60 83 def SetPadding(self, padding):
61 84 self.padding = padding
... ... @@ -92,11 +115,21 @@ class CLUTRaycastingWidget(wx.Panel):
92 115 p = self._has_clicked_in_line(evt.GetPositionTuple())
93 116 # The user clicked in the line. Insert a new point.
94 117 if p:
  118 + x, y = evt.GetPositionTuple()
95 119 n, p = p
96 120 self.points[n].insert(p, {'x': 0, 'y': 0})
97   - self.pixels_points[n].insert(p, list(evt.GetPositionTuple()))
98 121 self.colours[n].insert(p, {'red': 0, 'green': 0, 'blue': 0})
99   - self.PixelToHounsfield(n, p)
  122 + self.points[n][p]['x'] = self.PixelToHounsfield(x)
  123 + self.points[n][p]['y'] = self.PixelToOpacity(y)
  124 +
  125 + node = Node()
  126 + node.colour = (0, 0, 0)
  127 + node.x = x
  128 + node.y = y
  129 + node.graylevel = self.points[n][p]['x']
  130 + node.opacity = self.points[n][p]['y']
  131 + self.curves[n].nodes.insert(p, node)
  132 +
100 133 self.Refresh()
101 134 nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
102 135 self.GetEventHandler().ProcessEvent(nevt)
... ... @@ -116,6 +149,8 @@ class CLUTRaycastingWidget(wx.Panel):
116 149 self.colours[i][j]['red'] = r
117 150 self.colours[i][j]['green'] = g
118 151 self.colours[i][j]['blue'] = b
  152 + print self.curves[i].nodes
  153 + self.curves[i].nodes[j].colour = (r, g, b)
119 154 self.Refresh()
120 155 nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
121 156 self.GetEventHandler().ProcessEvent(nevt)
... ... @@ -183,17 +218,22 @@ class CLUTRaycastingWidget(wx.Panel):
183 218 x = width
184 219  
185 220 # A point must be greater than the previous one, but the first one
186   - if j > 0 and x <= self.pixels_points[i][j-1][0]:
187   - x = self.pixels_points[i][j-1][0] + 1
  221 + if j > 0 and x <= self.curves[i].nodes[j-1].x:
  222 + x = self.curves[i].nodes[j-1].x + 1
188 223  
189 224 # A point must be lower than the previous one, but the last one
190   - if j < len(self.pixels_points[i]) -1 \
191   - and x >= self.pixels_points[i][j+1][0]:
192   - x = self.pixels_points[i][j+1][0] - 1
193   -
194   - self.pixels_points[i][j][0] = x
195   - self.pixels_points[i][j][1] = y
196   - self.PixelToHounsfield(i,j)
  225 + if j < len(self.curves[i].nodes) -1 \
  226 + and x >= self.curves[i].nodes[j+1].x:
  227 + x = self.curves[i].nodes[j+1].x - 1
  228 +
  229 + graylevel = self.PixelToHounsfield(x)
  230 + opacity = self.PixelToOpacity(y)
  231 + self.points[i][j]['x'] = graylevel
  232 + self.points[i][j]['y'] = opacity
  233 + self.curves[i].nodes[j].x = x
  234 + self.curves[i].nodes[j].y = y
  235 + self.curves[i].nodes[j].graylevel = graylevel
  236 + self.curves[i].nodes[j].opacity = opacity
197 237 self.Refresh()
198 238 evt = CLUTEvent(myEVT_CLUT_POINT , self.GetId())
199 239 self.GetEventHandler().ProcessEvent(evt)
... ... @@ -208,38 +248,16 @@ class CLUTRaycastingWidget(wx.Panel):
208 248 self.Render(dc)
209 249  
210 250 def OnSize(self, evt):
211   - self.CreatePixelArray()
  251 + self.CalculatePixelPoints()
212 252 self.Refresh()
213 253  
214   - def _draw_gradient(self, ctx, height):
215   - #The gradient
216   - height += self.padding
217   - for i, curve in enumerate(self.pixels_points):
218   - x, y = curve[0]
219   - xini, yini = curve[0]
220   - xend, yend = curve[-1]
221   - gradient = cairo.LinearGradient(xini, height, xend, height)
222   - ctx.move_to(x, y)
223   - for j, point in enumerate(curve):
224   - x, y = point
225   - ctx.line_to(x, y)
226   - r = self.colours[i][j]['red']
227   - g = self.colours[i][j]['green']
228   - b = self.colours[i][j]['blue']
229   - gradient.add_color_stop_rgba((x - xini) * 1.0 / (xend - xini),
230   - r, g, b, GRADIENT_RGBA)
231   - ctx.line_to(x, height)
232   - ctx.line_to(xini, height)
233   - ctx.close_path()
234   - ctx.set_source(gradient)
235   - ctx.fill()
236 254 def _has_clicked_in_a_point(self, position):
237 255 """
238 256 returns the index from the selected point
239 257 """
240   - for i,curve in enumerate(self.pixels_points):
241   - for j,point in enumerate(curve):
242   - if self._calculate_distance(point, position) <= RADIUS:
  258 + for i, curve in enumerate(self.curves):
  259 + for j, node in enumerate(curve.nodes):
  260 + if self._calculate_distance(node, position) <= RADIUS:
243 261 return (i, j)
244 262 return None
245 263  
... ... @@ -248,27 +266,28 @@ class CLUTRaycastingWidget(wx.Panel):
248 266 Verify if was clicked in a line. If yes, it returns the insertion
249 267 position in the point list.
250 268 """
251   - for n, point in enumerate(self.pixels_points):
252   - p = bisect.bisect([i[0] for i in point], position[0])
  269 + for n, curve in enumerate(self.curves):
  270 + p = bisect.bisect([node.x for node in curve.nodes], position[0])
253 271 print p
254   - if p != 0 and p != len(point):
255   - x1, y1 = point[p-1]
  272 + if p != 0 and p != len(curve.nodes):
  273 + x1, y1 = curve.nodes[p-1].x, curve.nodes[p-1].y
256 274 x2, y2 = position
257   - x3, y3 = point[p]
  275 + x3, y3 = curve.nodes[p].x, curve.nodes[p].y
258 276 if int(float(x2 - x1) / (x3 - x2)) - int(float(y2 - y1) / (y3 - y2)) == 0:
259 277 return (n, p)
260 278 return None
261 279  
262 280 def _calculate_distance(self, p1, p2):
263   - return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) ** 0.5
  281 + return ((p1.x-p2[0])**2 + (p1.y-p2[1])**2) ** 0.5
264 282  
265 283 def RemovePoint(self, i, j):
266 284 """
267 285 The point the point in the given i,j index
268 286 """
269   - self.pixels_points[i].pop(j)
270 287 self.points[i].pop(j)
271 288 self.colours[i].pop(j)
  289 +
  290 + self.curves[i].nodes.pop(j)
272 291 # If the point to removed is that was selected before and have a
273 292 # textbox, then remove the point and the textbox
274 293 if (i, j) == self.point_dragged:
... ... @@ -283,43 +302,58 @@ class CLUTRaycastingWidget(wx.Panel):
283 302 # Can't have only one point in the curve
284 303 if len(self.points[i]) == 1:
285 304 self.points.pop(i)
286   - self.pixels_points.pop(i)
287 305 self.colours.pop(i)
288 306 self.point_dragged = None
289 307  
290   - def _draw_curves(self, ctx):
291   - #Drawing the lines
292   - for curve in self.pixels_points:
293   - x,y = curve[0]
294   - ctx.move_to(x, y)
295   - for point in curve:
296   - x,y = point
  308 + self.curves.pop(i)
  309 +
  310 + def _draw_gradient(self, ctx, height):
  311 + #The gradient
  312 + height += self.padding
  313 + for curve in self.curves:
  314 + first_node = curve.nodes[0]
  315 + last_node = curve.nodes[-1]
  316 + xini, yini = first_node.x, first_node.y
  317 + xend, yend = last_node.x, last_node.y
  318 + gradient = cairo.LinearGradient(xini, height, xend, height)
  319 + ctx.move_to(xini, yini)
  320 + for node in curve.nodes:
  321 + x, y = node.x, node.y
  322 + r, g, b = node.colour
297 323 ctx.line_to(x, y)
  324 + gradient.add_color_stop_rgba((x - xini) * 1.0 / (xend - xini),
  325 + r, g, b, GRADIENT_RGBA)
  326 + ctx.line_to(x, height)
  327 + ctx.line_to(xini, height)
  328 + ctx.close_path()
  329 + ctx.set_source(gradient)
  330 + ctx.fill()
  331 +
  332 + def _draw_curves(self, ctx):
  333 + for curve in self.curves:
  334 + ctx.move_to(curve.nodes[0].x, curve.nodes[0].y)
  335 + for node in curve.nodes:
  336 + ctx.line_to(node.x, node.y)
298 337 ctx.set_source_rgb(*LINE_COLOUR)
299 338 ctx.stroke()
300 339  
301 340 def _draw_points(self, ctx):
302   - #Drawing the circles that represents the points
303   - for i, curve in enumerate(self.pixels_points):
304   - for j, point in enumerate(curve):
305   - x,y = point
306   - r = self.colours[i][j]['red']
307   - g = self.colours[i][j]['green']
308   - b = self.colours[i][j]['blue']
309   - ctx.arc(x, y, RADIUS, 0, math.pi * 2)
310   - ctx.set_source_rgb(r, g, b)
  341 + for curve in self.curves:
  342 + for node in curve.nodes:
  343 + ctx.arc(node.x, node.y, RADIUS, 0, math.pi * 2)
  344 + ctx.set_source_rgb(*node.colour)
311 345 ctx.fill_preserve()
312 346 ctx.set_source_rgb(*LINE_COLOUR)
313 347 ctx.stroke()
314   - #ctx.move_to(x, y)
315 348  
316 349 def _draw_selected_point_text(self, ctx):
317 350 ctx.select_font_face('Sans')
318 351 ctx.set_font_size(15)
319 352 i,j = self.point_dragged
320   - x,y = self.pixels_points[i][j]
321   - value = self.points[i][j]['x']
322   - alpha = self.points[i][j]['y']
  353 + node = self.curves[i].nodes[j]
  354 + x,y = node.x, node.y
  355 + value = node.graylevel
  356 + alpha = node.opacity
323 357 x_bearing, y_bearing, width, height, x_advance, y_advance\
324 358 = ctx.text_extents("Value %6d" % value)
325 359  
... ... @@ -329,15 +363,15 @@ class CLUTRaycastingWidget(wx.Panel):
329 363 y_superior = y - RADIUS * 2 - 2 + y_bearing * 2
330 364 y_inferior = fheight * 2
331 365  
332   - # The bottom position of the text box mustn't be upper thant the top of
  366 + # The bottom position of the text box mustn't be upper than the top of
333 367 # the width to always appears in the widget
334 368 if y_superior <= self.padding:
335 369 y_superior = y
336 370 y_text1 = y + height
337 371 y_text2 = y_text1 + 1 + fheight
338 372 else:
339   - y_text1 = y - RADIUS - 1
340   - y_text2 = y_text1 - 1 - fheight
  373 + y_text2 = y - RADIUS - 1
  374 + y_text1 = y_text2 - 1 - fheight
341 375  
342 376 x_left = x + RADIUS + 1
343 377 rectangle_width = width + RADIUS + 1
... ... @@ -377,13 +411,12 @@ class CLUTRaycastingWidget(wx.Panel):
377 411 ctx.set_source_rgb(*HISTOGRAM_LINE_COLOUR)
378 412 ctx.stroke()
379 413  
380   - def _draw_selection_curve(self, ctx, width):
381   - for curve in self.pixels_points:
382   - x_center = (curve[0][0] + curve[-1][0])/2.0
383   - print "x_center", curve[0][0], curve[-1][0], x_center
  414 + def _draw_selection_curve(self, ctx, height):
  415 + for curve in self.curves:
  416 + x_center = (curve.nodes[0].x + curve.nodes[-1].x)/2.0
384 417 ctx.set_source_rgb(*LINE_COLOUR)
385 418 ctx.stroke()
386   - ctx.rectangle(x_center-5, width-5, 10, 10)
  419 + ctx.rectangle(x_center-5, height, 10, 10)
387 420 ctx.set_source_rgb(0,0,0)
388 421 ctx.fill_preserve()
389 422  
... ... @@ -398,11 +431,10 @@ class CLUTRaycastingWidget(wx.Panel):
398 431 self._draw_gradient(ctx, height)
399 432 self._draw_curves(ctx)
400 433 self._draw_points(ctx)
401   - self._draw_selection_curve(ctx, width)
  434 + self._draw_selection_curve(ctx, height)
402 435 if sys.platform != "darwin":
403 436 if self.point_dragged:
404 437 self._draw_selected_point_text(ctx)
405   -
406 438  
407 439 def _build_histogram(self):
408 440 width, height= self.GetVirtualSizeTuple()
... ... @@ -425,17 +457,6 @@ class CLUTRaycastingWidget(wx.Panel):
425 457 y = height - y * proportion_y
426 458 self.histogram_pixel_points.append((x, y))
427 459  
428   - def CreatePixelArray(self):
429   - """
430   - Create a list with points (in pixel x, y coordinate) to draw based in
431   - the preset points (Hounsfield scale, opacity).
432   - """
433   - self.pixels_points = []
434   - self.__sort_pixel_points()
435   - for curve in self.points:
436   - self.pixels_points.append([self.HounsfieldToPixel(i) for i in curve])
437   - self._build_histogram()
438   -
439 460 def __sort_pixel_points(self):
440 461 """
441 462 Sort the pixel points (colours and points) maintaining the reference
... ... @@ -448,30 +469,65 @@ class CLUTRaycastingWidget(wx.Panel):
448 469 self.points[n] = [i[0] for i in point_colour]
449 470 self.colours[n] = [i[1] for i in point_colour]
450 471  
451   - def HounsfieldToPixel(self, h_pt):
  472 + def CalculatePixelPoints(self):
452 473 """
453   - Given a Hounsfield point(graylevel, opacity), returns a pixel point in the canvas.
  474 + Create a list with points (in pixel x, y coordinate) to draw based in
  475 + the preset points (Hounsfield scale, opacity).
  476 + """
  477 + self.curves = []
  478 + self.__sort_pixel_points()
  479 + for points, colours in zip(self.points, self.colours):
  480 + curve = Curve()
  481 + for point, colour in zip(points, colours):
  482 + x = self.HounsfieldToPixel(point['x'])
  483 + y = self.OpacityToPixel(point['y'])
  484 + node = Node()
  485 + node.x = x
  486 + node.y = y
  487 + node.graylevel = point['x']
  488 + node.opacity = point['y']
  489 + node.colour = colour['red'], colour['green'], colour['blue']
  490 + curve.nodes.append(node)
  491 + self.curves.append(curve)
  492 + self._build_histogram()
  493 +
  494 + def HounsfieldToPixel(self, graylevel):
  495 + """
  496 + Given a Hounsfield point returns a pixel point in the canvas.
454 497 """
455 498 width,height = self.GetVirtualSizeTuple()
456 499 width -= self.padding
457   - height -= (self.padding * 2)
458 500 proportion = width * 1.0 / (self.end - self.init)
459   - x = (h_pt['x'] - self.init) * proportion
460   - y = height - (h_pt['y'] * height) + self.padding
461   - return [x,y]
  501 + x = (graylevel - self.init) * proportion
  502 + return x
462 503  
463   - def PixelToHounsfield(self, i, j):
  504 + def OpacityToPixel(self, opacity):
464 505 """
465   - Given a Hounsfield point(graylevel, opacity), returns a pixel point in the canvas.
  506 + Given a Opacity point returns a pixel point in the canvas.
  507 + """
  508 + width,height = self.GetVirtualSizeTuple()
  509 + height -= (self.padding * 2)
  510 + y = height - (opacity * height) + self.padding
  511 + return y
  512 +
  513 + def PixelToHounsfield(self, x):
  514 + """
  515 + Translate from pixel point to Hounsfield scale.
466 516 """
467 517 width, height= self.GetVirtualSizeTuple()
468 518 width -= self.padding
469   - height -= (self.padding * 2)
470 519 proportion = width * 1.0 / (self.end - self.init)
471   - x = self.pixels_points[i][j][0] / proportion - abs(self.init)
472   - y = (height - self.pixels_points[i][j][1] + self.padding) * 1.0 / height
473   - self.points[i][j]['x'] = x
474   - self.points[i][j]['y'] = y
  520 + graylevel = x / proportion - abs(self.init)
  521 + return graylevel
  522 +
  523 + def PixelToOpacity(self, y):
  524 + """
  525 + Translate from pixel point to opacity.
  526 + """
  527 + width, height= self.GetVirtualSizeTuple()
  528 + height -= (self.padding * 2)
  529 + opacity = (height - y + self.padding) * 1.0 / height
  530 + return opacity
475 531  
476 532 def SetRaycastPreset(self, preset):
477 533 preset = project.Project().raycasting_preset
... ... @@ -482,13 +538,13 @@ class CLUTRaycastingWidget(wx.Panel):
482 538 self.to_draw_points = 1
483 539 self.points = preset['16bitClutCurves']
484 540 self.colours = preset['16bitClutColors']
485   - self.CreatePixelArray()
  541 + self.CalculatePixelPoints()
486 542 else:
487 543 self.to_draw_points = 0
488 544 self.Refresh()
489 545  
490 546 def RefreshPoints(self, pubsub_evt):
491   - self.CreatePixelArray()
  547 + self.CalculatePixelPoints()
492 548 self.Refresh()
493 549  
494 550 def SetHistrogramArray(self, h_array):
... ...