Quaternion
Represents a unit quaternion rotation.
Parameters
| Field | Type | Description |
|---|---|---|
data | np.ndarray | list | tuple | Array-like input of shape (4,): [qx, qy, qz, qw], 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-1, or its shape isn't (4,) |
ValueError | data contains a non-finite value (NaN/Inf) |
ValueError | data is the zero quaternion (norm 0.0) |
ValueError | data's norm deviates from 1.0 by more than quat_norm_atol (1e-3) |
Attributes
| Attribute | Type | Description |
|---|---|---|
data | np.ndarray | Defensive copy of the underlying (4,) float32 array. Assigning a new value re-validates it (shape, finiteness, unit norm) the same way as construction. |
shape | tuple[int, ...] | Always (4,). |
ndim | int | Always 1. |
dtype | np.dtype | Always float32. |
size | int | Always 4. |
quat_slice | slice | Class-level, slice(0, 4) — the last-axis range checked for unit norm (the whole vector). |
quat_norm_atol | float | Class-level, 1e-3 — max allowed abs(norm - 1). |
quat_order | str | Class-level, "xyzw" (the mixin default, not overridden) — this class's native scalar-last component order. |
Methods
| Method | Description |
|---|---|
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
| Operation | Behavior |
|---|---|
q == other | True 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()
