1use crate::{EventStream, EventStreamBuilder};
8
9const INNER_CIRCLE: [(i32, i32); 16] = [
11 (0, 3),
12 (1, 3),
13 (2, 2),
14 (3, 1),
15 (3, 0),
16 (3, -1),
17 (2, -2),
18 (1, -3),
19 (0, -3),
20 (-1, -3),
21 (-2, -2),
22 (-3, -1),
23 (-3, 0),
24 (-3, 1),
25 (-2, 2),
26 (-1, 3),
27];
28
29const OUTER_CIRCLE: [(i32, i32); 20] = [
31 (0, 4),
32 (1, 4),
33 (2, 3),
34 (3, 2),
35 (4, 1),
36 (4, 0),
37 (4, -1),
38 (3, -2),
39 (2, -3),
40 (1, -4),
41 (0, -4),
42 (-1, -4),
43 (-2, -3),
44 (-3, -2),
45 (-4, -1),
46 (-4, 0),
47 (-4, 1),
48 (-3, 2),
49 (-2, 3),
50 (-1, 4),
51];
52
53const INNER_ARC: (usize, usize) = (3, 6);
56const OUTER_ARC: (usize, usize) = (4, 8);
57
58const HARRIS_RADIUS: usize = 4;
60const HARRIS_K: f64 = 0.04;
62
63impl EventStream {
64 pub fn efast(&self) -> EventStream {
71 let (width, height) = self.sensor_size();
72 let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
73 let mut builder =
74 EventStreamBuilder::with_capacity(width, height, self.timestamp_scale_ms(), self.len());
75 if width == 0 || height == 0 {
76 return builder.build();
77 }
78 let mut sae_on = vec![i64::MIN; width * height];
80 let mut sae_off = vec![i64::MIN; width * height];
81
82 for index in 0..self.len() {
83 let (x, y, t, p) = (xs[index] as usize, ys[index] as usize, ts[index], ps[index]);
84 let sae = if p { &mut sae_on } else { &mut sae_off };
85 sae[y * width + x] = t; if x < 4 || y < 4 || x + 4 >= width || y + 4 >= height {
89 continue;
90 }
91 if ring_is_corner(sae, x, y, width, &INNER_CIRCLE, INNER_ARC)
92 && ring_is_corner(sae, x, y, width, &OUTER_CIRCLE, OUTER_ARC)
93 {
94 builder.push(xs[index], ys[index], t, p);
95 }
96 }
97 builder.build()
98 }
99
100 pub fn harris_corners(&self, threshold: f64) -> EventStream {
109 let (width, height) = self.sensor_size();
110 let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
111 let scale = self.timestamp_scale_ms();
112 let mut builder =
113 EventStreamBuilder::with_capacity(width, height, self.timestamp_scale_ms(), self.len());
114 let margin = HARRIS_RADIUS + 1; if width < 2 * margin + 1 || height < 2 * margin + 1 {
116 return builder.build();
117 }
118 let mut sae = vec![i64::MIN; width * height];
120
121 for index in 0..self.len() {
122 let (x, y, t, p) = (xs[index] as usize, ys[index] as usize, ts[index], ps[index]);
123 sae[y * width + x] = t;
124
125 if x < margin || y < margin || x + margin >= width || y + margin >= height {
126 continue;
127 }
128 if harris_response(&sae, x, y, width, scale) > threshold {
129 builder.push(xs[index], ys[index], t, p);
130 }
131 }
132 builder.build()
133 }
134}
135
136fn ring_is_corner(
141 sae: &[i64],
142 cx: usize,
143 cy: usize,
144 width: usize,
145 circle: &[(i32, i32)],
146 (min_arc, max_arc): (usize, usize),
147) -> bool {
148 let n = circle.len();
149 let mut times = [i64::MIN; 20]; for (slot, &(dx, dy)) in times.iter_mut().zip(circle) {
151 let px = (cx as i32 + dx) as usize;
152 let py = (cy as i32 + dy) as usize;
153 *slot = sae[py * width + px];
154 }
155 let times = ×[..n];
156
157 for length in min_arc..=max_arc {
158 for start in 0..n {
159 let arc_min = (0..length)
160 .map(|k| times[(start + k) % n])
161 .min()
162 .expect("arc length is at least 1");
163 let rest_max = (length..n)
164 .map(|k| times[(start + k) % n])
165 .max()
166 .expect("ring is longer than the arc");
167 if arc_min > rest_max {
168 return true;
169 }
170 }
171 }
172 false
173}
174
175const HARRIS_MIN_SAMPLES: usize = 3;
177
178fn harris_response(sae: &[i64], cx: usize, cy: usize, width: usize, scale: f64) -> f64 {
185 let t_at = |x: usize, y: usize| -> Option<f64> {
186 let t = sae[y * width + x];
187 (t != i64::MIN).then_some(t as f64 * scale)
188 };
189 let gradient = |x: usize, y: usize| -> Option<(f64, f64)> {
191 t_at(x, y)?; let gx = (t_at(x + 1, y)? - t_at(x - 1, y)?) / 2.0;
193 let gy = (t_at(x, y + 1)? - t_at(x, y - 1)?) / 2.0;
194 Some((gx, gy))
195 };
196
197 let (mut sxx, mut syy, mut sxy) = (0.0, 0.0, 0.0);
198 let mut samples = 0;
199 for y in cy - HARRIS_RADIUS..=cy + HARRIS_RADIUS {
200 for x in cx - HARRIS_RADIUS..=cx + HARRIS_RADIUS {
201 if let Some((ix, iy)) = gradient(x, y) {
202 sxx += ix * ix;
203 syy += iy * iy;
204 sxy += ix * iy;
205 samples += 1;
206 }
207 }
208 }
209 if samples < HARRIS_MIN_SAMPLES {
210 return f64::NEG_INFINITY;
211 }
212 let det = sxx * syy - sxy * sxy;
213 let trace = sxx + syy;
214 det - HARRIS_K * trace * trace
215}
216
217#[cfg(test)]
218mod tests {
219 use ndarray::{array, Array2};
220
221 use super::{ring_is_corner, INNER_ARC, INNER_CIRCLE};
222 use crate::EventStream;
223
224 fn empty(width: usize, height: usize) -> EventStream {
225 EventStream::from_array2(Array2::zeros((0, 4)), width, height, 0.001)
226 }
227
228 fn sae_with_arc(recent: &[usize]) -> Vec<i64> {
230 let width = 7;
232 let mut sae = vec![0_i64; width * width];
233 for (k, &(dx, dy)) in INNER_CIRCLE.iter().enumerate() {
234 let x = (3 + dx) as usize;
235 let y = (3 + dy) as usize;
236 sae[y * width + x] = if recent.contains(&k) { 100 } else { 1 };
237 }
238 sae
239 }
240
241 #[test]
242 fn ring_detects_contiguous_arc_and_rejects_scattered_or_long() {
243 let width = 7;
244 let flat = sae_with_arc(&[]);
246 assert!(!ring_is_corner(
247 &flat,
248 3,
249 3,
250 width,
251 &INNER_CIRCLE,
252 INNER_ARC
253 ));
254 let corner = sae_with_arc(&[2, 3, 4, 5]);
256 assert!(ring_is_corner(
257 &corner,
258 3,
259 3,
260 width,
261 &INNER_CIRCLE,
262 INNER_ARC
263 ));
264 let edge = sae_with_arc(&[0, 1, 2, 3, 4, 5, 6, 7]);
266 assert!(!ring_is_corner(
267 &edge,
268 3,
269 3,
270 width,
271 &INNER_CIRCLE,
272 INNER_ARC
273 ));
274 let scattered = sae_with_arc(&[0, 8]);
276 assert!(!ring_is_corner(
277 &scattered,
278 3,
279 3,
280 width,
281 &INNER_CIRCLE,
282 INNER_ARC
283 ));
284 }
285
286 #[test]
287 fn efast_empty_stream_is_empty() {
288 assert_eq!(empty(20, 20).efast().len(), 0);
289 assert_eq!(empty(0, 0).efast().len(), 0);
290 }
291
292 #[test]
293 fn efast_and_harris_return_a_subset() {
294 let mut rows = Vec::new();
296 let mut t = 0_u64;
297 for x in 0..20u64 {
298 rows.push([x, 10, t, 1]);
299 t += 10;
300 }
301 for y in 0..20u64 {
302 rows.push([10, y, t, 1]);
303 t += 10;
304 }
305 let events = Array2::from_shape_vec((rows.len(), 4), rows.concat()).unwrap();
306 let stream = EventStream::from_array2(events, 20, 20, 0.001);
307
308 let corners = stream.efast();
309 assert!(corners.len() <= stream.len());
310 let harris = stream.harris_corners(0.0);
311 assert!(harris.len() <= stream.len());
312 }
313
314 #[test]
315 fn harris_empty_and_tiny_sensor_are_empty() {
316 assert_eq!(empty(20, 20).harris_corners(0.0).len(), 0);
317 assert_eq!(
319 EventStream::from_array2(array![[1, 1, 5, 1]], 4, 4, 0.001)
320 .harris_corners(0.0)
321 .len(),
322 0
323 );
324 }
325
326 #[test]
330 fn harris_rejects_a_straight_moving_edge() {
331 let mut rows = Vec::new();
332 let mut t = 0_u64;
333 for step in 0..30u64 {
334 for y in 2..28u64 {
335 rows.push([5 + step, y, t, 1]); }
337 t += 100;
338 }
339 let events = Array2::from_shape_vec((rows.len(), 4), rows.concat()).unwrap();
340 let stream = EventStream::from_array2(events, 40, 30, 0.001);
341 let kept = stream.harris_corners(0.0).len();
342 assert!(
344 kept * 20 < stream.len(),
345 "straight edge should yield ~no corners, got {kept}/{}",
346 stream.len()
347 );
348 }
349}