OrientedBox3D
Represents a single rotated 3D bounding box: a center, a size, and an orientation quaternion.
Parameters
| Field | Type | Description |
|---|---|---|
data | np.ndarray | list | tuple | Box coordinates [x, y, z, w, h, l, qx, qy, qz, qw] (center, size, unit quaternion scalar-last), convertible to a (10,) float32 array via np.asarray. |
Raises
| Exception | Condition |
|---|---|
TypeError | data can't be converted into a uniform float32 array (e.g. ragged nested lists) |
ValueError | data is not rank-1 (e.g. a (N, 10) batch — use OrientedBoxes3D instead) |
ValueError | data does not have exactly 10 elements |
ValueError | Any element is non-finite (NaN/Inf) |
ValueError | w, h, or l (data[3:6]) is negative |
ValueError | The quaternion (data[6:10]) has zero norm |
ValueError | The quaternion's norm deviates from 1.0 by more than 1e-3 (quat_norm_atol) |
A zero-sized w/h/l is allowed but logs a warning (volume will be 0).
Attributes
| Attribute | Type | Description |
|---|---|---|
data | np.ndarray | Defensive copy of the underlying (10,) array. Reading it returns a copy; writing re-validates the same way as construction (including the quaternion unit-norm check). |
shape | tuple[int, ...] | Always (10,). |
ndim | int | Always 1. |
dtype | np.dtype | Always float32. |
size | int | Always 10. |
shape_spec | tuple[int, ...] | Class-level shape spec (10,). |
width | np.ndarray (scalar) | data[3], before rotation. |
height | np.ndarray (scalar) | data[4], before rotation. |
depth | np.ndarray (scalar) | data[5], before rotation. |
volume | np.ndarray (scalar) | width * height * depth (rotation-invariant). |
center | np.ndarray | [cx, cy, cz] — data[:3] directly (already the center; no offset computation, unlike axis-aligned boxes). |
theta | (not available) | Inherited property; accessing it raises ValueError (OrientedBox3D stores rotation as a quaternion, not an angle — read data[6:10] instead). |
quat_slice | slice | Class-level slice slice(6, 10) locating the quaternion on the last axis. |
quat_order | str | "xyzw" (scalar-last) — the project default; not overridden by this class. |
quat_norm_atol | float | 1e-3 — max allowed deviation of the quaternion's norm from 1.0. |
Methods
| Method | Description |
|---|---|
to_numpy(copy=True) | Returns the (10,) array. Pass copy=False for a zero-copy view — mutating it mutates the OrientedBox3D. |
copy() | Returns a new OrientedBox3D with an independent data buffer. |
translate(offset) | Returns a new OrientedBox3D with [x, y, z] shifted by offset ([dx, dy, dz]). Size and rotation are unchanged. |
scale(factor) | Returns a new OrientedBox3D scaled around its center. factor is a scalar (uniform) or a length-3 array-like ([fw, fh, fl], per-axis). Rotation is unchanged; the center stays fixed. |
rotate(rotation) | Returns a new OrientedBox3D composing rotation (a quaternion array-like of shape (4,), in quat_order) with the existing orientation as a world-frame delta (rotation applied on top of the existing rotation, via scipy.spatial.transform.Rotation). Size and center are unchanged. |
OrientedBox3D.to_xyzw(q) | Classmethod. Converts a quaternion from quat_order to scalar-last [x, y, z, w]. Since quat_order is already "xyzw" for this class, this is a no-op (returns q unchanged) — provided for API symmetry with subclasses that might use "wxyz". |
OrientedBox3D.from_xyzw(q) | Classmethod. Converts a scalar-last quaternion to quat_order. Also a no-op for this class, for the same reason. |
OrientedBox3D.coerce(value) | Classmethod. Returns value unchanged if it's already an OrientedBox3D; otherwise wraps an array-like into one (same validation as the constructor). Raises TypeError for any other input. |
Operators
| Operation | Behavior |
|---|---|
box == other | True only if other is also an OrientedBox3D with element-equal data. False for anything else. |
len(box) | Returns 10 — the length of the coordinate vector, not a box count (a single box is conceptually one item). |
box[i] | Always raises ValueError — OrientedBox3D has no single_cls, so it isn't indexable/iterable like a batch. Use .data[i] for raw coordinate access instead. |
for x in box | Raises ValueError immediately (falls back to box[0], which raises). |
np.asarray(box) | Returns a copy of data as an np.ndarray; NumPy functions accept an OrientedBox3D directly. |
hash(box) | Not supported — an OrientedBox3D can't be used as a dict key or set member. |
Visualization
datatypes.visualize(box, entity_path=...) logs it as a single rr.Boxes3D(centers=[data[:3]], sizes=[data[3:6]], quaternions=[data[6:10]]) — rr.Boxes3D natively supports rotation, so no separate angle label is auto-attached (unlike OrientedBox2D) — alongside a small 3D reference frame and an "origin" text label logged to {entity_path}/origin. Passing label="..." attaches a floating text label anchored at the box's center.
Example
python
"""Demonstrates the Telekinesis OrientedBox3D datatype."""
import time
from loguru import logger
import rerun as rr
from telekinesis import datatypes
def oriented_box3d_example():
"""Demonstrate creation, access, visualization, translate/scale/rotate, and serialization."""
# ======================= Create ============================================
box = datatypes.OrientedBox3D([0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0])
logger.info(f"Created OrientedBox3D: {box}")
# ======================= Visualize =========================================
rr.init("oriented_box3d_example", spawn=True)
datatypes.visualize(
box, entity_path="/OrientedBox3D/my_oriented_box3d", label="My Oriented Box3D"
)
# ======================= Inspect ===========================================
logger.info(f"shape={box.shape}, dtype={box.dtype}, ndim={box.ndim}")
logger.info(f"NumPy array: {box.to_numpy()}")
logger.info(
f"center={box.center}, volume={box.volume}, width={box.width}, "
f"height={box.height}, depth={box.depth}"
)
logger.info(f"Quaternion [qx, qy, qz, qw]: {box.data[6:]}")
# ======================= Update ============================================
box.data = [2.0, 2.0, 2.0, 3.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0]
logger.info(f"Updated OrientedBox3D: {box}")
datatypes.visualize(
box, entity_path="/OrientedBox3D/my_updated_oriented_box3d", label="Updated Oriented Box3D"
)
# ======================= Translate =========================================
translated_box = box.translate([1.0, 1.0, 1.0])
logger.info(f"Translated center: {translated_box.center} (was {box.center})")
datatypes.visualize(
translated_box,
entity_path="/OrientedBox3D/my_translated_oriented_box3d",
label="Translated Oriented Box3D",
)
# ======================= Scale =============================================
scaled_box = box.scale(1.5)
logger.info(
f"Scaled width, height, and depth: {scaled_box.width} x {scaled_box.height} x "
f"{scaled_box.depth} (was {box.width} x {box.height} x {box.depth})"
)
datatypes.visualize(
scaled_box, entity_path="/OrientedBox3D/my_scaled_oriented_box3d", label="Scaled Oriented Box3D"
)
# ======================= Rotate ============================================
delta_quaternion = [0.0, 0.0, 0.70710678, 0.70710678]
rotated_box = box.rotate(delta_quaternion)
logger.info(f"Rotated quaternion: {rotated_box.data[6:]} (was {box.data[6:]})")
rotated_box_display = rotated_box.translate([4.0, 0.0, 0.0])
datatypes.visualize(
rotated_box_display,
entity_path="/OrientedBox3D/my_rotated_oriented_box3d",
label="Rotated Oriented Box3D",
)
# ======================= Serialize / Deserialize ===========================
start = time.perf_counter()
serialized = datatypes.serialize(box)
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 OrientedBox3D: {deserialized}")
logger.info(f"Round-trip successful: {deserialized == box}")
logger.info(f"Serialization time: {serialization_ms:.3f} ms")
logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")
if __name__ == "__main__":
oriented_box3d_example()
