Skip to content

Commit

Permalink
FEAT: 3D edit interaction style; polygon select
Browse files Browse the repository at this point in the history
UI modifications to allow drawing a polygon in the volume renderer

- Canvas Handler for simple polygon selection and rendering;
- Interaction style to create this polygon and communicate to mask edit.
  • Loading branch information
henriquenunez committed Aug 9, 2024
1 parent 3acdd1b commit c24cd83
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
71 changes: 71 additions & 0 deletions invesalius/data/polygon_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# --------------------------------------------------------------------------
# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
# Homepage: http://www.softwarepublico.gov.br
# Contact: [email protected]
# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
# --------------------------------------------------------------------------
# Este programa e software livre; voce pode redistribui-lo e/ou
# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
# publicada pela Free Software Foundation; de acordo com a versao 2
# da Licenca.
#
# Este programa eh distribuido na expectativa de ser util, mas SEM
# QUALQUER GARANTIA; sem mesmo a garantia implicita de
# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
# detalhes.
# --------------------------------------------------------------------------

import invesalius.constants as const
from invesalius.gui.widgets.canvas_renderer import CanvasHandlerBase, Polygon

# import invesalius.session as ses


class PolygonSelectCanvas(CanvasHandlerBase):
"""
Inspired on PolygonDensityMeasure, stores and renders a polygon for a canvas
"""

def __init__(self, colour=(255, 0, 0, 255), interactive=True):
super(PolygonSelectCanvas, self).__init__(None)
self.parent = None
self.children = []
self.layer = 0

self.colour = colour
self.points = []

self.complete = False

self.location = const.SURFACE
self.index = 0

self.polygon = Polygon(self, fill=False, closed=False, line_colour=self.colour, is_3d=False)
self.polygon.layer = 1
self.add_child(self.polygon)

self.text_box = None
self.interactive = interactive

def draw_to_canvas(self, gc, canvas):
pass

def insert_point(self, point):
self.polygon.append_point(point)

def complete_polygon(self):
if len(self.polygon.points) >= 3:
self.polygon.closed = True
self.complete = True

def IsComplete(self):
return self.complete

def SetVisibility(self, value):
self.polygon.visible = value

def set_interactive(self, value):
self.interactive = bool(value)
self.polygon.interactive = self.interactive
90 changes: 90 additions & 0 deletions invesalius/data/styles_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,24 @@

import invesalius.constants as const
import invesalius.project as prj
import invesalius.segmentation.mask_3d_edit as m3e
from invesalius.data.polygon_select import PolygonSelectCanvas
from invesalius.pubsub import pub as Publisher

PROP_MEASURE = 0.8

import numpy as np


# TODO: find a better place
def vtkarray_to_numpy(m):
nm = np.zeros((4, 4))
for i in range(4):
for j in range(4):
nm[i, j] = m.GetElement(i, j)

return nm


class Base3DInteractorStyle(vtkInteractorStyleTrackballCamera):
def __init__(self, viewer):
Expand Down Expand Up @@ -620,6 +634,81 @@ def OnScrollBackward(self, evt, obj):
super().OnScrollBackward(evt, obj)


class Mask3DEditorInteractorStyle(DefaultInteractorStyle):
"""
Interactor style for selecting a polygon of interest and performing a mesh edit based on that.
"""

def __init__(self, viewer):
super().__init__(viewer)
self.viewer = viewer

# Performs the cutting
self.mask3deditor = m3e.Mask3DEditor()

self.picker = vtkCellPicker()
self.picker.PickFromListOn()

self.has_poly = False
self.poly = None

self.RemoveObservers("LeftButtonPressEvent")
self.AddObserver("LeftButtonPressEvent", self.OnInsertPolygonPoint)
self.AddObserver("RightButtonPressEvent", self.OnInsertPolygon)

def CleanUp(self):
self.RemoveObservers("LeftButtonPressEvent")
# self.viewer.canvas.unsubscribe_event('LeftButtonPressEvent')
self.RemoveObservers("RightButtonPressEvent")

def OnInsertPolygonPoint(self, obj, evt):
# TODO: Check if camera should be resized
renderer = self.viewer.ren
interactor = self.viewer.interactor

renderer.ResetCamera()
renderer.ResetCameraClippingRange()
interactor.Render()

mouse_x, mouse_y = self.viewer.interactor.GetEventPosition()

if not self.has_poly:
self.poly = PolygonSelectCanvas()
self.has_poly = True
self.viewer.canvas.draw_list.append(self.poly)

self.poly.insert_point((mouse_x, mouse_y))
self.viewer.UpdateCanvas()

def __get_model_to_screen_volume(self):
w, h = self.viewer.GetSize()
self.viewer.ren.Render()
cam = self.viewer.ren.GetActiveCamera()
near, far = cam.GetClippingRange()

# Composite transform world to viewport (projection * view)
M = cam.GetCompositeProjectionTransformMatrix(w / float(h), near, far)
M = vtkarray_to_numpy(M)

MV = cam.GetViewTransformMatrix()
MV = vtkarray_to_numpy(MV)

return M

def OnInsertPolygon(self, obj, evt):
self.poly.complete_polygon()
self.viewer.UpdateCanvas()

Publisher.sendMessage(
"M3E add polygon",
points=self.poly.polygon.points,
screen=self.viewer.ren.GetRenderWindow().GetSize(),
)
Publisher.sendMessage(
"M3E set model_to_screen", model_to_screen=self.__get_model_to_screen_volume()
)


class Styles:
styles = {
const.STATE_DEFAULT: DefaultInteractorStyle,
Expand All @@ -634,6 +723,7 @@ class Styles:
const.SLICE_STATE_CROSS: CrossInteractorStyle,
const.STATE_NAVIGATION: NavigationInteractorStyle,
const.STATE_REGISTRATION: RegistrationInteractorStyle,
const.STATE_MASK_3D_EDIT: Mask3DEditorInteractorStyle,
}

@classmethod
Expand Down

0 comments on commit c24cd83

Please sign in to comment.