Created
September 9, 2020 04:32
-
-
Save mhogg/4863cf0b91e2f85a56d52b8a6981e325 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import vtkmodules.all as vtk | |
from vtkmodules.util import numpy_support as nps | |
from vtkmodules.vtkCommonCore import vtkCommand | |
class InteractorStyleDrawPolygon(vtk.vtkInteractorStyle): | |
""" | |
InteractorStyleDrawPolygon is a re-write of the vtk class of the same name in python. | |
The python class vtk.vtkInteractorStyleDrawPolygon is not a full wrap of the underlying | |
++ class. For example, vtkInteractorStyleDrawPolygon doesn't have a method for | |
'GetPolygonPoints' in Python. It's in the header, source and documentation, but not python. | |
Reports are this is because it returns a vtkVector2i, a template based generic class, which | |
is difficult to wrap. | |
References: | |
- http://vtk.1045678.n5.nabble.com/Missing-method-for-vtkInteractorStyleDrawPolygon-td5747100.html | |
- https://gitlab.kitware.com/vtk/vtk/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.h | |
- https://gitlab.kitware.com/vtk/vtk/-/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.cxx | |
""" | |
class vtkInternal(object): | |
""" | |
Class internal to InteractorStyleDrawPolygon to help with drawing the polygon | |
""" | |
def __init__(self, parent=None): | |
super().__init__() | |
self._points = [] | |
@property | |
def points(self): | |
return np.array(self._points) | |
def AddPoint(self, x, y): | |
self._points.append([x,y]) | |
def GetPoint(self, index): | |
if index < 0 or index > len(self._points)-1: return | |
return np.array(self._points[index]) | |
def GetNumberOfPoints(self): | |
return len(self._points) | |
def Clear(self): | |
self._points = [] | |
def DrawPixels(self, StartPos, EndPos, pixels, size): | |
# C++ args: const vtkVector2i& StartPos, const vtkVector2i& EndPos, unsigned char* pixels, const int* size | |
# NOTE: ^ operator = bitwise exclusive OR. Same in C++ and Python | |
length = int(round(np.linalg.norm(StartPos-EndPos))) | |
if length == 0: return | |
x1, y1 = StartPos | |
x2, y2 = EndPos | |
x = [int(round(v)) for v in np.linspace(x1,x2,length)] | |
y = [int(round(v)) for v in np.linspace(y1,y2,length)] | |
indices = np.array([row * size[0] + col for col,row in zip(x,y)]) | |
pixels[indices] = 255 ^ pixels[indices] | |
def __init__(self, parent=None, interactor=None, renderer=None): | |
self.parent = parent | |
self.interactor = interactor | |
self.renderer = renderer | |
self.AddObserver("MouseMoveEvent", self.OnMouseMove) | |
self.AddObserver("LeftButtonPressEvent", self.OnLeftButtonDown) | |
self.AddObserver("LeftButtonReleaseEvent", self.OnLeftButtonUp) | |
self.setup() | |
def setup(self): | |
self.Internal = self.vtkInternal() | |
self.StartPosition = np.zeros(2, dtype=np.int32) | |
self.EndPosition = np.zeros(2, dtype=np.int32) | |
self.Moving = False | |
self.DrawPolygonPixels = True | |
self.PixelArray = vtk.vtkUnsignedCharArray() | |
def DrawPolygon(self): | |
""" | |
Draw the polygon defined by the mouse move points | |
""" | |
tmpPixelArray = vtk.vtkUnsignedCharArray() | |
tmpPixelArray.DeepCopy(self.PixelArray) | |
pixels = nps.vtk_to_numpy(tmpPixelArray) | |
renWin = self.interactor.GetRenderWindow() | |
size = renWin.GetSize() | |
# Draw each line segment | |
for i in range(self.Internal.GetNumberOfPoints()-1): | |
a = self.Internal.GetPoint(i) | |
b = self.Internal.GetPoint(i+1) | |
self.Internal.DrawPixels(a, b, pixels, size) | |
# Draw a line from the end to the start | |
if (self.Internal.GetNumberOfPoints() >= 3): | |
start = self.Internal.GetPoint(0) | |
end = self.Internal.GetPoint(self.Internal.GetNumberOfPoints()-1) | |
self.Internal.DrawPixels(start, end, pixels, size) | |
# NOTE: In SetPixelData, must add 0 as 7th variable (in C++ it has a default | |
# value of 0, but not in Python) | |
# NOTE: SetPixelData takes a long time to run, particularly if the screen is | |
# maximised, which increases the number of pixels | |
renWin.SetPixelData(0, 0, size[0]-1, size[1]-1, pixels.flatten(), 0, 0) | |
renWin.Frame() | |
def DrawPolygonPixelsOn(self): | |
self.DrawPolygonPixels = True | |
def DrawPolygonPixelsOff(self): | |
self.DrawPolygonPixels = False | |
def GetPolygonPoints(self): | |
""" | |
Return the polygon points as a numpy array | |
NOTE: This function is not available in wrapped Python vtk | |
""" | |
return self.Internal.points | |
def OnLeftButtonDown(self, obj, event): | |
""" | |
Left mouse button press event | |
""" | |
if self.interactor is None: return | |
self.Moving = True | |
renWin = self.interactor.GetRenderWindow() | |
eventPos = self.interactor.GetEventPosition() | |
self.StartPosition[0], self.StartPosition[1] = eventPos[0], eventPos[1] | |
self.EndPosition = self.StartPosition | |
self.PixelArray.Initialize() | |
self.PixelArray.SetNumberOfComponents(3) | |
size = renWin.GetSize() | |
self.PixelArray.SetNumberOfTuples(size[0] * size[1]) | |
self.pixels = None | |
# Note: In GetPixelData, must add 0 as 7th variable (in C++ it has a default | |
# value of 0, but not in Python) | |
renWin.GetPixelData(0, 0, size[0]-1, size[1]-1, 1, self.PixelArray, 0) | |
self.Internal.Clear() | |
self.Internal.AddPoint(self.StartPosition[0], self.StartPosition[1]) | |
self.InvokeEvent(vtk.vtkCommand.StartInteractionEvent) | |
# Call parent function | |
#super().OnLeftButtonDown() | |
def OnLeftButtonUp(self, obj, event): | |
""" | |
Left mouse button release event | |
When LMB is released, a EndPickEvent and EndInteractionEvent are emitted | |
NOTE: This is different to the C++ class, which emits a SelectionChangedEvent | |
instead of an EndPickEvent | |
""" | |
if self.interactor is None or not self.Moving: return | |
if self.DrawPolygonPixels: | |
renWin = self.interactor.GetRenderWindow() | |
size = renWin.GetSize() | |
pixels = nps.vtk_to_numpy(self.PixelArray) | |
renWin.SetPixelData(0, 0, size[0]-1, size[1]-1, pixels.flatten(), 0, 0) | |
renWin.Frame() | |
self.Moving = False | |
self.InvokeEvent(vtkCommand.SelectionChangedEvent) | |
self.InvokeEvent(vtkCommand.EndPickEvent) | |
self.InvokeEvent(vtkCommand.EndInteractionEvent) | |
# Call parent function | |
#super().OnLeftButtonUp() | |
def OnMouseMove(self, obj, event): | |
""" | |
On mouse move event | |
""" | |
if self.interactor is None or not self.Moving: return | |
# Get lastest mouse position | |
eventPos = self.interactor.GetEventPosition() | |
self.EndPosition[0], self.EndPosition[1] = eventPos[0], eventPos[1] | |
size = self.interactor.GetRenderWindow().GetSize() | |
if self.EndPosition[0] > size[0]-1: self.EndPosition[0] = size[0]-1 | |
if self.EndPosition[0] < 0: self.EndPosition[0] = 0 | |
if self.EndPosition[1] > size[1]-1: self.EndPosition[1] = size[1]-1 | |
if self.EndPosition[1] < 0: self.EndPosition[1] = 0 | |
# Update the polygon to include the lastest mouse position | |
lastPoint = self.Internal.GetPoint(self.Internal.GetNumberOfPoints()-1) | |
newPoint = self.EndPosition | |
if np.linalg.norm(lastPoint-newPoint) > 10: | |
self.Internal.AddPoint(*newPoint) | |
if self.DrawPolygonPixels: | |
self.DrawPolygon() | |
# Call parent function | |
#super().OnMouseMove() | |
def SetDrawPolygonPixels(self, drawPolygonPixels): | |
self.DrawPolygonPixels = drawPolygonPixels | |
def __str__(self): | |
""" | |
Replaces PrintSelf in C++ class | |
""" | |
indent = 2*' ' | |
s = super().__str__().rstrip()+'\n' | |
s += f'{indent}Moving : {self.Moving}\n' | |
s += f'{indent}DrawPolygonPixels: {self.DrawPolygonPixels}\n' | |
s += f'{indent}StartPosition: {self.StartPosition[0]}, {self.StartPosition[1]}\n' | |
s += f'{indent}EndPosition: {self.EndPosition[0]}, {self.EndPosition[1]}\n' | |
return s |
I try to use the vtkInteractorStyle as the interactor ,but get the error
AttributeError: 'vtkmodules.vtkRenderingCore.vtkInteractorStyle' object has no attribute 'GetEventPosition'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hi, thank you for your sharing of your code. Can you show how to use this style? I use it by
interactorStyle1 = InteractorStyleDrawPolygon()
but it will not work. I see in the init part, there are parent=None, interactor=None, renderer=None, how to define them?
before use it, show define an interactor ?