From 0f30fe61b8c6b18f491c123b13f7fae165c26f7e Mon Sep 17 00:00:00 2001 From: tfmoraes Date: Tue, 23 Feb 2010 13:28:47 +0000 Subject: [PATCH] ADD: Initial works in measures --- .gitattributes | 1 + invesalius/constants.py | 7 +++++-- invesalius/data/measures.py | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/data/viewer_volume.py | 30 +++++++++++++++++++++++++++++- invesalius/gui/data_notebook.py | 4 ++-- invesalius/gui/default_tasks.py | 18 +++++++++--------- invesalius/gui/task_tools.py | 9 +++++++-- 7 files changed, 394 insertions(+), 16 deletions(-) create mode 100644 invesalius/data/measures.py diff --git a/.gitattributes b/.gitattributes index b8f1dae..db23fbc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -141,6 +141,7 @@ invesalius/data/cursor_actors.py -text invesalius/data/editor.py -text invesalius/data/imagedata_utils.py -text invesalius/data/mask.py -text +invesalius/data/measures.py -text invesalius/data/orientation.py -text invesalius/data/polydata_utils.py -text invesalius/data/slice_.py -text diff --git a/invesalius/constants.py b/invesalius/constants.py index 4fd9fbe..51728ef 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -350,6 +350,7 @@ MODE_ZOOM_SELECTION = 1 #:"Set Zoom Select Mode", MODE_ROTATE = 2#:"Set Spin Mode", MODE_MOVE = 3#:"Set Pan Mode", MODE_WW_WL = 4#:"Bright and contrast adjustment"} +MODE_LINEAR_MEASURE = 5 # self.states = {0:"Set Zoom Mode", 1:"Set Zoom Select Mode", @@ -431,6 +432,7 @@ SLICE_STATE_CROSS = 1006 SLICE_STATE_SCROLL = 1007 SLICE_STATE_EDITOR = 1008 VOLUME_STATE_SEED = 2001 +STATE_LINEAR_MEASURE = 3001 TOOL_STATES = [ STATE_WL, STATE_SPIN, STATE_ZOOM, @@ -443,7 +445,7 @@ SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES SLICE_STYLES.append(STATE_DEFAULT) SLICE_STYLES.append(SLICE_STATE_EDITOR) -VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED] +VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_LINEAR_MEASURE] VOLUME_STYLES.append(STATE_DEFAULT) @@ -456,4 +458,5 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, STATE_ZOOM: 2, STATE_ZOOM_SL: 2, STATE_PAN:2, - VOLUME_STATE_SEED:1} + VOLUME_STATE_SEED:1, + STATE_LINEAR_MEASURE: 2} diff --git a/invesalius/data/measures.py b/invesalius/data/measures.py new file mode 100644 index 0000000..d2a34b5 --- /dev/null +++ b/invesalius/data/measures.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- + +# This example demonstrates the use of vtkSTLReader to load data into +# VTK from a file. This example also uses vtkLODActor which changes +# its graphical representation of the data to maintain interactive +# performance. + +import wx +import sys +import os +import time +import math + +from itertools import cycle +from wx.grid import Grid, GridCellBoolRenderer, GridCellBoolEditor, EVT_GRID_CELL_CHANGE + +import vtk +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor + +class CirclePointRepresentation(object): + """ + This class represents a circle that indicate a point in the surface + """ + def __init__(self, color=(1, 0, 0), radius=1.0): + """ + color: the color of the representation + radius: the radius of circle representation + """ + self.color = color + self.radius = radius + + def GetRepresentation(self, x, y, z): + """ + Return a actor that represents the point in the given x, y, z point + """ + sphere = vtk.vtkSphereSource() + sphere.SetCenter(x, y, z) + sphere.SetRadius(self.radius) + + c = vtk.vtkCoordinate() + c.SetCoordinateSystemToWorld() + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(sphere.GetOutputPort()) + m.SetTransformCoordinate(c) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.GetProperty().SetColor(self.color) + + return a + +class CrossPointRepresentation(object): + """ + This class represents a cross that indicate a point in the surface + """ + def __init__(self, camera, color=(1, 0, 0), size=1.0): + """ + color: the color of the representation + size: the size of the representation + camera: the active camera, to get the orientation to draw the cross + """ + self.camera = camera + self.color = color + self.size = size + + def GetRepresentation(self, x, y, z): + pc = self.camera.GetPosition() # camera position + pf = self.camera.GetFocalPoint() # focal position + pp = (x, y, z) # point where the user clicked + + # Vector from camera position to user clicked point + vcp = [j-i for i,j in zip(pc, pp)] + # Vector from camera position to camera focal point + vcf = [j-i for i,j in zip(pc, pf)] + # the vector where the perpendicular vector will be given + n = [0,0,0] + # The cross, or vectorial product, give a vector perpendicular to vcp + # and vcf, in this case this vector will be in horizontal, this vector + # will be stored in the variable "n" + vtk.vtkMath.Cross(vcp, vcf, n) + # then normalize n to only indicate the direction of this vector + vtk.vtkMath.Normalize(n) + # then + p1 = [i*self.size + j for i,j in zip(n, pp)] + p2 = [i*-self.size + j for i,j in zip(n, pp)] + + sh = vtk.vtkLineSource() + sh.SetPoint1(p1) + sh.SetPoint2(p2) + + n = [0,0,0] + vcn = [j-i for i,j in zip(p1, pc)] + vtk.vtkMath.Cross(vcp, vcn, n) + vtk.vtkMath.Normalize(n) + p3 = [i*self.size + j for i,j in zip(n, pp)] + p4 = [i*-self.size +j for i,j in zip(n, pp)] + + sv = vtk.vtkLineSource() + sv.SetPoint1(p3) + sv.SetPoint2(p4) + + cruz = vtk.vtkAppendPolyData() + cruz.AddInput(sv.GetOutput()) + cruz.AddInput(sh.GetOutput()) + + c = vtk.vtkCoordinate() + c.SetCoordinateSystemToWorld() + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(cruz.GetOutputPort()) + m.SetTransformCoordinate(c) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.GetProperty().SetColor(self.color) + return a + +class LinearMeasure(object): + def __init__(self, render, color=(1, 0, 0), representation=None): + self.color = color + self.points = [] + self.point_actor1 = None + self.point_actor2 = None + self.line_actor = None + self.render = render + if not representation: + representation = CirclePointRepresentation() + self.representation = representation + print color + + def SetPoint1(self, x, y, z): + self.points.append((x, y, z)) + self.point_actor1 = self.representation.GetRepresentation(x, y, z) + self.render.AddActor(self.point_actor1) + + def SetPoint2(self, x, y, z): + self.points.append((x, y, z)) + self.point_actor2 = self.representation.GetRepresentation(x, y, z) + self.render.AddActor(self.point_actor2) + self.CreateMeasure() + + def CreateMeasure(self): + self._draw_line() + self._draw_text() + + def _draw_line(self): + line = vtk.vtkLineSource() + line.SetPoint1(self.points[0]) + line.SetPoint2(self.points[1]) + + c = vtk.vtkCoordinate() + c.SetCoordinateSystemToWorld() + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(line.GetOutputPort()) + m.SetTransformCoordinate(c) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.GetProperty().SetColor(self.color) + self.line_actor = a + self.render.AddActor(self.line_actor) + + def _draw_text(self): + p1, p2 = self.points + text = ' %.2f mm ' % \ + math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2)) + x,y,z=[(i+j)/2 for i,j in zip(p1, p2)] + textsource = vtk.vtkTextSource() + textsource.SetText(text) + textsource.SetBackgroundColor((250/255.0, 247/255.0, 218/255.0)) + textsource.SetForegroundColor(self.color) + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(textsource.GetOutputPort()) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.DragableOn() + a.GetPositionCoordinate().SetCoordinateSystemToWorld() + a.GetPositionCoordinate().SetValue(x,y,z) + self.text_actor = a + self.render.AddActor(self.text_actor) + + def GetNumberOfPoints(self): + return len(self.points) + + def GetValue(self): + p1, p2 = self.points + return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2)) + + def SetVisibility(self, v): + self.point_actor1.SetVisibility(v) + self.point_actor2.SetVisibility(v) + self.line_actor.SetVisibility(v) + self.text_actor.SetVisibility(v) + + +class AngularMeasure(object): + def __init__(self, render, color=(1, 0, 0), representation=None): + self.color = color + self.points = [0, 0, 0] + self.number_of_points = 0 + self.point_actor1 = None + self.point_actor2 = None + self.point_actor3 = None + self.line_actor = None + self.render = render + if not representation: + representation = CirclePointRepresentation() + self.representation = representation + print color + + def SetPoint1(self, x, y, z): + self.points[0] = (x, y, z) + self.number_of_points = 1 + self.point_actor1 = self.representation.GetRepresentation(x, y, z) + self.render.AddActor(self.point_actor1) + + def SetPoint2(self, x, y, z): + self.number_of_points = 2 + self.points[1] = (x, y, z) + self.point_actor2 = self.representation.GetRepresentation(x, y, z) + self.render.AddActor(self.point_actor2) + + def SetPoint3(self, x, y, z): + self.number_of_points = 3 + self.points[2] = (x, y, z) + self.point_actor3 = self.representation.GetRepresentation(x, y, z) + self.render.AddActor(self.point_actor3) + + def _draw_line(self): + line1 = vtk.vtkLineSource() + line1.SetPoint1(self.points[0]) + line1.SetPoint2(self.points[1]) + + line2 = vtk.vtkLineSource() + line2.SetPoint1(self.points[1]) + line2.SetPoint2(self.points[2]) + + arc = self.DrawArc() + + line = vtk.vtkAppendPolyData() + line.AddInput(line1.GetOutput()) + line.AddInput(line2.GetOutput()) + line.AddInput(arc.GetOutput()) + + c = vtk.vtkCoordinate() + c.SetCoordinateSystemToWorld() + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(line.GetOutputPort()) + m.SetTransformCoordinate(c) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.GetProperty().SetColor(self.color) + self.line_actor = a + return a + + def DrawArc(self): + + d1 = math.sqrt(vtk.vtkMath.Distance2BetweenPoints(self.points[0], + self.points[1])) + d2 = math.sqrt(vtk.vtkMath.Distance2BetweenPoints(self.points[2], + self.points[1])) + + if d1 < d2: + d = d1 + p1 = self.points[0] + a,b,c = [j-i for i,j in zip(self.points[1], self.points[2])] + else: + d = d2 + p1 = self.points[2] + a,b,c = [j-i for i,j in zip(self.points[1], self.points[0])] + + t = (d / math.sqrt(a**2 + b**2 + c**2)) + x = self.points[1][0] + a*t + y = self.points[1][1] + b*t + z = self.points[1][2] + c*t + p2 = (x, y, z) + + arc = vtk.vtkArcSource() + arc.SetPoint1(p1) + arc.SetPoint2(p2) + arc.SetCenter(self.points[1]) + arc.SetResolution(20) + return arc + + def _draw_text(self): + text = u' %.2f ' % \ + self.CalculateAngle() + x,y,z= self.points[1] + textsource = vtk.vtkTextSource() + textsource.SetText(text) + textsource.SetBackgroundColor((250/255.0, 247/255.0, 218/255.0)) + textsource.SetForegroundColor(self.color) + + m = vtk.vtkPolyDataMapper2D() + m.SetInputConnection(textsource.GetOutputPort()) + + a = vtk.vtkActor2D() + a.SetMapper(m) + a.DragableOn() + a.GetPositionCoordinate().SetCoordinateSystemToWorld() + a.GetPositionCoordinate().SetValue(x,y,z) + self.text_actor = a + return a + + def GetNumberOfPoints(self): + return self.number_of_points + + def GetValue(self): + return self.CalculateAngle() + + def SetVisibility(self, v): + self.point_actor1.SetVisibility(v) + self.point_actor2.SetVisibility(v) + self.point_actor3.SetVisibility(v) + self.line_actor.SetVisibility(v) + self.text_actor.SetVisibility(v) + + def CalculateAngle(self): + """ + Calculate the angle between 2 vectors in 3D space. It is based on law of + cosines for vector. + The Alpha Cosine is equal the dot product from two vector divided for + product between the magnitude from that vectors. Then the angle is inverse + cosine. + """ + v1 = [j-i for i,j in zip(self.points[0], self.points[1])] + v2 = [j-i for i,j in zip(self.points[2], self.points[1])] + #print vtk.vtkMath.Normalize(v1) + #print vtk.vtkMath.Normalize(v2) + cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2)) + angle = math.degrees(math.acos(cos)) + return angle + + diff --git a/invesalius/data/viewer_volume.py b/invesalius/data/viewer_volume.py index ab72913..cfaa172 100755 --- a/invesalius/data/viewer_volume.py +++ b/invesalius/data/viewer_volume.py @@ -30,6 +30,8 @@ import project as prj import style as st import utils +from data import measures + class Viewer(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, size=wx.Size(320, 320)) @@ -88,6 +90,10 @@ class Viewer(wx.Panel): self.seed_points = [] self.points_reference = [] + + self.measure_picker = vtk.vtkPointPicker() + self.measure_picker.SetTolerance(0.005) + self.measures = [] def __bind_events(self): @@ -287,7 +293,11 @@ class Viewer(wx.Panel): const.VOLUME_STATE_SEED: { "LeftButtonPressEvent": self.OnInsertSeed - } + }, + const.STATE_LINEAR_MEASURE: + { + "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint + } } if state == const.STATE_WL: @@ -309,6 +319,9 @@ class Viewer(wx.Panel): self.interactor.SetInteractorStyle(style) self.style = style + if state == const.STATE_LINEAR_MEASURE: + self.interactor.SetPicker(self.measure_picker) + # Check each event available for each mode for event in action[state]: # Bind event @@ -590,6 +603,21 @@ class Viewer(wx.Panel): self.seed_points.append(point_id) self.interactor.Render() + def OnInsertLinearMeasurePoint(self, obj, evt): + print "Hey, you inserted measure point" + x,y = self.interactor.GetEventPosition() + self.measure_picker.Pick(x, y, 0, self.ren) + x, y, z = self.measure_picker.GetPickPosition() + if self.measure_picker.GetPointId() != -1: + if not self.measures or self.measures[-1].point_actor2: + m = measures.LinearMeasure(self.ren) + m.SetPoint1(x, y, z) + self.measures.append(m) + else: + m = self.measures[-1] + m.SetPoint2(x, y, z) + self.interactor.Render() + class SlicePlane: def __init__(self): diff --git a/invesalius/gui/data_notebook.py b/invesalius/gui/data_notebook.py index c8e7b0d..30b9ffb 100644 --- a/invesalius/gui/data_notebook.py +++ b/invesalius/gui/data_notebook.py @@ -50,8 +50,8 @@ class NotebookPanel(wx.Panel): book.AddPage(MaskPage(book), _("Masks")) book.AddPage(SurfacePage(book), _("Surfaces")) - #book.AddPage(MeasuresListCtrlPanel(book), _("Measures")) - #book.AddPage(AnnotationsListCtrlPanel(book), _("Annotations")) + book.AddPage(MeasuresListCtrlPanel(book), _("Measures")) + book.AddPage(AnnotationsListCtrlPanel(book), _("Annotations")) book.SetSelection(0) diff --git a/invesalius/gui/default_tasks.py b/invesalius/gui/default_tasks.py index 72a461e..a5fae6b 100755 --- a/invesalius/gui/default_tasks.py +++ b/invesalius/gui/default_tasks.py @@ -144,15 +144,15 @@ class LowerTaskPanel(wx.Panel): # Fold 2 - Tools # Measures # Text Annotations - #item = fold_panel.AddFoldPanel(_("Tools"), collapsed=False, - # foldIcons=image_list) - #style = fold_panel.GetCaptionStyle(item) - #col = style.GetFirstColour() - #self.enable_items.append(item) - # - #fold_panel.AddFoldPanelWindow(item, tools.TaskPanel(item), Spacing= 0, - # leftSpacing=0, rightSpacing=0) - #fold_panel.Expand(fold_panel.GetFoldPanel(1)) + item = fold_panel.AddFoldPanel(_("Tools"), collapsed=False, + foldIcons=image_list) + style = fold_panel.GetCaptionStyle(item) + col = style.GetFirstColour() + self.enable_items.append(item) + + fold_panel.AddFoldPanelWindow(item, tools.TaskPanel(item), Spacing= 0, + leftSpacing=0, rightSpacing=0) + fold_panel.Expand(fold_panel.GetFoldPanel(1)) self.SetStateProjectClose() self.__bind_events() diff --git a/invesalius/gui/task_tools.py b/invesalius/gui/task_tools.py index 0234713..573fd8f 100644 --- a/invesalius/gui/task_tools.py +++ b/invesalius/gui/task_tools.py @@ -18,9 +18,12 @@ #-------------------------------------------------------------------------- import wx +import wx.lib.embeddedimage as emb import wx.lib.hyperlink as hl import wx.lib.platebtn as pbtn -import wx.lib.embeddedimage as emb +import wx.lib.pubsub as ps + +import constants ID_BTN_MEASURE_LINEAR = wx.NewId() ID_BTN_MEASURE_ANGULAR = wx.NewId() @@ -118,7 +121,9 @@ class InnerTaskPanel(wx.Panel): print "TODO: Send Signal - Add text annotation (both 2d and 3d)" def OnLinkLinearMeasure(self): - print "TODO: Send Signal - Add linear measure (both 2d and 3d)" + #print "TODO: Send Signal - Add linear measure (both 2d and 3d)" + ps.Publisher().sendMessage('Enable style', + constants.STATE_LINEAR_MEASURE) def OnLinkAngularMeasure(self): print "TODO: Send Signal - Add angular measure (both 2d and 3d)" -- libgit2 0.21.2