Commit a5e04a63aa32e508c6b710c2636b70be4947d89e

Authored by tfmoraes
1 parent 41a8eeff

ADD: Added the clut_raycasting widget

.gitattributes
... ... @@ -102,6 +102,7 @@ invesalius/gui/task_slice.py -text
102 102 invesalius/gui/task_surface.py -text
103 103 invesalius/gui/task_tools.py -text
104 104 invesalius/gui/widgets/__init__.py -text
  105 +invesalius/gui/widgets/clut_raycasting.py -text
105 106 invesalius/gui/widgets/foldpanelbar.py -text
106 107 invesalius/gui/widgets/gradient.py -text
107 108 invesalius/gui/widgets/listctrl.py -text
... ...
invesalius/gui/widgets/clut_raycasting.py 0 → 100644
... ... @@ -0,0 +1,416 @@
  1 +import bisect
  2 +import math
  3 +import plistlib
  4 +import sys
  5 +
  6 +import cairo
  7 +import numpy
  8 +import wx
  9 +import wx.lib.pubsub as ps
  10 +import wx.lib.wxcairo
  11 +
  12 +FONT_COLOUR = (1, 1, 1)
  13 +LINE_COLOUR = (0.5, 0.5, 0.5)
  14 +BACKGROUND_TEXT_COLOUR_RGBA = (1, 0, 0, 0.5)
  15 +GRADIENT_RGBA = 0.75
  16 +RADIUS = 5
  17 +
  18 +class CLUTRaycastingWidget(wx.Panel):
  19 + """
  20 + This class represents the frame where images is showed
  21 + """
  22 +
  23 + def __init__(self, parent, id):
  24 + """
  25 + Constructor.
  26 +
  27 + parent -- parent of this frame
  28 + """
  29 + super(CLUTRaycastingWidget, self).__init__(parent, id)
  30 + self.points = []#plistlib.readPlist(sys.argv[-1])['16bitClutCurves']
  31 + self.colours = []#plistlib.readPlist(sys.argv[-1])['16bitClutColors']
  32 + self.init = -1024
  33 + self.end = 2000
  34 + self.padding = 10
  35 + self.to_render = False
  36 + self.histogram_pixel_points = [[0,0]]
  37 + self.histogram_array = [[100,100],[100,100]]
  38 + self.CreatePixelArray()
  39 + #self.sizer = wx.BoxSizer(wx.HORIZONTAL)
  40 + #self.SetSizer(self.sizer)
  41 + #self.DrawControls()
  42 + self.dragged = False
  43 + self.point_dragged = None
  44 + self.DoBind()
  45 + #self.__bind_events()
  46 + #self.SetAutoLayout(True)
  47 + #self.sizer.Fit(self)
  48 + self.Show()
  49 + #self.LoadVolume()
  50 +
  51 + def SetRange(self, range):
  52 + self.init, self.end = range
  53 + print "Range", range
  54 + self.CreatePixelArray()
  55 +
  56 + def DoBind(self):
  57 + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
  58 + self.Bind(wx.EVT_LEFT_DOWN , self.OnClick)
  59 + self.Bind(wx.EVT_LEFT_DCLICK , self.OnDoubleClick)
  60 + self.Bind(wx.EVT_LEFT_UP , self.OnRelease)
  61 + self.Bind(wx.EVT_RIGHT_DOWN , self.OnRighClick)
  62 + self.Bind(wx.EVT_MOTION, self.OnMotion)
  63 + self.Bind(wx.EVT_PAINT, self.OnPaint)
  64 + self.Bind(wx.EVT_SIZE, self.OnSize)
  65 + self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
  66 +
  67 + ps.Publisher().subscribe(self.SetRaycastPreset,
  68 + 'Set raycasting preset')
  69 +
  70 + def OnEraseBackground(self, evt):
  71 + pass
  72 +
  73 + def OnClick(self, evt):
  74 + point = self._has_clicked_in_a_point(evt.GetPositionTuple())
  75 + if point:
  76 + self.dragged = True
  77 + self.point_dragged = point
  78 + self.Refresh()
  79 + return
  80 + else:
  81 + p = self._has_clicked_in_line(evt.GetPositionTuple())
  82 + if p:
  83 + n, p = p
  84 + self.points[n].insert(p, {'x': 0, 'y': 0})
  85 + self.pixels_points[n].insert(p, list(evt.GetPositionTuple()))
  86 + self.colours[n].insert(p, {'red': 0, 'green': 0, 'blue': 0})
  87 + self.PixelToHounsfield(n, p)
  88 + self.Refresh()
  89 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
  90 + self.GetEventHandler().ProcessEvent(nevt)
  91 + return
  92 + evt.Skip()
  93 +
  94 + def OnDoubleClick(self, evt):
  95 + point = self._has_clicked_in_a_point(evt.GetPositionTuple())
  96 + if point:
  97 + colour = wx.GetColourFromUser(self)
  98 + if colour.IsOk():
  99 + i,j = self.point_dragged
  100 + r, g, b = [x/255.0 for x in colour.Get()]
  101 + self.colours[i][j]['red'] = r
  102 + self.colours[i][j]['green'] = g
  103 + self.colours[i][j]['blue'] = b
  104 + self.Refresh()
  105 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
  106 + self.GetEventHandler().ProcessEvent(nevt)
  107 + return
  108 + evt.Skip()
  109 +
  110 + def _has_clicked_in_a_point(self, position):
  111 + """
  112 + returns the index from the selected point
  113 + """
  114 + for i,curve in enumerate(self.pixels_points):
  115 + for j,point in enumerate(curve):
  116 + if self._calculate_distance(point, position) <= RADIUS:
  117 + return (i, j)
  118 + return None
  119 +
  120 + def _has_clicked_in_line(self, position):
  121 + for n, point in enumerate(self.pixels_points):
  122 + p = bisect.bisect([i[0] for i in point], position[0])
  123 + print p
  124 + if p != 0 and p != len(point):
  125 + x1, y1 = point[p-1]
  126 + x2, y2 = position
  127 + x3, y3 = point[p]
  128 + if int(float(x2 - x1) / (x3 - x2)) - int(float(y2 - y1) / (y3 - y2)) == 0:
  129 + return (n, p)
  130 + return None
  131 +
  132 + def _calculate_distance(self, p1, p2):
  133 + return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) ** 0.5
  134 +
  135 + def OnRighClick(self, evt):
  136 + point = self._has_clicked_in_a_point(evt.GetPositionTuple())
  137 + if point:
  138 + i, j = point
  139 + print "RightClick", i, j
  140 + self.pixels_points[i].pop(j)
  141 + self.points[i].pop(j)
  142 + self.colours[i].pop(j)
  143 + if (i, j) == self.point_dragged:
  144 + self.point_dragged = None
  145 + if len(self.points[i]) == 1:
  146 + self.points.pop(i)
  147 + self.pixels_points.pop(i)
  148 + self.colours.pop(i)
  149 + self.Refresh()
  150 + nevt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
  151 + self.GetEventHandler().ProcessEvent(nevt)
  152 + return
  153 + evt.Skip()
  154 +
  155 + def OnRelease(self, evt):
  156 + if self.to_render:
  157 + evt = CLUTEvent(myEVT_CLUT_POINT_CHANGED, self.GetId())
  158 + self.GetEventHandler().ProcessEvent(evt)
  159 + self.dragged = False
  160 + self.to_render = False
  161 +
  162 + def OnWheel(self, evt):
  163 + direction = evt.GetWheelRotation() / evt.GetWheelDelta()
  164 + init = self.init - 10 * direction
  165 + end = self.end + 10 * direction
  166 + print direction, init, end
  167 + self.SetRange((init, end))
  168 + self.Refresh()
  169 +
  170 + def OnMotion(self, evt):
  171 + if self.dragged:
  172 + self.to_render = True
  173 + i,j = self.point_dragged
  174 + x = evt.GetX()
  175 + y = evt.GetY()
  176 +
  177 + if y > self.GetVirtualSizeTuple()[1] - self.padding:
  178 + y = self.GetVirtualSizeTuple()[1] - self.padding
  179 +
  180 + if y <= 0:
  181 + y = 0
  182 +
  183 + if x < 0:
  184 + x = 0
  185 +
  186 + if x > self.GetVirtualSizeTuple()[0]:
  187 + x = self.GetVirtualSizeTuple()[0]
  188 +
  189 + # A point must be greater than the previous one, but the first one
  190 + if j > 0 and x <= self.pixels_points[i][j-1][0]:
  191 + x = self.pixels_points[i][j-1][0] + 1
  192 +
  193 + # A point must be lower than the previous one, but the last one
  194 + if j < len(self.pixels_points[i]) -1 \
  195 + and x >= self.pixels_points[i][j+1][0]:
  196 + x = self.pixels_points[i][j+1][0] - 1
  197 +
  198 + self.pixels_points[i][j][0] = x
  199 + self.pixels_points[i][j][1] = y
  200 + self.PixelToHounsfield(i,j)
  201 + self.Refresh()
  202 + evt = CLUTEvent(myEVT_CLUT_POINT , self.GetId())
  203 + self.GetEventHandler().ProcessEvent(evt)
  204 + else:
  205 + evt.Skip()
  206 +
  207 + def OnPaint(self, evt):
  208 + dc = wx.BufferedPaintDC(self)
  209 + dc.SetBackground(wx.Brush('Black'))
  210 + dc.Clear()
  211 + self.Render(dc)
  212 +
  213 + def OnSize(self, evt):
  214 + print "Resizing"
  215 + self.CreatePixelArray()
  216 + self.Refresh()
  217 +
  218 + def _draw_gradient(self, ctx, height):
  219 + #The gradient
  220 + for i, curve in enumerate(self.pixels_points):
  221 + x, y = curve[0]
  222 + xini, yini = curve[0]
  223 + xend, yend = curve[-1]
  224 + gradient = cairo.LinearGradient(xini, height, xend, height)
  225 + ctx.move_to(x, y)
  226 + for j, point in enumerate(curve):
  227 + x, y = point
  228 + ctx.line_to(x, y)
  229 + r = self.colours[i][j]['red']
  230 + g = self.colours[i][j]['green']
  231 + b = self.colours[i][j]['blue']
  232 + gradient.add_color_stop_rgba((x - xini) * 1.0 / (xend - xini),
  233 + r, g, b, GRADIENT_RGBA)
  234 + ctx.line_to(x, height)
  235 + ctx.line_to(xini, height)
  236 + ctx.close_path()
  237 + ctx.set_source(gradient)
  238 + ctx.fill()
  239 +
  240 + def _draw_curves(self, ctx):
  241 + #Drawing the lines
  242 + for curve in self.pixels_points:
  243 + x,y = curve[0]
  244 + ctx.move_to(x, y)
  245 + for point in curve:
  246 + x,y = point
  247 + ctx.line_to(x, y)
  248 + ctx.set_source_rgb(*LINE_COLOUR)
  249 + ctx.stroke()
  250 +
  251 + def _draw_points(self, ctx):
  252 + #Drawing the circles that represents the points
  253 + for i, curve in enumerate(self.pixels_points):
  254 + for j, point in enumerate(curve):
  255 + x,y = point
  256 + r = self.colours[i][j]['red']
  257 + g = self.colours[i][j]['green']
  258 + b = self.colours[i][j]['blue']
  259 + ctx.arc(x, y, RADIUS, 0, math.pi * 2)
  260 + ctx.set_source_rgb(r, g, b)
  261 + ctx.fill_preserve()
  262 + ctx.set_source_rgb(*LINE_COLOUR)
  263 + ctx.stroke()
  264 + #ctx.move_to(x, y)
  265 +
  266 + def _draw_selected_point_text(self, ctx):
  267 + ctx.select_font_face('Sans')
  268 + ctx.set_font_size(15)
  269 + i,j = self.point_dragged
  270 + x,y = self.pixels_points[i][j]
  271 + value = self.points[i][j]['x']
  272 + alpha = self.points[i][j]['y']
  273 + x_bearing, y_bearing, width, height, x_advance, y_advance\
  274 + = ctx.text_extents("Value %6d" % value)
  275 +
  276 + fheight = ctx.font_extents()[2]
  277 + print "font", x_bearing, y_bearing
  278 +
  279 + ctx.set_source_rgba(*BACKGROUND_TEXT_COLOUR_RGBA)
  280 + ctx.rectangle(x + RADIUS + 1 + x_bearing, y - RADIUS * 2 - 2 +
  281 + y_bearing * 2, width + RADIUS + 1, fheight * 2)
  282 + ctx.fill()
  283 +
  284 + ctx.set_source_rgb(1, 1, 1)
  285 + ctx.move_to(x + RADIUS + 1, y - RADIUS - 1)
  286 + ctx.show_text("Alpha: %.3f" % alpha)
  287 + ctx.move_to(x + RADIUS + 1, y - RADIUS - 1 - fheight)
  288 + ctx.show_text("Value: %6d" % value)
  289 +
  290 + def _draw_histogram(self, ctx):
  291 + # The histogram
  292 + ctx.set_source_rgb(0.5, 0.5, 0.5)
  293 + x,y = self.histogram_pixel_points[0]
  294 + print "=>", x,y
  295 + ctx.move_to(x,y)
  296 + for x,y in self.histogram_pixel_points:
  297 + ctx.line_to(x,y)
  298 + ctx.stroke()
  299 +
  300 + def _draw_selection_curve(self, ctx, width):
  301 + for curve in self.pixels_points:
  302 + x_center = (curve[0][0] + curve[-1][0])/2.0
  303 + print "x_center", curve[0][0], curve[-1][0], x_center
  304 + ctx.set_source_rgb(*LINE_COLOUR)
  305 + ctx.stroke()
  306 + ctx.rectangle(x_center-5, width-5, 10, 10)
  307 + ctx.set_source_rgb(0,0,0)
  308 + ctx.fill_preserve()
  309 +
  310 + def Render(self, dc):
  311 + ctx = wx.lib.wxcairo.ContextFromDC(dc)
  312 + width, height= self.GetVirtualSizeTuple()
  313 + height -= self.padding
  314 + width -= self.padding
  315 +
  316 + self._draw_gradient(ctx, height)
  317 + self._draw_curves(ctx)
  318 + self._draw_points(ctx)
  319 + self._draw_selection_curve(ctx, width)
  320 + if self.point_dragged:
  321 + self._draw_selected_point_text(ctx)
  322 +
  323 +
  324 + def _build_histogram(self):
  325 + width, height= self.GetVirtualSizeTuple()
  326 + width -= self.padding
  327 + height -= self.padding
  328 + y_init = 0
  329 + y_end = max(self.histogram_array[0])
  330 + proportion_x = width * 1.0 / (self.end - self.init)
  331 + proportion_y = height * 1.0 / (y_end - y_init)
  332 + print ":) ", y_end, proportion_y
  333 + self.histogram_pixel_points = []
  334 + for i in xrange(len(self.histogram_array[0])):
  335 + x = self.histogram_array[1][i]
  336 + y = self.histogram_array[0][i]
  337 + print x, y
  338 + x = (x + abs(self.init)) * proportion_x
  339 + y = height - (y + abs(y_init)) * proportion_y
  340 + self.histogram_pixel_points.append((x, y))
  341 +
  342 +
  343 + def CreatePixelArray(self):
  344 + self.pixels_points = []
  345 + for curve in self.points:
  346 + self.pixels_points.append([self.HounsfieldToPixel(i) for i in curve])
  347 + #self._build_histogram()
  348 +
  349 + def HounsfieldToPixel(self, h_pt):
  350 + """
  351 + Given a Hounsfield point(graylevel, opacity), returns a pixel point in the canvas.
  352 + """
  353 + width, height= self.GetVirtualSizeTuple()
  354 + width -= self.padding
  355 + height -= self.padding
  356 + proportion = width * 1.0 / (self.end - self.init)
  357 + x = (h_pt['x'] - self.init) * proportion
  358 + y = height - (h_pt['y'] * height)
  359 + return [x,y]
  360 +
  361 + def PixelToHounsfield(self, i, j):
  362 + """
  363 + Given a Hounsfield point(graylevel, opacity), returns a pixel point in the canvas.
  364 + """
  365 + width, height= self.GetVirtualSizeTuple()
  366 + width -= self.padding
  367 + height -= self.padding
  368 + proportion = width * 1.0 / (self.end - self.init)
  369 + x = self.pixels_points[i][j][0] / proportion - abs(self.init)
  370 + y = (height - self.pixels_points[i][j][1]) * 1.0 / height
  371 + self.points[i][j]['x'] = x
  372 + self.points[i][j]['y'] = y
  373 + self.colours[i][j]
  374 + print x,y
  375 +
  376 + def SetRaycastPreset(self, preset):
  377 + self.points = preset.data['16bitClutCurves']
  378 + self.colours = preset.data['16bitClutColors']
  379 + self.CreatePixelArray()
  380 +
  381 + def SetHistrogramArray(self, h_array):
  382 + self.histogram_array = h_array
  383 +
  384 +class CLUTEvent(wx.PyCommandEvent):
  385 + def __init__(self , evtType, id):
  386 + wx.PyCommandEvent.__init__(self, evtType, id)
  387 +
  388 +
  389 +# Occurs when CLUT is sliding
  390 +myEVT_CLUT_SLIDER = wx.NewEventType()
  391 +EVT_CLUT_SLIDER = wx.PyEventBinder(myEVT_CLUT_SLIDER, 1)
  392 +
  393 +# Occurs when CLUT was slided
  394 +myEVT_CLUT_SLIDER_CHANGED = wx.NewEventType()
  395 +EVT_CLUT_SLIDER_CHANGED = wx.PyEventBinder(myEVT_CLUT_SLIDER_CHANGED, 1)
  396 +
  397 +# Occurs when CLUT point is changing
  398 +myEVT_CLUT_POINT = wx.NewEventType()
  399 +EVT_CLUT_POINT = wx.PyEventBinder(myEVT_CLUT_POINT, 1)
  400 +
  401 +# Occurs when a CLUT point was changed
  402 +myEVT_CLUT_POINT_CHANGED = wx.NewEventType()
  403 +EVT_CLUT_POINT_CHANGED = wx.PyEventBinder(myEVT_CLUT_POINT_CHANGED, 1)
  404 +
  405 +class App(wx.App):
  406 + def OnInit(self):
  407 + str_type = sys.argv[-1].split("/")[-1].split(".")[0]
  408 + self.frame = CLUTRaycastingWidget(None, -1, "InVesalius 3 - Raycasting: "+ str_type)
  409 + self.frame.SetPreset(plistlib.readPlist(sys.argv[-1]))
  410 + self.frame.Center()
  411 + self.SetTopWindow(self.frame)
  412 + return True
  413 +
  414 +if __name__ == '__main__':
  415 + app = App()
  416 + app.MainLoop()
... ...