Commit d7c4240ebed155ff41ce8a4a790f8a6c0e37b375
1 parent
15b8a469
Exists in
master
and in
68 other branches
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): | ... | ... |