Skip to content

Transform2D

Represents a rigid 2D transform (SE(2)): a rotation plus a translation.

Parameters

FieldTypeDescription
datanp.ndarray | list | tupleArray-like input of shape (3, 3), converted to a contiguous float32 array.

Raises

ExceptionCondition
TypeErrordata can't be converted to float32 (e.g. non-numeric elements)
ValueErrordata is not rank-2, or its shape isn't (3, 3)
ValueErrordata contains a non-finite value (NaN/Inf)
ValueErrorLast row isn't [0, 0, 1] (within transform_atol)
ValueErrorThe 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

AttributeTypeDescription
datanp.ndarrayDefensive copy of the underlying (3, 3) float32 matrix. Assigning a new value re-validates it the same way as construction.
shapetuple[int, ...]Always (3, 3).
ndimintAlways 2.
dtypenp.dtypeAlways float32.
sizeintAlways 9.
transform_atolfloatClass-level absolute tolerance (1e-4) used when validating the last row and the rotation block.

Methods

MethodDescription
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

OperationBehavior
t == otherTrue 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()