use crate::kernels::features::{match_features, Feature};
use crate::kernels::geometry::{js_hypot, Rect};
use crate::kernels::motion::{
refine_patches, refine_spatial_patches, translation_hypotheses, Gray, MatchPoints, Motion,
Patch, Point,
};
pub fn reacquire_hypotheses(anchor_features: &[Feature], own_features: &[Feature]) -> Vec<Motion> {
let raw_matches = match_features(anchor_features, own_features, true);
let match_points: Vec<MatchPoints> = raw_matches
.iter()
.map(|m| {
let a = &anchor_features[m.a as usize];
let b = &own_features[m.b as usize];
MatchPoints {
ax: a.x as f64,
ay: a.y as f64,
bx: b.x as f64,
by: b.y as f64,
unique: m.unique,
}
})
.collect();
translation_hypotheses(&match_points, 8)
.into_iter()
.filter(|m| m.support >= 6)
.collect()
}
pub struct ReacquireEstimate {
pub x: i32,
pub y: i32,
pub ambiguous: bool,
pub confidence: f64,
pub error: f64,
}
pub fn reacquire_refine(
models: &[Motion],
patches: &[Patch<'_>],
native: Gray<'_>,
rect: Rect,
f: f64,
radius: i32,
) -> Option<ReacquireEstimate> {
let mut options: Vec<_> = models
.iter()
.take(4)
.map(|m| {
let n = refine_spatial_patches(
patches,
native,
rect,
Point {
x: m.x * f,
y: m.y * f,
},
radius,
);
(m, n)
})
.filter(|(_, n)| n.error < 12.0)
.collect();
options.sort_by(|a, b| {
a.1.error
.partial_cmp(&b.1.error)
.unwrap_or(std::cmp::Ordering::Equal)
});
let (top_m, top_n) = options.first()?;
let has_rival = options.iter().skip(1).any(|(_, n)| {
js_hypot(n.x as f64 - top_n.x as f64, n.y as f64 - top_n.y as f64) > 2.0
&& n.error < top_n.error + 2.0
});
if has_rival {
return None;
}
let ambiguous = top_m.ambiguous;
let error = refine_patches(
patches,
native,
rect,
Point {
x: top_n.x as f64,
y: top_n.y as f64,
},
0,
)
.error;
let confidence =
top_m.confidence.max(0.05) * libm::exp(-error / 20.0) * if ambiguous { 0.6 } else { 1.0 };
Some(ReacquireEstimate {
x: top_n.x,
y: top_n.y,
ambiguous,
confidence,
error,
})
}
pub struct DriftEstimate {
pub x: f64,
pub y: f64,
pub error: f64,
pub confidence_floor: f64,
}
pub fn drift_correction(
patches: &[Patch<'_>],
native: Gray<'_>,
rect: Rect,
anchor: (f64, f64),
pose: (f64, f64),
radius: i32,
) -> Option<DriftEstimate> {
let expected = Point {
x: pose.0 - anchor.0,
y: pose.1 - anchor.1,
};
let n = refine_patches(patches, native, rect, expected, radius);
if n.error < 12.0 && n.runner_up > n.error + 1.5 {
Some(DriftEstimate {
x: anchor.0 + n.x as f64,
y: anchor.1 + n.y as f64,
error: n.error,
confidence_floor: 0.96 * libm::exp(-n.error / 20.0),
})
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reacquisition_balances_clustered_controls_against_the_page() {
let (w, h, step, size) = (256, 960, 96, 16);
let page = |x: usize, y: usize| 180 + ((x * 37 + y * 71 + x * y * 13) % 64) as u8;
let frame = |dy: usize| {
let mut data = vec![0; w * h];
for y in 0..h {
for x in 0..w {
data[y * w + x] = if !(64..h - 64).contains(&y) {
64 + ((x * 43 + y * 29 + x * y * 17) % 128) as u8
} else {
page(x, y + dy)
};
}
}
data
};
let anchor = frame(0);
let current = frame(step);
let mut origins: Vec<_> = [16, h - 32]
.into_iter()
.flat_map(|y| (0..8).map(move |i| (8 + i * 30, y)))
.collect();
origins.extend([200, 400, 600, 800].map(|y| (120, y)));
let data: Vec<Vec<u8>> = origins
.iter()
.map(|&(x, y)| {
(0..size)
.flat_map(|py| {
anchor[(y + py) * w + x..(y + py) * w + x + size]
.iter()
.copied()
})
.collect()
})
.collect();
let patches: Vec<_> = origins
.iter()
.zip(&data)
.map(|(&(x, y), data)| Patch {
x: x as i32,
y: y as i32,
size,
data,
})
.collect();
let rect = Rect {
x: 0.0,
y: 0.0,
width: w as f64,
height: h as f64,
};
let models: Vec<_> = [step, 0]
.map(|dy| Motion {
x: 0.0,
y: dy as f64,
support: 30,
unique: 30,
confidence: 1.0,
error: 0.0,
ambiguous: false,
})
.into();
for (pixels, expected) in [(¤t, step), (&anchor, 0)] {
let found = reacquire_refine(
&models,
&patches,
Gray {
width: w,
height: h,
data: pixels,
},
rect,
1.0,
3,
)
.expect("one globally supported anchor");
assert_eq!((found.x, found.y), (0, expected as i32));
}
}
#[test]
fn anchor_quality_does_not_depend_on_unsampled_pane_extent() {
let (w, h, size) = (160, 320, 32);
let reference: Vec<u8> = (0..w * h)
.map(|i| {
let (x, y) = (i % w, i / w);
32 + ((x * 13 + y * 31 + x * y * 17) % 128) as u8
})
.collect();
let origins = [(16, 16), (96, 112)];
let data: Vec<Vec<u8>> = origins
.iter()
.map(|&(x, y)| {
(0..size)
.flat_map(|dy| {
reference[(y + dy) * w + x..(y + dy) * w + x + size]
.iter()
.copied()
})
.collect()
})
.collect();
let patches: Vec<_> = origins
.iter()
.zip(&data)
.map(|(&(x, y), data)| Patch {
x: x as i32,
y: y as i32,
size,
data,
})
.collect();
let mut current = reference.clone();
for y in 112..112 + size {
for x in 96..96 + size {
current[y * w + x] += 8;
}
}
let models = [Motion {
x: 0.0,
y: 0.0,
support: 30,
unique: 30,
confidence: 1.0,
error: 0.0,
ambiguous: false,
}];
let estimates: Vec<_> = [160, 320]
.into_iter()
.map(|height| {
reacquire_refine(
&models,
&patches,
Gray {
width: w,
height: h,
data: ¤t,
},
Rect {
x: 0.0,
y: 0.0,
width: w as f64,
height: height as f64,
},
1.0,
3,
)
.unwrap()
})
.collect();
for estimate in &estimates {
assert_eq!((estimate.x, estimate.y), (0, 0));
}
assert_eq!(estimates[0].confidence, estimates[1].confidence);
assert_eq!(estimates[0].error, 4.0); }
}