Python - VTK 限制对象旋转问题(四元数)

Python - VTK limit rotation of object issue (Quaternion)

我正在尝试通过交互限制框小部件的旋转,以使用户能够围绕上轴旋转框。

因此我尝试了向"InteractionEvent"添加观察者的方法,并使用回调函数来反转绕x轴和z轴的旋转。

实施后我遇到了两个问题:

-) 对象变换的变化 - 在 window 中没有明显效果(也许我只是在处理 3D 对象的副本?)

-) 我所做的计算改变了四元数 - 但不是假设的那样。 有人提到只是否定 x 和 z 的符号并保持相同的程度。 我希望看到修改后的旋转结果 x 和 z 值变为零......我假设我一般误解了四元数,或者我在 vtk 函数调用中犯了一个错误

Old Orientation                                   [11.79730158  0.87192947  0.29194216  0.39307604]
Will rotate object with the following quaternion: [11.79730158 -0.87192947 -0.         -0.39307604]
New Orientation                                   [ 3.47624906 -0.17562846  0.98392544  0.03233235]
Different Access: Orientation                     [ 3.47624906 -0.17562846  0.98392544  0.03233235]

结果由以下代码产生:

import numpy as np
import vtk
import math
from math import atan2, sqrt, pi
from vtk.util import numpy_support

##########################################################################
def transform(T, points):
    assert(points.shape[0] >= 3)
    assert(T.shape == (4, 4))
    points_hom = np.vstack((points[0:3], np.ones((1, points.shape[1]))))
    points_hom = np.matmul(T, points_hom)
    points_hom = points_hom[0:3, :] / points_hom[3, :]
    points = np.vstack((points_hom, points[3:, :]))
    return points


def LeftButtonPressEvent(obj, event):
    t = vtk.vtkTransform()
    obj.GetTransform(t)
    q = np.zeros(4)
    t.GetOrientationWXYZ(q)
    print(f"Old Orientation {q}")
    q[2] = 0
    q[1:] = -q[1:]
    #angle = 2.0 * atan2(np.linalg.norm(q[1:]), q[0]/180.0*pi)*180.0/pi
    print(f"Will rotate object with the following quaternion: {q}")
    t.RotateWXYZ(q[0], q[1:])
    obj.GetProp3D().SetUserTransform( t )
    t.GetOrientationWXYZ(q)
    print(f"New Orientation {q}")
    obj_transform = obj.GetProp3D().GetUserTransform()
    obj_transform.GetOrientationWXYZ(q)
    print(f"Different Access: Orientation {q}\n\n\n")


##########################################################################
def init_vtk_pointcloud(pts, point_size=2.0, points_per_row=True):
    if not points_per_row:
        pts = pts.T
    assert (pts.shape[1] == 3 or
            pts.shape[1] == 4 or
            pts.shape[1] == 6)
    if pts.shape[1] == 3:
        color = np.ones((pts.shape[0], 3), np.uint8) * 255
    elif pts.shape[1] == 4:
        color = (pts[:, 3] * np.ones((3, 1)) * 255).astype(np.uint8).T
    elif pts.shape[1] == 6 or pts.shape[1] == 7:
        color = pts[:, 3:6]
    else:
        assert False, "Dimension of points is not 3 (without color), " \
                      "4 (with intensity) or 6/7 (with color and optionally intensity)"
    vtk_point_cloud = VtkPointCloud(point_size=point_size)
    vtk_point_cloud.set_points(pts[:, :3], color)
    return vtk_point_cloud


##########################################################################
def init_rendering(axes_scaling, background_color, camera, point_size, other_actors,
                   points_per_row, pts, window_name, window_size):
    vtk_point_cloud = init_vtk_pointcloud(pts, point_size, points_per_row)

    # Renderer
    renderer = vtk.vtkRenderer()
    renderer.SetActiveCamera(camera)
    renderer.SetBackground(*background_color)

    # Render Window
    render_window = vtk.vtkRenderWindow()
    render_window.AddRenderer(renderer)
    render_window.SetWindowName(window_name)
    render_window.SetSize(*window_size)

    # Add actor
    renderer.AddActor(vtk_point_cloud.vtkActor)
    return render_window, vtk_point_cloud


##########################################################################
def show_pointcloud(pts, window_name='Debug', points_per_row=True, background_color=[.0, .3, .4], camera=None,
                    axes_scaling=1.0, point_size=1.0, other_actors=[], window_size=(1280, 720), finalize=True):
    renderWindow, vtk_point_cloud = init_rendering(axes_scaling, background_color, camera, point_size,
                                                   other_actors, points_per_row, pts, window_name, window_size)

    #  Interactor
    renderWindowInteractor = vtk.vtkRenderWindowInteractor()
    renderWindowInteractor.SetInteractorStyle(
        PointSizeInteractorStyle(renderWindowInteractor, vtk_point_cloud))
    renderWindowInteractor.SetRenderWindow(renderWindow)

    boxActor = vtk.vtkActor()
    boxWidget = vtk.vtkBoxWidget()
    boxWidget.SetInteractor(renderWindowInteractor)#renderer interactor
    boxWidget.SetProp3D(boxActor)#actor here
    boxWidget.SetPlaceFactor(1.0)
    boxWidget.PlaceWidget()

    #ToDo: first interaction does not work except user scrolls beforehand... why?

    boxWidget.On()
    boxWidget.AddObserver("InteractionEvent", LeftButtonPressEvent)
    renderWindow.Render()
    renderWindowInteractor.Start()

    if finalize:
        renderWindow.Finalize()


##########################################################################
def create_cylinder(radius=1.0, height=1.0, pos=[0.0, 0.0, 0.0], approx_samples=1000):
    perimeter = 2 * math.pi * radius
    base_area = radius**2 * math.pi
    area = perimeter * height + 2*base_area

    # points per areal unit
    pts_per_au = approx_samples / area

    no_base_square_samples = int(pts_per_au*4*radius**2)
    base = np.random.rand(no_base_square_samples, 2) * 2 * radius - radius
    radii = np.linalg.norm(base, axis=1)
    mask = radii < radius
    base = base[mask, :]

    max_dim = max(perimeter, height)
    surface_samples = int(pts_per_au * (max_dim**2))

    surface = np.random.rand(surface_samples, 2) * max_dim
    mask = (surface[:, 0] < perimeter) & (surface[:, 1] < height)
    surface = surface[mask, :]
    surface = np.vstack((radius*np.sin(surface[:, 0]/radius), radius*np.cos(surface[:, 0]/radius),
                         surface[:, 1] - height/2.0)).T
    top = np.hstack((base, np.ones((base.shape[0], 1)) * height/2.0))
    bottom = top * (1.0, 1.0, -1.0)
    pts = np.vstack((top, bottom, surface)) + pos
    return pts



##########################################################################
class VtkPointCloud:

    def add_point(self, point):
        if self.vtkPoints.GetNumberOfPoints() < self.maxNumPoints:
            pointId = self.vtkPoints.InsertNextPoint(point[:])
            self.vtkDepth.InsertNextValue(point[2])
            self.vtkCells.InsertNextCell(1)
            self.vtkCells.InsertCellPoint(pointId)
        else:
            r = np.random.randint(0, self.maxNumPoints)
            self.vtkPoints.SetPoint(r, point[:])
        self.vtkCells.Modified()
        self.vtkPoints.Modified()
        self.vtkDepth.Modified()

    ##########################################################################
    def __init__(self, zMin=-10.0, zMax=10.0, maxNumPoints=1e6, point_size=2):
        self.maxNumPoints = maxNumPoints
        self.vtkPolyData = vtk.vtkPolyData()
        self.clear_points()
        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputData(self.vtkPolyData)
        mapper.SetColorModeToDefault()
        mapper.SetScalarRange(zMin, zMax)
        mapper.SetScalarVisibility(1)
        self.vtkActor = vtk.vtkActor()
        self.vtkActor.SetMapper(mapper)
        self.vtkActor.GetProperty().SetPointSize(point_size)

    ##########################################################################
    def set_points(self, points, color=None):
        if color is None:
            if points.shape[1] == 3:
                color = np.ones((points.shape[0], 3), np.uint8) * 255
            elif points.shape[1] == 4:
                color = value2hsvcolormap(points[:, 3])
                # color = (points[:, 3] * np.ones((3, 1)) * 255).astype(np.uint8).T
            elif points.shape[1] == 6 or points.shape[1] == 7:
                color = points[:, 3:6]

        color = np.ascontiguousarray(color)
        points = np.ascontiguousarray(points[:, :3])

        vtk_cell_idc = np.zeros((points.shape[0] * 2,), dtype=np.int64)
        vtk_cell_idc[0::2] = 1
        vtk_cell_idc[1::2] = np.arange(points.shape[0])

        vtk_point_data = numpy_support.numpy_to_vtk(num_array=points, deep=True, array_type=vtk.VTK_FLOAT)
        self.vtkPoints.SetData(vtk_point_data)

        if color is not None:
            vtk_color_data = numpy_support.numpy_to_vtk(num_array=color, deep=True, array_type=vtk.VTK_UNSIGNED_CHAR)
            vtk_color_data.SetName('Color')
            self.vtkPolyData.GetPointData().SetScalars(vtk_color_data)
            self.vtkPolyData.GetPointData().SetActiveScalars('Color')

        vtk_cell_data = numpy_support.numpy_to_vtk(num_array=vtk_cell_idc, deep=True, array_type=vtk.VTK_ID_TYPE)
        self.vtkCells.SetCells(points.shape[0], vtk_cell_data)


        self.vtkPoints.Modified()
        self.vtkCells.Modified()
        self.vtkPolyData.Modified()

    ##########################################################################
    def clear_points(self):
        self.vtkPoints = vtk.vtkPoints()
        self.vtkCells = vtk.vtkCellArray()
        self.vtkPolyData.SetPoints(self.vtkPoints)
        self.vtkPolyData.SetVerts(self.vtkCells)

##########################################################################
class PointSizeInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):

    def __init__(self, parent, point_cloud=None, max_pt_size=20):
        self.AddObserver("MouseWheelForwardEvent", self.mouse_wheel_forward_event)
        self.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward_event)
        self.AddObserver("LeftButtonPressEvent", self.LeftButtonPressEvent)
        self._parent = parent
        self._point_cloud = point_cloud
        self._max_pt_size = max_pt_size

    ##########################################################################
    def mouse_wheel_forward_event(self, obj, event):
        if self._parent.GetAltKey():
            new_point_size = self._point_cloud_actor.GetProperty().GetPointSize() + 1
            new_point_size = self._max_pt_size if new_point_size > self._max_pt_size else new_point_size
            self._point_cloud.vtkActor.GetProperty().SetPointSize(new_point_size)
            self._parent.GetRenderWindow().Render()
        else:
            self.OnMouseWheelForward()
        return

    def LeftButtonPressEvent(self, obj, event):
        print("Global interactor")
        self.OnLeftButtonDown()

    ##########################################################################
    def mouse_wheel_backward_event(self, obj, event):
        if self._parent.GetAltKey():
            new_point_size = self._point_cloud_actor.GetProperty().GetPointSize() - 1
            new_point_size = 1 if  new_point_size < 1 else new_point_size
            self._point_cloud.vtkActor.GetProperty().SetPointSize(new_point_size)
            self._parent.GetRenderWindow().Render()
        else:
            self.OnMouseWheelBackward()
        return

##########################################################################
class PointCloudInteractorStyle(PointSizeInteractorStyle):

    def __init__(self, parent, image_actor, point_cloud, caption_renderer, pc_gen=None, max_pt_size=20):
        # self.AddObserver("KeyPressEvent", self.key_press_event)
        self._parent = parent
        self._image_actor = image_actor
        self._pc_gen = pc_gen
        self._caption_renderer = caption_renderer

        self._caption_renderer.RemoveAllViewProps()

        point_cloud.set_points(pts)
        super().__init__(parent, point_cloud)

#########################################################################
def main():
    point_cloud_cube = create_cylinder()
    # point_cloud_cube = point_cloud_cube + np.random.rand(*point_cloud_cube.shape) * 0.02
    R, _ = np.linalg.qr(np.random.rand(3, 3))
    #point_cloud_cube = transformation.rotate(R, point_cloud_cube.T).T
    point_cloud = np.vstack((point_cloud_cube))

    color = np.ones((point_cloud.shape[0], 3), dtype=point_cloud.dtype) * 255

    camera = vtk.vtkCamera()
    camera.SetViewUp(0, -1, 0)
    camera.SetPosition(2, -1, -4)
    camera.SetFocalPoint(0, 0, 0)
    point_cloud = np.hstack((point_cloud[:, :3], color))

    show_pointcloud(point_cloud, 'Test', camera=camera, axes_scaling=0.5)


if __name__ == '__main__':
    main()

我能够解决它 - 因此我不得不使用 oldTransform.PostMultiply() 推迟某些矩阵操作,正如您在这里看到的...

class ModBoxWidget(vtk.vtkBoxWidget):

    def __init__(self, fixed_Y_pos=None):
        self.AddObserver("InteractionEvent", self.horizontal_move_event)
        #super().__init__(self)
        self.fixed_Y_pos = fixed_Y_pos

    def horizontal_move_event(self, obj, event):
        oldTransform = vtk.vtkTransform()
        obj.GetTransform(oldTransform)
        q = np.zeros(4)
        oldTransform.GetOrientationWXYZ(q)
        oldTransform.PostMultiply()

        newTransform = vtk.vtkTransform()
        pos = np.zeros(3)
        polyData = vtk.vtkPolyData()
        obj.GetPolyData(polyData)
        num_points = polyData.GetNumberOfPoints()
        coords = np.zeros((num_points, 3))
        for i in range(num_points):
            polyData.GetPoint(i, coords[i])

        pos[0] = coords[14][0]
        pos[1] = coords[14][1]
        pos[2] = coords[14][2]

        newTransform.PostMultiply()
        newTransform.Identity()
        newTransform.Translate(-pos[0], -pos[1], -pos[2])
        print(f"{-q[0]*q[1]:3.2f} {-q[0]*q[1]:3.2f} {-q[0]*q[2]:3.2f}")
        newTransform.RotateX(-q[0]*q[1])
        newTransform.RotateY(-q[0]*q[2])

        newTransform.Translate(pos[0], pos[1], self.fixed_Y_pos or obj.GetProp3D().GetScale()[1]/2.0)
        oldTransform.Concatenate(newTransform)
        obj.SetTransform(oldTransform)
        obj.SetHandleSize(0.001)