use ados_protocol::framebus::{BoundingBox, Detection, LockState};
use ados_vision::tracker::{
Appearance, Candidate, SingleObjectTracker, TrackState, TrackerConfig, APPEARANCE_DIM,
};
fn det(x: f32, y: f32, w: f32, h: f32, conf: f32, label: &str) -> Detection {
Detection {
bbox: Some(BoundingBox {
x,
y,
width: w,
height: h,
}),
class_label: label.into(),
confidence: conf,
track_id: None,
assoc_confidence: None,
lock_state: None,
attributes: None,
mask: None,
keypoints: None,
depth: None,
world_pos: None,
}
}
fn target_app() -> Appearance {
let mut v = vec![0.0f32; APPEARANCE_DIM];
v[0] = 1.0;
Appearance::from_features(v)
}
fn distractor_app(sim: f32) -> Appearance {
let s = sim.clamp(0.0, 1.0);
let ortho = (1.0 - s * s).max(0.0).sqrt();
let mut v = vec![0.0f32; APPEARANCE_DIM];
v[0] = s;
v[1] = ortho;
Appearance::from_features(v)
}
fn center_err(report: &Detection, truth: &Detection) -> f32 {
let rb = report
.bbox
.as_ref()
.expect("reported detection carries a bbox");
let tb = truth.bbox.as_ref().expect("truth detection carries a bbox");
let rx = rb.x + rb.width / 2.0;
let ry = rb.y + rb.height / 2.0;
let tx = tb.x + tb.width / 2.0;
let ty = tb.y + tb.height / 2.0;
((rx - tx).powi(2) + (ry - ty).powi(2)).sqrt()
}
struct FrameRecord {
frame: usize,
to_target: f32,
to_distractor: f32,
separated: bool,
lock_state: Option<LockState>,
assoc_confidence: Option<f32>,
measured: bool,
needs_redesignation: bool,
track_id: Option<u64>,
}
fn run_crossing(sim: f32) -> (Vec<FrameRecord>, u64) {
let cfg = TrackerConfig::default();
let mut t = SingleObjectTracker::new(cfg);
for f in 0..4usize {
t.update_with_appearance(&[Candidate::with_appearance(
det(f as f32 * 8.0, 100.0, 40.0, 40.0, 0.90, "object"),
target_app(),
)]);
}
let locked_id = t.current_id().expect("target locked before crossing");
let mut records = Vec::new();
for f in 4usize..18 {
let pred_x = f as f32 * 8.0;
let target = Candidate::with_appearance(
det(
pred_x,
100.0 + (f - 4) as f32 * 8.0, 40.0,
40.0,
0.90,
"object",
),
target_app(),
);
let distractor = Candidate::with_appearance(
det(pred_x, 100.0, 40.0, 40.0, 0.99, "object"),
distractor_app(sim),
);
let u = t.update_with_appearance(&[distractor.clone(), target.clone()]);
if let Some(rep) = &u.detection {
records.push(FrameRecord {
frame: f,
to_target: center_err(rep, &target.detection),
to_distractor: center_err(rep, &distractor.detection),
separated: center_err(&target.detection, &distractor.detection) > 50.0,
lock_state: rep.lock_state,
assoc_confidence: rep.assoc_confidence,
measured: u.measured,
needs_redesignation: u.needs_redesignation,
track_id: u.track_id,
});
} else {
records.push(FrameRecord {
frame: f,
to_target: f32::NAN,
to_distractor: f32::NAN,
separated: false,
lock_state: None,
assoc_confidence: None,
measured: u.measured,
needs_redesignation: u.needs_redesignation,
track_id: u.track_id,
});
}
}
(records, locked_id)
}
fn silent_swap_frame(r: &FrameRecord) -> bool {
let following_distractor =
r.separated && r.to_distractor.is_finite() && r.to_distractor + 1.0 < r.to_target;
following_distractor
&& r.measured
&& r.lock_state == Some(LockState::Locked)
&& !r.needs_redesignation
}
fn following_distractor(r: &FrameRecord) -> bool {
r.separated && r.to_distractor.is_finite() && r.to_distractor + 1.0 < r.to_target
}
fn true_inlock_swap_frames(records: &[FrameRecord], locked_id: u64) -> Vec<usize> {
let mut dropped_since_lock = false;
let mut out = Vec::new();
for r in records {
match r.track_id {
None => dropped_since_lock = true,
Some(id) if id != locked_id => dropped_since_lock = true,
Some(_) => {}
}
if !dropped_since_lock && r.track_id == Some(locked_id) && silent_swap_frame(r) {
out.push(r.frame);
}
}
out
}
#[test]
fn danger_band_sweep_never_silent_swaps() {
let mut offenders: Vec<(f32, usize)> = Vec::new();
let mut sim = 0.0f32;
while sim <= 1.0001 {
let (records, locked_id) = run_crossing(sim);
for frame in true_inlock_swap_frames(&records, locked_id) {
offenders.push((sim, frame));
}
sim += 0.02;
}
assert!(
offenders.is_empty(),
"TRUE in-lock SILENT SWAP (original id dragged onto distractor, no honesty signal) \
detected at (similarity, frame) pairs: {offenders:?}"
);
}
#[test]
fn every_distractor_follow_is_honest_or_a_fresh_reacquire() {
let mut sim = 0.0f32;
while sim <= 1.0001 {
let (records, locked_id) = run_crossing(sim);
let mut dropped = false;
for r in &records {
match r.track_id {
None => dropped = true,
Some(id) if id != locked_id => dropped = true,
Some(_) => {}
}
if following_distractor(r) {
let honest = r.lock_state == Some(LockState::Uncertain)
|| !r.measured
|| r.needs_redesignation;
let fresh_reacquire = dropped && r.track_id != Some(locked_id);
assert!(
honest || fresh_reacquire,
"sim={sim} frame {} followed the distractor under the original id \
{locked_id:?} with a clean measured Locked and no re-designation \
(lock={:?}, measured={}, redesig={}, id={:?})",
r.frame,
r.lock_state,
r.measured,
r.needs_redesignation,
r.track_id
);
}
}
sim += 0.02;
}
}
#[test]
fn worst_case_admissible_distractor_holds_or_uncertain_never_silent() {
let sim = 0.6f32;
let (records, locked_id) = run_crossing(sim);
let mut silent = Vec::new();
let mut followed = Vec::new();
for r in &records {
if silent_swap_frame(r) {
silent.push(r.frame);
}
if following_distractor(r) {
followed.push((
r.frame,
r.lock_state,
r.measured,
r.needs_redesignation,
r.assoc_confidence,
));
}
}
assert!(
silent.is_empty(),
"SILENT SWAP onto admissible distractor at frames {silent:?}; \
following records (frame, lock, measured, redesig, assoc) = {followed:?}"
);
assert!(
followed.is_empty(),
"reported box followed the distractor (even if flagged): {followed:?}"
);
assert_eq!(
run_crossing(sim).0.last().and_then(|r| r.track_id),
Some(locked_id),
"the held id must remain the original target's id, never the distractor's"
);
}
#[test]
fn just_above_gate_distractor_is_not_silently_captured() {
let sim = 0.52f32;
let (records, _id) = run_crossing(sim);
let offenders: Vec<usize> = records
.iter()
.filter(|r| silent_swap_frame(r))
.map(|r| r.frame)
.collect();
assert!(
offenders.is_empty(),
"barely-admissible distractor silently captured at frames {offenders:?}"
);
}
#[test]
fn admissible_distractor_provokes_an_honest_response_not_a_clean_lie() {
let sim = 0.6f32;
let (records, _id) = run_crossing(sim);
let separated: Vec<&FrameRecord> = records.iter().filter(|r| r.separated).collect();
assert!(
!separated.is_empty(),
"the scenario must actually separate the objects, else it is vacuous"
);
for r in &separated {
let on_target = r.to_target.is_finite() && r.to_target + 1.0 < r.to_distractor;
let honestly_flagged =
r.lock_state == Some(LockState::Uncertain) || !r.measured || r.needs_redesignation;
assert!(
on_target || honestly_flagged,
"separated frame {} reported a clean measured Locked while NOT on the target \
(to_target={}, to_distractor={}, lock={:?}, measured={}, redesig={})",
r.frame,
r.to_target,
r.to_distractor,
r.lock_state,
r.measured,
r.needs_redesignation
);
}
}
#[test]
fn scenario_is_non_vacuous_motion_only_would_capture() {
let cfg = TrackerConfig::default();
let mut t = SingleObjectTracker::new(cfg);
for f in 0..4usize {
t.update(&[det(f as f32 * 8.0, 100.0, 40.0, 40.0, 0.90, "object")]);
}
assert_eq!(t.state(), TrackState::Confirmed);
let mut box_pulled_toward_distractor = false;
for f in 4usize..18 {
let pred_x = f as f32 * 8.0;
let target = det(
pred_x,
100.0 + (f - 4) as f32 * 8.0,
40.0,
40.0,
0.90,
"object",
);
let distractor = det(pred_x, 100.0, 40.0, 40.0, 0.99, "object");
let u = t.update(&[distractor.clone(), target.clone()]);
if let Some(rep) = &u.detection {
let to_t = center_err(rep, &target);
let to_d = center_err(rep, &distractor);
let separated = center_err(&target, &distractor) > 50.0;
if separated && to_d + 1.0 < to_t {
box_pulled_toward_distractor = true;
}
}
}
assert!(
box_pulled_toward_distractor,
"the motion-only path must be tempted by this geometry, else the appearance \
test proves nothing"
);
}