use crate::{EventStream, EventStreamBuilder};
const INNER_CIRCLE: [(i32, i32); 16] = [
(0, 3),
(1, 3),
(2, 2),
(3, 1),
(3, 0),
(3, -1),
(2, -2),
(1, -3),
(0, -3),
(-1, -3),
(-2, -2),
(-3, -1),
(-3, 0),
(-3, 1),
(-2, 2),
(-1, 3),
];
const OUTER_CIRCLE: [(i32, i32); 20] = [
(0, 4),
(1, 4),
(2, 3),
(3, 2),
(4, 1),
(4, 0),
(4, -1),
(3, -2),
(2, -3),
(1, -4),
(0, -4),
(-1, -4),
(-2, -3),
(-3, -2),
(-4, -1),
(-4, 0),
(-4, 1),
(-3, 2),
(-2, 3),
(-1, 4),
];
const INNER_ARC: (usize, usize) = (3, 6);
const OUTER_ARC: (usize, usize) = (4, 8);
const HARRIS_RADIUS: usize = 4;
const HARRIS_K: f64 = 0.04;
impl EventStream {
pub fn efast(&self) -> EventStream {
let (width, height) = self.sensor_size();
let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
let mut builder =
EventStreamBuilder::with_capacity(width, height, self.timestamp_scale_ms(), self.len());
if width == 0 || height == 0 {
return builder.build();
}
let mut sae_on = vec![i64::MIN; width * height];
let mut sae_off = vec![i64::MIN; width * height];
for index in 0..self.len() {
let (x, y, t, p) = (xs[index] as usize, ys[index] as usize, ts[index], ps[index]);
let sae = if p { &mut sae_on } else { &mut sae_off };
sae[y * width + x] = t;
if x < 4 || y < 4 || x + 4 >= width || y + 4 >= height {
continue;
}
if ring_is_corner(sae, x, y, width, &INNER_CIRCLE, INNER_ARC)
&& ring_is_corner(sae, x, y, width, &OUTER_CIRCLE, OUTER_ARC)
{
builder.push(xs[index], ys[index], t, p);
}
}
builder.build()
}
pub fn harris_corners(&self, threshold: f64) -> EventStream {
let (width, height) = self.sensor_size();
let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
let scale = self.timestamp_scale_ms();
let mut builder =
EventStreamBuilder::with_capacity(width, height, self.timestamp_scale_ms(), self.len());
let margin = HARRIS_RADIUS + 1; if width < 2 * margin + 1 || height < 2 * margin + 1 {
return builder.build();
}
let mut sae = vec![i64::MIN; width * height];
for index in 0..self.len() {
let (x, y, t, p) = (xs[index] as usize, ys[index] as usize, ts[index], ps[index]);
sae[y * width + x] = t;
if x < margin || y < margin || x + margin >= width || y + margin >= height {
continue;
}
if harris_response(&sae, x, y, width, scale) > threshold {
builder.push(xs[index], ys[index], t, p);
}
}
builder.build()
}
}
fn ring_is_corner(
sae: &[i64],
cx: usize,
cy: usize,
width: usize,
circle: &[(i32, i32)],
(min_arc, max_arc): (usize, usize),
) -> bool {
let n = circle.len();
let mut times = [i64::MIN; 20]; for (slot, &(dx, dy)) in times.iter_mut().zip(circle) {
let px = (cx as i32 + dx) as usize;
let py = (cy as i32 + dy) as usize;
*slot = sae[py * width + px];
}
let times = ×[..n];
for length in min_arc..=max_arc {
for start in 0..n {
let arc_min = (0..length)
.map(|k| times[(start + k) % n])
.min()
.expect("arc length is at least 1");
let rest_max = (length..n)
.map(|k| times[(start + k) % n])
.max()
.expect("ring is longer than the arc");
if arc_min > rest_max {
return true;
}
}
}
false
}
const HARRIS_MIN_SAMPLES: usize = 3;
fn harris_response(sae: &[i64], cx: usize, cy: usize, width: usize, scale: f64) -> f64 {
let t_at = |x: usize, y: usize| -> Option<f64> {
let t = sae[y * width + x];
(t != i64::MIN).then_some(t as f64 * scale)
};
let gradient = |x: usize, y: usize| -> Option<(f64, f64)> {
t_at(x, y)?; let gx = (t_at(x + 1, y)? - t_at(x - 1, y)?) / 2.0;
let gy = (t_at(x, y + 1)? - t_at(x, y - 1)?) / 2.0;
Some((gx, gy))
};
let (mut sxx, mut syy, mut sxy) = (0.0, 0.0, 0.0);
let mut samples = 0;
for y in cy - HARRIS_RADIUS..=cy + HARRIS_RADIUS {
for x in cx - HARRIS_RADIUS..=cx + HARRIS_RADIUS {
if let Some((ix, iy)) = gradient(x, y) {
sxx += ix * ix;
syy += iy * iy;
sxy += ix * iy;
samples += 1;
}
}
}
if samples < HARRIS_MIN_SAMPLES {
return f64::NEG_INFINITY;
}
let det = sxx * syy - sxy * sxy;
let trace = sxx + syy;
det - HARRIS_K * trace * trace
}
#[cfg(test)]
mod tests {
use ndarray::{array, Array2};
use super::{ring_is_corner, INNER_ARC, INNER_CIRCLE};
use crate::EventStream;
fn empty(width: usize, height: usize) -> EventStream {
EventStream::from_array2(Array2::zeros((0, 4)), width, height, 0.001)
}
fn sae_with_arc(recent: &[usize]) -> Vec<i64> {
let width = 7;
let mut sae = vec![0_i64; width * width];
for (k, &(dx, dy)) in INNER_CIRCLE.iter().enumerate() {
let x = (3 + dx) as usize;
let y = (3 + dy) as usize;
sae[y * width + x] = if recent.contains(&k) { 100 } else { 1 };
}
sae
}
#[test]
fn ring_detects_contiguous_arc_and_rejects_scattered_or_long() {
let width = 7;
let flat = sae_with_arc(&[]);
assert!(!ring_is_corner(
&flat,
3,
3,
width,
&INNER_CIRCLE,
INNER_ARC
));
let corner = sae_with_arc(&[2, 3, 4, 5]);
assert!(ring_is_corner(
&corner,
3,
3,
width,
&INNER_CIRCLE,
INNER_ARC
));
let edge = sae_with_arc(&[0, 1, 2, 3, 4, 5, 6, 7]);
assert!(!ring_is_corner(
&edge,
3,
3,
width,
&INNER_CIRCLE,
INNER_ARC
));
let scattered = sae_with_arc(&[0, 8]);
assert!(!ring_is_corner(
&scattered,
3,
3,
width,
&INNER_CIRCLE,
INNER_ARC
));
}
#[test]
fn efast_empty_stream_is_empty() {
assert_eq!(empty(20, 20).efast().len(), 0);
assert_eq!(empty(0, 0).efast().len(), 0);
}
#[test]
fn efast_and_harris_return_a_subset() {
let mut rows = Vec::new();
let mut t = 0_u64;
for x in 0..20u64 {
rows.push([x, 10, t, 1]);
t += 10;
}
for y in 0..20u64 {
rows.push([10, y, t, 1]);
t += 10;
}
let events = Array2::from_shape_vec((rows.len(), 4), rows.concat()).unwrap();
let stream = EventStream::from_array2(events, 20, 20, 0.001);
let corners = stream.efast();
assert!(corners.len() <= stream.len());
let harris = stream.harris_corners(0.0);
assert!(harris.len() <= stream.len());
}
#[test]
fn harris_empty_and_tiny_sensor_are_empty() {
assert_eq!(empty(20, 20).harris_corners(0.0).len(), 0);
assert_eq!(
EventStream::from_array2(array![[1, 1, 5, 1]], 4, 4, 0.001)
.harris_corners(0.0)
.len(),
0
);
}
#[test]
fn harris_rejects_a_straight_moving_edge() {
let mut rows = Vec::new();
let mut t = 0_u64;
for step in 0..30u64 {
for y in 2..28u64 {
rows.push([5 + step, y, t, 1]); }
t += 100;
}
let events = Array2::from_shape_vec((rows.len(), 4), rows.concat()).unwrap();
let stream = EventStream::from_array2(events, 40, 30, 0.001);
let kept = stream.harris_corners(0.0).len();
assert!(
kept * 20 < stream.len(),
"straight edge should yield ~no corners, got {kept}/{}",
stream.len()
);
}
}