oximedia_align/
gradient_flow.rs1#![allow(dead_code)]
7#![allow(clippy::cast_precision_loss)]
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct FlowVector {
12 pub dx: f32,
14 pub dy: f32,
16}
17
18impl FlowVector {
19 #[must_use]
21 pub fn new(dx: f32, dy: f32) -> Self {
22 Self { dx, dy }
23 }
24
25 #[must_use]
27 pub fn magnitude(&self) -> f32 {
28 (self.dx * self.dx + self.dy * self.dy).sqrt()
29 }
30
31 #[must_use]
33 pub fn angle_rad(&self) -> f32 {
34 self.dy.atan2(self.dx)
35 }
36
37 #[must_use]
39 pub fn is_zero(&self) -> bool {
40 self.dx == 0.0 && self.dy == 0.0
41 }
42}
43
44impl Default for FlowVector {
45 fn default() -> Self {
46 Self::new(0.0, 0.0)
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct FlowField {
53 pub vectors: Vec<FlowVector>,
55 pub width: usize,
57 pub height: usize,
59}
60
61impl FlowField {
62 #[must_use]
64 pub fn new(width: usize, height: usize, block_size: usize) -> Self {
65 let cols = width.div_ceil(block_size.max(1));
66 let rows = height.div_ceil(block_size.max(1));
67 Self {
68 vectors: vec![FlowVector::default(); cols * rows],
69 width,
70 height,
71 }
72 }
73
74 #[must_use]
76 pub fn block_cols(&self, block_size: usize) -> usize {
77 self.width.div_ceil(block_size.max(1))
78 }
79
80 #[must_use]
82 pub fn block_rows(&self, block_size: usize) -> usize {
83 self.height.div_ceil(block_size.max(1))
84 }
85
86 #[must_use]
92 pub fn at(&self, x: usize, y: usize) -> &FlowVector {
93 let cols = self.width.div_ceil(1); &self.vectors[y * cols + x]
95 }
96
97 #[must_use]
101 pub fn average_magnitude(&self) -> f32 {
102 if self.vectors.is_empty() {
103 return 0.0;
104 }
105 let sum: f32 = self.vectors.iter().map(FlowVector::magnitude).sum();
106 sum / self.vectors.len() as f32
107 }
108
109 #[must_use]
112 pub fn dominant_direction(&self) -> FlowVector {
113 if self.vectors.is_empty() {
114 return FlowVector::default();
115 }
116 let n = self.vectors.len() as f32;
117 let sum_dx: f32 = self.vectors.iter().map(|v| v.dx).sum();
118 let sum_dy: f32 = self.vectors.iter().map(|v| v.dy).sum();
119 FlowVector::new(sum_dx / n, sum_dy / n)
120 }
121}
122
123#[must_use]
139pub fn lucas_kanade_block(prev_block: &[f32], curr_block: &[f32], block_size: usize) -> FlowVector {
140 if prev_block.is_empty() || curr_block.is_empty() || block_size == 0 {
141 return FlowVector::default();
142 }
143
144 let n = (block_size * block_size) as f32;
145
146 let mut sum_ix2 = 0.0_f32;
148 let mut sum_iy2 = 0.0_f32;
149 let mut sum_ix_iy = 0.0_f32;
150 let mut sum_ix_it = 0.0_f32;
151 let mut sum_iy_it = 0.0_f32;
152
153 let len = prev_block.len().min(curr_block.len());
154 for i in 0..len {
155 let it = curr_block[i] - prev_block[i];
156 let ix = if i + 1 < len {
158 curr_block[i + 1] - curr_block[i]
159 } else {
160 0.0
161 };
162 let iy = if i + block_size < len {
163 curr_block[i + block_size] - curr_block[i]
164 } else {
165 0.0
166 };
167 sum_ix2 += ix * ix;
168 sum_iy2 += iy * iy;
169 sum_ix_iy += ix * iy;
170 sum_ix_it += ix * it;
171 sum_iy_it += iy * it;
172 }
173
174 let a = sum_ix2 / n;
176 let b = sum_ix_iy / n;
177 let c = sum_iy2 / n;
178 let d = -sum_ix_it / n;
179 let e = -sum_iy_it / n;
180
181 let det = a * c - b * b;
183 if det.abs() < 1e-8 {
184 return FlowVector::default();
185 }
186
187 let vx = (d * c - e * b) / det;
188 let vy = (a * e - b * d) / det;
189 FlowVector::new(vx, vy)
190}
191
192#[must_use]
201pub fn compute_flow_field(
202 prev_frame: &[f32],
203 curr_frame: &[f32],
204 width: usize,
205 height: usize,
206 block_size: usize,
207) -> FlowField {
208 let bs = block_size.max(1);
209 let block_cols = width.div_ceil(bs);
210 let block_rows = height.div_ceil(bs);
211 let mut vectors = Vec::with_capacity(block_cols * block_rows);
212
213 for by in 0..block_rows {
214 for bx in 0..block_cols {
215 let x0 = bx * bs;
216 let y0 = by * bs;
217
218 let mut prev_block = Vec::with_capacity(bs * bs);
220 let mut curr_block = Vec::with_capacity(bs * bs);
221
222 for row in 0..bs {
223 let y = y0 + row;
224 if y >= height {
225 break;
226 }
227 for col in 0..bs {
228 let x = x0 + col;
229 if x >= width {
230 break;
231 }
232 let idx = y * width + x;
233 if idx < prev_frame.len() {
234 prev_block.push(prev_frame[idx]);
235 }
236 if idx < curr_frame.len() {
237 curr_block.push(curr_frame[idx]);
238 }
239 }
240 }
241
242 let fv = lucas_kanade_block(&prev_block, &curr_block, bs);
243 vectors.push(fv);
244 }
245 }
246
247 FlowField {
248 vectors,
249 width,
250 height,
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use std::f32::consts::PI;
258
259 #[test]
260 fn test_flow_vector_magnitude_zero() {
261 let v = FlowVector::new(0.0, 0.0);
262 assert_eq!(v.magnitude(), 0.0);
263 }
264
265 #[test]
266 fn test_flow_vector_magnitude_345() {
267 let v = FlowVector::new(3.0, 4.0);
268 assert!((v.magnitude() - 5.0).abs() < 1e-5);
269 }
270
271 #[test]
272 fn test_flow_vector_angle_right() {
273 let v = FlowVector::new(1.0, 0.0);
274 assert!(v.angle_rad().abs() < 1e-5);
275 }
276
277 #[test]
278 fn test_flow_vector_angle_up() {
279 let v = FlowVector::new(0.0, 1.0);
280 assert!((v.angle_rad() - PI / 2.0).abs() < 1e-5);
281 }
282
283 #[test]
284 fn test_flow_vector_is_zero_true() {
285 let v = FlowVector::new(0.0, 0.0);
286 assert!(v.is_zero());
287 }
288
289 #[test]
290 fn test_flow_vector_is_zero_false() {
291 let v = FlowVector::new(0.0, 0.001);
292 assert!(!v.is_zero());
293 }
294
295 #[test]
296 fn test_flow_field_average_magnitude_empty_vectors() {
297 let field = FlowField {
299 vectors: vec![FlowVector::default(); 4],
300 width: 8,
301 height: 8,
302 };
303 assert_eq!(field.average_magnitude(), 0.0);
304 }
305
306 #[test]
307 fn test_flow_field_average_magnitude_uniform() {
308 let v = FlowVector::new(3.0, 4.0); let field = FlowField {
310 vectors: vec![v; 4],
311 width: 8,
312 height: 8,
313 };
314 assert!((field.average_magnitude() - 5.0).abs() < 1e-5);
315 }
316
317 #[test]
318 fn test_flow_field_dominant_direction_uniform() {
319 let field = FlowField {
320 vectors: vec![FlowVector::new(2.0, -1.0); 6],
321 width: 12,
322 height: 8,
323 };
324 let d = field.dominant_direction();
325 assert!((d.dx - 2.0).abs() < 1e-5);
326 assert!((d.dy + 1.0).abs() < 1e-5);
327 }
328
329 #[test]
330 fn test_flow_field_dominant_direction_cancels() {
331 let field = FlowField {
333 vectors: vec![FlowVector::new(1.0, 0.0), FlowVector::new(-1.0, 0.0)],
334 width: 4,
335 height: 4,
336 };
337 let d = field.dominant_direction();
338 assert!(d.dx.abs() < 1e-5);
339 }
340
341 #[test]
342 fn test_lucas_kanade_block_empty_returns_zero() {
343 let fv = lucas_kanade_block(&[], &[], 8);
344 assert!(fv.is_zero());
345 }
346
347 #[test]
348 fn test_lucas_kanade_block_constant_frames_returns_zero() {
349 let prev: Vec<f32> = vec![128.0; 64];
350 let curr = prev.clone();
351 let fv = lucas_kanade_block(&prev, &curr, 8);
352 assert!(fv.is_zero());
354 }
355
356 #[test]
357 fn test_compute_flow_field_dimensions() {
358 let prev = vec![0.0_f32; 64 * 48];
359 let curr = prev.clone();
360 let field = compute_flow_field(&prev, &curr, 64, 48, 8);
361 assert_eq!(field.vectors.len(), 48);
363 }
364
365 #[test]
366 fn test_compute_flow_field_constant_frames() {
367 let frame = vec![100.0_f32; 16 * 16];
368 let field = compute_flow_field(&frame, &frame, 16, 16, 4);
369 for v in &field.vectors {
370 assert!(v.is_zero());
371 }
372 }
373}