tests/test_geometry.py
import torch
from mcss.geometry import (
generate_rays,
intersect_aabb,
look_at,
make_intrinsics,
make_voxel_centers,
project_world,
transform_cameras,
transform_directions,
transform_points,
)
from mcss.types import Cameras
def test_identity_camera_center_ray_points_forward() -> None:
intrinsics = torch.eye(3).view(1, 1, 3, 3)
c2w = torch.eye(4).view(1, 1, 4, 4)
cameras = Cameras(intrinsics, c2w, (1, 1))
origins, directions = generate_rays(cameras)
torch.testing.assert_close(origins[0, 0, 0, 0], torch.zeros(3))
torch.testing.assert_close(directions[0, 0, 0, 0], torch.tensor([0.0, 0.0, 1.0]))
def test_projection_roundtrip_for_optical_axis() -> None:
intrinsics = torch.tensor([[[[100.0, 0.0, 5.0], [0.0, 100.0, 4.0], [0.0, 0.0, 1.0]]]])
cameras = Cameras(intrinsics, torch.eye(4).view(1, 1, 4, 4), (9, 11))
points = torch.tensor([[[[0.0, 0.0, 2.0]]]])
pixels, depth, valid = project_world(points, cameras)
torch.testing.assert_close(pixels[0, 0, 0], torch.tensor([5.0, 4.0]))
torch.testing.assert_close(depth[0, 0, 0], torch.tensor(2.0))
assert valid.item()
def test_ray_aabb_intersection_marks_hits_and_misses() -> None:
origins = torch.tensor([[0.0, 0.0, -3.0], [3.0, 0.0, -3.0]])
directions = torch.tensor([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]])
bounds = torch.tensor([[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]])
near, far, hit = intersect_aabb(origins, directions, bounds)
torch.testing.assert_close(near[0], torch.tensor(2.0))
torch.testing.assert_close(far[0], torch.tensor(4.0))
assert hit.tolist() == [True, False]
def test_camera_and_voxel_helpers_have_expected_conventions() -> None:
pose = look_at(torch.tensor([0.0, 0.0, -3.0]), torch.zeros(3))
intrinsics = make_intrinsics((9, 11), 60.0)
centers = make_voxel_centers(torch.tensor([[[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]]]), 2)
torch.testing.assert_close(pose[:3, 2], torch.tensor([0.0, 0.0, 1.0]))
torch.testing.assert_close(intrinsics[0, 2], torch.tensor(5.0))
assert centers.shape == (1, 2, 2, 2, 3)
torch.testing.assert_close(centers.amin(dim=(1, 2, 3))[0], torch.full((3,), -0.5))
torch.testing.assert_close(centers.amax(dim=(1, 2, 3))[0], torch.full((3,), 0.5))
def test_rigid_transform_rebases_cameras_points_and_directions() -> None:
rotation = torch.tensor([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
anchor = torch.eye(4)
anchor[:3, :3] = rotation
anchor[:3, 3] = torch.tensor([10.0, 0.0, 0.0])
second = anchor.clone()
second[:3, 3] = torch.tensor([10.0, 1.0, 0.0])
cameras = Cameras(torch.eye(3).repeat(2, 1, 1), torch.stack((anchor, second)), (1, 1))
world_to_anchor = torch.linalg.inv(anchor)
local_cameras = transform_cameras(cameras, world_to_anchor)
local_points = transform_points(torch.tensor([[10.0, 2.0, 3.0]]), world_to_anchor)
local_directions = transform_directions(torch.tensor([[0.0, 1.0, 0.0]]), world_to_anchor)
torch.testing.assert_close(local_cameras.c2w[0], torch.eye(4))
torch.testing.assert_close(local_cameras.c2w[1, :3, 3], torch.tensor([1.0, 0.0, 0.0]))
torch.testing.assert_close(local_points, torch.tensor([[2.0, 0.0, 3.0]]))
torch.testing.assert_close(local_directions, torch.tensor([[1.0, 0.0, 0.0]]))
def test_voxel_centers_accept_anisotropic_dhw_resolution() -> None:
bounds = torch.tensor([[[-2.0, -3.0, 0.0], [2.0, 3.0, 8.0]]])
centers = make_voxel_centers(bounds, (4, 3, 2))
assert centers.shape == (1, 4, 3, 2, 3)
torch.testing.assert_close(centers[0, 0, 0, 0], torch.tensor([-1.0, -2.0, 1.0]))
torch.testing.assert_close(centers[0, -1, -1, -1], torch.tensor([1.0, 2.0, 7.0]))