You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MAI/tests/test_target_handoff.py

151 lines
3.9 KiB
Python

from pathlib import Path
import sys
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from target_handoff import (
HandoffDecision,
compute_fast_handoff_hits,
evaluate_stale_lock,
pick_guidance_override_box,
should_override_guidance,
update_guidance_override_latch,
)
def test_evaluate_stale_lock_requires_fresh_candidate_and_weak_anchor():
decision = evaluate_stale_lock(
confirmed=True,
have_fresh_candidate=True,
target_has_fresh_update=False,
candidate_dist_px=420.0,
pred_diag_px=70.0,
miss_streak=8,
klt_valid=False,
klt_quality=0.18,
klt_iou=0.12,
candidate_motion_ok=True,
target_motion_ok=False,
stale_break_dist_diag=3.5,
stale_break_min_miss=2,
stale_break_klt_quality=0.35,
stale_break_klt_iou=0.25,
)
assert decision.stale_lock_active is True
assert "target_not_fresh" in decision.reasons
assert "weak_klt_quality" in decision.reasons
def test_evaluate_stale_lock_rejects_close_candidate_when_anchor_is_healthy():
decision = evaluate_stale_lock(
confirmed=True,
have_fresh_candidate=True,
target_has_fresh_update=True,
candidate_dist_px=90.0,
pred_diag_px=70.0,
miss_streak=0,
klt_valid=True,
klt_quality=0.92,
klt_iou=0.71,
candidate_motion_ok=True,
target_motion_ok=True,
stale_break_dist_diag=3.5,
stale_break_min_miss=2,
stale_break_klt_quality=0.35,
stale_break_klt_iou=0.25,
)
assert decision.stale_lock_active is False
assert decision.reasons == []
def test_compute_fast_handoff_hits_reduces_confirmation_under_stale_lock():
hits = compute_fast_handoff_hits(
stale_lock_active=True,
motion_switch_mode=False,
approach_extra_hits=2,
fast_maneuver_extra_hits=2,
default_switch_hits=4,
fast_handoff_hits=2,
motion_switch_hits=6,
)
assert hits == 2
def test_handoff_decision_repr_is_stable_for_logging():
decision = HandoffDecision(
stale_lock_active=True,
allow_fast_handoff=True,
required_switch_hits=2,
disable_prediction_hold=True,
reasons=["target_not_fresh", "weak_klt_quality"],
)
assert decision.reason_text() == "target_not_fresh|weak_klt_quality"
def test_should_override_guidance_when_target_is_missing_but_fresh_candidate_exists():
assert should_override_guidance(
confirmed=True,
target_track_missing=True,
have_fresh_candidate=True,
candidate_matches_target=False,
miss_streak=10,
override_miss_ge=2,
)
def test_should_not_override_guidance_before_miss_threshold():
assert not should_override_guidance(
confirmed=True,
target_track_missing=True,
have_fresh_candidate=True,
candidate_matches_target=False,
miss_streak=1,
override_miss_ge=2,
)
def test_pick_guidance_override_box_falls_back_to_raw_yolo_box():
yolo_box = [100.0, 120.0, 180.0, 200.0]
picked = pick_guidance_override_box(
track_candidate_box=None,
raw_yolo_box=yolo_box,
)
assert picked == yolo_box
def test_update_guidance_override_latch_keeps_recent_box_when_new_candidate_is_missing():
prev_box = [100.0, 120.0, 180.0, 200.0]
latched_box, ttl = update_guidance_override_latch(
new_box=None,
prev_box=prev_box,
prev_ttl=2,
max_ttl=2,
)
assert latched_box == prev_box
assert ttl == 1
def test_update_guidance_override_latch_replaces_memory_when_new_candidate_arrives():
new_box = [300.0, 320.0, 380.0, 400.0]
latched_box, ttl = update_guidance_override_latch(
new_box=new_box,
prev_box=None,
prev_ttl=0,
max_ttl=2,
)
assert latched_box == new_box
assert ttl == 2