Commit 0f30fe61b8c6b18f491c123b13f7fae165c26f7e

Authored by tfmoraes
1 parent 259d97fe

ADD: Initial works in measures

.gitattributes
... ... @@ -141,6 +141,7 @@ invesalius/data/cursor_actors.py -text
141 141 invesalius/data/editor.py -text
142 142 invesalius/data/imagedata_utils.py -text
143 143 invesalius/data/mask.py -text
  144 +invesalius/data/measures.py -text
144 145 invesalius/data/orientation.py -text
145 146 invesalius/data/polydata_utils.py -text
146 147 invesalius/data/slice_.py -text
... ...
invesalius/constants.py
... ... @@ -350,6 +350,7 @@ MODE_ZOOM_SELECTION = 1 #:"Set Zoom Select Mode",
350 350 MODE_ROTATE = 2#:"Set Spin Mode",
351 351 MODE_MOVE = 3#:"Set Pan Mode",
352 352 MODE_WW_WL = 4#:"Bright and contrast adjustment"}
  353 +MODE_LINEAR_MEASURE = 5
353 354  
354 355  
355 356 # self.states = {0:"Set Zoom Mode", 1:"Set Zoom Select Mode",
... ... @@ -431,6 +432,7 @@ SLICE_STATE_CROSS = 1006
431 432 SLICE_STATE_SCROLL = 1007
432 433 SLICE_STATE_EDITOR = 1008
433 434 VOLUME_STATE_SEED = 2001
  435 +STATE_LINEAR_MEASURE = 3001
434 436  
435 437  
436 438 TOOL_STATES = [ STATE_WL, STATE_SPIN, STATE_ZOOM,
... ... @@ -443,7 +445,7 @@ SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES
443 445 SLICE_STYLES.append(STATE_DEFAULT)
444 446 SLICE_STYLES.append(SLICE_STATE_EDITOR)
445 447  
446   -VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED]
  448 +VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_LINEAR_MEASURE]
447 449 VOLUME_STYLES.append(STATE_DEFAULT)
448 450  
449 451  
... ... @@ -456,4 +458,5 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1,
456 458 STATE_ZOOM: 2,
457 459 STATE_ZOOM_SL: 2,
458 460 STATE_PAN:2,
459   - VOLUME_STATE_SEED:1}
  461 + VOLUME_STATE_SEED:1,
  462 + STATE_LINEAR_MEASURE: 2}
... ...
invesalius/data/measures.py 0 → 100644
... ... @@ -0,0 +1,341 @@
  1 +#!/usr/bin/env python
  2 +# -*- coding: UTF-8 -*-
  3 +
  4 +# This example demonstrates the use of vtkSTLReader to load data into
  5 +# VTK from a file. This example also uses vtkLODActor which changes
  6 +# its graphical representation of the data to maintain interactive
  7 +# performance.
  8 +
  9 +import wx
  10 +import sys
  11 +import os
  12 +import time
  13 +import math
  14 +
  15 +from itertools import cycle
  16 +from wx.grid import Grid, GridCellBoolRenderer, GridCellBoolEditor, EVT_GRID_CELL_CHANGE
  17 +
  18 +import vtk
  19 +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
  20 +
  21 +class CirclePointRepresentation(object):
  22 + """
  23 + This class represents a circle that indicate a point in the surface
  24 + """
  25 + def __init__(self, color=(1, 0, 0), radius=1.0):
  26 + """
  27 + color: the color of the representation
  28 + radius: the radius of circle representation
  29 + """
  30 + self.color = color
  31 + self.radius = radius
  32 +
  33 + def GetRepresentation(self, x, y, z):
  34 + """
  35 + Return a actor that represents the point in the given x, y, z point
  36 + """
  37 + sphere = vtk.vtkSphereSource()
  38 + sphere.SetCenter(x, y, z)
  39 + sphere.SetRadius(self.radius)
  40 +
  41 + c = vtk.vtkCoordinate()
  42 + c.SetCoordinateSystemToWorld()
  43 +
  44 + m = vtk.vtkPolyDataMapper2D()
  45 + m.SetInputConnection(sphere.GetOutputPort())
  46 + m.SetTransformCoordinate(c)
  47 +
  48 + a = vtk.vtkActor2D()
  49 + a.SetMapper(m)
  50 + a.GetProperty().SetColor(self.color)
  51 +
  52 + return a
  53 +
  54 +class CrossPointRepresentation(object):
  55 + """
  56 + This class represents a cross that indicate a point in the surface
  57 + """
  58 + def __init__(self, camera, color=(1, 0, 0), size=1.0):
  59 + """
  60 + color: the color of the representation
  61 + size: the size of the representation
  62 + camera: the active camera, to get the orientation to draw the cross
  63 + """
  64 + self.camera = camera
  65 + self.color = color
  66 + self.size = size
  67 +
  68 + def GetRepresentation(self, x, y, z):
  69 + pc = self.camera.GetPosition() # camera position
  70 + pf = self.camera.GetFocalPoint() # focal position
  71 + pp = (x, y, z) # point where the user clicked
  72 +
  73 + # Vector from camera position to user clicked point
  74 + vcp = [j-i for i,j in zip(pc, pp)]
  75 + # Vector from camera position to camera focal point
  76 + vcf = [j-i for i,j in zip(pc, pf)]
  77 + # the vector where the perpendicular vector will be given
  78 + n = [0,0,0]
  79 + # The cross, or vectorial product, give a vector perpendicular to vcp
  80 + # and vcf, in this case this vector will be in horizontal, this vector
  81 + # will be stored in the variable "n"
  82 + vtk.vtkMath.Cross(vcp, vcf, n)
  83 + # then normalize n to only indicate the direction of this vector
  84 + vtk.vtkMath.Normalize(n)
  85 + # then
  86 + p1 = [i*self.size + j for i,j in zip(n, pp)]
  87 + p2 = [i*-self.size + j for i,j in zip(n, pp)]
  88 +
  89 + sh = vtk.vtkLineSource()
  90 + sh.SetPoint1(p1)
  91 + sh.SetPoint2(p2)
  92 +
  93 + n = [0,0,0]
  94 + vcn = [j-i for i,j in zip(p1, pc)]
  95 + vtk.vtkMath.Cross(vcp, vcn, n)
  96 + vtk.vtkMath.Normalize(n)
  97 + p3 = [i*self.size + j for i,j in zip(n, pp)]
  98 + p4 = [i*-self.size +j for i,j in zip(n, pp)]
  99 +
  100 + sv = vtk.vtkLineSource()
  101 + sv.SetPoint1(p3)
  102 + sv.SetPoint2(p4)
  103 +
  104 + cruz = vtk.vtkAppendPolyData()
  105 + cruz.AddInput(sv.GetOutput())
  106 + cruz.AddInput(sh.GetOutput())
  107 +
  108 + c = vtk.vtkCoordinate()
  109 + c.SetCoordinateSystemToWorld()
  110 +
  111 + m = vtk.vtkPolyDataMapper2D()
  112 + m.SetInputConnection(cruz.GetOutputPort())
  113 + m.SetTransformCoordinate(c)
  114 +
  115 + a = vtk.vtkActor2D()
  116 + a.SetMapper(m)
  117 + a.GetProperty().SetColor(self.color)
  118 + return a
  119 +
  120 +class LinearMeasure(object):
  121 + def __init__(self, render, color=(1, 0, 0), representation=None):
  122 + self.color = color
  123 + self.points = []
  124 + self.point_actor1 = None
  125 + self.point_actor2 = None
  126 + self.line_actor = None
  127 + self.render = render
  128 + if not representation:
  129 + representation = CirclePointRepresentation()
  130 + self.representation = representation
  131 + print color
  132 +
  133 + def SetPoint1(self, x, y, z):
  134 + self.points.append((x, y, z))
  135 + self.point_actor1 = self.representation.GetRepresentation(x, y, z)
  136 + self.render.AddActor(self.point_actor1)
  137 +
  138 + def SetPoint2(self, x, y, z):
  139 + self.points.append((x, y, z))
  140 + self.point_actor2 = self.representation.GetRepresentation(x, y, z)
  141 + self.render.AddActor(self.point_actor2)
  142 + self.CreateMeasure()
  143 +
  144 + def CreateMeasure(self):
  145 + self._draw_line()
  146 + self._draw_text()
  147 +
  148 + def _draw_line(self):
  149 + line = vtk.vtkLineSource()
  150 + line.SetPoint1(self.points[0])
  151 + line.SetPoint2(self.points[1])
  152 +
  153 + c = vtk.vtkCoordinate()
  154 + c.SetCoordinateSystemToWorld()
  155 +
  156 + m = vtk.vtkPolyDataMapper2D()
  157 + m.SetInputConnection(line.GetOutputPort())
  158 + m.SetTransformCoordinate(c)
  159 +
  160 + a = vtk.vtkActor2D()
  161 + a.SetMapper(m)
  162 + a.GetProperty().SetColor(self.color)
  163 + self.line_actor = a
  164 + self.render.AddActor(self.line_actor)
  165 +
  166 + def _draw_text(self):
  167 + p1, p2 = self.points
  168 + text = ' %.2f mm ' % \
  169 + math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2))
  170 + x,y,z=[(i+j)/2 for i,j in zip(p1, p2)]
  171 + textsource = vtk.vtkTextSource()
  172 + textsource.SetText(text)
  173 + textsource.SetBackgroundColor((250/255.0, 247/255.0, 218/255.0))
  174 + textsource.SetForegroundColor(self.color)
  175 +
  176 + m = vtk.vtkPolyDataMapper2D()
  177 + m.SetInputConnection(textsource.GetOutputPort())
  178 +
  179 + a = vtk.vtkActor2D()
  180 + a.SetMapper(m)
  181 + a.DragableOn()
  182 + a.GetPositionCoordinate().SetCoordinateSystemToWorld()
  183 + a.GetPositionCoordinate().SetValue(x,y,z)
  184 + self.text_actor = a
  185 + self.render.AddActor(self.text_actor)
  186 +
  187 + def GetNumberOfPoints(self):
  188 + return len(self.points)
  189 +
  190 + def GetValue(self):
  191 + p1, p2 = self.points
  192 + return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2))
  193 +
  194 + def SetVisibility(self, v):
  195 + self.point_actor1.SetVisibility(v)
  196 + self.point_actor2.SetVisibility(v)
  197 + self.line_actor.SetVisibility(v)
  198 + self.text_actor.SetVisibility(v)
  199 +
  200 +
  201 +class AngularMeasure(object):
  202 + def __init__(self, render, color=(1, 0, 0), representation=None):
  203 + self.color = color
  204 + self.points = [0, 0, 0]
  205 + self.number_of_points = 0
  206 + self.point_actor1 = None
  207 + self.point_actor2 = None
  208 + self.point_actor3 = None
  209 + self.line_actor = None
  210 + self.render = render
  211 + if not representation:
  212 + representation = CirclePointRepresentation()
  213 + self.representation = representation
  214 + print color
  215 +
  216 + def SetPoint1(self, x, y, z):
  217 + self.points[0] = (x, y, z)
  218 + self.number_of_points = 1
  219 + self.point_actor1 = self.representation.GetRepresentation(x, y, z)
  220 + self.render.AddActor(self.point_actor1)
  221 +
  222 + def SetPoint2(self, x, y, z):
  223 + self.number_of_points = 2
  224 + self.points[1] = (x, y, z)
  225 + self.point_actor2 = self.representation.GetRepresentation(x, y, z)
  226 + self.render.AddActor(self.point_actor2)
  227 +
  228 + def SetPoint3(self, x, y, z):
  229 + self.number_of_points = 3
  230 + self.points[2] = (x, y, z)
  231 + self.point_actor3 = self.representation.GetRepresentation(x, y, z)
  232 + self.render.AddActor(self.point_actor3)
  233 +
  234 + def _draw_line(self):
  235 + line1 = vtk.vtkLineSource()
  236 + line1.SetPoint1(self.points[0])
  237 + line1.SetPoint2(self.points[1])
  238 +
  239 + line2 = vtk.vtkLineSource()
  240 + line2.SetPoint1(self.points[1])
  241 + line2.SetPoint2(self.points[2])
  242 +
  243 + arc = self.DrawArc()
  244 +
  245 + line = vtk.vtkAppendPolyData()
  246 + line.AddInput(line1.GetOutput())
  247 + line.AddInput(line2.GetOutput())
  248 + line.AddInput(arc.GetOutput())
  249 +
  250 + c = vtk.vtkCoordinate()
  251 + c.SetCoordinateSystemToWorld()
  252 +
  253 + m = vtk.vtkPolyDataMapper2D()
  254 + m.SetInputConnection(line.GetOutputPort())
  255 + m.SetTransformCoordinate(c)
  256 +
  257 + a = vtk.vtkActor2D()
  258 + a.SetMapper(m)
  259 + a.GetProperty().SetColor(self.color)
  260 + self.line_actor = a
  261 + return a
  262 +
  263 + def DrawArc(self):
  264 +
  265 + d1 = math.sqrt(vtk.vtkMath.Distance2BetweenPoints(self.points[0],
  266 + self.points[1]))
  267 + d2 = math.sqrt(vtk.vtkMath.Distance2BetweenPoints(self.points[2],
  268 + self.points[1]))
  269 +
  270 + if d1 < d2:
  271 + d = d1
  272 + p1 = self.points[0]
  273 + a,b,c = [j-i for i,j in zip(self.points[1], self.points[2])]
  274 + else:
  275 + d = d2
  276 + p1 = self.points[2]
  277 + a,b,c = [j-i for i,j in zip(self.points[1], self.points[0])]
  278 +
  279 + t = (d / math.sqrt(a**2 + b**2 + c**2))
  280 + x = self.points[1][0] + a*t
  281 + y = self.points[1][1] + b*t
  282 + z = self.points[1][2] + c*t
  283 + p2 = (x, y, z)
  284 +
  285 + arc = vtk.vtkArcSource()
  286 + arc.SetPoint1(p1)
  287 + arc.SetPoint2(p2)
  288 + arc.SetCenter(self.points[1])
  289 + arc.SetResolution(20)
  290 + return arc
  291 +
  292 + def _draw_text(self):
  293 + text = u' %.2f ' % \
  294 + self.CalculateAngle()
  295 + x,y,z= self.points[1]
  296 + textsource = vtk.vtkTextSource()
  297 + textsource.SetText(text)
  298 + textsource.SetBackgroundColor((250/255.0, 247/255.0, 218/255.0))
  299 + textsource.SetForegroundColor(self.color)
  300 +
  301 + m = vtk.vtkPolyDataMapper2D()
  302 + m.SetInputConnection(textsource.GetOutputPort())
  303 +
  304 + a = vtk.vtkActor2D()
  305 + a.SetMapper(m)
  306 + a.DragableOn()
  307 + a.GetPositionCoordinate().SetCoordinateSystemToWorld()
  308 + a.GetPositionCoordinate().SetValue(x,y,z)
  309 + self.text_actor = a
  310 + return a
  311 +
  312 + def GetNumberOfPoints(self):
  313 + return self.number_of_points
  314 +
  315 + def GetValue(self):
  316 + return self.CalculateAngle()
  317 +
  318 + def SetVisibility(self, v):
  319 + self.point_actor1.SetVisibility(v)
  320 + self.point_actor2.SetVisibility(v)
  321 + self.point_actor3.SetVisibility(v)
  322 + self.line_actor.SetVisibility(v)
  323 + self.text_actor.SetVisibility(v)
  324 +
  325 + def CalculateAngle(self):
  326 + """
  327 + Calculate the angle between 2 vectors in 3D space. It is based on law of
  328 + cosines for vector.
  329 + The Alpha Cosine is equal the dot product from two vector divided for
  330 + product between the magnitude from that vectors. Then the angle is inverse
  331 + cosine.
  332 + """
  333 + v1 = [j-i for i,j in zip(self.points[0], self.points[1])]
  334 + v2 = [j-i for i,j in zip(self.points[2], self.points[1])]
  335 + #print vtk.vtkMath.Normalize(v1)
  336 + #print vtk.vtkMath.Normalize(v2)
  337 + cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2))
  338 + angle = math.degrees(math.acos(cos))
  339 + return angle
  340 +
  341 +
... ...
invesalius/data/viewer_volume.py
... ... @@ -30,6 +30,8 @@ import project as prj
30 30 import style as st
31 31 import utils
32 32  
  33 +from data import measures
  34 +
33 35 class Viewer(wx.Panel):
34 36 def __init__(self, parent):
35 37 wx.Panel.__init__(self, parent, size=wx.Size(320, 320))
... ... @@ -88,6 +90,10 @@ class Viewer(wx.Panel):
88 90 self.seed_points = []
89 91  
90 92 self.points_reference = []
  93 +
  94 + self.measure_picker = vtk.vtkPointPicker()
  95 + self.measure_picker.SetTolerance(0.005)
  96 + self.measures = []
91 97  
92 98  
93 99 def __bind_events(self):
... ... @@ -287,7 +293,11 @@ class Viewer(wx.Panel):
287 293 const.VOLUME_STATE_SEED:
288 294 {
289 295 "LeftButtonPressEvent": self.OnInsertSeed
290   - }
  296 + },
  297 + const.STATE_LINEAR_MEASURE:
  298 + {
  299 + "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint
  300 + }
291 301 }
292 302  
293 303 if state == const.STATE_WL:
... ... @@ -309,6 +319,9 @@ class Viewer(wx.Panel):
309 319 self.interactor.SetInteractorStyle(style)
310 320 self.style = style
311 321  
  322 + if state == const.STATE_LINEAR_MEASURE:
  323 + self.interactor.SetPicker(self.measure_picker)
  324 +
312 325 # Check each event available for each mode
313 326 for event in action[state]:
314 327 # Bind event
... ... @@ -590,6 +603,21 @@ class Viewer(wx.Panel):
590 603 self.seed_points.append(point_id)
591 604 self.interactor.Render()
592 605  
  606 + def OnInsertLinearMeasurePoint(self, obj, evt):
  607 + print "Hey, you inserted measure point"
  608 + x,y = self.interactor.GetEventPosition()
  609 + self.measure_picker.Pick(x, y, 0, self.ren)
  610 + x, y, z = self.measure_picker.GetPickPosition()
  611 + if self.measure_picker.GetPointId() != -1:
  612 + if not self.measures or self.measures[-1].point_actor2:
  613 + m = measures.LinearMeasure(self.ren)
  614 + m.SetPoint1(x, y, z)
  615 + self.measures.append(m)
  616 + else:
  617 + m = self.measures[-1]
  618 + m.SetPoint2(x, y, z)
  619 + self.interactor.Render()
  620 +
593 621  
594 622 class SlicePlane:
595 623 def __init__(self):
... ...
invesalius/gui/data_notebook.py
... ... @@ -50,8 +50,8 @@ class NotebookPanel(wx.Panel):
50 50  
51 51 book.AddPage(MaskPage(book), _("Masks"))
52 52 book.AddPage(SurfacePage(book), _("Surfaces"))
53   - #book.AddPage(MeasuresListCtrlPanel(book), _("Measures"))
54   - #book.AddPage(AnnotationsListCtrlPanel(book), _("Annotations"))
  53 + book.AddPage(MeasuresListCtrlPanel(book), _("Measures"))
  54 + book.AddPage(AnnotationsListCtrlPanel(book), _("Annotations"))
55 55  
56 56 book.SetSelection(0)
57 57  
... ...
invesalius/gui/default_tasks.py
... ... @@ -144,15 +144,15 @@ class LowerTaskPanel(wx.Panel):
144 144 # Fold 2 - Tools
145 145 # Measures
146 146 # Text Annotations
147   - #item = fold_panel.AddFoldPanel(_("Tools"), collapsed=False,
148   - # foldIcons=image_list)
149   - #style = fold_panel.GetCaptionStyle(item)
150   - #col = style.GetFirstColour()
151   - #self.enable_items.append(item)
152   - #
153   - #fold_panel.AddFoldPanelWindow(item, tools.TaskPanel(item), Spacing= 0,
154   - # leftSpacing=0, rightSpacing=0)
155   - #fold_panel.Expand(fold_panel.GetFoldPanel(1))
  147 + item = fold_panel.AddFoldPanel(_("Tools"), collapsed=False,
  148 + foldIcons=image_list)
  149 + style = fold_panel.GetCaptionStyle(item)
  150 + col = style.GetFirstColour()
  151 + self.enable_items.append(item)
  152 +
  153 + fold_panel.AddFoldPanelWindow(item, tools.TaskPanel(item), Spacing= 0,
  154 + leftSpacing=0, rightSpacing=0)
  155 + fold_panel.Expand(fold_panel.GetFoldPanel(1))
156 156  
157 157 self.SetStateProjectClose()
158 158 self.__bind_events()
... ...
invesalius/gui/task_tools.py
... ... @@ -18,9 +18,12 @@
18 18 #--------------------------------------------------------------------------
19 19  
20 20 import wx
  21 +import wx.lib.embeddedimage as emb
21 22 import wx.lib.hyperlink as hl
22 23 import wx.lib.platebtn as pbtn
23   -import wx.lib.embeddedimage as emb
  24 +import wx.lib.pubsub as ps
  25 +
  26 +import constants
24 27  
25 28 ID_BTN_MEASURE_LINEAR = wx.NewId()
26 29 ID_BTN_MEASURE_ANGULAR = wx.NewId()
... ... @@ -118,7 +121,9 @@ class InnerTaskPanel(wx.Panel):
118 121 print "TODO: Send Signal - Add text annotation (both 2d and 3d)"
119 122  
120 123 def OnLinkLinearMeasure(self):
121   - print "TODO: Send Signal - Add linear measure (both 2d and 3d)"
  124 + #print "TODO: Send Signal - Add linear measure (both 2d and 3d)"
  125 + ps.Publisher().sendMessage('Enable style',
  126 + constants.STATE_LINEAR_MEASURE)
122 127  
123 128 def OnLinkAngularMeasure(self):
124 129 print "TODO: Send Signal - Add angular measure (both 2d and 3d)"
... ...