Download tests/test_accumulate.py from SmaugC137/splasher: direct link, hf CLI and curl.
- Browser
- Download file 7.2 kB
-
https://huggingface.co/spaces/SmaugC137/splasher/resolve/main/tests/test_accumulate.py
- Command line
-
hf download hf://spaces/SmaugC137/splasher/tests/test_accumulate.py
-
curl -L -o test_accumulate.py https://huggingface.co/spaces/SmaugC137/splasher/resolve/main/tests/test_accumulate.py
7.2 kB
| """Headless tests: poses, accumulation by registration, point-label de-accumulation.""" | |
| import numpy as np | |
| from splasher import ArraySource, ChannelKind, ChannelSpec | |
| from splasher.core.accumulate import accumulate, window_indices | |
| from splasher.core.poses import invert, pose_to_matrix, transform_points | |
| from splasher.core.source import Frame | |
| from splasher.core.target import PointTarget | |
| def test_pose_to_matrix_forms(): | |
| assert pose_to_matrix(np.eye(4)).shape == (4, 4) | |
| T = pose_to_matrix(np.array([1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0])) # identity quaternion | |
| assert np.allclose(T[:3, 3], [1, 2, 3]) | |
| assert np.allclose(T[:3, :3], np.eye(3)) | |
| def test_invert_and_transform(): | |
| T = np.eye(4) | |
| T[:3, 3] = [5.0, 0.0, 0.0] | |
| pts = np.array([[1.0, 1.0, 0.0]]) | |
| back = transform_points(transform_points(pts, T), invert(T)) | |
| assert np.allclose(back[:, :3], pts) | |
| def test_window_indices_clipped(): | |
| assert window_indices(0, 2, 10) == [0, 1, 2] | |
| assert window_indices(5, 2, 10) == [3, 4, 5, 6, 7] | |
| assert window_indices(9, 3, 10) == [6, 7, 8, 9] | |
| def _moving_source(n=5): | |
| specs = [ | |
| ChannelSpec("lidar", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3)), | |
| ChannelSpec("pose", ChannelKind.POSE, np.dtype("float32"), (4, 4)), | |
| ] | |
| frames = [] | |
| for t in range(n): | |
| # a point fixed in the WORLD at x=10; the ego moves forward by 1 per frame in x | |
| pose = np.eye(4, dtype=np.float32) | |
| pose[0, 3] = float(t) | |
| world_x = 10.0 | |
| pt_ego = np.array([[world_x - t, 0.0, 0.0]], dtype=np.float32) # seen from the ego | |
| frames.append({"lidar": pt_ego, "pose": pose}) | |
| return ArraySource(specs, frames) | |
| def test_accumulate_registers_to_reference(): | |
| src = _moving_source(5) | |
| ref = 2 | |
| acc = accumulate(src, ref, window_indices(ref, 2, 5), ["lidar"], "pose") | |
| # all points (a single fixed world point) must land at the same place | |
| # in the ref frame's frame of reference: x = 10 - ref = 8 | |
| assert len(acc.points) == 5 | |
| assert np.allclose(acc.points[:, 0], 8.0, atol=1e-5) | |
| assert set(acc.frame_id.tolist()) == {0, 1, 2, 3, 4} | |
| def test_accumulate_two_channels_chan_and_point_ids(): | |
| specs = [ | |
| ChannelSpec("a", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3)), | |
| ChannelSpec("b", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3)), | |
| ChannelSpec("pose", ChannelKind.POSE, np.dtype("float32"), (4, 4)), | |
| ] | |
| frame = { | |
| "a": np.zeros((3, 3), np.float32), # 3 points -> chan 0, point_id 0..2 | |
| "b": np.ones((2, 3), np.float32), # 2 points -> chan 1, point_id 3..4 (full concat) | |
| "pose": np.eye(4, dtype=np.float32), | |
| } | |
| src = ArraySource(specs, [frame]) | |
| acc = accumulate(src, 0, [0], ["a", "b"], "pose") | |
| assert acc.counts[0] == 5 | |
| assert acc.chan_id.tolist() == [0, 0, 0, 1, 1] | |
| assert acc.point_id.tolist() == [0, 1, 2, 3, 4] | |
| # visibility filter: keep only channel b (index 1) | |
| vis = acc.visible_mask([1]) | |
| assert vis.tolist() == [False, False, False, True, True] | |
| def test_point_features_maps_sibling_channels(): | |
| from splasher.core.source import ordered_features, point_features | |
| specs = [ | |
| ChannelSpec("velodyne_0", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3)), | |
| ChannelSpec("velodyne_0_intensity", ChannelKind.SCALAR, np.dtype("uint8"), (None,)), | |
| ChannelSpec("velodyne_0_range", ChannelKind.SCALAR, np.dtype("float32"), (None,)), | |
| ChannelSpec("pose", ChannelKind.POSE, np.dtype("float32"), (4, 4)), | |
| ] | |
| # every sibling scalar becomes a named feature; unrelated kinds (pose) never pair. | |
| assert point_features(specs, ["velodyne_0"]) == { | |
| "velodyne_0": {"intensity": "velodyne_0_intensity", "range": "velodyne_0_range"} | |
| } | |
| # a cloud with no sibling scalar is simply absent from the mapping | |
| assert point_features(specs, ["lidar_other"]) == {} | |
| # column order: intensity first (keeps col 3), then alphabetical | |
| assert ordered_features({"range", "intensity", "azimuth"}) == ["intensity", "azimuth", "range"] | |
| def test_accumulate_folds_features_into_trailing_columns(): | |
| specs = [ | |
| ChannelSpec("velodyne_0", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3)), | |
| ChannelSpec("velodyne_0_intensity", ChannelKind.SCALAR, np.dtype("uint8"), (None,)), | |
| ChannelSpec("velodyne_0_range", ChannelKind.SCALAR, np.dtype("float32"), (None,)), | |
| ] | |
| xyz = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], np.float32) | |
| inten = np.array([12, 200], np.uint8) # (N,) sidecars, KITTI/Tartan-style | |
| rng = np.array([7.5, 8.5], np.float32) | |
| src = ArraySource(specs, [{"velodyne_0": xyz, "velodyne_0_intensity": inten, | |
| "velodyne_0_range": rng}]) | |
| fmap = {"velodyne_0": {"intensity": "velodyne_0_intensity", "range": "velodyne_0_range"}} | |
| acc = accumulate(src, 0, [0], ["velodyne_0"], None, fmap, ["intensity", "range"]) | |
| assert acc.points.shape == (2, 5) # [x, y, z, intensity, range] | |
| np.testing.assert_allclose(acc.points[:, :3], xyz) | |
| np.testing.assert_allclose(acc.points[:, 3], [12.0, 200.0]) | |
| np.testing.assert_allclose(acc.points[:, 4], [7.5, 8.5]) | |
| def test_accumulate_missing_feature_is_nan(): | |
| specs = [ChannelSpec("lidar", ChannelKind.POINTCLOUD, np.dtype("float32"), (None, 3))] | |
| src = ArraySource(specs, [{"lidar": np.zeros((3, 3), np.float32)}]) | |
| acc = accumulate(src, 0, [0], ["lidar"], None, None, ["intensity"]) | |
| assert acc.points.shape == (3, 4) | |
| assert np.isnan(acc.points[:, 3]).all() | |
| # no feature layout requested → plain xyz | |
| assert accumulate(src, 0, [0], ["lidar"], None).points.shape == (3, 3) | |
| def test_accumulate_skips_frames_missing_pose(): | |
| # A real (apairo) source can yield frames lacking the pose channel after sync; | |
| # accumulate must skip those for registration instead of crashing. | |
| class _Src: | |
| def __len__(self): return 3 | |
| def channels(self): return [] | |
| def __getitem__(self, i): | |
| ch = {"lidar": np.full((2, 3), float(i), np.float32)} | |
| if i != 1: # frame 1 has no pose | |
| ch["pose"] = np.eye(4, dtype=np.float32) | |
| return Frame(channels=ch) | |
| acc = accumulate(_Src(), ref_idx=0, indices=[0, 1, 2], cloud_keys=["lidar"], pose_key="pose") | |
| assert set(acc.frame_id.tolist()) == {0, 2} # frame 1 skipped, no KeyError | |
| def test_decumul_via_apply_scatter(): | |
| # the brush hits a point coming from 3 frames -> labels spread per frame | |
| src = _moving_source(5) | |
| ref = 2 | |
| acc = accumulate(src, ref, window_indices(ref, 2, 5), ["lidar"], "pose") | |
| pt = PointTarget(ignore_id=0) | |
| frame_to_sel = {int(f): (acc.point_id[acc.frame_id == f], acc.counts[int(f)]) | |
| for f in np.unique(acc.frame_id)} | |
| assert pt.apply_scatter(ref, frame_to_sel, class_id=4) | |
| # each frame received its label on its point 0 | |
| for f in range(5): | |
| assert pt.labels(f).tolist() == [4] | |
| pt.undo(ref) # atomic undo under the reference frame: everything reverts | |
| for f in range(5): | |
| assert pt.labels(f).tolist() == [0] | |