Files
2026-07-13 13:05:14 +08:00

228 lines
7.6 KiB
Python

from __future__ import annotations
import itertools
import math
from fractions import Fraction
from typing import cast
import numpy as np
import pytest
import rerun as rr
import torch
from rerun.datatypes import (
Angle,
Float64ArrayLike,
Quaternion,
RotationAxisAngle,
Vec3D,
)
from .common_arrays import none_empty_or_value
from .test_matnxn import MAT_3X3_INPUT
from .test_vecnd import VEC_3D_INPUT
SCALE_3D_INPUT = [
# Uniform
4,
4.0,
Fraction(8, 2),
# ThreeD
*VEC_3D_INPUT,
]
ROTATION_3D_INPUT = [
# Quaternion
[1, 2, 3, 4],
[1.0, 2.0, 3.0, 4.0],
np.array([1, 2, 3, 4]),
torch.tensor([1, 2, 3, 4]),
Quaternion(xyzw=[1, 2, 3, 4]),
Quaternion(xyzw=[1.0, 2.0, 3.0, 4.0]),
Quaternion(xyzw=np.array([1, 2, 3, 4])),
# RotationAxisAngle
RotationAxisAngle([1, 2, 3], 4),
RotationAxisAngle([1.0, 2.0, 3.0], Angle(4)),
RotationAxisAngle(Vec3D([1, 2, 3]), Angle(4)),
RotationAxisAngle(np.array([1, 2, 3], dtype=np.uint8), Angle(rad=4)),
]
def test_angle() -> None:
five_rad = [
Angle(5),
Angle(5.0),
Angle(rad=5.0),
]
for a in five_rad:
assert a.radians == 5.0
five_deg = [
Angle(deg=5),
Angle(deg=5.0),
]
for a in five_deg:
assert a.radians == math.radians(5.0)
def test_transform3d() -> None:
rotation_axis_angle_original = [None, RotationAxisAngle([1, 2, 3], rr.Angle(deg=10))]
quaternion_arrays = [None, Quaternion(xyzw=[1, 2, 3, 4])]
scale_arrays = [None, 1.0, 1, [1.0, 2.0, 3.0], rr.Scale3D([1.0, 2.0, 3.0])]
relations = [
None,
rr.TransformRelation.ParentFromChild,
rr.TransformRelation.ChildFromParent,
"parentfromchild",
"childfromparent",
]
all_arrays = itertools.zip_longest(
[*VEC_3D_INPUT, None],
rotation_axis_angle_original,
quaternion_arrays,
scale_arrays,
[*MAT_3X3_INPUT, None],
relations,
)
for (
translation,
rotation_axis_angle,
quaternion,
scale,
mat3x3,
relation,
) in all_arrays:
translation = cast("rr.datatypes.Vec3DLike | None", translation)
quaternion = cast("rr.datatypes.QuaternionLike | None", quaternion)
scale = cast("rr.datatypes.Vec3DLike | None", scale)
mat3x3 = cast("rr.datatypes.Mat3x3Like | None", mat3x3)
relation = cast("rr.components.TransformRelationLike | None", relations)
print(
f"rr.Transform3D(\n"
f" translation={translation!r}\n"
f" rotation_axis_angle={rotation_axis_angle!r}\n"
f" quaternion={quaternion!r}\n"
f" scale={scale!r}\n"
f" mat3x3={mat3x3!r}\n"
f" relation={relation!r}\n"
f")",
)
arch = rr.Transform3D(
translation=translation,
rotation_axis_angle=rotation_axis_angle, # type: ignore[arg-type] # prior cast didn't work here
quaternion=quaternion,
scale=scale,
mat3x3=mat3x3,
relation=relation,
)
print(f"{arch}\n")
assert arch.scale == none_empty_or_value(scale, rr.components.Scale3DBatch(rr.components.Scale3D(scale)))
assert arch.rotation_axis_angle == none_empty_or_value(
rotation_axis_angle,
rr.components.RotationAxisAngleBatch(rr.components.RotationAxisAngle([1, 2, 3], Angle(deg=10))),
)
assert arch.quaternion == none_empty_or_value(
quaternion,
rr.components.RotationQuatBatch(rr.components.RotationQuat(xyzw=[1, 2, 3, 4])),
)
assert arch.translation == none_empty_or_value(
translation,
rr.components.Translation3DBatch(rr.components.Translation3D([1, 2, 3])),
)
assert arch.mat3x3 == none_empty_or_value(
mat3x3,
rr.components.TransformMat3x3Batch(rr.components.TransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]])),
)
assert arch.relation == rr.components.TransformRelationBatch(relation)
def test_transform_axes_3d() -> None:
axis_lengths = [1, 1.0]
for axis_length in axis_lengths:
axes = rr.TransformAxes3D(axis_length)
assert axes.axis_length == rr.components.AxisLengthBatch(rr.components.AxisLength(axis_length))
def test_transform_mat3x3_snippets() -> None:
np.testing.assert_array_equal(
rr.components.TransformMat3x3([1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns,
np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32),
)
np.testing.assert_array_equal(
rr.components.TransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns,
np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32),
)
np.testing.assert_array_equal(
rr.components.TransformMat3x3(columns=[1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns,
np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32),
)
np.testing.assert_array_equal(
rr.components.TransformMat3x3(columns=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns,
np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32),
)
def test_transform3d_rotation() -> None:
assert rr.Transform3D(rotation=RotationAxisAngle([1, 2, 3], rr.Angle(deg=10))) == rr.Transform3D(
rotation_axis_angle=RotationAxisAngle([1, 2, 3], rr.Angle(deg=10)),
)
assert rr.Transform3D(rotation=Quaternion(xyzw=[1, 2, 3, 4])) == rr.Transform3D(
quaternion=Quaternion(xyzw=[1, 2, 3, 4]),
)
TRANSLATION_CASES: list[tuple[Float64ArrayLike, Float64ArrayLike]] = [
([], []),
(np.ones((10, 3)), np.ones((10, 1, 3)).tolist()),
(np.zeros((5, 3)), np.zeros((5, 1, 3)).tolist()),
(np.array([[1.0, 2.0, 3.0]]), [[[1.0, 2.0, 3.0]]]),
(np.array([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]), [[[0.0, 0.0, 0.0]], [[1.0, 1.0, 1.0]]]),
(np.array([[5.5, -3.2, 0.0], [0.0, 10.7, -8.9]]), [[[5.5, -3.2, 0.0]], [[0.0, 10.7, -8.9]]]),
(
np.array([[-1.0, -2.0, -3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]),
[[[-1.0, -2.0, -3.0]], [[4.0, 5.0, 6.0]], [[7.0, 8.0, 9.0]]],
),
(np.zeros((0, 3)), []),
(np.array([[1000.0, 2000.0, 3000.0]]), [[[1000.0, 2000.0, 3000.0]]]),
]
def test_transform3d_translation_columns() -> None:
for input, expected in TRANSLATION_CASES:
data = [*rr.Transform3D.columns(translation=input)]
assert np.allclose(data[0].as_arrow_array().to_pylist(), np.asarray(expected))
MAT_3X3_CASES: list[tuple[Float64ArrayLike, Float64ArrayLike]] = [
([], []),
(np.ones((10, 3, 3)), np.ones((10, 1, 9)).tolist()),
(np.eye(3).reshape(1, 3, 3), [[[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]]]),
(
np.array([
[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
[[0, 1, 0], [0, 0, 1], [1, 0, 0]],
]),
[[[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]], [[0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]]],
),
(np.zeros((5, 3, 3)), np.zeros((5, 1, 9)).tolist()),
(np.array([[[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]]), [[[-1, -4, -7, -2, -5, -8, -3, -6, -9]]]),
(
np.array([[[1000, 2000, 3000], [4000, 5000, 6000], [7000, 8000, 9000]]]),
[[[1000, 4000, 7000, 2000, 5000, 8000, 3000, 6000, 9000]]],
),
(np.zeros((0, 3, 3)), []),
]
@pytest.mark.parametrize("matrix_input, matrix_expected", MAT_3X3_CASES)
def test_transform3d_mat3x3_columns(matrix_input: Float64ArrayLike, matrix_expected: Float64ArrayLike) -> None:
print(matrix_input)
data = [*rr.Transform3D.columns(mat3x3=matrix_input)]
assert np.allclose(data[0].as_arrow_array().to_pylist(), np.asarray(matrix_expected))