| """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])) |
| 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): |
| |
| 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) |
| 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") |
| |
| |
| 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), |
| "b": np.ones((2, 3), np.float32), |
| "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] |
| |
| 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)), |
| ] |
| |
| assert point_features(specs, ["velodyne_0"]) == { |
| "velodyne_0": {"intensity": "velodyne_0_intensity", "range": "velodyne_0_range"} |
| } |
| |
| assert point_features(specs, ["lidar_other"]) == {} |
| |
| 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) |
| 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) |
| 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() |
| |
| assert accumulate(src, 0, [0], ["lidar"], None).points.shape == (3, 3) |
|
|
|
|
| def test_accumulate_skips_frames_missing_pose(): |
| |
| |
| 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: |
| 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} |
|
|
|
|
| def test_decumul_via_apply_scatter(): |
| |
| 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) |
| |
| for f in range(5): |
| assert pt.labels(f).tolist() == [4] |
| pt.undo(ref) |
| for f in range(5): |
| assert pt.labels(f).tolist() == [0] |
|
|