Skip to content

Quaternion

Represents a unit quaternion rotation.

Parameters

FieldTypeDescription
datanp.ndarray | list | tupleArray-like input of shape (4,): [qx, qy, qz, qw], converted to a contiguous float32 array.

Raises

ExceptionCondition
TypeErrordata can't be converted to float32 (e.g. non-numeric elements)
ValueErrordata is not rank-1, or its shape isn't (4,)
ValueErrordata contains a non-finite value (NaN/Inf)
ValueErrordata is the zero quaternion (norm 0.0)
ValueErrordata's norm deviates from 1.0 by more than quat_norm_atol (1e-3)

Attributes

AttributeTypeDescription
datanp.ndarrayDefensive copy of the underlying (4,) float32 array. Assigning a new value re-validates it (shape, finiteness, unit norm) the same way as construction.
shapetuple[int, ...]Always (4,).
ndimintAlways 1.
dtypenp.dtypeAlways float32.
sizeintAlways 4.
quat_slicesliceClass-level, slice(0, 4) — the last-axis range checked for unit norm (the whole vector).
quat_norm_atolfloatClass-level, 1e-3 — max allowed abs(norm - 1).
quat_orderstrClass-level, "xyzw" (the mixin default, not overridden) — this class's native scalar-last component order.

Methods

MethodDescription
Quaternion.to_xyzw(q)Inherited from QuaternionValidationMixin. Converts a quaternion array from quat_order to scalar-last [x, y, z, w]. A no-op here since quat_order is already "xyzw"; provided for API consistency with classes that use a different order (e.g. Pose3D, Transform3D).
Quaternion.from_xyzw(q)Inherited from QuaternionValidationMixin. Converts a quaternion array from scalar-last [x, y, z, w] to quat_order. Also a no-op here for the same reason.
to_numpy(copy=True)Returns the quaternion as np.ndarray. Pass copy=False for a reference to the internal array instead — faster, but mutating it mutates the Quaternion too.
copy()Returns a new Quaternion with an independent copy of the data.
Quaternion.coerce(value)Returns value unchanged if it's already a Quaternion; otherwise wraps an array-like into one (running full validation, including the unit-norm check). Raises TypeError for any other input.

Operators

OperationBehavior
q == otherTrue only if other is also a Quaternion with element-equal data. False for anything else, including a Vector4D with the same values.
len(q)Always 4.
np.asarray(q)Works directly via __array__. Always returns a copy; use to_numpy(copy=False) for a zero-copy view.
hash(q)Not supported — mutable via the data setter.

Visualization

datatypes.visualize(quaternion, entity_path=...) logs the quaternion's rotated basis frame (X/Y/Z arrows) alongside a labeled world-origin frame — both coincide at the origin, since a bare quaternion carries no translation. Per-axis labels are omitted; only the world-origin frame is labeled "origin". Passing label="..." attaches a floating text label at that same origin point.

Example

python
"""Demonstrates the Telekinesis Quaternion datatype."""

import time

import numpy as np
from loguru import logger
import rerun as rr
from scipy.spatial.transform import Rotation

from telekinesis import datatypes


def quaternion_example():
    """Demonstrate creation, access, visualization, update, NumPy/SciPy interop, and serialization."""

    # ======================= Create ============================================
    quaternion = datatypes.Quaternion([0.4619398, 0.1913417, 0.4619398, 0.7325378])
    logger.info(f"Original Quaternion: {quaternion}")

    # ======================= Inspect ===========================================
    data = quaternion.data
    shape = quaternion.shape
    size = quaternion.size
    dtype = quaternion.dtype
    ndim = quaternion.ndim
    numpy_array = quaternion.to_numpy()
    copy = quaternion.copy()

    logger.info(f"data={data}, shape={shape}, size={size}, ndim={ndim}, dtype={dtype}")
    logger.info(f"NumPy array: {numpy_array}")
    logger.info(f"Copied Quaternion: {copy}")

    # ======================= Visualize =========================================
    rr.init("quaternion_example", spawn=True)
    datatypes.visualize(quaternion, entity_path="/Quaternion", label="My Quaternion")

    # ======================= Update ============================================
    quaternion.data = [0.0, 0.0, 0.7071068, 0.7071068]
    logger.info(f"Updated Quaternion: {quaternion}")
    datatypes.visualize(
        quaternion, entity_path="/Quaternion/updated", label="Updated Quaternion"
    )

    # ======================= NumPy Interop =====================================
    norm = np.linalg.norm(quaternion.data)
    rotation_matrix = Rotation.from_quat(quaternion.data).as_matrix()

    logger.info(f"Quaternion norm (np.linalg.norm): {norm}")
    logger.info(f"Equivalent rotation matrix (scipy Rotation):\n{rotation_matrix}")

    # ======================= Serialize / Deserialize ===========================
    start = time.perf_counter()
    serialized = datatypes.serialize(quaternion)
    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 Quaternion: {deserialized}")
    logger.info(f"Round-trip successful: {quaternion == deserialized}")
    logger.info(f"Serialization time: {serialization_ms:.3f} ms")
    logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")


if __name__ == "__main__":
    quaternion_example()