"""匹配器测试(规格 §6.2/§11.4:硬约束各一例、贪心排序、一一配对、cost ≥ 1 拒绝)。""" import pytest from stereo_ranger import Detection, GreedyMatcher, SamplePoint, StereoConfig, sample_point CFG = StereoConfig() MATCHER = GreedyMatcher() def _point(cx: float, cy: float, *, w: float = 60.0, h: float = 40.0, class_id: int = 0) -> SamplePoint: return sample_point( Detection(cx - w / 2.0, cy - h / 2.0, cx + w / 2.0, cy + h / 2.0, class_id=class_id) ) def test_class_mismatch_rejected(): assert MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 500.0, class_id=1)], CFG) == [] def test_dy_beyond_tau_rejected(): assert MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 502.1)], CFG) == [] def test_dy_within_tau_accepted_with_cost(): pairs = MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 501.9)], CFG) assert len(pairs) == 1 assert pairs[0].cost == pytest.approx(1.9 / CFG.tau_y_px) def test_height_ratio_beyond_tau_rejected(): assert MATCHER.match([_point(1000.0, 500.0, h=100.0)], [_point(1000.0, 500.0, h=140.0)], CFG) == [] def test_height_ratio_near_tau_accepted(): pairs = MATCHER.match([_point(1000.0, 500.0, h=120.0)], [_point(1000.0, 500.0, h=159.0)], CFG) assert len(pairs) == 1 def test_cost_at_one_rejected(): # dy = tau_y ⇒ cost = 1.0,严格 cost < 1 才接受(§6.2) assert MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 502.0)], CFG) == [] def test_combined_cost_at_one_rejected(): # dy/tau = 0.5 与 Δh/(tau_h·h_max) = 0.5 相加恰为 1.0 → 拒绝 assert MATCHER.match([_point(1000.0, 500.0, h=112.0)], [_point(1000.0, 501.0, h=128.0)], CFG) == [] def test_greedy_pairs_cheapest_first_and_one_to_one(): left = [_point(1000.0, 500.0), _point(1000.0, 501.0)] right = [_point(1000.0, 500.0), _point(1000.0, 501.0)] pairs = MATCHER.match(left, right, CFG) assert [(p.left_index, p.right_index) for p in pairs] == [(0, 0), (1, 1)] assert pairs[0].cost == pytest.approx(0.0) def test_one_left_claims_cheapest_right(): pairs = MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 500.0), _point(1000.0, 500.5)], CFG) assert [(p.left_index, p.right_index) for p in pairs] == [(0, 0)] def test_two_lefts_cannot_share_one_right(): left = [_point(1000.0, 500.0), _point(1000.0, 500.2)] right = [_point(1000.0, 500.0)] pairs = MATCHER.match(left, right, CFG) assert len(pairs) == 1 assert pairs[0].left_index == 0 def test_unmatched_right_is_not_paired(): pairs = MATCHER.match([_point(1000.0, 500.0)], [_point(1000.0, 500.0), _point(1400.0, 500.0)], CFG) assert len(pairs) == 1 assert pairs[0].right_index == 0 def test_non_finite_points_excluded(): bad = sample_point(Detection(float("nan"), 500.0, 1060.0, 540.0)) good = _point(1000.0, 500.0) assert MATCHER.match([bad], [good], CFG) == [] assert MATCHER.match([good], [bad], CFG) == [] def test_zero_height_box_rejected(): left = sample_point(Detection(970.0, 500.0, 1030.0, 500.0)) right = sample_point(Detection(970.0, 500.0, 1030.0, 500.0)) assert MATCHER.match([left], [right], CFG) == []