1use std::{error::Error, fmt};
12
13use crate::representation::{EventFrame, EventFrameData, RepresentationKind};
14use crate::EventStream;
15
16const COND_RATIO: f64 = 1e-3;
21const FLAT_EPSILON: f64 = 1e-12;
23const NORMAL_FLOW_MIN_COHERENCE: f64 = 0.35;
26
27impl EventStream {
28 pub fn optical_flow(&self, window: usize) -> Result<EventFrame, FlowError> {
33 if window == 0 {
34 return Err(FlowError::InvalidParameter("window"));
35 }
36 let (width, height) = self.sensor_size();
37 let plane = width
38 .checked_mul(height)
39 .ok_or(FlowError::SizeOverflow)?
40 .checked_mul(2)
41 .ok_or(FlowError::SizeOverflow)?;
42 let mut flow = vec![0.0_f32; plane];
43 if width < 3 || height < 3 {
44 return Ok(flow_frame(flow, width, height));
45 }
46
47 let scale = self.timestamp_scale_ms();
50 let (xs, ys, ts) = (self.xs(), self.ys(), self.ts());
51 let mut latest = vec![i64::MIN; width * height];
52 for index in 0..self.len() {
53 let i = ys[index] as usize * width + xs[index] as usize;
54 if ts[index] > latest[i] {
55 latest[i] = ts[index];
56 }
57 }
58 let t_ms: Vec<f64> = latest
59 .iter()
60 .map(|&t| {
61 if t == i64::MIN {
62 f64::NAN
63 } else {
64 t as f64 * scale
65 }
66 })
67 .collect();
68
69 let plane_len = width * height;
71 let (mut gx, mut gy) = (vec![f64::NAN; plane_len], vec![f64::NAN; plane_len]);
72 for y in 1..height - 1 {
73 for x in 1..width - 1 {
74 let i = y * width + x;
75 if t_ms[i].is_nan() {
76 continue;
77 }
78 let (l, r) = (t_ms[i - 1], t_ms[i + 1]);
79 let (u, d) = (t_ms[i - width], t_ms[i + width]);
80 if !l.is_nan() && !r.is_nan() {
81 gx[i] = (r - l) / 2.0;
82 }
83 if !u.is_nan() && !d.is_nan() {
84 gy[i] = (d - u) / 2.0;
85 }
86 }
87 }
88
89 let w = window as isize;
90 for y in 0..height {
91 for x in 0..width {
92 if t_ms[y * width + x].is_nan() {
93 continue; }
95 let (mut sxx, mut syy, mut sxy, mut bx, mut by) = (0.0, 0.0, 0.0, 0.0, 0.0);
96 let mut count = 0.0;
97 for dy in -w..=w {
98 let ny = y as isize + dy;
99 if ny < 0 || ny >= height as isize {
100 continue;
101 }
102 for dx in -w..=w {
103 let nx = x as isize + dx;
104 if nx < 0 || nx >= width as isize {
105 continue;
106 }
107 let i = ny as usize * width + nx as usize;
108 let (ix, iy) = (gx[i], gy[i]);
109 if ix.is_nan() || iy.is_nan() {
110 continue;
111 }
112 sxx += ix * ix;
114 syy += iy * iy;
115 sxy += ix * iy;
116 bx += ix;
117 by += iy;
118 count += 1.0;
119 }
120 }
121 let trace = sxx + syy;
122 if trace <= FLAT_EPSILON {
123 continue; }
125 let det = sxx * syy - sxy * sxy;
126 let (u, v) = if det > COND_RATIO * trace * trace {
127 ((syy * bx - sxy * by) / det, (sxx * by - sxy * bx) / det)
129 } else {
130 let coherence = (bx * bx + by * by) / (count * trace);
136 if coherence < NORMAL_FLOW_MIN_COHERENCE {
137 continue;
138 }
139 let (gx_bar, gy_bar) = (bx / count, by / count);
140 let mag2 = gx_bar * gx_bar + gy_bar * gy_bar;
141 (gx_bar / mag2, gy_bar / mag2)
142 };
143 let i = y * width + x;
144 flow[i] = u as f32;
145 flow[plane_len + i] = v as f32;
146 }
147 }
148
149 Ok(flow_frame(flow, width, height))
150 }
151}
152
153fn flow_frame(flow: Vec<f32>, width: usize, height: usize) -> EventFrame {
154 EventFrame::from_parts(
155 EventFrameData::F32(flow),
156 width,
157 height,
158 RepresentationKind::Flow,
159 vec!["flow_x".to_owned(), "flow_y".to_owned()],
160 )
161}
162
163#[derive(Debug, PartialEq, Eq)]
164pub enum FlowError {
165 SizeOverflow,
166 InvalidParameter(&'static str),
167}
168
169impl fmt::Display for FlowError {
170 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 Self::SizeOverflow => formatter.write_str("flow field dimensions are too large"),
173 Self::InvalidParameter("window") => formatter.write_str("window must be at least 1"),
174 Self::InvalidParameter(name) => write!(formatter, "{name} is invalid"),
175 }
176 }
177}
178
179impl Error for FlowError {}
180
181#[cfg(test)]
182mod tests {
183 use ndarray::Array2;
184
185 use super::{EventFrameData, EventStream, FlowError};
186
187 fn stream(rows: Vec<[u64; 4]>, width: usize, height: usize) -> EventStream {
188 let flat: Vec<u64> = rows.iter().flatten().copied().collect();
189 EventStream::from_array2(
190 Array2::from_shape_vec((rows.len(), 4), flat).unwrap(),
191 width,
192 height,
193 0.001,
194 )
195 }
196
197 #[test]
198 fn rejects_zero_window() {
199 assert_eq!(
200 stream(vec![], 8, 8).optical_flow(0).unwrap_err(),
201 FlowError::InvalidParameter("window")
202 );
203 }
204
205 #[test]
206 fn empty_stream_is_all_zero_two_channel() {
207 let frame = stream(vec![], 8, 8).optical_flow(2).unwrap();
208 assert_eq!(frame.shape(), (2, 8, 8));
209 let EventFrameData::F32(values) = frame.data() else {
210 panic!("flow frames are float32");
211 };
212 assert!(values.iter().all(|&v| v == 0.0));
213 }
214
215 #[test]
216 fn horizontal_edge_flow_points_along_x() {
217 let mut rows = Vec::new();
220 for x in 0..8u64 {
221 for y in 0..8u64 {
222 rows.push([x, y, 10 * x, 1]);
223 }
224 }
225 let frame = stream(rows, 8, 8).optical_flow(2).unwrap();
226 let EventFrameData::F32(values) = frame.data() else {
227 panic!("flow frames are float32");
228 };
229 let plane = 8 * 8;
230 let i = 4 * 8 + 4;
232 let (fx, fy) = (values[i], values[plane + i]);
233 assert!(fx > 0.0, "flow_x should be positive, got {fx}");
235 assert!(fy.abs() < fx.abs() * 0.1, "flow_y should be ~0, got {fy}");
236 }
237}