Transform2D
Represents a rigid 2D transform (SE(2)): a rotation plus a translation.
Parameters
| Field | Type | Description |
|---|---|---|
data | np.ndarray | list | tuple | Array-like input of shape (3, 3), 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 (3, 3) |
ValueError | data contains a non-finite value (NaN/Inf) |
ValueError | Last row isn't [0, 0, 1] (within transform_atol) |
ValueError | The 2x2 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 (3, 3) float32 matrix. Assigning a new value re-validates it the same way as construction. |
shape | tuple[int, ...] | Always (3, 3). |
ndim | int | Always 2. |
dtype | np.dtype | Always float32. |
size | int | Always 9. |
transform_atol | float | Class-level absolute tolerance (1e-4) used when validating the last row and the rotation block. |
Methods
| Method | Description |
|---|---|
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 Transform2D too. |
copy() | Returns a new Transform2D with an independent copy of the data. |
Transform2D.coerce(value) | Returns value unchanged if it's already a Transform2D; 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 Transform2D with element-equal data. False for anything else. |
len(t) | Always 3 (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 (X/Y axes rotated by atan2(data[1,0], data[0,0]) and translated by data[:2, 2]) alongside a labeled world-origin frame, so the rotation/translation are visible relative to identity. Passing label="..." attaches a floating text label at the posed frame's translation.
Example
python
"""Demonstrates the Telekinesis Transform2D datatype."""
import time
import numpy as np
from loguru import logger
import rerun as rr
from telekinesis import datatypes
def transform2d_example():
"""Demonstrate creation, access, visualization, update, NumPy interop, and serialization."""
# ======================= Create ============================================
theta = np.pi / 4
matrix = np.array(
[
[np.cos(theta), -np.sin(theta), 1.0],
[np.sin(theta), np.cos(theta), 2.0],
[0.0, 0.0, 1.0],
]
)
transform2d = datatypes.Transform2D(matrix)
logger.info(f"Original Transform2D: {transform2d}")
# ======================= Inspect ===========================================
data = transform2d.data
shape = transform2d.shape
size = transform2d.size
dtype = transform2d.dtype
ndim = transform2d.ndim
numpy_array = transform2d.to_numpy()
copy = transform2d.copy()
logger.info(f"shape={shape}, size={size}, ndim={ndim}, dtype={dtype}")
logger.info(f"Transform2D data:\n{data}")
logger.info(f"NumPy array:\n{numpy_array}")
logger.info(f"Copied Transform2D: {copy}")
# ======================= Visualize =========================================
rr.init("transform2d_example", spawn=True)
datatypes.visualize(
transform2d,
entity_path="/Transform2D/main",
label="My Transform2D",
)
# ======================= Update ============================================
updated_theta = np.pi / 2
updated_matrix = np.array(
[
[np.cos(updated_theta), -np.sin(updated_theta), 1.5],
[np.sin(updated_theta), np.cos(updated_theta), 2.5],
[0.0, 0.0, 1.0],
]
)
transform2d.data = updated_matrix
logger.info(f"Updated Transform2D: {transform2d}")
datatypes.visualize(
transform2d,
entity_path="/Transform2D/updated",
label="Updated Transform2D",
)
# ======================= Arithmetic ========================================
total = np.array([1, 1, 0]) + numpy_array
logger.info(f"Sum of Transform2D with numpy array: {total}")
# ======================= Serialize / Deserialize ===========================
start = time.perf_counter()
serialized = datatypes.serialize(transform2d)
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 Transform2D: {deserialized}")
logger.info(f"Round-trip successful: {transform2d == deserialized}")
logger.info(f"Serialization time: {serialization_ms:.3f} ms")
logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")
if __name__ == "__main__":
transform2d_example()
