Commit 76643a9e3602a8d1ac3bacd933812b19711dbe25

Authored by Thiago Franco de Moraes
Committed by GitHub
2 parents 30c6a6ab 43729e8f

Merge pull request #42 from tfmoraes/measure_canvas

Add a canvas to draw over the Slice Viewer. This canvas is able to draw geometric forms, unicode text and has support to RGBA. Also, this adapts the measurement system to show the measurements using this canvas system.
invesalius/data/converters.py
@@ -56,3 +56,25 @@ def to_vtk(n_array, spacing, slice_number, orientation): @@ -56,3 +56,25 @@ def to_vtk(n_array, spacing, slice_number, orientation):
56 image_copy.DeepCopy(image) 56 image_copy.DeepCopy(image)
57 57
58 return image_copy 58 return image_copy
  59 +
  60 +
  61 +def np_rgba_to_vtk(n_array, spacing=(1.0, 1.0, 1.0)):
  62 + dy, dx, dc = n_array.shape
  63 + v_image = numpy_support.numpy_to_vtk(n_array.reshape(dy*dx, dc))
  64 +
  65 + extent = (0, dx -1, 0, dy -1, 0, 0)
  66 +
  67 + # Generating the vtkImageData
  68 + image = vtk.vtkImageData()
  69 + image.SetOrigin(0, 0, 0)
  70 + image.SetSpacing(spacing)
  71 + image.SetDimensions(dx, dy, 1)
  72 + # SetNumberOfScalarComponents and SetScalrType were replaced by
  73 + # AllocateScalars
  74 + # image.SetNumberOfScalarComponents(1)
  75 + # image.SetScalarType(numpy_support.get_vtk_array_type(n_array.dtype))
  76 + image.AllocateScalars(numpy_support.get_vtk_array_type(n_array.dtype), dc)
  77 + image.SetExtent(extent)
  78 + image.GetPointData().SetScalars(v_image)
  79 +
  80 + return image
invesalius/data/measures.py
@@ -5,6 +5,8 @@ import math @@ -5,6 +5,8 @@ import math
5 import random 5 import random
6 6
7 from wx.lib.pubsub import pub as Publisher 7 from wx.lib.pubsub import pub as Publisher
  8 +
  9 +import numpy as np
8 import vtk 10 import vtk
9 11
10 import constants as const 12 import constants as const
@@ -137,7 +139,7 @@ class MeasurementManager(object): @@ -137,7 +139,7 @@ class MeasurementManager(object):
137 (actors, m.slice_number)) 139 (actors, m.slice_number))
138 self.current = None 140 self.current = None
139 141
140 - if not m.is_shown: 142 + if not m.visible:
141 mr.SetVisibility(False) 143 mr.SetVisibility(False)
142 if m.location == const.SURFACE: 144 if m.location == const.SURFACE:
143 Publisher.sendMessage('Render volume viewer') 145 Publisher.sendMessage('Render volume viewer')
@@ -307,7 +309,7 @@ class MeasurementManager(object): @@ -307,7 +309,7 @@ class MeasurementManager(object):
307 def _set_visibility(self, pubsub_evt): 309 def _set_visibility(self, pubsub_evt):
308 index, visibility = pubsub_evt.data 310 index, visibility = pubsub_evt.data
309 m, mr = self.measures[index] 311 m, mr = self.measures[index]
310 - m.is_shown = visibility 312 + m.visible = visibility
311 mr.SetVisibility(visibility) 313 mr.SetVisibility(visibility)
312 if m.location == const.SURFACE: 314 if m.location == const.SURFACE:
313 Publisher.sendMessage('Render volume viewer') 315 Publisher.sendMessage('Render volume viewer')
@@ -318,22 +320,24 @@ class MeasurementManager(object): @@ -318,22 +320,24 @@ class MeasurementManager(object):
318 if self.current is None: 320 if self.current is None:
319 return 321 return
320 322
321 - mr = self.current[1]  
322 - print "RM INC M", self.current, mr.IsComplete() 323 + m, mr = self.current
323 if not mr.IsComplete(): 324 if not mr.IsComplete():
324 print "---To REMOVE" 325 print "---To REMOVE"
325 - self.measures.pop() 326 + idx = self.measures._list_measures.index((m, mr))
  327 + self.measures.remove((m, mr))
  328 + Publisher.sendMessage("Remove GUI measurement", idx)
326 actors = mr.GetActors() 329 actors = mr.GetActors()
327 slice_number = self.current[0].slice_number 330 slice_number = self.current[0].slice_number
328 - Publisher.sendMessage(('Remove actors ' + str(self.current[0].location)),  
329 - (actors, slice_number)) 331 + if m.location == const.SURFACE:
  332 + Publisher.sendMessage(('Remove actors ' + str(self.current[0].location)),
  333 + (actors, slice_number))
330 if self.current[0].location == const.SURFACE: 334 if self.current[0].location == const.SURFACE:
331 Publisher.sendMessage('Render volume viewer') 335 Publisher.sendMessage('Render volume viewer')
332 else: 336 else:
333 Publisher.sendMessage('Update slice viewer') 337 Publisher.sendMessage('Update slice viewer')
334 338
335 - if self.measures:  
336 - self.measures.pop() 339 + # if self.measures:
  340 + # self.measures.pop()
337 self.current = None 341 self.current = None
338 342
339 343
@@ -349,7 +353,7 @@ class Measurement(): @@ -349,7 +353,7 @@ class Measurement():
349 self.type = const.LINEAR # ANGULAR 353 self.type = const.LINEAR # ANGULAR
350 self.slice_number = 0 354 self.slice_number = 0
351 self.points = [] 355 self.points = []
352 - self.is_shown = True 356 + self.visible = True
353 357
354 def Load(self, info): 358 def Load(self, info):
355 self.index = info["index"] 359 self.index = info["index"]
@@ -360,7 +364,7 @@ class Measurement(): @@ -360,7 +364,7 @@ class Measurement():
360 self.type = info["type"] 364 self.type = info["type"]
361 self.slice_number = info["slice_number"] 365 self.slice_number = info["slice_number"]
362 self.points = info["points"] 366 self.points = info["points"]
363 - self.is_shown = info["visible"] 367 + self.visible = info["visible"]
364 368
365 class CirclePointRepresentation(object): 369 class CirclePointRepresentation(object):
366 """ 370 """
@@ -559,12 +563,38 @@ class LinearMeasure(object): @@ -559,12 +563,38 @@ class LinearMeasure(object):
559 a.GetProperty().SetOpacity(0.75) 563 a.GetProperty().SetOpacity(0.75)
560 self.text_actor = a 564 self.text_actor = a
561 565
  566 + def draw_to_canvas(self, gc, canvas):
  567 + """
  568 + Draws to an wx.GraphicsContext.
  569 +
  570 + Parameters:
  571 + gc: is a wx.GraphicsContext
  572 + canvas: the canvas it's being drawn.
  573 + """
  574 + coord = vtk.vtkCoordinate()
  575 + points = []
  576 + for p in self.points:
  577 + coord.SetValue(p)
  578 + cx, cy = coord.GetComputedDisplayValue(canvas.viewer.slice_data.renderer)
  579 + # canvas.draw_circle((cx, cy), 2.5)
  580 + points.append((cx, cy))
  581 +
  582 + if len(points) > 1:
  583 + for (p0, p1) in zip(points[:-1:], points[1::]):
  584 + canvas.draw_line(p0, p1)
  585 +
  586 + txt = u"%.3f mm" % self.GetValue()
  587 + canvas.draw_text_box(txt, ((points[0][0]+points[1][0])/2.0, (points[0][1]+points[1][1])/2.0))
  588 +
562 def GetNumberOfPoints(self): 589 def GetNumberOfPoints(self):
563 return len(self.points) 590 return len(self.points)
564 591
565 def GetValue(self): 592 def GetValue(self):
566 - p1, p2 = self.points  
567 - return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2)) 593 + if self.IsComplete():
  594 + p1, p2 = self.points
  595 + return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2))
  596 + else:
  597 + return 0.0
568 598
569 def SetRenderer(self, renderer): 599 def SetRenderer(self, renderer):
570 if self.point_actor1: 600 if self.point_actor1:
@@ -607,21 +637,22 @@ class LinearMeasure(object): @@ -607,21 +637,22 @@ class LinearMeasure(object):
607 return actors 637 return actors
608 638
609 def Remove(self): 639 def Remove(self):
610 - if self.point_actor1:  
611 - self.renderer.RemoveActor(self.point_actor1)  
612 - del self.point_actor1 640 + pass
  641 + # if self.point_actor1:
  642 + # self.renderer.RemoveActor(self.point_actor1)
  643 + # del self.point_actor1
613 644
614 - if self.point_actor2:  
615 - self.renderer.RemoveActor(self.point_actor2)  
616 - del self.point_actor2 645 + # if self.point_actor2:
  646 + # self.renderer.RemoveActor(self.point_actor2)
  647 + # del self.point_actor2
617 648
618 - if self.line_actor:  
619 - self.renderer.RemoveActor(self.line_actor)  
620 - del self.line_actor 649 + # if self.line_actor:
  650 + # self.renderer.RemoveActor(self.line_actor)
  651 + # del self.line_actor
621 652
622 - if self.text_actor:  
623 - self.renderer.RemoveActor(self.text_actor)  
624 - del self.text_actor 653 + # if self.text_actor:
  654 + # self.renderer.RemoveActor(self.text_actor)
  655 + # del self.text_actor
625 656
626 # def __del__(self): 657 # def __del__(self):
627 # self.Remove() 658 # self.Remove()
@@ -630,7 +661,7 @@ class LinearMeasure(object): @@ -630,7 +661,7 @@ class LinearMeasure(object):
630 class AngularMeasure(object): 661 class AngularMeasure(object):
631 def __init__(self, colour=(1, 0, 0), representation=None): 662 def __init__(self, colour=(1, 0, 0), representation=None):
632 self.colour = colour 663 self.colour = colour
633 - self.points = [0, 0, 0] 664 + self.points = []
634 self.number_of_points = 0 665 self.number_of_points = 0
635 self.point_actor1 = None 666 self.point_actor1 = None
636 self.point_actor2 = None 667 self.point_actor2 = None
@@ -658,7 +689,7 @@ class AngularMeasure(object): @@ -658,7 +689,7 @@ class AngularMeasure(object):
658 689
659 def SetPoint1(self, x, y, z): 690 def SetPoint1(self, x, y, z):
660 if self.number_of_points == 0: 691 if self.number_of_points == 0:
661 - self.points[0] = (x, y, z) 692 + self.points.append((x, y, z))
662 self.number_of_points = 1 693 self.number_of_points = 1
663 self.point_actor1 = self.representation.GetRepresentation(x, y, z) 694 self.point_actor1 = self.representation.GetRepresentation(x, y, z)
664 else: 695 else:
@@ -677,7 +708,7 @@ class AngularMeasure(object): @@ -677,7 +708,7 @@ class AngularMeasure(object):
677 def SetPoint2(self, x, y, z): 708 def SetPoint2(self, x, y, z):
678 if self.number_of_points == 1: 709 if self.number_of_points == 1:
679 self.number_of_points = 2 710 self.number_of_points = 2
680 - self.points[1] = (x, y, z) 711 + self.points.append((x, y, z))
681 self.point_actor2 = self.representation.GetRepresentation(x, y, z) 712 self.point_actor2 = self.representation.GetRepresentation(x, y, z)
682 else: 713 else:
683 self.points[1] = (x, y, z) 714 self.points[1] = (x, y, z)
@@ -695,7 +726,7 @@ class AngularMeasure(object): @@ -695,7 +726,7 @@ class AngularMeasure(object):
695 def SetPoint3(self, x, y, z): 726 def SetPoint3(self, x, y, z):
696 if self.number_of_points == 2: 727 if self.number_of_points == 2:
697 self.number_of_points = 3 728 self.number_of_points = 3
698 - self.points[2] = (x, y, z) 729 + self.points.append((x, y, z))
699 self.point_actor3 = self.representation.GetRepresentation(x, y, z) 730 self.point_actor3 = self.representation.GetRepresentation(x, y, z)
700 self.CreateMeasure() 731 self.CreateMeasure()
701 else: 732 else:
@@ -793,11 +824,41 @@ class AngularMeasure(object): @@ -793,11 +824,41 @@ class AngularMeasure(object):
793 a.GetPositionCoordinate().SetValue(x,y,z) 824 a.GetPositionCoordinate().SetValue(x,y,z)
794 self.text_actor = a 825 self.text_actor = a
795 826
  827 + def draw_to_canvas(self, gc, canvas):
  828 + """
  829 + Draws to an wx.GraphicsContext.
  830 +
  831 + Parameters:
  832 + gc: is a wx.GraphicsContext
  833 + canvas: the canvas it's being drawn.
  834 + """
  835 +
  836 + coord = vtk.vtkCoordinate()
  837 + points = []
  838 + for p in self.points:
  839 + coord.SetValue(p)
  840 + cx, cy = coord.GetComputedDisplayValue(canvas.viewer.slice_data.renderer)
  841 + # canvas.draw_circle((cx, cy), 2.5)
  842 + points.append((cx, cy))
  843 +
  844 + if len(points) > 1:
  845 + for (p0, p1) in zip(points[:-1:], points[1::]):
  846 + canvas.draw_line(p0, p1)
  847 +
  848 + if len(points) == 3:
  849 + txt = u"%.3f° / %.3f°" % (self.GetValue(), 360.0 - self.GetValue())
  850 +
  851 + canvas.draw_arc(points[1], points[0], points[2])
  852 + canvas.draw_text_box(txt, (points[1][0], points[1][1]))
  853 +
796 def GetNumberOfPoints(self): 854 def GetNumberOfPoints(self):
797 return self.number_of_points 855 return self.number_of_points
798 856
799 def GetValue(self): 857 def GetValue(self):
800 - return self.CalculateAngle() 858 + if self.IsComplete():
  859 + return self.CalculateAngle()
  860 + else:
  861 + return 0.0
801 862
802 def SetVisibility(self, v): 863 def SetVisibility(self, v):
803 self.point_actor1.SetVisibility(v) 864 self.point_actor1.SetVisibility(v)
@@ -835,30 +896,35 @@ class AngularMeasure(object): @@ -835,30 +896,35 @@ class AngularMeasure(object):
835 v2 = [j-i for i,j in zip(self.points[2], self.points[1])] 896 v2 = [j-i for i,j in zip(self.points[2], self.points[1])]
836 #print vtk.vtkMath.Normalize(v1) 897 #print vtk.vtkMath.Normalize(v1)
837 #print vtk.vtkMath.Normalize(v2) 898 #print vtk.vtkMath.Normalize(v2)
838 - cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2)) 899 + try:
  900 + cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2))
  901 + except ZeroDivisionError:
  902 + return 0.0
  903 +
839 angle = math.degrees(math.acos(cos)) 904 angle = math.degrees(math.acos(cos))
840 return angle 905 return angle
841 906
842 def Remove(self): 907 def Remove(self):
843 - if self.point_actor1:  
844 - self.renderer.RemoveActor(self.point_actor1)  
845 - del self.point_actor1  
846 -  
847 - if self.point_actor2:  
848 - self.renderer.RemoveActor(self.point_actor2)  
849 - del self.point_actor2  
850 -  
851 - if self.point_actor3:  
852 - self.renderer.RemoveActor(self.point_actor3)  
853 - del self.point_actor3  
854 -  
855 - if self.line_actor:  
856 - self.renderer.RemoveActor(self.line_actor)  
857 - del self.line_actor  
858 -  
859 - if self.text_actor:  
860 - self.renderer.RemoveActor(self.text_actor)  
861 - del self.text_actor 908 + pass
  909 + # if self.point_actor1:
  910 + # self.renderer.RemoveActor(self.point_actor1)
  911 + # del self.point_actor1
  912 +
  913 + # if self.point_actor2:
  914 + # self.renderer.RemoveActor(self.point_actor2)
  915 + # del self.point_actor2
  916 +
  917 + # if self.point_actor3:
  918 + # self.renderer.RemoveActor(self.point_actor3)
  919 + # del self.point_actor3
  920 +
  921 + # if self.line_actor:
  922 + # self.renderer.RemoveActor(self.line_actor)
  923 + # del self.line_actor
  924 +
  925 + # if self.text_actor:
  926 + # self.renderer.RemoveActor(self.text_actor)
  927 + # del self.text_actor
862 928
863 def SetRenderer(self, renderer): 929 def SetRenderer(self, renderer):
864 if self.point_actor1: 930 if self.point_actor1:
invesalius/data/slice_data.py
@@ -38,6 +38,7 @@ class SliceData(object): @@ -38,6 +38,7 @@ class SliceData(object):
38 self.number = 0 38 self.number = 0
39 self.orientation = 'AXIAL' 39 self.orientation = 'AXIAL'
40 self.renderer = None 40 self.renderer = None
  41 + self.canvas_renderer = None
41 self.overlay_renderer = None 42 self.overlay_renderer = None
42 self.__create_text() 43 self.__create_text()
43 self.__create_box() 44 self.__create_box()
invesalius/data/styles.py
@@ -354,6 +354,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): @@ -354,6 +354,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle):
354 354
355 self.measures = MeasureData() 355 self.measures = MeasureData()
356 self.selected = None 356 self.selected = None
  357 + self.creating = None
357 358
358 self._type = const.LINEAR 359 self._type = const.LINEAR
359 360
@@ -377,25 +378,57 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): @@ -377,25 +378,57 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle):
377 self._bind_events() 378 self._bind_events()
378 379
379 def _bind_events(self): 380 def _bind_events(self):
380 - self.AddObserver("LeftButtonPressEvent", self.OnInsertLinearMeasurePoint) 381 + self.AddObserver("LeftButtonPressEvent", self.OnInsertMeasurePoint)
381 self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseMeasurePoint) 382 self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseMeasurePoint)
382 self.AddObserver("MouseMoveEvent", self.OnMoveMeasurePoint) 383 self.AddObserver("MouseMoveEvent", self.OnMoveMeasurePoint)
  384 + self.AddObserver("LeaveEvent", self.OnLeaveMeasureInteractor)
383 385
384 - def OnInsertLinearMeasurePoint(self, obj, evt): 386 + def OnInsertMeasurePoint(self, obj, evt):
385 slice_number = self.slice_data.number 387 slice_number = self.slice_data.number
386 x, y, z = self._get_pos_clicked() 388 x, y, z = self._get_pos_clicked()
  389 + mx, my = self.viewer.interactor.GetEventPosition()
387 390
388 - selected = self._verify_clicked(x, y, z) 391 + if self.selected:
  392 + self.selected = None
  393 + self.viewer.scroll_enabled = True
  394 + return
  395 +
  396 + if self.creating:
  397 + n, m, mr = self.creating
  398 + if mr.IsComplete():
  399 + self.creating = None
  400 + self.viewer.scroll_enabled = True
  401 + else:
  402 + Publisher.sendMessage("Add measurement point",
  403 + ((x, y, z), self._type,
  404 + ORIENTATIONS[self.orientation],
  405 + slice_number, self.radius))
  406 + n = len(m.points)-1
  407 + self.creating = n, m, mr
  408 + Publisher.sendMessage('Reload actual slice %s' % self.orientation)
  409 + self.viewer.scroll_enabled = False
  410 + return
  411 +
  412 + selected = self._verify_clicked_display(mx, my)
389 if selected: 413 if selected:
390 self.selected = selected 414 self.selected = selected
  415 + self.viewer.scroll_enabled = False
391 else: 416 else:
392 if self.picker.GetViewProp(): 417 if self.picker.GetViewProp():
393 renderer = self.viewer.slice_data.renderer 418 renderer = self.viewer.slice_data.renderer
394 Publisher.sendMessage("Add measurement point", 419 Publisher.sendMessage("Add measurement point",
395 - ((x, y,z), self._type, 420 + ((x, y, z), self._type,
  421 + ORIENTATIONS[self.orientation],
  422 + slice_number, self.radius))
  423 + Publisher.sendMessage("Add measurement point",
  424 + ((x, y, z), self._type,
396 ORIENTATIONS[self.orientation], 425 ORIENTATIONS[self.orientation],
397 slice_number, self.radius)) 426 slice_number, self.radius))
  427 +
  428 + n, (m, mr) = 1, self.measures.measures[self._ori][slice_number][-1]
  429 + self.creating = n, m, mr
398 Publisher.sendMessage('Reload actual slice %s' % self.orientation) 430 Publisher.sendMessage('Reload actual slice %s' % self.orientation)
  431 + self.viewer.scroll_enabled = False
399 432
400 def OnReleaseMeasurePoint(self, obj, evt): 433 def OnReleaseMeasurePoint(self, obj, evt):
401 if self.selected: 434 if self.selected:
@@ -405,16 +438,41 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): @@ -405,16 +438,41 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle):
405 Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z))) 438 Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z)))
406 Publisher.sendMessage('Reload actual slice %s' % self.orientation) 439 Publisher.sendMessage('Reload actual slice %s' % self.orientation)
407 self.selected = None 440 self.selected = None
  441 + self.viewer.scroll_enabled = True
408 442
409 def OnMoveMeasurePoint(self, obj, evt): 443 def OnMoveMeasurePoint(self, obj, evt):
  444 + x, y, z = self._get_pos_clicked()
410 if self.selected: 445 if self.selected:
411 n, m, mr = self.selected 446 n, m, mr = self.selected
412 - x, y, z = self._get_pos_clicked()  
413 idx = self.measures._list_measures.index((m, mr)) 447 idx = self.measures._list_measures.index((m, mr))
414 Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z))) 448 Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z)))
415 449
416 Publisher.sendMessage('Reload actual slice %s' % self.orientation) 450 Publisher.sendMessage('Reload actual slice %s' % self.orientation)
417 451
  452 + elif self.creating:
  453 + n, m, mr = self.creating
  454 + idx = self.measures._list_measures.index((m, mr))
  455 + Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z)))
  456 +
  457 + Publisher.sendMessage('Reload actual slice %s' % self.orientation)
  458 +
  459 + else:
  460 + mx, my = self.viewer.interactor.GetEventPosition()
  461 + if self._verify_clicked_display(mx, my):
  462 + self.viewer.interactor.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
  463 + else:
  464 + self.viewer.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
  465 +
  466 + def OnLeaveMeasureInteractor(self, obj, evt):
  467 + if self.creating or self.selected:
  468 + n, m, mr = self.creating
  469 + if not mr.IsComplete():
  470 + Publisher.sendMessage("Remove incomplete measurements")
  471 + self.creating = None
  472 + self.selected = None
  473 + Publisher.sendMessage('Update slice viewer')
  474 + self.viewer.scroll_enabled = True
  475 +
418 def CleanUp(self): 476 def CleanUp(self):
419 self.picker.PickFromListOff() 477 self.picker.PickFromListOff()
420 Publisher.sendMessage("Remove incomplete measurements") 478 Publisher.sendMessage("Remove incomplete measurements")
@@ -449,6 +507,21 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): @@ -449,6 +507,21 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle):
449 return (n, m, mr) 507 return (n, m, mr)
450 return None 508 return None
451 509
  510 + def _verify_clicked_display(self, x, y, max_dist=5.0):
  511 + slice_number = self.slice_data.number
  512 + max_dist = max_dist**2
  513 + coord = vtk.vtkCoordinate()
  514 + if slice_number in self.measures.measures[self._ori]:
  515 + for m, mr in self.measures.measures[self._ori][slice_number]:
  516 + if mr.IsComplete():
  517 + for n, p in enumerate(m.points):
  518 + coord.SetValue(p)
  519 + cx, cy = coord.GetComputedDisplayValue(self.viewer.slice_data.renderer)
  520 + dist = ((cx-x)**2 + (cy-y)**2)
  521 + if dist <= max_dist:
  522 + return (n, m, mr)
  523 + return None
  524 +
452 class AngularMeasureInteractorStyle(LinearMeasureInteractorStyle): 525 class AngularMeasureInteractorStyle(LinearMeasureInteractorStyle):
453 def __init__(self, viewer): 526 def __init__(self, viewer):
454 LinearMeasureInteractorStyle.__init__(self, viewer) 527 LinearMeasureInteractorStyle.__init__(self, viewer)
invesalius/data/viewer_slice.py
@@ -23,7 +23,7 @@ import collections @@ -23,7 +23,7 @@ import collections
23 import itertools 23 import itertools
24 import tempfile 24 import tempfile
25 25
26 -import numpy 26 +import numpy as np
27 27
28 import vtk 28 import vtk
29 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor 29 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
@@ -46,6 +46,8 @@ import project @@ -46,6 +46,8 @@ import project
46 import slice_data as sd 46 import slice_data as sd
47 import utils 47 import utils
48 48
  49 +from data import converters
  50 +
49 from data import measures 51 from data import measures
50 52
51 ID_TO_TOOL_ITEM = {} 53 ID_TO_TOOL_ITEM = {}
@@ -87,7 +89,7 @@ class ContourMIPConfig(wx.Panel): @@ -87,7 +89,7 @@ class ContourMIPConfig(wx.Panel):
87 " order to compound the" 89 " order to compound the"
88 " visualization instead of" 90 " visualization instead of"
89 " ascending order."))) 91 " ascending order.")))
90 - 92 +
91 txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL) 93 txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL)
92 self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness")) 94 self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness"))
93 95
@@ -111,7 +113,7 @@ class ContourMIPConfig(wx.Panel): @@ -111,7 +113,7 @@ class ContourMIPConfig(wx.Panel):
111 self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize) 113 self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize)
112 self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder) 114 self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder)
113 self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted) 115 self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted)
114 - 116 +
115 Publisher.subscribe(self._set_projection_type, 'Set projection type') 117 Publisher.subscribe(self._set_projection_type, 'Set projection type')
116 118
117 def OnSetMIPSize(self, evt): 119 def OnSetMIPSize(self, evt):
@@ -134,7 +136,7 @@ class ContourMIPConfig(wx.Panel): @@ -134,7 +136,7 @@ class ContourMIPConfig(wx.Panel):
134 self.inverted.Enable() 136 self.inverted.Enable()
135 else: 137 else:
136 self.inverted.Disable() 138 self.inverted.Disable()
137 - 139 +
138 if tprojection in (const.PROJECTION_CONTOUR_MIP, 140 if tprojection in (const.PROJECTION_CONTOUR_MIP,
139 const.PROJECTION_CONTOUR_MIDA): 141 const.PROJECTION_CONTOUR_MIDA):
140 self.border_spin.Enable() 142 self.border_spin.Enable()
@@ -144,6 +146,262 @@ class ContourMIPConfig(wx.Panel): @@ -144,6 +146,262 @@ class ContourMIPConfig(wx.Panel):
144 self.txt_mip_border.Disable() 146 self.txt_mip_border.Disable()
145 147
146 148
  149 +class CanvasRendererCTX:
  150 + def __init__(self, viewer):
  151 + self.viewer = viewer
  152 + self.canvas_renderer = viewer.slice_data.canvas_renderer
  153 + self._size = self.canvas_renderer.GetSize()
  154 + self.gc = None
  155 + self._init_canvas()
  156 + viewer.slice_data.renderer.AddObserver("StartEvent", self.OnPaint)
  157 +
  158 + def _init_canvas(self):
  159 + w, h = self._size
  160 + self._array = np.zeros((h, w, 4), dtype=np.uint8)
  161 +
  162 + self._cv_image = converters.np_rgba_to_vtk(self._array)
  163 +
  164 + self.mapper = vtk.vtkImageMapper()
  165 + self.mapper.SetInputData(self._cv_image)
  166 + self.mapper.SetColorWindow(255)
  167 + self.mapper.SetColorLevel(128)
  168 +
  169 + self.actor = vtk.vtkActor2D()
  170 + self.actor.SetPosition(0, 0)
  171 + self.actor.SetMapper(self.mapper)
  172 + self.actor.GetProperty().SetOpacity(0.99)
  173 +
  174 + self.canvas_renderer.AddActor2D(self.actor)
  175 +
  176 + self.rgb = np.zeros((h, w, 3), dtype=np.uint8)
  177 + self.alpha = np.zeros((h, w, 1), dtype=np.uint8)
  178 +
  179 + self.bitmap = wx.EmptyBitmapRGBA(w, h)
  180 + self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha)
  181 +
  182 + def _resize_canvas(self, w, h):
  183 + self._array = np.zeros((h, w, 4), dtype=np.uint8)
  184 + self._cv_image = converters.np_rgba_to_vtk(self._array)
  185 + self.mapper.SetInputData(self._cv_image)
  186 + self.mapper.Update()
  187 +
  188 + self.rgb = np.zeros((h, w, 3), dtype=np.uint8)
  189 + self.alpha = np.zeros((h, w, 1), dtype=np.uint8)
  190 +
  191 + self.bitmap = wx.EmptyBitmapRGBA(w, h)
  192 + self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha)
  193 +
  194 + def OnPaint(self, evt, obj):
  195 + self._array[:] = 0
  196 + size = self.canvas_renderer.GetSize()
  197 + w, h = size
  198 + if self._size != size:
  199 + self._size = size
  200 + self._resize_canvas(w, h)
  201 +
  202 + coord = vtk.vtkCoordinate()
  203 +
  204 + self.image.SetDataBuffer(self.rgb)
  205 + self.image.SetAlphaBuffer(self.alpha)
  206 + self.image.Clear()
  207 + gc = wx.GraphicsContext.Create(self.image)
  208 + gc.SetAntialiasMode(0)
  209 +
  210 + self.gc = gc
  211 +
  212 + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
  213 + # font.SetWeight(wx.BOLD)
  214 + font = gc.CreateFont(font, (0, 0, 255))
  215 + gc.SetFont(font)
  216 +
  217 + pen = wx.Pen(wx.Colour(255, 0, 0, 128), 2, wx.SOLID)
  218 + brush = wx.Brush(wx.Colour(0, 255, 0, 128))
  219 + gc.SetPen(pen)
  220 + gc.SetBrush(brush)
  221 + gc.Scale(1, -1)
  222 +
  223 + modified = False
  224 + for (m, mr) in self.viewer.measures.get(self.viewer.orientation, self.viewer.slice_data.number):
  225 + if not m.visible:
  226 + continue
  227 + mr.draw_to_canvas(gc, self)
  228 + modified = True
  229 +
  230 + gc.Destroy()
  231 +
  232 + self.gc = None
  233 +
  234 + if modified:
  235 + self.bitmap = self.image.ConvertToBitmap()
  236 + self.bitmap.CopyToBuffer(self._array, wx.BitmapBufferFormat_RGBA)
  237 +
  238 + self._cv_image.Modified()
  239 +
  240 + def draw_line(self, pos0, pos1, arrow_start=False, arrow_end=False, colour=(255, 0, 0, 128), width=2, style=wx.SOLID):
  241 + """
  242 + Draw a line from pos0 to pos1
  243 + """
  244 + if self.gc is None:
  245 + return None
  246 + gc = self.gc
  247 +
  248 + p0x, p0y = pos0
  249 + p1x, p1y = pos1
  250 +
  251 + p0y = -p0y
  252 + p1y = -p1y
  253 +
  254 + pen = wx.Pen(wx.Colour(*colour), width, wx.SOLID)
  255 + pen.SetCap(wx.CAP_BUTT)
  256 + gc.SetPen(pen)
  257 +
  258 + path = gc.CreatePath()
  259 + path.MoveToPoint(p0x, p0y)
  260 + path.AddLineToPoint(p1x, p1y)
  261 + gc.StrokePath(path)
  262 +
  263 + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
  264 + font = gc.CreateFont(font)
  265 + gc.SetFont(font)
  266 + w, h = gc.GetTextExtent("M")
  267 +
  268 + p0 = np.array((p0x, p0y))
  269 + p3 = np.array((p1x, p1y))
  270 + if arrow_start:
  271 + v = p3 - p0
  272 + v = v / np.linalg.norm(v)
  273 + iv = np.array((v[1], -v[0]))
  274 + p1 = p0 + w*v + iv*w/2.0
  275 + p2 = p0 + w*v + (-iv)*w/2.0
  276 +
  277 + path = gc.CreatePath()
  278 + path.MoveToPoint(p0)
  279 + path.AddLineToPoint(p1)
  280 + path.MoveToPoint(p0)
  281 + path.AddLineToPoint(p2)
  282 + gc.StrokePath(path)
  283 +
  284 + if arrow_end:
  285 + v = p3 - p0
  286 + v = v / np.linalg.norm(v)
  287 + iv = np.array((v[1], -v[0]))
  288 + p1 = p3 - w*v + iv*w/2.0
  289 + p2 = p3 - w*v + (-iv)*w/2.0
  290 +
  291 + path = gc.CreatePath()
  292 + path.MoveToPoint(p3)
  293 + path.AddLineToPoint(p1)
  294 + path.MoveToPoint(p3)
  295 + path.AddLineToPoint(p2)
  296 + gc.StrokePath(path)
  297 +
  298 + def draw_circle(self, center, radius, width=2, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)):
  299 + """
  300 + Draw a circle centered at center with the given radius.
  301 +
  302 + Params:
  303 + center: (x, y) position.
  304 + radius: float number.
  305 + width: line width.
  306 + line_colour: RGBA line colour
  307 + fill_colour: RGBA fill colour.
  308 + """
  309 + if self.gc is None:
  310 + return None
  311 + gc = self.gc
  312 +
  313 + pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID)
  314 + gc.SetPen(pen)
  315 +
  316 + brush = wx.Brush(wx.Colour(*fill_colour))
  317 + gc.SetBrush(brush)
  318 +
  319 + cx, cy = center
  320 + cy = -cy
  321 +
  322 + path = gc.CreatePath()
  323 + path.AddCircle(cx, cy, 2.5)
  324 + gc.StrokePath(path)
  325 + gc.FillPath(path)
  326 +
  327 +
  328 + def draw_text_box(self, text, pos, font=None, txt_colour=(255, 255, 255), bg_colour=(128, 128, 128, 128), border=5):
  329 + """
  330 + Draw text inside a text box.
  331 +
  332 + Params:
  333 + text: an unicode text.
  334 + pos: (x, y) position.
  335 + font: if None it'll use the default gui font.
  336 + txt_colour: RGB text colour
  337 + bg_colour: RGBA box colour
  338 + border: the border size.
  339 + """
  340 + if self.gc is None:
  341 + return None
  342 + gc = self.gc
  343 +
  344 + if font is None:
  345 + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
  346 +
  347 + font = gc.CreateFont(font, txt_colour)
  348 + gc.SetFont(font)
  349 + w, h = gc.GetTextExtent(text)
  350 +
  351 + px, py = pos
  352 + py = -py
  353 +
  354 + # Drawing the box
  355 + cw, ch = w + border * 2, h + border * 2
  356 + gc.SetBrush(wx.Brush(bg_colour))
  357 + gc.SetPen(wx.Pen(bg_colour))
  358 + gc.DrawRectangle(px, py, cw, ch)
  359 +
  360 + tpx, tpy = px + border, py + border
  361 + gc.DrawText(text, tpx, tpy)
  362 +
  363 + def draw_arc(self, center, p0, p1, line_colour=(255, 0, 0, 128), width=2):
  364 + """
  365 + Draw an arc passing in p0 and p1 centered at center.
  366 +
  367 + Params:
  368 + center: (x, y) center of the arc.
  369 + p0: (x, y).
  370 + p1: (x, y).
  371 + line_colour: RGBA line colour.
  372 + width: width of the line.
  373 + """
  374 + if self.gc is None:
  375 + return None
  376 + gc = self.gc
  377 + pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID)
  378 + gc.SetPen(pen)
  379 +
  380 + c = np.array(center)
  381 + v0 = np.array(p0) - c
  382 + v1 = np.array(p1) - c
  383 +
  384 + c[1] = -c[1]
  385 + v0[1] = -v0[1]
  386 + v1[1] = -v1[1]
  387 +
  388 + s0 = np.linalg.norm(v0)
  389 + s1 = np.linalg.norm(v1)
  390 +
  391 + a0 = np.arctan2(v0[1] , v0[0])
  392 + a1 = np.arctan2(v1[1] , v1[0])
  393 +
  394 + if (a1 - a0) % (np.pi*2) < (a0 - a1) % (np.pi*2):
  395 + sa = a0
  396 + ea = a1
  397 + else:
  398 + sa = a1
  399 + ea = a0
  400 +
  401 + path = gc.CreatePath()
  402 + path.AddArc((c[0], c[1]), min(s0, s1), sa, ea)
  403 + gc.StrokePath(path)
  404 +
147 405
148 class Viewer(wx.Panel): 406 class Viewer(wx.Panel):
149 407
@@ -157,7 +415,7 @@ class Viewer(wx.Panel): @@ -157,7 +415,7 @@ class Viewer(wx.Panel):
157 415
158 self._number_slices = const.PROJECTION_MIP_SIZE 416 self._number_slices = const.PROJECTION_MIP_SIZE
159 self._mip_inverted = False 417 self._mip_inverted = False
160 - 418 +
161 self.style = None 419 self.style = None
162 self.last_position_mouse_move = () 420 self.last_position_mouse_move = ()
163 self.state = const.STATE_DEFAULT 421 self.state = const.STATE_DEFAULT
@@ -178,6 +436,7 @@ class Viewer(wx.Panel): @@ -178,6 +436,7 @@ class Viewer(wx.Panel):
178 436
179 self.orientation = orientation 437 self.orientation = orientation
180 self.slice_number = 0 438 self.slice_number = 0
  439 + self.scroll_enabled = True
181 440
182 self.__init_gui() 441 self.__init_gui()
183 442
@@ -230,7 +489,7 @@ class Viewer(wx.Panel): @@ -230,7 +489,7 @@ class Viewer(wx.Panel):
230 self.menu.caller = self 489 self.menu.caller = self
231 self.PopupMenu(self.menu) 490 self.PopupMenu(self.menu)
232 evt.Skip() 491 evt.Skip()
233 - 492 +
234 def SetPopupMenu(self, menu): 493 def SetPopupMenu(self, menu):
235 self.menu = menu 494 self.menu = menu
236 495
@@ -316,14 +575,14 @@ class Viewer(wx.Panel): @@ -316,14 +575,14 @@ class Viewer(wx.Panel):
316 (slc.window_width, slc.window_level)) 575 (slc.window_width, slc.window_level))
317 576
318 def SetWLText(self, window_width, window_level): 577 def SetWLText(self, window_width, window_level):
319 - value = STR_WL%(window_level, window_width) 578 + value = STR_WL%(window_level, window_width)
320 if (self.wl_text): 579 if (self.wl_text):
321 self.wl_text.SetValue(value) 580 self.wl_text.SetValue(value)
322 #self.interactor.Render() 581 #self.interactor.Render()
323 582
324 def EnableText(self): 583 def EnableText(self):
325 if not (self.wl_text): 584 if not (self.wl_text):
326 - proj = project.Project() 585 + proj = project.Project()
327 colour = const.ORIENTATION_COLOUR[self.orientation] 586 colour = const.ORIENTATION_COLOUR[self.orientation]
328 587
329 # Window & Level text 588 # Window & Level text
@@ -336,7 +595,7 @@ class Viewer(wx.Panel): @@ -336,7 +595,7 @@ class Viewer(wx.Panel):
336 values = [_('P'), _('A'), _('T'), _('B')] 595 values = [_('P'), _('A'), _('T'), _('B')]
337 else: 596 else:
338 values = [_('R'), _('L'), _('T'), _('B')] 597 values = [_('R'), _('L'), _('T'), _('B')]
339 - 598 +
340 left_text = self.left_text = vtku.TextZero() 599 left_text = self.left_text = vtku.TextZero()
341 left_text.ShadowOff() 600 left_text.ShadowOff()
342 left_text.SetColour(colour) 601 left_text.SetColour(colour)
@@ -415,30 +674,30 @@ class Viewer(wx.Panel): @@ -415,30 +674,30 @@ class Viewer(wx.Panel):
415 674
416 elif(croll > 91 and croll <= 135): 675 elif(croll > 91 and croll <= 135):
417 self.RenderTextDirection([_("LP"), _("AL"), _("RA"), _("PR")]) 676 self.RenderTextDirection([_("LP"), _("AL"), _("RA"), _("PR")])
418 - 677 +
419 elif(croll > 135 and croll <= 177): 678 elif(croll > 135 and croll <= 177):
420 self.RenderTextDirection([_("PL"), _("LA"), _("AR"), _("RP")]) 679 self.RenderTextDirection([_("PL"), _("LA"), _("AR"), _("RP")])
421 - 680 +
422 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): 681 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177):
423 self.RenderTextDirection([_("P"), _("L"), _("A"), _("R")]) 682 self.RenderTextDirection([_("P"), _("L"), _("A"), _("R")])
424 - 683 +
425 elif(croll >= -177 and croll <= -133): 684 elif(croll >= -177 and croll <= -133):
426 self.RenderTextDirection([_("PR"), _("LP"), _("AL"), _("RA")]) 685 self.RenderTextDirection([_("PR"), _("LP"), _("AL"), _("RA")])
427 - 686 +
428 elif(croll >= -132 and croll <= -101): 687 elif(croll >= -132 and croll <= -101):
429 self.RenderTextDirection([_("RP"), _("PL"), _("LA"), _("AR")]) 688 self.RenderTextDirection([_("RP"), _("PL"), _("LA"), _("AR")])
430 689
431 elif(croll >= -101 and croll <= -87): 690 elif(croll >= -101 and croll <= -87):
432 self.RenderTextDirection([_("R"), _("P"), _("L"), _("A")]) 691 self.RenderTextDirection([_("R"), _("P"), _("L"), _("A")])
433 - 692 +
434 elif(croll >= -86 and croll <= -42): 693 elif(croll >= -86 and croll <= -42):
435 self.RenderTextDirection([_("RA"), _("PR"), _("LP"), _("AL")]) 694 self.RenderTextDirection([_("RA"), _("PR"), _("LP"), _("AL")])
436 - 695 +
437 elif(croll >= -41 and croll <= -2): 696 elif(croll >= -41 and croll <= -2):
438 self.RenderTextDirection([_("AR"), _("RP"), _("PL"), _("LA")]) 697 self.RenderTextDirection([_("AR"), _("RP"), _("PL"), _("LA")])
439 698
440 elif(self.orientation == "CORONAL"): 699 elif(self.orientation == "CORONAL"):
441 - 700 +
442 if (croll >= -2 and croll <= 1): 701 if (croll >= -2 and croll <= 1):
443 self.RenderTextDirection([_("T"), _("R"), _("B"), _("L")]) 702 self.RenderTextDirection([_("T"), _("R"), _("B"), _("L")])
444 703
@@ -453,25 +712,25 @@ class Viewer(wx.Panel): @@ -453,25 +712,25 @@ class Viewer(wx.Panel):
453 712
454 elif(croll > 91 and croll <= 135): 713 elif(croll > 91 and croll <= 135):
455 self.RenderTextDirection([_("LB"), _("TL"), _("RT"), _("BR")]) 714 self.RenderTextDirection([_("LB"), _("TL"), _("RT"), _("BR")])
456 - 715 +
457 elif(croll > 135 and croll <= 177): 716 elif(croll > 135 and croll <= 177):
458 self.RenderTextDirection([_("BL"), _("LT"), _("TR"), _("RB")]) 717 self.RenderTextDirection([_("BL"), _("LT"), _("TR"), _("RB")])
459 - 718 +
460 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): 719 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177):
461 self.RenderTextDirection([_("B"), _("L"), _("T"), _("R")]) 720 self.RenderTextDirection([_("B"), _("L"), _("T"), _("R")])
462 - 721 +
463 elif(croll >= -177 and croll <= -133): 722 elif(croll >= -177 and croll <= -133):
464 self.RenderTextDirection([_("BR"), _("LB"), _("TL"), _("RT")]) 723 self.RenderTextDirection([_("BR"), _("LB"), _("TL"), _("RT")])
465 - 724 +
466 elif(croll >= -132 and croll <= -101): 725 elif(croll >= -132 and croll <= -101):
467 self.RenderTextDirection([_("RB"), _("BL"), _("LT"), _("TR")]) 726 self.RenderTextDirection([_("RB"), _("BL"), _("LT"), _("TR")])
468 727
469 elif(croll >= -101 and croll <= -87): 728 elif(croll >= -101 and croll <= -87):
470 self.RenderTextDirection([_("R"), _("B"), _("L"), _("T")]) 729 self.RenderTextDirection([_("R"), _("B"), _("L"), _("T")])
471 - 730 +
472 elif(croll >= -86 and croll <= -42): 731 elif(croll >= -86 and croll <= -42):
473 self.RenderTextDirection([_("RT"), _("BR"), _("LB"), _("TL")]) 732 self.RenderTextDirection([_("RT"), _("BR"), _("LB"), _("TL")])
474 - 733 +
475 elif(croll >= -41 and croll <= -2): 734 elif(croll >= -41 and croll <= -2):
476 self.RenderTextDirection([_("TR"), _("RB"), _("BL"), _("LT")]) 735 self.RenderTextDirection([_("TR"), _("RB"), _("BL"), _("LT")])
477 736
@@ -479,13 +738,13 @@ class Viewer(wx.Panel): @@ -479,13 +738,13 @@ class Viewer(wx.Panel):
479 738
480 if(croll >= -101 and croll <= -87): 739 if(croll >= -101 and croll <= -87):
481 self.RenderTextDirection([_("T"), _("P"), _("B"), _("A")]) 740 self.RenderTextDirection([_("T"), _("P"), _("B"), _("A")])
482 - 741 +
483 elif(croll >= -86 and croll <= -42): 742 elif(croll >= -86 and croll <= -42):
484 self.RenderTextDirection([_("TA"), _("PT"), _("BP"), _("AB")]) 743 self.RenderTextDirection([_("TA"), _("PT"), _("BP"), _("AB")])
485 - 744 +
486 elif(croll >= -41 and croll <= -2): 745 elif(croll >= -41 and croll <= -2):
487 self.RenderTextDirection([_("AT"), _("TP"), _("PB"), _("BA")]) 746 self.RenderTextDirection([_("AT"), _("TP"), _("PB"), _("BA")])
488 - 747 +
489 elif (croll >= -2 and croll <= 1): 748 elif (croll >= -2 and croll <= 1):
490 self.RenderTextDirection([_("A"), _("T"), _("P"), _("B")]) 749 self.RenderTextDirection([_("A"), _("T"), _("P"), _("B")])
491 750
@@ -500,16 +759,16 @@ class Viewer(wx.Panel): @@ -500,16 +759,16 @@ class Viewer(wx.Panel):
500 759
501 elif(croll > 91 and croll <= 135): 760 elif(croll > 91 and croll <= 135):
502 self.RenderTextDirection([_("BP"), _("AB"), _("TA"), _("PT")]) 761 self.RenderTextDirection([_("BP"), _("AB"), _("TA"), _("PT")])
503 - 762 +
504 elif(croll > 135 and croll <= 177): 763 elif(croll > 135 and croll <= 177):
505 self.RenderTextDirection([_("PB"), _("BA"), _("AT"), _("TP")]) 764 self.RenderTextDirection([_("PB"), _("BA"), _("AT"), _("TP")])
506 - 765 +
507 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): 766 elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177):
508 self.RenderTextDirection([_("P"), _("B"), _("A"), _("T")]) 767 self.RenderTextDirection([_("P"), _("B"), _("A"), _("T")])
509 - 768 +
510 elif(croll >= -177 and croll <= -133): 769 elif(croll >= -177 and croll <= -133):
511 self.RenderTextDirection([_("PT"), _("BP"), _("AB"), _("TA")]) 770 self.RenderTextDirection([_("PT"), _("BP"), _("AB"), _("TA")])
512 - 771 +
513 elif(croll >= -132 and croll <= -101): 772 elif(croll >= -132 and croll <= -101):
514 self.RenderTextDirection([_("TP"), _("PB"), _("BA"), _("AT")]) 773 self.RenderTextDirection([_("TP"), _("PB"), _("BA"), _("AT")])
515 774
@@ -544,12 +803,12 @@ class Viewer(wx.Panel): @@ -544,12 +803,12 @@ class Viewer(wx.Panel):
544 def Navigation(self, pubsub_evt): 803 def Navigation(self, pubsub_evt):
545 # Get point from base change 804 # Get point from base change
546 x, y, z = pubsub_evt.data 805 x, y, z = pubsub_evt.data
547 - coord_cross = x, y, z 806 + coord_cross = x, y, z
548 position = self.slice_data.actor.GetInput().FindPoint(x, y, z) 807 position = self.slice_data.actor.GetInput().FindPoint(x, y, z)
549 coord_cross = self.slice_data.actor.GetInput().GetPoint(position) 808 coord_cross = self.slice_data.actor.GetInput().GetPoint(position)
550 - coord = self.calcultate_scroll_position(position) 809 + coord = self.calcultate_scroll_position(position)
551 Publisher.sendMessage('Update cross position', coord_cross) 810 Publisher.sendMessage('Update cross position', coord_cross)
552 - 811 +
553 self.ScrollSlice(coord) 812 self.ScrollSlice(coord)
554 self.interactor.Render() 813 self.interactor.Render()
555 814
@@ -574,7 +833,7 @@ class Viewer(wx.Panel): @@ -574,7 +833,7 @@ class Viewer(wx.Panel):
574 #for slice_data in self.slice_data_list: 833 #for slice_data in self.slice_data_list:
575 #if slice_data.renderer is render: 834 #if slice_data.renderer is render:
576 #return slice_data 835 #return slice_data
577 - # WARN: Return the only slice_data used in this slice_viewer. 836 + # WARN: Return the only slice_data used in this slice_viewer.
578 return self.slice_data 837 return self.slice_data
579 838
580 def calcultate_scroll_position(self, position): 839 def calcultate_scroll_position(self, position):
@@ -701,7 +960,7 @@ class Viewer(wx.Panel): @@ -701,7 +960,7 @@ class Viewer(wx.Panel):
701 'Hide text actors on viewers') 960 'Hide text actors on viewers')
702 Publisher.subscribe(self.OnExportPicture,'Export picture to file') 961 Publisher.subscribe(self.OnExportPicture,'Export picture to file')
703 Publisher.subscribe(self.SetDefaultCursor, 'Set interactor default cursor') 962 Publisher.subscribe(self.SetDefaultCursor, 'Set interactor default cursor')
704 - 963 +
705 Publisher.subscribe(self.AddActors, 'Add actors ' + str(ORIENTATIONS[self.orientation])) 964 Publisher.subscribe(self.AddActors, 'Add actors ' + str(ORIENTATIONS[self.orientation]))
706 Publisher.subscribe(self.RemoveActors, 'Remove actors ' + str(ORIENTATIONS[self.orientation])) 965 Publisher.subscribe(self.RemoveActors, 'Remove actors ' + str(ORIENTATIONS[self.orientation]))
707 Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') 966 Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes')
@@ -727,11 +986,11 @@ class Viewer(wx.Panel): @@ -727,11 +986,11 @@ class Viewer(wx.Panel):
727 986
728 def SetDefaultCursor(self, pusub_evt): 987 def SetDefaultCursor(self, pusub_evt):
729 self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) 988 self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
730 - 989 +
731 def OnExportPicture(self, pubsub_evt): 990 def OnExportPicture(self, pubsub_evt):
732 Publisher.sendMessage('Begin busy cursor') 991 Publisher.sendMessage('Begin busy cursor')
733 view_prop_list = [] 992 view_prop_list = []
734 - view_prop_list.append(self.slice_data.box_actor) 993 + view_prop_list.append(self.slice_data.box_actor)
735 self.slice_data.renderer.RemoveViewProp(self.slice_data.box_actor) 994 self.slice_data.renderer.RemoveViewProp(self.slice_data.box_actor)
736 995
737 id, filename, filetype = pubsub_evt.data 996 id, filename, filetype = pubsub_evt.data
@@ -793,7 +1052,7 @@ class Viewer(wx.Panel): @@ -793,7 +1052,7 @@ class Viewer(wx.Panel):
793 def CloseProject(self): 1052 def CloseProject(self):
794 for slice_data in self.slice_data_list: 1053 for slice_data in self.slice_data_list:
795 del slice_data 1054 del slice_data
796 - 1055 +
797 self.slice_data_list = [] 1056 self.slice_data_list = []
798 self.layout = (1, 1) 1057 self.layout = (1, 1)
799 self.orientation_texts = [] 1058 self.orientation_texts = []
@@ -805,10 +1064,10 @@ class Viewer(wx.Panel): @@ -805,10 +1064,10 @@ class Viewer(wx.Panel):
805 def OnSetInteractorStyle(self, pubsub_evt): 1064 def OnSetInteractorStyle(self, pubsub_evt):
806 state = pubsub_evt.data 1065 state = pubsub_evt.data
807 self.SetInteractorStyle(state) 1066 self.SetInteractorStyle(state)
808 - 1067 +
809 if (state != const.SLICE_STATE_EDITOR): 1068 if (state != const.SLICE_STATE_EDITOR):
810 Publisher.sendMessage('Set interactor default cursor') 1069 Publisher.sendMessage('Set interactor default cursor')
811 - 1070 +
812 def __bind_events_wx(self): 1071 def __bind_events_wx(self):
813 self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar) 1072 self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar)
814 self.scroll.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScrollBarRelease) 1073 self.scroll.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScrollBarRelease)
@@ -896,6 +1155,8 @@ class Viewer(wx.Panel): @@ -896,6 +1155,8 @@ class Viewer(wx.Panel):
896 self.cam = self.slice_data.renderer.GetActiveCamera() 1155 self.cam = self.slice_data.renderer.GetActiveCamera()
897 self.__build_cross_lines() 1156 self.__build_cross_lines()
898 1157
  1158 + canvas = CanvasRendererCTX(self)
  1159 +
899 # Set the slice number to the last slice to ensure the camera if far 1160 # Set the slice number to the last slice to ensure the camera if far
900 # enough to show all slices. 1161 # enough to show all slices.
901 self.set_slice_number(max_slice_number - 1) 1162 self.set_slice_number(max_slice_number - 1)
@@ -958,13 +1219,20 @@ class Viewer(wx.Panel): @@ -958,13 +1219,20 @@ class Viewer(wx.Panel):
958 renderer.SetLayer(0) 1219 renderer.SetLayer(0)
959 cam = renderer.GetActiveCamera() 1220 cam = renderer.GetActiveCamera()
960 1221
  1222 + canvas_renderer = vtk.vtkRenderer()
  1223 + canvas_renderer.SetLayer(1)
  1224 + canvas_renderer.SetActiveCamera(cam)
  1225 + canvas_renderer.SetInteractive(0)
  1226 + canvas_renderer.PreserveDepthBufferOn()
  1227 +
961 overlay_renderer = vtk.vtkRenderer() 1228 overlay_renderer = vtk.vtkRenderer()
962 - overlay_renderer.SetLayer(1) 1229 + overlay_renderer.SetLayer(2)
963 overlay_renderer.SetActiveCamera(cam) 1230 overlay_renderer.SetActiveCamera(cam)
964 overlay_renderer.SetInteractive(0) 1231 overlay_renderer.SetInteractive(0)
965 -  
966 - self.interactor.GetRenderWindow().SetNumberOfLayers(2) 1232 +
  1233 + self.interactor.GetRenderWindow().SetNumberOfLayers(3)
967 self.interactor.GetRenderWindow().AddRenderer(overlay_renderer) 1234 self.interactor.GetRenderWindow().AddRenderer(overlay_renderer)
  1235 + self.interactor.GetRenderWindow().AddRenderer(canvas_renderer)
968 self.interactor.GetRenderWindow().AddRenderer(renderer) 1236 self.interactor.GetRenderWindow().AddRenderer(renderer)
969 1237
970 actor = vtk.vtkImageActor() 1238 actor = vtk.vtkImageActor()
@@ -974,12 +1242,14 @@ class Viewer(wx.Panel): @@ -974,12 +1242,14 @@ class Viewer(wx.Panel):
974 slice_data = sd.SliceData() 1242 slice_data = sd.SliceData()
975 slice_data.SetOrientation(self.orientation) 1243 slice_data.SetOrientation(self.orientation)
976 slice_data.renderer = renderer 1244 slice_data.renderer = renderer
  1245 + slice_data.canvas_renderer = canvas_renderer
977 slice_data.overlay_renderer = overlay_renderer 1246 slice_data.overlay_renderer = overlay_renderer
978 slice_data.actor = actor 1247 slice_data.actor = actor
979 slice_data.SetBorderStyle(sd.BORDER_ALL) 1248 slice_data.SetBorderStyle(sd.BORDER_ALL)
980 renderer.AddActor(actor) 1249 renderer.AddActor(actor)
981 renderer.AddActor(slice_data.text.actor) 1250 renderer.AddActor(slice_data.text.actor)
982 renderer.AddViewProp(slice_data.box_actor) 1251 renderer.AddViewProp(slice_data.box_actor)
  1252 +
983 return slice_data 1253 return slice_data
984 1254
985 def __update_camera(self): 1255 def __update_camera(self):
@@ -1027,15 +1297,15 @@ class Viewer(wx.Panel): @@ -1027,15 +1297,15 @@ class Viewer(wx.Panel):
1027 def set_scroll_position(self, position): 1297 def set_scroll_position(self, position):
1028 self.scroll.SetThumbPosition(position) 1298 self.scroll.SetThumbPosition(position)
1029 self.OnScrollBar() 1299 self.OnScrollBar()
1030 - 1300 +
1031 def UpdateSlice3D(self, pos): 1301 def UpdateSlice3D(self, pos):
1032 original_orientation = project.Project().original_orientation 1302 original_orientation = project.Project().original_orientation
1033 pos = self.scroll.GetThumbPosition() 1303 pos = self.scroll.GetThumbPosition()
1034 Publisher.sendMessage('Change slice from slice plane',\ 1304 Publisher.sendMessage('Change slice from slice plane',\
1035 (self.orientation, pos)) 1305 (self.orientation, pos))
1036 - 1306 +
1037 def OnScrollBar(self, evt=None, update3D=True): 1307 def OnScrollBar(self, evt=None, update3D=True):
1038 - pos = self.scroll.GetThumbPosition() 1308 + pos = self.scroll.GetThumbPosition()
1039 self.set_slice_number(pos) 1309 self.set_slice_number(pos)
1040 if update3D: 1310 if update3D:
1041 self.UpdateSlice3D(pos) 1311 self.UpdateSlice3D(pos)
@@ -1044,14 +1314,14 @@ class Viewer(wx.Panel): @@ -1044,14 +1314,14 @@ class Viewer(wx.Panel):
1044 # the actual orientation. 1314 # the actual orientation.
1045 focal_point = self.cross.GetFocalPoint() 1315 focal_point = self.cross.GetFocalPoint()
1046 Publisher.sendMessage('Update cross position', focal_point) 1316 Publisher.sendMessage('Update cross position', focal_point)
1047 - Publisher.sendMessage('Update slice viewer') 1317 + Publisher.sendMessage('Update slice viewer')
1048 else: 1318 else:
1049 - self.interactor.Render() 1319 + self.interactor.Render()
1050 if evt: 1320 if evt:
1051 if self._flush_buffer: 1321 if self._flush_buffer:
1052 self.slice_.apply_slice_buffer_to_mask(self.orientation) 1322 self.slice_.apply_slice_buffer_to_mask(self.orientation)
1053 evt.Skip() 1323 evt.Skip()
1054 - 1324 +
1055 def OnScrollBarRelease(self, evt): 1325 def OnScrollBarRelease(self, evt):
1056 pos = self.scroll.GetThumbPosition() 1326 pos = self.scroll.GetThumbPosition()
1057 evt.Skip() 1327 evt.Skip()
@@ -1077,7 +1347,7 @@ class Viewer(wx.Panel): @@ -1077,7 +1347,7 @@ class Viewer(wx.Panel):
1077 if (evt.GetKeyCode() == wx.WXK_UP and pos > min): 1347 if (evt.GetKeyCode() == wx.WXK_UP and pos > min):
1078 self.OnScrollForward() 1348 self.OnScrollForward()
1079 self.OnScrollBar() 1349 self.OnScrollBar()
1080 - 1350 +
1081 elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max): 1351 elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max):
1082 self.OnScrollBackward() 1352 self.OnScrollBackward()
1083 self.OnScrollBar() 1353 self.OnScrollBar()
@@ -1101,7 +1371,7 @@ class Viewer(wx.Panel): @@ -1101,7 +1371,7 @@ class Viewer(wx.Panel):
1101 Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()]) 1371 Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()])
1102 Publisher.sendMessage('Reload actual slice') 1372 Publisher.sendMessage('Reload actual slice')
1103 skip = False 1373 skip = False
1104 - 1374 +
1105 self.UpdateSlice3D(pos) 1375 self.UpdateSlice3D(pos)
1106 self.interactor.Render() 1376 self.interactor.Render()
1107 1377
@@ -1109,20 +1379,24 @@ class Viewer(wx.Panel): @@ -1109,20 +1379,24 @@ class Viewer(wx.Panel):
1109 evt.Skip() 1379 evt.Skip()
1110 1380
1111 def OnScrollForward(self, evt=None, obj=None): 1381 def OnScrollForward(self, evt=None, obj=None):
  1382 + if not self.scroll_enabled:
  1383 + return
1112 pos = self.scroll.GetThumbPosition() 1384 pos = self.scroll.GetThumbPosition()
1113 min = 0 1385 min = 0
1114 - 1386 +
1115 if(pos > min): 1387 if(pos > min):
1116 if self._flush_buffer: 1388 if self._flush_buffer:
1117 self.slice_.apply_slice_buffer_to_mask(self.orientation) 1389 self.slice_.apply_slice_buffer_to_mask(self.orientation)
1118 pos = pos - 1 1390 pos = pos - 1
1119 self.scroll.SetThumbPosition(pos) 1391 self.scroll.SetThumbPosition(pos)
1120 self.OnScrollBar() 1392 self.OnScrollBar()
1121 - 1393 +
1122 def OnScrollBackward(self, evt=None, obj=None): 1394 def OnScrollBackward(self, evt=None, obj=None):
  1395 + if not self.scroll_enabled:
  1396 + return
1123 pos = self.scroll.GetThumbPosition() 1397 pos = self.scroll.GetThumbPosition()
1124 max = self.slice_.GetMaxSliceNumber(self.orientation) 1398 max = self.slice_.GetMaxSliceNumber(self.orientation)
1125 - 1399 +
1126 if(pos < max): 1400 if(pos < max):
1127 if self._flush_buffer: 1401 if self._flush_buffer:
1128 self.slice_.apply_slice_buffer_to_mask(self.orientation) 1402 self.slice_.apply_slice_buffer_to_mask(self.orientation)
@@ -1131,7 +1405,7 @@ class Viewer(wx.Panel): @@ -1131,7 +1405,7 @@ class Viewer(wx.Panel):
1131 self.OnScrollBar() 1405 self.OnScrollBar()
1132 1406
1133 def OnSize(self, evt): 1407 def OnSize(self, evt):
1134 - w, h = evt.GetSize() 1408 + w, h = evt.GetSize()
1135 w = float(w) 1409 w = float(w)
1136 h = float(h) 1410 h = float(h)
1137 if self.slice_data: 1411 if self.slice_data:
@@ -1168,7 +1442,7 @@ class Viewer(wx.Panel): @@ -1168,7 +1442,7 @@ class Viewer(wx.Panel):
1168 self.mip_ctrls.Hide() 1442 self.mip_ctrls.Hide()
1169 self.GetSizer().Remove(self.mip_ctrls) 1443 self.GetSizer().Remove(self.mip_ctrls)
1170 self.Layout() 1444 self.Layout()
1171 - 1445 +
1172 def OnSetOverwriteMask(self, pubsub_evt): 1446 def OnSetOverwriteMask(self, pubsub_evt):
1173 value = pubsub_evt.data 1447 value = pubsub_evt.data
1174 self.overwrite_mask = value 1448 self.overwrite_mask = value
@@ -1184,14 +1458,14 @@ class Viewer(wx.Panel): @@ -1184,14 +1458,14 @@ class Viewer(wx.Panel):
1184 for actor in self.actors_by_slice_number[index]: 1458 for actor in self.actors_by_slice_number[index]:
1185 self.slice_data.renderer.AddActor(actor) 1459 self.slice_data.renderer.AddActor(actor)
1186 1460
1187 - for (m, mr) in self.measures.get(self.orientation, self.slice_data.number):  
1188 - for actor in mr.GetActors():  
1189 - self.slice_data.renderer.RemoveActor(actor) 1461 + # for (m, mr) in self.measures.get(self.orientation, self.slice_data.number):
  1462 + # for actor in mr.GetActors():
  1463 + # self.slice_data.renderer.RemoveActor(actor)
1190 1464
1191 - for (m, mr) in self.measures.get(self.orientation, index):  
1192 - mr.renderer = self.slice_data.renderer  
1193 - for actor in mr.GetActors():  
1194 - self.slice_data.renderer.AddActor(actor) 1465 + # for (m, mr) in self.measures.get(self.orientation, index):
  1466 + # mr.renderer = self.slice_data.renderer
  1467 + # for actor in mr.GetActors():
  1468 + # self.slice_data.renderer.AddActor(actor)
1195 1469
1196 if self.slice_._type_projection == const.PROJECTION_NORMAL: 1470 if self.slice_._type_projection == const.PROJECTION_NORMAL:
1197 self.slice_data.SetNumber(index) 1471 self.slice_data.SetNumber(index)
@@ -1225,7 +1499,7 @@ class Viewer(wx.Panel): @@ -1225,7 +1499,7 @@ class Viewer(wx.Panel):
1225 # orientation 1499 # orientation
1226 axis0, axis1 = pubsub_evt.data 1500 axis0, axis1 = pubsub_evt.data
1227 cursor = self.slice_data.cursor 1501 cursor = self.slice_data.cursor
1228 - spacing = cursor.spacing 1502 + spacing = cursor.spacing
1229 if (axis0, axis1) == (2, 1): 1503 if (axis0, axis1) == (2, 1):
1230 cursor.SetSpacing((spacing[1], spacing[0], spacing[2])) 1504 cursor.SetSpacing((spacing[1], spacing[0], spacing[2]))
1231 elif (axis0, axis1) == (2, 0): 1505 elif (axis0, axis1) == (2, 0):
invesalius/gui/data_notebook.py
@@ -943,6 +943,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): @@ -943,6 +943,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
943 Publisher.subscribe(self.OnShowSingle, 'Show single measurement') 943 Publisher.subscribe(self.OnShowSingle, 'Show single measurement')
944 Publisher.subscribe(self.OnShowMultiple, 'Show multiple measurements') 944 Publisher.subscribe(self.OnShowMultiple, 'Show multiple measurements')
945 Publisher.subscribe(self.OnLoadData, 'Load measurement dict') 945 Publisher.subscribe(self.OnLoadData, 'Load measurement dict')
  946 + Publisher.subscribe(self.OnRemoveGUIMeasure, 'Remove GUI measurement')
946 947
947 def __bind_events_wx(self): 948 def __bind_events_wx(self):
948 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) 949 self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
@@ -959,6 +960,19 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): @@ -959,6 +960,19 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
959 elif (keycode == wx.WXK_DELETE): 960 elif (keycode == wx.WXK_DELETE):
960 self.RemoveMeasurements() 961 self.RemoveMeasurements()
961 962
  963 + def OnRemoveGUIMeasure(self, pubsub_evt):
  964 + idx = pubsub_evt.data
  965 + self.DeleteItem(idx)
  966 +
  967 + old_dict = self._list_index
  968 + new_dict = {}
  969 + j = 0
  970 + for i in old_dict:
  971 + if i != idx:
  972 + new_dict[j] = old_dict[i]
  973 + j+=1
  974 + self._list_index = new_dict
  975 +
962 def RemoveMeasurements(self): 976 def RemoveMeasurements(self):
963 """ 977 """
964 Remove items selected. 978 Remove items selected.
@@ -1105,7 +1119,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): @@ -1105,7 +1119,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
1105 value = (u"%.2f°") % m.value 1119 value = (u"%.2f°") % m.value
1106 self.InsertNewItem(m.index, m.name, colour, location, type, value) 1120 self.InsertNewItem(m.index, m.name, colour, location, type, value)
1107 1121
1108 - if not m.is_shown: 1122 + if not m.visible:
1109 self.SetItemImage(i, False) 1123 self.SetItemImage(i, False)
1110 1124
1111 def AddItem_(self, pubsub_evt): 1125 def AddItem_(self, pubsub_evt):
invesalius/project.py
@@ -190,7 +190,7 @@ class Project(object): @@ -190,7 +190,7 @@ class Project(object):
190 item["type"] = m.type 190 item["type"] = m.type
191 item["slice_number"] = m.slice_number 191 item["slice_number"] = m.slice_number
192 item["points"] = m.points 192 item["points"] = m.points
193 - item["visible"] = m.is_shown 193 + item["visible"] = m.visible
194 measures[str(m.index)] = item 194 measures[str(m.index)] = item
195 return measures 195 return measures
196 196