3D Transformations

This page shows how to apply translations, rotations, and scales using the Transform class from pchandler.geometry.transforms. A Transform is a 4x4 affine operator that can be chained and applied succinctly to pchandler.core.PointCloudData.

By using the transform class, writing code can now follow the mathematical form y = Tx and not having to handle matrix transpositions.

Key points

  • Import with: from pchandler.geometry.transforms import Transform

  • Build transforms via helpers: - Transform.from_translation(vector) - Transform.from_rotation(R3x3) - Transform.from_scale(scalar_or_vec3) - Transform.from_affine(A4x4) - Transform.generate(rotation=..., translation=..., scale=...)

  • Chain transforms with @= (matrix multiply in-place).

  • Apply to point clouds with T @ pcd to get a transformed copy.

Translate and scale

import numpy as np
from pchandler.core import PointCloudData
from pchandler.geometry.transforms import Transform

pcd = PointCloudData(np.random.rand(100, 3), numerical_optimization_shift=None)

# Build a translation transform
t = np.array([10.0, -2.0, 3.5], dtype=float)
T_translate = Transform.from_translation(t)

# Build a uniform scale transform
s = 2.0
T_scale = Transform.from_scale(s)

# Option A: apply individually
translated = T_translate @ pcd
scaled = T_scale @ pcd

# Option B: chain and apply once (scale, then translate)
T = Transform.from_scale(s)
T @= Transform.from_translation(t)
moved = T @ pcd

Rigid transform with a rotation matrix

import numpy as np
from pchandler.core import PointCloudData
from pchandler.geometry.transforms import Transform

def rot_z(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[ c, -s, 0.0],
                     [ s,  c, 0.0],
                     [ 0.0, 0.0, 1.0]], dtype=float)

pcd = PointCloudData(np.random.rand(100, 3), numerical_optimization_shift=None)

R = rot_z(np.deg2rad(30.0))
t = np.array([1.0, 0.0, 0.0], dtype=float)

# Build a single affine transform with rotation and translation
T_rt = Transform.generate(rotation=R, translation=t)

transformed = T_rt @ pcd  # returns a transformed copy

Using a 4x4 affine matrix directly

import numpy as np
from pchandler.core import PointCloudData
from pchandler.geometry.transforms import Transform

pcd = PointCloudData(np.random.rand(200, 3), numerical_optimization_shift=None)

# Example 4x4 matrix (row-major): rotation + translation
A = np.eye(4, dtype=float)
A[:3, :3] = np.array([[0.0, -1.0, 0.0],
                      [1.0,  0.0, 0.0],
                      [0.0,  0.0, 1.0]], dtype=float)
A[:3, 3] = np.array([2.0, 0.0, 0.0], dtype=float)

T = Transform.from_affine(A)
pcd_affine = T @ pcd

Chaining multiple transforms

import numpy as np
from pchandler.core import PointCloudData
from pchandler.geometry.transforms import Transform

pcd = PointCloudData(np.random.rand(50, 3), numerical_optimization_shift=None)

R = np.eye(3)
t1 = np.array([0.5, 0.0, 0.0])
t2 = np.array([0.0, 1.0, 0.0])

T = Transform.from_rotation(R)
T @= Transform.from_translation(t1)   # first translate by t1
T @= Transform.from_translation(t2)   # then translate by t2

pcd_moved = T @ pcd

Merging transformed clouds

import numpy as np
from pchandler.core import PointCloudData
from pchandler.geometry.transforms import Transform

a = PointCloudData(np.random.rand(200, 3), numerical_optimization_shift=None)

# Shift 'a' by +5 along X using a transform
T = Transform.from_translation(np.array([5.0, 0.0, 0.0], dtype=float))
b = T @ a

merged = PointCloudData.merge(a, b)

Notes on ordering

  • T @= U composes transforms so that when later applied as (T @ pcd), the effect of U happens after the transforms already in T.

  • Transform.generate(rotation=R, translation=t, scale=s) constructs a single affine with those components combined.