Transform3D
Represents a rigid 3D transform (SE(3)): a rotation plus a translation.
Parameters
| Field | Type | Description |
|---|---|---|
data | np.ndarray | list | tuple | Array-like input of shape (4, 4), converted to a contiguous float32 array. |
Raises
| Exception | Condition |
|---|---|
TypeError | data can't be converted to float32 (e.g. non-numeric elements) |
ValueError | data is not rank-2, or its shape isn't (4, 4) |
ValueError | data contains a non-finite value (NaN/Inf) |
ValueError | Last row isn't [0, 0, 0, 1] (within transform_atol) |
ValueError | The 3x3 rotation block isn't orthonormal (within transform_atol), or its determinant isn't ~1.0 (a determinant of -1 is a reflection, not a rotation) |
Attributes
| Attribute | Type | Description |
|---|---|---|
data | np.ndarray | Defensive copy of the underlying (4, 4) float32 matrix. Assigning a new value re-validates it the same way as construction. |
shape | tuple[int, ...] | Always (4, 4). |
ndim | int | Always 2. |
dtype | np.dtype | Always float32. |
size | int | Always 16. |
transform_atol | float | Class-level absolute tolerance (1e-4) used when validating the last row and the rotation block. |
quat_order | str | Class-level, "wxyz" — the quaternion component order produced/consumed by to_pose/from_pose when rot_type=RotationType.QUATERNION, and by the inherited to_xyzw/from_xyzw helpers. |
Methods
| Method | Description |
|---|---|
to_pose(rot_type=RotationType.QUATERNION) | Converts to a flat pose vector [x, y, z, ...]: shape (7,) with a scalar-first quaternion [qw, qx, qy, qz] for RotationType.QUATERNION (default), or shape (6,) with Euler angles (degrees/radians) or a rotation vector otherwise. rot_type accepts a RotationType member or its string value. Raises TypeError/ValueError for an unrecognized rot_type. |
Transform3D.from_pose(pose, rot_type=RotationType.QUATERNION) | Classmethod; the inverse of to_pose. Builds a Transform3D from a flat pose vector, shape (7,) for QUATERNION or (6,) for the other rotation types. Raises TypeError if pose isn't an np.ndarray/list or rot_type is invalid; ValueError on a shape/rot_type mismatch. |
inverse() | Returns the inverse homogeneous transform as a plain (4, 4) np.ndarray (not a Transform3D): [[Rᵗ, -Rᵗ·t], [0, 1]]. |
compute_transformation_error(other_transform) | Computes a scalar SE(3) pose error (combined rotation + translation magnitude, via the matrix logarithm) between this transform and other_transform. Raises ValueError if other_transform is not a Transform3D. |
Transform3D.to_xyzw(q) | Inherited from QuaternionValidationMixin. Converts a quaternion array from quat_order ("wxyz") to scalar-last [x, y, z, w], e.g. before handing it to scipy. |
Transform3D.from_xyzw(q) | Inherited from QuaternionValidationMixin. Converts a quaternion array from scalar-last [x, y, z, w] to quat_order ("wxyz"), e.g. after reading scipy's Rotation.as_quat(). |
to_numpy(copy=True) | Returns the matrix as np.ndarray. Pass copy=False for a reference to the internal array instead — faster, but mutating it mutates the Transform3D too. |
copy() | Returns a new Transform3D with an independent copy of the data. |
Transform3D.coerce(value) | Returns value unchanged if it's already a Transform3D; otherwise wraps an array-like into one (running full validation). Raises TypeError for any other input. |
Operators
| Operation | Behavior |
|---|---|
t == other | True only if other is also a Transform3D with element-equal data. False for anything else. |
len(t) | Always 4 (length of the first axis). |
np.asarray(t) | Works directly via __array__. Always returns a copy; use to_numpy(copy=False) for a zero-copy view. |
hash(t) | Not supported — mutable via the data setter. |
Visualization
datatypes.visualize(transform, entity_path=...) logs the transform's posed frame (rotated basis axes from data[:3, :3], translated by data[:3, 3]) alongside a labeled world-origin frame, so the rotation/translation are visible relative to identity. Per-axis X/Y/Z labels are omitted on both frames; only the world-origin frame gets an "origin" text label, distinguishing it from the posed one. Passing label="..." attaches a floating text label at the posed frame's translation.
Example
python
"""Demonstrates the Telekinesis Transform3D datatype."""
import time
import numpy as np
from loguru import logger
import rerun as rr
from telekinesis import datatypes
def transform3d_example():
"""Demonstrate creation, access, update, inverse, pose conversion, and serialization."""
# ======================= Create ============================================
matrix = np.array(
[
[0.5000000, -0.5000000, 0.7071068, 1],
[0.8535534, 0.1464466, -0.5000000, 2],
[0.1464466, 0.8535534, 0.5000000, 3],
[0, 0, 0, 1],
]
)
transform3d = datatypes.Transform3D(matrix)
logger.info(f"Created Transform3D: {transform3d}")
# ======================= Inspect ===========================================
data = transform3d.data
shape = transform3d.shape
size = transform3d.size
dtype = transform3d.dtype
ndim = transform3d.ndim
numpy_array = transform3d.to_numpy()
transform3d_copy = transform3d.copy()
logger.info(f"shape={shape}, size={size}, ndim={ndim}, dtype={dtype}")
logger.info(f"Transform3D data:\n{data}")
logger.info(f"NumPy array:\n{numpy_array}")
logger.info(f"Copied Transform3D: {transform3d_copy}")
# ======================= Visualize =========================================
rr.init("transform3d_example", spawn=True)
datatypes.visualize(transform3d, entity_path="/Transform3D/main", label="transform3d")
# ======================= Update ============================================
new_matrix = np.array(
[
[0.5000000, -0.5000000, 0.7071068, 1.5],
[0.8535534, 0.1464466, -0.5000000, 2.5],
[0.1464466, 0.8535534, 0.5000000, 3],
[0, 0, 0, 1],
]
)
transform3d.data = new_matrix
logger.info(f"Updated Transform3D: {transform3d}")
datatypes.visualize(
transform3d, entity_path="/Transform3D/updated", label="updated_transform3d"
)
# ======================= Inverse ===========================================
inverse_matrix = transform3d.inverse()
inverse = datatypes.Transform3D(inverse_matrix)
logger.info(f"Inverse Transform3D: {inverse_matrix}")
datatypes.visualize(inverse, entity_path="/Transform3D/inverse", label="inverse_transform3d")
# ======================= Pose Conversion ===================================
pose_deg = transform3d.to_pose(rot_type="deg")
pose_rotvec = transform3d.to_pose(rot_type="rotvec")
pose_rad = transform3d.to_pose(rot_type="rad")
pose_quat = transform3d.to_pose(rot_type="quat")
logger.info(f"Pose (deg): {pose_deg}")
logger.info(f"Pose (rotvec): {pose_rotvec}")
logger.info(f"Pose (rad): {pose_rad}")
logger.info(f"Pose (quat): {pose_quat}")
new_transform3d = datatypes.Transform3D.from_pose(pose_quat)
error = transform3d.compute_transformation_error(new_transform3d)
logger.info(f"New Transform3D from pose: {new_transform3d}")
logger.info(f"Transformation error: {error}")
# ======================= NumPy Interop =====================================
sum_result = np.array([1, 1, 1, 0]) + numpy_array
logger.info(f"Sum of Transform3D with numpy array: {sum_result}")
# ======================= Serialize / Deserialize ===========================
start = time.perf_counter()
serialized = datatypes.serialize(transform3d)
serialization_ms = (time.perf_counter() - start) * 1000
start = time.perf_counter()
deserialized = datatypes.deserialize(serialized)["param_0"]
deserialization_ms = (time.perf_counter() - start) * 1000
logger.info(f"Deserialized Transform3D: {deserialized}")
logger.info(f"Round-trip successful: {deserialized == transform3d}")
logger.info(f"Serialization time: {serialization_ms:.3f} ms")
logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")
if __name__ == "__main__":
transform3d_example()
