Commit 9ab6e22819730b7536214aa6851bbabb05f75135

Authored by tfmoraes
1 parent a8886d17

ADD: Support to MIP and save raycasting presets

.gitattributes
... ... @@ -14,6 +14,7 @@ docs/devel/example_singleton.py -text
14 14 docs/devel/example_singleton_pubsub.py -text
15 15 docs/devel/sendmessages.txt -text
16 16 docs/devel/subscribes.txt -text
  17 +icons/Floppy.png -text
17 18 icons/annotation.png -text
18 19 icons/brush_circle.jpg -text
19 20 icons/brush_square.jpg -text
... ...
icons/Floppy.png 0 → 100755

3.29 KB

invesalius/control.py
... ... @@ -403,9 +403,11 @@ class Controller():
403 403 ps.Publisher().sendMessage("Hide raycasting volume")
404 404  
405 405 def SaveRaycastingPreset(self, pubsub_evt):
406   - preset_name = pubsub_evt.data + '.plist'
  406 + preset_name = pubsub_evt.data
407 407 preset = prj.Project().raycasting_preset
408   - preset_dir = os.path.join(const.USER_RAYCASTING_PRESETS_DIRECTORY, preset_name)
  408 + preset['name'] = preset_name
  409 + preset_dir = os.path.join(const.USER_RAYCASTING_PRESETS_DIRECTORY,
  410 + preset_name + '.plist')
409 411 plistlib.writePlist(preset, preset_dir)
410 412  
411 413  
... ...
invesalius/data/volume.py
... ... @@ -157,6 +157,7 @@ class Volume():
157 157  
158 158 # Update other information
159 159 self.SetShading()
  160 + self.SetTypeRaycasting()
160 161 colour = self.GetBackgroundColour()
161 162 ps.Publisher.sendMessage('Change volume viewer background colour', colour)
162 163 ps.Publisher.sendMessage('Change volume viewer gui colour', colour)
... ... @@ -385,6 +386,14 @@ class Volume():
385 386 self.volume_properties.SetSpecular(shading['specular'])
386 387 self.volume_properties.SetSpecularPower(shading['specularPower'])
387 388  
  389 + def SetTypeRaycasting(self):
  390 + if self.config['name'].upper().startswith('MIP'):
  391 + print "MIP"
  392 + self.volume_mapper.SetBlendModeToMaximumIntensity()
  393 + else:
  394 + print "Composite"
  395 + self.volume_mapper.SetBlendModeToComposite()
  396 +
388 397 def ApplyConvolution(self, imagedata, update_progress = None):
389 398 number_filters = len(self.config['convolutionFilters'])
390 399 if number_filters:
... ... @@ -457,24 +466,23 @@ class Volume():
457 466 volume_mapper.SetVolumeRayCastFunction(composite_function)
458 467 #volume_mapper.SetGradientEstimator(gradientEstimator)
459 468 volume_mapper.IntermixIntersectingGeometryOn()
  469 + self.volume_mapper = volume_mapper
460 470 else:
461 471 volume_mapper = vtk.vtkFixedPointVolumeRayCastMapper()
462 472 #volume_mapper.AutoAdjustSampleDistancesOff()
463   -
  473 + self.volume_mapper = volume_mapper
  474 + self.SetTypeRaycasting()
464 475 volume_mapper.IntermixIntersectingGeometryOn()
465   - #volume_mapper.SetBlendModeToMaximumIntensity()
  476 +
466 477 volume_mapper.SetInput(image2)
467   - self.volume_mapper = volume_mapper
468   -
469   -
470   -
  478 +
471 479 # TODO: Look to this
472 480 #volume_mapper_hw = vtk.vtkVolumeTextureMapper3D()
473 481 #volume_mapper_hw.SetInput(image2)
474 482  
475 483 #Cut Plane
476 484 #CutPlane(image2, volume_mapper)
477   -
  485 +
478 486 #self.color_transfer = color_transfer
479 487  
480 488 volume_properties = vtk.vtkVolumeProperty()
... ...
invesalius/gui/widgets/clut_raycasting.py
... ... @@ -19,12 +19,17 @@
19 19  
20 20 import bisect
21 21 import math
  22 +import os
22 23 import sys
23 24  
24 25 import cairo
25 26 import numpy
26 27 import wx
27 28 import wx.lib.wxcairo
  29 +import wx.lib.pubsub as ps
  30 +
  31 +import gui.dialogs as dialog
  32 +import constants as const
28 33  
29 34 FONT_COLOUR = (1, 1, 1)
30 35 LINE_COLOUR = (0.5, 0.5, 0.5)
... ... @@ -37,6 +42,7 @@ TEXT_COLOUR = (1, 1, 1)
37 42 GRADIENT_RGBA = 0.75
38 43 RADIUS = 5
39 44 SELECTION_SIZE = 10
  45 +TOOLBAR_SIZE = 30
40 46  
41 47 class Node(object):
42 48 """
... ... @@ -74,6 +80,32 @@ class Histogram(object):
74 80 def __init__(self):
75 81 self.init = -1024
76 82 self.end = 2000
  83 + self.points = ()
  84 +
  85 +
  86 +class Button(object):
  87 + """
  88 + The button in the clut raycasting.
  89 + """
  90 + def __init__(self):
  91 + self.image = None
  92 + self.position = (0, 0)
  93 + self.size = (24, 24)
  94 +
  95 + def HasClicked(self, position):
  96 + """
  97 + Test if the button was clicked.
  98 + """
  99 + print self.position
  100 + print self.size
  101 + m_x, m_y = position
  102 + i_x, i_y = self.position
  103 + w, h = self.size
  104 + if i_x < m_x < i_x + w and \
  105 + i_y < m_y < i_y + h:
  106 + return True
  107 + else:
  108 + return False
77 109  
78 110  
79 111 class CLUTRaycastingWidget(wx.Panel):
... ... @@ -98,13 +130,14 @@ class CLUTRaycastingWidget(wx.Panel):
98 130 self.previous_wl = 0
99 131 self.to_render = False
100 132 self.dragged = False
  133 + self.middle_drag = False
101 134 self.to_draw_points = 0
102 135 self.point_dragged = None
103 136 self.curve_dragged = None
104   - self.histogram_pixel_points = [[0,0]]
105 137 self.histogram_array = [100,100]
106 138 self.CalculatePixelPoints()
107 139 self.__bind_events_wx()
  140 + self._build_buttons()
108 141 self.Show()
109 142  
110 143 def SetRange(self, range):
... ... @@ -128,12 +161,19 @@ class CLUTRaycastingWidget(wx.Panel):
128 161 self.Bind(wx.EVT_PAINT, self.OnPaint)
129 162 self.Bind(wx.EVT_SIZE, self.OnSize)
130 163 self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
  164 + self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleClick)
  165 + self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleRelease)
131 166  
132 167 def OnEraseBackground(self, evt):
133 168 pass
134 169  
135 170 def OnClick(self, evt):
136 171 x, y = evt.GetPositionTuple()
  172 + if self.save_button.HasClicked(evt.GetPositionTuple()):
  173 + print "Salvando"
  174 + filename = dialog.ShowSavePresetDialog()
  175 + if filename:
  176 + ps.Publisher().sendMessage('Save raycasting preset', filename)
137 177 point = self._has_clicked_in_a_point((x, y))
138 178 # A point has been selected. It can be dragged.
139 179 if point:
... ... @@ -244,69 +284,25 @@ class CLUTRaycastingWidget(wx.Panel):
244 284 self.SetRange((init, end))
245 285 self.Refresh()
246 286  
  287 + def OnMiddleClick(self, evt):
  288 + self.middle_drag = True
  289 + self.last_position = evt.GetX()
  290 +
  291 + def OnMiddleRelease(self, evt):
  292 + self.middle_drag = False
  293 +
247 294 def OnMotion(self, evt):
248 295 # User dragging a point
249 296 x = evt.GetX()
250 297 y = evt.GetY()
251 298 if self.dragged and self.point_dragged:
252   - self.to_render = True
253   - i,j = self.point_dragged
254   -
255   - width, height= self.GetVirtualSizeTuple()
256   -
257   - if y >= height - self.padding:
258   - y = height - self.padding
259   -
260   - if y <= self.padding:
261   - y = self.padding
262   -
263   - if x < 0:
264   - x = 0
265   -
266   - if x > width:
267   - x = width
268   -
269   - # A point must be greater than the previous one, but the first one
270   - if j > 0 and x <= self.curves[i].nodes[j-1].x:
271   - x = self.curves[i].nodes[j-1].x + 1
272   -
273   - # A point must be lower than the previous one, but the last one
274   - if j < len(self.curves[i].nodes) -1 \
275   - and x >= self.curves[i].nodes[j+1].x:
276   - x = self.curves[i].nodes[j+1].x - 1
277   -
278   - graylevel = self.PixelToHounsfield(x)
279   - opacity = self.PixelToOpacity(y)
280   - self.points[i][j]['x'] = graylevel
281   - self.points[i][j]['y'] = opacity
282   - self.curves[i].nodes[j].x = x
283   - self.curves[i].nodes[j].y = y
284   - self.curves[i].nodes[j].graylevel = graylevel
285   - self.curves[i].nodes[j].opacity = opacity
286   - for curve in self.curves:
287   - curve.CalculateWWWl()
288   - curve.wl_px = (self.HounsfieldToPixel(curve.wl),
289   - self.OpacityToPixel(0))
290   - self.Refresh()
291   -
292   - # A point in the preset has been changed, raising a event
293   - evt = CLUTEvent(myEVT_CLUT_POINT_MOVE , self.GetId(), i)
294   - self.GetEventHandler().ProcessEvent(evt)
295   -
  299 + self._move_node(x, y)
296 300 elif self.dragged and self.curve_dragged is not None:
297   - curve = self.curves[self.curve_dragged]
298   - curve.wl = self.PixelToHounsfield(x)
299   - curve.wl_px = x, self.OpacityToPixel(0)
300   - for node in curve.nodes:
301   - node.x += (x - self.previous_wl)
302   - node.graylevel = self.PixelToHounsfield(node.x)
303   -
304   - # The window level has been changed, raising a event!
305   - evt = CLUTEvent(myEVT_CLUT_CURVE_WL_CHANGE, self.GetId(),
306   - self.curve_dragged)
307   - self.GetEventHandler().ProcessEvent(evt)
308   -
309   - self.previous_wl = x
  301 + self._move_curve(x, y)
  302 + elif self.middle_drag:
  303 + d = self.PixelToHounsfield(x) - self.PixelToHounsfield(self.last_position)
  304 + self.SetRange((self.init - d, self.end - d))
  305 + self.last_position = x
310 306 self.Refresh()
311 307 else:
312 308 evt.Skip()
... ... @@ -371,9 +367,82 @@ class CLUTRaycastingWidget(wx.Panel):
371 367 return (n, position)
372 368 return None
373 369  
  370 + def _has_clicked_in_save(self, clicked_point):
  371 + x, y = clicked_point
  372 + print x, y
  373 + if self.padding < x < self.padding + 24 and \
  374 + self.padding < y < self.padding + 24:
  375 + return True
  376 + else:
  377 + return False
  378 +
374 379 def _calculate_distance(self, p1, p2):
375 380 return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) ** 0.5
376 381  
  382 + def _move_node(self, x, y):
  383 + self.to_render = True
  384 + i,j = self.point_dragged
  385 +
  386 + width, height= self.GetVirtualSizeTuple()
  387 +
  388 + if y >= height - self.padding:
  389 + y = height - self.padding
  390 +
  391 + if y <= self.padding:
  392 + y = self.padding
  393 +
  394 + if x < 0:
  395 + x = 0
  396 +
  397 + if x > width:
  398 + x = width
  399 +
  400 + if x < TOOLBAR_SIZE:
  401 + x = TOOLBAR_SIZE
  402 +
  403 + # A point must be greater than the previous one, but the first one
  404 + if j > 0 and x <= self.curves[i].nodes[j-1].x:
  405 + x = self.curves[i].nodes[j-1].x + 1
  406 +
  407 + # A point must be lower than the previous one, but the last one
  408 + if j < len(self.curves[i].nodes) -1 \
  409 + and x >= self.curves[i].nodes[j+1].x:
  410 + x = self.curves[i].nodes[j+1].x - 1
  411 +
  412 + graylevel = self.PixelToHounsfield(x)
  413 + opacity = self.PixelToOpacity(y)
  414 + self.points[i][j]['x'] = graylevel
  415 + self.points[i][j]['y'] = opacity
  416 + self.curves[i].nodes[j].x = x
  417 + self.curves[i].nodes[j].y = y
  418 + self.curves[i].nodes[j].graylevel = graylevel
  419 + self.curves[i].nodes[j].opacity = opacity
  420 + for curve in self.curves:
  421 + curve.CalculateWWWl()
  422 + curve.wl_px = (self.HounsfieldToPixel(curve.wl),
  423 + self.OpacityToPixel(0))
  424 + self.Refresh()
  425 +
  426 + # A point in the preset has been changed, raising a event
  427 + evt = CLUTEvent(myEVT_CLUT_POINT_MOVE , self.GetId(), i)
  428 + self.GetEventHandler().ProcessEvent(evt)
  429 +
  430 + def _move_curve(self, x, y):
  431 + curve = self.curves[self.curve_dragged]
  432 + curve.wl = self.PixelToHounsfield(x)
  433 + curve.wl_px = x, self.OpacityToPixel(0)
  434 + for node in curve.nodes:
  435 + node.x += (x - self.previous_wl)
  436 + node.graylevel = self.PixelToHounsfield(node.x)
  437 +
  438 + # The window level has been changed, raising a event!
  439 + evt = CLUTEvent(myEVT_CLUT_CURVE_WL_CHANGE, self.GetId(),
  440 + self.curve_dragged)
  441 + self.GetEventHandler().ProcessEvent(evt)
  442 +
  443 + self.previous_wl = x
  444 + self.Refresh()
  445 +
377 446 def RemovePoint(self, i, j):
378 447 """
379 448 The point the point in the given i,j index
... ... @@ -506,17 +575,17 @@ class CLUTRaycastingWidget(wx.Panel):
506 575  
507 576 def _draw_histogram(self, ctx, height):
508 577 # The histogram
509   - x,y = self.histogram_pixel_points[0]
  578 + x,y = self.Histogram.points[0]
510 579 print "=>", x,y
511 580 ctx.move_to(x,y)
512 581 ctx.set_line_width(HISTOGRAM_LINE_WIDTH)
513   - for x,y in self.histogram_pixel_points:
  582 + for x,y in self.Histogram.points:
514 583 ctx.line_to(x,y)
515 584 ctx.set_source_rgb(*HISTOGRAM_LINE_COLOUR)
516 585 ctx.stroke_preserve()
517 586 ctx.line_to(x, height + self.padding)
518 587 ctx.line_to(self.HounsfieldToPixel(self.Histogram.init), height + self.padding)
519   - x,y = self.histogram_pixel_points[0]
  588 + x,y = self.Histogram.points[0]
520 589 ctx.line_to(x, y)
521 590 ctx.set_source_rgb(*HISTOGRAM_FILL_COLOUR)
522 591 ctx.fill()
... ... @@ -531,6 +600,20 @@ class CLUTRaycastingWidget(wx.Panel):
531 600 ctx.set_source_rgb(*LINE_COLOUR)
532 601 ctx.stroke()
533 602  
  603 + def _draw_tool_bar(self, ctx, height):
  604 + ctx.rectangle(0, 0, TOOLBAR_SIZE, height + self.padding * 2)
  605 + ctx.set_source_rgb(0.1,0.1,0.1)
  606 + ctx.fill()
  607 + #ctx.set_source_rgb(1, 1, 1)
  608 + #ctx.stroke()
  609 + image = self.save_button.image
  610 + w, h = self.save_button.size
  611 + x = (TOOLBAR_SIZE - w) / 2.0
  612 + y = self.padding
  613 + self.save_button.position = (x, y)
  614 + ctx.set_source_surface(image, x, y)
  615 + ctx.paint()
  616 +
534 617 def Render(self, dc):
535 618 ctx = wx.lib.wxcairo.ContextFromDC(dc)
536 619 width, height= self.GetVirtualSizeTuple()
... ... @@ -543,6 +626,7 @@ class CLUTRaycastingWidget(wx.Panel):
543 626 self._draw_curves(ctx)
544 627 self._draw_points(ctx)
545 628 self._draw_selection_curve(ctx, height)
  629 + self._draw_tool_bar(ctx, height)
546 630 if sys.platform != "darwin":
547 631 if self.point_dragged:
548 632 self._draw_selected_point_text(ctx)
... ... @@ -557,15 +641,23 @@ class CLUTRaycastingWidget(wx.Panel):
557 641 y_end = math.log(max(self.histogram_array))
558 642 proportion_x = width * 1.0 / (x_end - x_init)
559 643 proportion_y = height * 1.0 / (y_end - y_init)
560   - self.histogram_pixel_points = []
561   - for i in xrange(len(self.histogram_array)):
  644 + self.Histogram.points = []
  645 + for i in xrange(0, len(self.histogram_array), 5):
562 646 if self.histogram_array[i]:
563 647 y = math.log(self.histogram_array[i])
564 648 else:
565 649 y = 0
566 650 x = self.HounsfieldToPixel(x_init + i)
567 651 y = height - y * proportion_y + self.padding
568   - self.histogram_pixel_points.append((x, y))
  652 + self.Histogram.points.append((x, y))
  653 +
  654 + def _build_buttons(self):
  655 + img = cairo.ImageSurface.create_from_png(os.path.join(const.ICON_DIR, 'Floppy.png'))
  656 + width = img.get_width()
  657 + height = img.get_height()
  658 + self.save_button = Button()
  659 + self.save_button.image = img
  660 + self.save_button.size = (width, height)
569 661  
570 662 def __sort_pixel_points(self):
571 663 """
... ... @@ -609,9 +701,9 @@ class CLUTRaycastingWidget(wx.Panel):
609 701 Given a Hounsfield point returns a pixel point in the canvas.
610 702 """
611 703 width,height = self.GetVirtualSizeTuple()
612   - width -= self.padding
  704 + width -= (TOOLBAR_SIZE)
613 705 proportion = width * 1.0 / (self.end - self.init)
614   - x = (graylevel - self.init) * proportion
  706 + x = (graylevel - self.init) * proportion + TOOLBAR_SIZE
615 707 return x
616 708  
617 709 def OpacityToPixel(self, opacity):
... ... @@ -628,9 +720,9 @@ class CLUTRaycastingWidget(wx.Panel):
628 720 Translate from pixel point to Hounsfield scale.
629 721 """
630 722 width, height= self.GetVirtualSizeTuple()
631   - width -= self.padding
  723 + width -= (TOOLBAR_SIZE)
632 724 proportion = width * 1.0 / (self.end - self.init)
633   - graylevel = x / proportion - abs(self.init)
  725 + graylevel = (x - TOOLBAR_SIZE) / proportion - abs(self.init)
634 726 return graylevel
635 727  
636 728 def PixelToOpacity(self, y):
... ...