twanghcmut/backup-foundation-physics / tests /test_datagen_discovery.py
twanghcmut's picture
download
raw
26.3 kB
"""Synthetic, GPU-free tests for fpgm.datagen.discovery.
No TAPNext++/SAM3/PointWorld h5 dependency anywhere here -- every test builds
its own small :class:`~fpgm.types.Track2D` and/or robot-seg array by hand, per
the repo's "synthetic, no GPU, no network" test convention.
"""
from __future__ import annotations
import numpy as np
import pytest
from fpgm.config import DatagenConfig
from fpgm.datagen.discovery import (
DiscoveredObject,
DiscoveryResult,
DiscoveryStats,
build_cluster_features,
build_densify_queries,
cluster_tracks,
compute_displacement,
compute_object_bbox_px,
compute_robot_overlap,
densify_undersampled_clusters,
discover_objects,
filter_robot_tracks,
filter_static_tracks,
merge_consistent_clusters,
read_track2d,
sample_dense_grid_in_bbox,
sample_grid_points,
write_track2d,
)
from fpgm.types import DataError, Track2D
def _make_track(
uv: np.ndarray, visible: np.ndarray | None = None, resolution=(200, 200)
) -> Track2D:
t, q, _ = uv.shape
if visible is None:
visible = np.ones((t, q), dtype=bool)
return Track2D(
point_id=np.arange(q, dtype=np.int32),
frames=np.arange(t, dtype=np.int32),
uv=uv.astype(np.float32),
visible=visible.astype(bool),
resolution=resolution,
)
def _linear_track(starts: np.ndarray, deltas: np.ndarray, n_frames: int = 5) -> Track2D:
"""Q points moving linearly from `starts` to `starts + deltas` over n_frames."""
uv = np.stack(
[starts + deltas * (t / (n_frames - 1)) for t in range(n_frames)], axis=0
)
return _make_track(uv)
# --------------------------------------------------------------------------- #
# Grid sampler
# --------------------------------------------------------------------------- #
class TestSampleGridPoints:
def test_count_and_bounds(self):
pts = sample_grid_points(width=1280, height=720, n_cols=40, n_rows=24)
assert pts.shape == (40 * 24, 2)
assert pts.dtype == np.float32
assert np.all(pts[:, 0] >= 0) and np.all(pts[:, 0] < 1280)
assert np.all(pts[:, 1] >= 0) and np.all(pts[:, 1] < 720)
def test_no_jitter_is_a_regular_lattice(self):
pts = sample_grid_points(width=100, height=100, n_cols=10, n_rows=10)
xs = sorted(set(np.round(pts[:, 0], 4)))
assert len(xs) == 10
def test_deterministic_under_a_seed(self):
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
a = sample_grid_points(200, 200, 10, 10, jitter_frac=0.5, rng=rng1)
b = sample_grid_points(200, 200, 10, 10, jitter_frac=0.5, rng=rng2)
np.testing.assert_array_equal(a, b)
def test_jitter_changes_output(self):
a = sample_grid_points(200, 200, 10, 10, jitter_frac=0.5, rng=np.random.default_rng(1))
b = sample_grid_points(200, 200, 10, 10, jitter_frac=0.0)
assert not np.array_equal(a, b)
def test_rejects_nonpositive_args(self):
with pytest.raises(ValueError):
sample_grid_points(0, 100, 5, 5)
with pytest.raises(ValueError):
sample_grid_points(100, 100, 0, 5)
# --------------------------------------------------------------------------- #
# Robot subtraction
# --------------------------------------------------------------------------- #
class TestRobotSubtraction:
def test_drops_track_at_query_frame_inside_robot(self):
# point 0 sits inside the robot box every frame; point 1 never does.
uv = np.zeros((3, 2, 2), dtype=np.float32)
uv[:, 0] = [10, 10]
uv[:, 1] = [150, 150]
track = _make_track(uv)
robot_seg = np.zeros((3, 200, 200), dtype=np.uint8)
robot_seg[:, 5:15, 5:15] = 1
keep = filter_robot_tracks(track, robot_seg, query_frame_idx=0)
assert keep.tolist() == [False, True]
def test_drops_track_by_majority_life_fraction_even_off_query_frame(self):
# point 0 is OUTSIDE the robot at the query frame (t=0) but spends 3/4
# of its visible life inside it afterwards -> still dropped.
uv = np.zeros((4, 1, 2), dtype=np.float32)
uv[0, 0] = [150, 150] # query frame: outside
uv[1, 0] = [10, 10]
uv[2, 0] = [10, 10]
uv[3, 0] = [10, 10]
track = _make_track(uv)
robot_seg = np.zeros((4, 200, 200), dtype=np.uint8)
robot_seg[:, 5:15, 5:15] = 1
at_query, life_fraction = compute_robot_overlap(track, robot_seg, query_frame_idx=0)
assert at_query.tolist() == [False]
assert life_fraction[0] == pytest.approx(0.75)
keep = filter_robot_tracks(track, robot_seg, query_frame_idx=0, life_fraction_gate=0.5)
assert keep.tolist() == [False]
def test_keeps_track_never_touching_robot(self):
uv = np.full((3, 1, 2), 150.0, dtype=np.float32)
track = _make_track(uv)
robot_seg = np.zeros((3, 200, 200), dtype=np.uint8)
robot_seg[:, 5:15, 5:15] = 1
keep = filter_robot_tracks(track, robot_seg, query_frame_idx=0)
assert keep.tolist() == [True]
# --------------------------------------------------------------------------- #
# Static rejection
# --------------------------------------------------------------------------- #
class TestStaticRejection:
def test_zero_displacement_is_dropped(self):
uv = np.full((5, 1, 2), 50.0, dtype=np.float32)
track = _make_track(uv)
_, magnitude = compute_displacement(track)
assert magnitude[0] == pytest.approx(0.0)
keep = filter_static_tracks(track, displacement_gate_px=6.0)
assert keep.tolist() == [False]
def test_large_displacement_is_kept(self):
track = _linear_track(np.array([[50.0, 50.0]]), np.array([[20.0, 0.0]]))
_, magnitude = compute_displacement(track)
assert magnitude[0] == pytest.approx(20.0, abs=1e-3)
keep = filter_static_tracks(track, displacement_gate_px=6.0)
assert keep.tolist() == [True]
def test_below_threshold_is_dropped(self):
track = _linear_track(np.array([[50.0, 50.0]]), np.array([[3.0, 0.0]]))
keep = filter_static_tracks(track, displacement_gate_px=6.0)
assert keep.tolist() == [False]
def test_never_visible_track_has_zero_displacement(self):
uv = np.zeros((5, 1, 2), dtype=np.float32)
visible = np.zeros((5, 1), dtype=bool)
track = _make_track(uv, visible=visible)
_, magnitude = compute_displacement(track)
assert magnitude[0] == 0.0
# --------------------------------------------------------------------------- #
# Clustering
# --------------------------------------------------------------------------- #
class TestClusterTracks:
def test_two_well_separated_groups_give_two_clusters(self):
rng = np.random.default_rng(0)
group_a = rng.normal(loc=[0.0, 0.0, 0.5, 0.0, 0.0], scale=0.003, size=(10, 5))
group_b = rng.normal(loc=[1.0, 1.0, 1.0, 0.05, 0.0], scale=0.003, size=(10, 5))
features = np.concatenate([group_a, group_b], axis=0)
labels = cluster_tracks(features, eps_m=0.05, min_samples=4)
non_noise = set(labels.tolist()) - {-1}
assert len(non_noise) == 2
assert (labels[:10] == labels[0]).all()
assert (labels[10:] == labels[10]).all()
assert labels[0] != labels[10]
def test_one_group_gives_one_cluster(self):
rng = np.random.default_rng(1)
features = rng.normal(loc=[0.2, 0.2, 0.6, 0.01, 0.0], scale=0.003, size=(12, 5))
labels = cluster_tracks(features, eps_m=0.05, min_samples=4)
non_noise = set(labels.tolist()) - {-1}
assert len(non_noise) == 1
def test_pure_noise_yields_no_clusters(self):
rng = np.random.default_rng(2)
features = rng.uniform(low=0.0, high=10.0, size=(30, 5))
labels = cluster_tracks(features, eps_m=0.05, min_samples=6)
non_noise = set(labels.tolist()) - {-1}
assert len(non_noise) == 0, "clustering hallucinated a cluster out of uniform noise"
def test_empty_features(self):
labels = cluster_tracks(np.zeros((0, 5)), eps_m=0.05, min_samples=4)
assert labels.shape == (0,)
class TestBuildClusterFeatures:
def test_shape_and_metric_conversion(self):
world_xyz = np.array([[0.0, 0.0, 1.0], [1.0, 1.0, 2.0]])
net_vector_px = np.array([[100.0, 0.0], [0.0, 50.0]])
z_cam = np.array([1.0, 2.0])
features = build_cluster_features(world_xyz, net_vector_px, z_cam, fx=500.0, fy=500.0)
assert features.shape == (2, 5)
# d_m = d_px * z / f
assert features[0, 3] == pytest.approx(100.0 * 1.0 / 500.0)
assert features[1, 4] == pytest.approx(50.0 * 2.0 / 500.0)
# --------------------------------------------------------------------------- #
# End-to-end discover_objects (still synthetic: no GPU, no h5)
# --------------------------------------------------------------------------- #
class TestDiscoverObjects:
def _two_object_scene(self):
n_frames = 5
# group A: 6 points near pixel (100, 100), moving +30px in x
a_start = np.array([[100.0 + 2 * i, 100.0 + 2 * i] for i in range(6)])
a_delta = np.tile([30.0, 0.0], (6, 1))
# group B: 6 points near pixel (500, 400), moving +30px in y
b_start = np.array([[500.0 + 2 * i, 400.0 + 2 * i] for i in range(6)])
b_delta = np.tile([0.0, 30.0], (6, 1))
starts = np.concatenate([a_start, b_start], axis=0)
deltas = np.concatenate([a_delta, b_delta], axis=0)
track = _linear_track(starts, deltas, n_frames=n_frames)
robot_seg = np.zeros((n_frames, 720, 1280), dtype=np.uint8)
rng_a, rng_b = np.random.default_rng(0), np.random.default_rng(1)
world_xyz = np.concatenate(
[
np.tile([0.0, 0.0, 0.5], (6, 1)) + rng_a.normal(scale=0.002, size=(6, 3)),
np.tile([1.0, 1.0, 1.0], (6, 1)) + rng_b.normal(scale=0.002, size=(6, 3)),
],
axis=0,
)
z_cam = world_xyz[:, 2].copy()
return track, robot_seg, world_xyz, z_cam
def test_two_clusters_found(self):
track, robot_seg, world_xyz, z_cam = self._two_object_scene()
cfg = DatagenConfig(dbscan_eps_m=0.05, dbscan_min_samples=4)
result = discover_objects(
track, robot_seg, query_frame_idx=0, world_xyz=world_xyz, z_cam=z_cam,
fx=500.0, fy=500.0, cfg=cfg,
)
assert result.stats.n_clusters == 2
assert len(result.objects) == 2
total_tracks = sum(obj.n_tracks for obj in result.objects)
assert total_tracks == 12
for obj in result.objects:
assert obj.n_tracks == 6
assert np.all(np.isfinite(obj.mean_3d_position))
assert obj.total_displacement_px == pytest.approx(30.0, abs=1.0)
def test_robot_filter_removes_a_whole_object(self):
track, robot_seg, world_xyz, z_cam = self._two_object_scene()
# Robot covers group A's whole neighbourhood for every frame.
robot_seg[:, 90:120, 90:120] = 1
cfg = DatagenConfig(dbscan_eps_m=0.05, dbscan_min_samples=4)
result = discover_objects(
track, robot_seg, query_frame_idx=0, world_xyz=world_xyz, z_cam=z_cam,
fx=500.0, fy=500.0, cfg=cfg,
)
assert result.stats.n_after_robot_filter == 6
assert result.stats.n_clusters == 1
assert result.objects[0].n_tracks == 6
def test_everything_filtered_gives_no_objects_not_a_crash(self):
n_frames = 3
uv = np.full((n_frames, 2, 2), 50.0, dtype=np.float32) # static
track = _make_track(uv)
robot_seg = np.zeros((n_frames, 200, 200), dtype=np.uint8)
world_xyz = np.zeros((2, 3))
z_cam = np.ones(2)
cfg = DatagenConfig()
result = discover_objects(
track, robot_seg, query_frame_idx=0, world_xyz=world_xyz, z_cam=z_cam,
fx=500.0, fy=500.0, cfg=cfg,
)
assert result.objects == ()
assert result.stats.n_clusters == 0
assert result.track.uv.shape[1] == 0
# --------------------------------------------------------------------------- #
# Dataclass validation
# --------------------------------------------------------------------------- #
class TestValidation:
def test_discovered_object_rejects_bad_query_uv_shape(self):
with pytest.raises(DataError):
DiscoveredObject(
cluster_id=0,
query_uv=np.zeros((3,), dtype=np.float32), # wrong: not (n, 2)
centroid_uv_per_frame=np.zeros((5, 2), dtype=np.float32),
n_tracks=3,
total_displacement_px=10.0,
mean_3d_position=np.zeros(3),
)
def test_discovery_result_rejects_misaligned_labels(self):
track = _make_track(np.zeros((2, 4, 2), dtype=np.float32))
with pytest.raises(DataError):
DiscoveryResult(
objects=(),
track=track,
cluster_labels=np.zeros((3,), dtype=np.int32), # should be (4,)
stats=None, # not touched before the shape check raises
seed_frame_idx=0,
)
# --------------------------------------------------------------------------- #
# Track2D (de)serialization
# --------------------------------------------------------------------------- #
class TestTrack2DRoundTrip:
def test_round_trip(self, tmp_path):
starts = np.array([[10.0, 20.0], [30.0, 40.0]])
deltas = np.array([[5.0, 0.0], [0.0, 5.0]])
track = _linear_track(starts, deltas)
path = tmp_path / "track.npz"
write_track2d(path, track)
loaded = read_track2d(path)
np.testing.assert_array_equal(loaded.point_id, track.point_id)
np.testing.assert_array_equal(loaded.frames, track.frames)
np.testing.assert_array_almost_equal(loaded.uv, track.uv)
np.testing.assert_array_equal(loaded.visible, track.visible)
assert loaded.resolution == track.resolution
# --------------------------------------------------------------------------- #
# Cluster merging (over-segmentation cleanup)
# --------------------------------------------------------------------------- #
class TestMergeConsistentClusters:
def _three_cluster_scene(self):
"""Two spatially-close, same-direction "drawer patch" groups (A, B) that
DBSCAN keeps apart at a tight eps, plus one far, differently-moving
"brick" group (C) that must never merge with anything.
"""
n_frames = 5
# Group A: 4 points near pixel (100, 100), net +10px in x.
a_start = np.array([[100.0 + 2 * i, 100.0 + 2 * i] for i in range(4)])
a_delta = np.tile([10.0, 0.0], (4, 1))
# Group B: 4 points near pixel (300, 100) -- close to A in *world*
# position (0.05 m, see world_xyz below) and moving almost exactly
# the same way (+10.5px x, +0.3px y).
b_start = np.array([[300.0 + 2 * i, 100.0 + 2 * i] for i in range(4)])
b_delta = np.tile([10.5, 0.3], (4, 1))
# Group C: 4 points far away, moving in a different direction.
c_start = np.array([[500.0 + 2 * i, 400.0 + 2 * i] for i in range(4)])
c_delta = np.tile([0.0, 10.0], (4, 1))
starts = np.concatenate([a_start, b_start, c_start], axis=0)
deltas = np.concatenate([a_delta, b_delta, c_delta], axis=0)
track = _linear_track(starts, deltas, n_frames=n_frames)
robot_seg = np.zeros((n_frames, 720, 1280), dtype=np.uint8)
rng = np.random.default_rng(0)
world_xyz = np.concatenate(
[
np.tile([0.0, 0.0, 0.5], (4, 1)) + rng.normal(scale=0.001, size=(4, 3)),
np.tile([0.05, 0.0, 0.5], (4, 1)) + rng.normal(scale=0.001, size=(4, 3)),
np.tile([1.0, 1.0, 1.0], (4, 1)) + rng.normal(scale=0.001, size=(4, 3)),
],
axis=0,
)
z_cam = np.ones(12) # z=1 everywhere -> dx_m = dx_px / fx exactly
return track, robot_seg, world_xyz, z_cam
def _discover(self, eps_m=0.03, min_samples=3):
track, robot_seg, world_xyz, z_cam = self._three_cluster_scene()
cfg = DatagenConfig(dbscan_eps_m=eps_m, dbscan_min_samples=min_samples)
return discover_objects(
track, robot_seg, query_frame_idx=0, world_xyz=world_xyz, z_cam=z_cam,
fx=500.0, fy=500.0, cfg=cfg,
)
def test_raw_dbscan_keeps_three_clusters(self):
# Sanity check on the fixture itself: at a tight eps, A and B (0.05 m
# apart) are NOT joined by DBSCAN alone -- merging is this function's job.
result = self._discover()
assert result.stats.n_clusters == 3
def test_close_same_direction_clusters_merge(self):
result = self._discover()
merged, groups = merge_consistent_clusters(result)
assert merged.stats.n_clusters == 2
sizes = sorted(o.n_tracks for o in merged.objects)
assert sizes == [4, 8]
# the far/differently-moving group must be a singleton in the merge map
singleton_groups = [g for g in groups if len(g) == 1]
assert len(singleton_groups) == 1
def test_merged_object_stats_are_track_weighted_means(self):
result = self._discover()
merged, groups = merge_consistent_clusters(result)
big = next(o for o in merged.objects if o.n_tracks == 8)
# exact weighted mean of A (n=4, x=0.0) and B (n=4, x=0.05): 0.025
assert big.mean_3d_position[0] == pytest.approx(0.025, abs=5e-3)
assert big.mean_displacement_m[0] > 0 # still pointing the same (+x) way
def test_no_spurious_merge_when_nothing_agrees(self):
# Re-run with min_samples=1 so every point is its own tiny cluster's
# worth of "signal", but drop A/B close enough only by chance -- use
# the original three well-separated-by-direction groups from
# TestDiscoverObjects to confirm merge is a no-op when it should be.
track, robot_seg, world_xyz, z_cam = TestDiscoverObjects()._two_object_scene()
cfg = DatagenConfig(dbscan_eps_m=0.05, dbscan_min_samples=4)
result = discover_objects(
track, robot_seg, query_frame_idx=0, world_xyz=world_xyz, z_cam=z_cam,
fx=500.0, fy=500.0, cfg=cfg,
)
assert result.stats.n_clusters == 2
merged, groups = merge_consistent_clusters(result)
assert merged.stats.n_clusters == 2
assert all(len(g) == 1 for g in groups)
def test_merge_is_a_noop_on_zero_or_one_cluster(self):
empty = DiscoveryResult(
objects=(),
track=_make_track(np.zeros((2, 0, 2), dtype=np.float32)),
cluster_labels=np.zeros((0,), dtype=np.int32),
stats=DiscoveryStats(0, 0, 0, 0, 0, 0),
seed_frame_idx=0,
)
merged, groups = merge_consistent_clusters(empty)
assert merged.stats.n_clusters == 0
assert groups == ()
# --------------------------------------------------------------------------- #
# Second-pass densification
# --------------------------------------------------------------------------- #
class TestComputeObjectBboxPx:
def test_pads_around_a_tight_cluster(self):
query_uv = np.array([[10.0, 10.0], [14.0, 14.0]])
x0, y0, x1, y1 = compute_object_bbox_px(query_uv, width=200, height=200)
# raw extent (4, 4) is floored to the 8px minimum, then padded 1x each side
assert x1 - x0 == pytest.approx(16.0, abs=1e-6)
assert y1 - y0 == pytest.approx(16.0, abs=1e-6)
assert x0 <= 10.0 and x1 >= 14.0
def test_clamped_to_frame_bounds(self):
query_uv = np.array([[1.0, 1.0], [2.0, 2.0]])
x0, y0, x1, y1 = compute_object_bbox_px(query_uv, width=50, height=50)
assert x0 >= 0.0 and y0 >= 0.0
assert x1 <= 49.0 and y1 <= 49.0
def test_rejects_empty_query_uv(self):
with pytest.raises(ValueError):
compute_object_bbox_px(np.zeros((0, 2)), width=100, height=100)
class TestSampleDenseGridInBbox:
def test_count_and_containment(self):
pts = sample_dense_grid_in_bbox((10.0, 10.0, 30.0, 30.0), n_cols=4, n_rows=4)
assert pts.shape == (16, 2)
assert np.all(pts[:, 0] >= 10.0) and np.all(pts[:, 0] <= 30.0)
assert np.all(pts[:, 1] >= 10.0) and np.all(pts[:, 1] <= 30.0)
class TestBuildDensifyQueries:
def _two_object_result(self, small_n=3, big_n=50):
small = DiscoveredObject(
cluster_id=0,
query_uv=np.array([[100.0, 100.0], [104.0, 100.0], [100.0, 104.0]][:small_n]),
centroid_uv_per_frame=np.zeros((5, 2), dtype=np.float32),
n_tracks=small_n,
total_displacement_px=15.0,
mean_3d_position=np.array([0.0, 0.0, 0.5]),
mean_displacement_m=np.array([0.02, 0.0]),
)
big = DiscoveredObject(
cluster_id=1,
query_uv=np.tile([500.0, 400.0], (big_n, 1)).astype(np.float32),
centroid_uv_per_frame=np.zeros((5, 2), dtype=np.float32),
n_tracks=big_n,
total_displacement_px=20.0,
mean_3d_position=np.array([1.0, 1.0, 1.0]),
mean_displacement_m=np.array([0.0, 0.02]),
)
return DiscoveryResult(
objects=(small, big),
track=_make_track(np.zeros((5, 0, 2), dtype=np.float32)),
cluster_labels=np.zeros((0,), dtype=np.int32),
stats=DiscoveryStats(0, 0, 0, 0, 2, 0),
seed_frame_idx=0,
)
def test_only_undersampled_cluster_gets_reseeded(self):
result = self._two_object_result()
uv, source = build_densify_queries(
result, width=1280, height=720, min_tracks=20, grid=(3, 3)
)
assert uv.shape == (9, 2)
assert set(source.tolist()) == {0}
def test_no_reseed_when_everything_is_dense_enough(self):
result = self._two_object_result(small_n=25)
uv, source = build_densify_queries(
result, width=1280, height=720, min_tracks=20, grid=(3, 3)
)
assert uv.shape == (0, 2)
assert source.shape == (0,)
class TestDensifyUndersampledClusters:
def _sparse_result(self):
query_uv = np.array([[100.0, 100.0], [104.0, 100.0]], dtype=np.float32)
track = _make_track(
np.stack([query_uv, query_uv + [5, 0], query_uv + [10, 0]], axis=0)
)
obj = DiscoveredObject(
cluster_id=0,
query_uv=query_uv,
centroid_uv_per_frame=track.uv.mean(axis=1),
n_tracks=2,
total_displacement_px=10.0,
mean_3d_position=np.array([0.0, 0.0, 0.5]),
mean_displacement_m=np.array([0.02, 0.0]),
)
result = DiscoveryResult(
objects=(obj,),
track=track,
cluster_labels=np.zeros(2, dtype=np.int32),
stats=DiscoveryStats(
n_query_points=100, n_after_robot_filter=50, n_after_static_filter=2,
n_with_3d_lift=2, n_clusters=1, n_noise=0,
),
seed_frame_idx=0,
)
return result
def test_surviving_reseed_points_grow_the_cluster(self):
result = self._sparse_result()
n_frames = result.track.uv.shape[0]
dense_uv = np.array([[102.0, 102.0], [98.0, 98.0], [101.0, 99.0]], dtype=np.float32)
dense_track = _make_track(
np.stack([dense_uv, dense_uv + [5, 0], dense_uv + [10, 0]], axis=0)
)
assert dense_track.uv.shape[0] == n_frames
dense_world_xyz = np.tile([0.0, 0.0, 0.5], (3, 1)) + np.array(
[[0.001, 0.0, 0.0], [-0.001, 0.0, 0.0], [0.0005, 0.0, 0.0]]
)
dense_z_cam = np.ones(3)
source_cluster_id = np.zeros(3, dtype=np.int32)
robot_seg = np.zeros((n_frames, 720, 1280), dtype=np.uint8)
merged = densify_undersampled_clusters(
result, dense_track, dense_world_xyz, dense_z_cam, source_cluster_id,
robot_seg, query_frame_idx=0,
)
assert merged.objects[0].n_tracks == 5 # 2 original + 3 new
assert merged.track.uv.shape[1] == 5
assert merged.cluster_labels.shape == (5,)
# exact weighted-mean identity: (2*0.0 + 3*mean(new x)) / 5
expected_x = (2 * 0.0 + 3 * np.mean([0.001, -0.001, 0.0005])) / 5
assert merged.objects[0].mean_3d_position[0] == pytest.approx(expected_x, abs=1e-6)
# funnel stats accumulate the second pass on top of the first
assert merged.stats.n_query_points == 100 + 3
assert merged.stats.n_after_robot_filter == 50 + 3
def test_points_failing_the_robot_gate_are_dropped_not_reassigned(self):
result = self._sparse_result()
n_frames = result.track.uv.shape[0]
dense_uv = np.array([[10.0, 10.0]], dtype=np.float32) # inside the robot box below
dense_track = _make_track(np.tile(dense_uv, (n_frames, 1, 1)))
dense_world_xyz = np.array([[0.0, 0.0, 0.5]])
dense_z_cam = np.array([1.0])
source_cluster_id = np.zeros(1, dtype=np.int32)
robot_seg = np.zeros((n_frames, 720, 1280), dtype=np.uint8)
robot_seg[:, 0:20, 0:20] = 1 # covers the reseed point's query frame
merged = densify_undersampled_clusters(
result, dense_track, dense_world_xyz, dense_z_cam, source_cluster_id,
robot_seg, query_frame_idx=0,
)
assert merged.objects[0].n_tracks == 2 # unchanged -- the reseed point was dropped
assert merged.stats.n_query_points == 100 + 1
assert merged.stats.n_after_robot_filter == 50 + 0
def test_empty_dense_track_is_a_noop(self):
result = self._sparse_result()
empty_dense = _make_track(np.zeros((result.track.uv.shape[0], 0, 2), dtype=np.float32))
merged = densify_undersampled_clusters(
result, empty_dense, np.zeros((0, 3)), np.zeros(0), np.zeros(0, dtype=np.int32),
np.zeros((3, 720, 1280), dtype=np.uint8), query_frame_idx=0,
)
assert merged is result

Xet Storage Details

Size:
26.3 kB
·
Xet hash:
b0a023b77f4102dc94a3ab09ca4cc3d9590cbc23f8bb0b4138482e8af5cbd089

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.