oxihuman_viewer/
motion_vector.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, Default)]
10pub struct MotionVector {
11 pub du: f32,
12 pub dv: f32,
13}
14
15impl MotionVector {
16 #[allow(dead_code)]
17 pub fn magnitude(&self) -> f32 {
18 (self.du * self.du + self.dv * self.dv).sqrt()
19 }
20}
21
22#[allow(dead_code)]
24#[derive(Clone, Debug)]
25pub struct MotionVectorBuffer {
26 pub width: u32,
27 pub height: u32,
28 pub vectors: Vec<MotionVector>,
29}
30
31impl MotionVectorBuffer {
32 #[allow(dead_code)]
33 pub fn new(width: u32, height: u32) -> Self {
34 let count = (width * height) as usize;
35 MotionVectorBuffer {
36 width,
37 height,
38 vectors: vec![MotionVector::default(); count],
39 }
40 }
41}
42
43#[allow(dead_code)]
44pub fn mv_set(buf: &mut MotionVectorBuffer, x: u32, y: u32, du: f32, dv: f32) {
45 if x < buf.width && y < buf.height {
46 let idx = (y * buf.width + x) as usize;
47 buf.vectors[idx] = MotionVector { du, dv };
48 }
49}
50
51#[allow(dead_code)]
52pub fn mv_get(buf: &MotionVectorBuffer, x: u32, y: u32) -> Option<MotionVector> {
53 if x < buf.width && y < buf.height {
54 Some(buf.vectors[(y * buf.width + x) as usize])
55 } else {
56 None
57 }
58}
59
60#[allow(dead_code)]
61pub fn mv_clear(buf: &mut MotionVectorBuffer) {
62 for v in &mut buf.vectors {
63 *v = MotionVector::default();
64 }
65}
66
67#[allow(dead_code)]
68pub fn mv_max_magnitude(buf: &MotionVectorBuffer) -> f32 {
69 buf.vectors
70 .iter()
71 .map(|v| v.magnitude())
72 .fold(0.0f32, f32::max)
73}
74
75#[allow(dead_code)]
76pub fn mv_average_magnitude(buf: &MotionVectorBuffer) -> f32 {
77 if buf.vectors.is_empty() {
78 return 0.0;
79 }
80 buf.vectors.iter().map(|v| v.magnitude()).sum::<f32>() / buf.vectors.len() as f32
81}
82
83#[allow(dead_code)]
84pub fn mv_pixel_count(buf: &MotionVectorBuffer) -> usize {
85 buf.vectors.len()
86}
87
88#[allow(dead_code)]
89pub fn mv_to_json(buf: &MotionVectorBuffer) -> String {
90 format!(
91 "{{\"width\":{},\"height\":{},\"max_mag\":{:.4}}}",
92 buf.width,
93 buf.height,
94 mv_max_magnitude(buf)
95 )
96}
97
98#[allow(dead_code)]
99pub fn mv_scale(buf: &mut MotionVectorBuffer, scale: f32) {
100 for v in &mut buf.vectors {
101 v.du *= scale;
102 v.dv *= scale;
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn new_all_zero() {
112 let b = MotionVectorBuffer::new(4, 4);
113 assert!((mv_max_magnitude(&b)).abs() < 1e-5);
114 }
115
116 #[test]
117 fn set_and_get() {
118 let mut b = MotionVectorBuffer::new(8, 8);
119 mv_set(&mut b, 2, 3, 0.1, 0.2);
120 let v = mv_get(&b, 2, 3).expect("should succeed");
121 assert!((v.du - 0.1).abs() < 1e-5);
122 }
123
124 #[test]
125 fn out_of_bounds_get_none() {
126 let b = MotionVectorBuffer::new(4, 4);
127 assert!(mv_get(&b, 10, 10).is_none());
128 }
129
130 #[test]
131 fn clear_zeroes() {
132 let mut b = MotionVectorBuffer::new(2, 2);
133 mv_set(&mut b, 0, 0, 1.0, 1.0);
134 mv_clear(&mut b);
135 assert!((mv_max_magnitude(&b)).abs() < 1e-5);
136 }
137
138 #[test]
139 fn magnitude_formula() {
140 let v = MotionVector { du: 3.0, dv: 4.0 };
141 assert!((v.magnitude() - 5.0).abs() < 1e-4);
142 }
143
144 #[test]
145 fn pixel_count() {
146 let b = MotionVectorBuffer::new(10, 10);
147 assert_eq!(mv_pixel_count(&b), 100);
148 }
149
150 #[test]
151 fn scale_doubles() {
152 let mut b = MotionVectorBuffer::new(2, 2);
153 mv_set(&mut b, 0, 0, 1.0, 0.0);
154 mv_scale(&mut b, 2.0);
155 let v = mv_get(&b, 0, 0).expect("should succeed");
156 assert!((v.du - 2.0).abs() < 1e-5);
157 }
158
159 #[test]
160 fn average_magnitude_uniform() {
161 let mut b = MotionVectorBuffer::new(1, 1);
162 mv_set(&mut b, 0, 0, 1.0, 0.0);
163 assert!((mv_average_magnitude(&b) - 1.0).abs() < 1e-5);
164 }
165
166 #[test]
167 fn json_has_width() {
168 assert!(mv_to_json(&MotionVectorBuffer::new(3, 3)).contains("width"));
169 }
170
171 #[test]
172 fn out_of_bounds_set_ignored() {
173 let mut b = MotionVectorBuffer::new(4, 4);
174 mv_set(&mut b, 99, 99, 1.0, 1.0); assert_eq!(mv_pixel_count(&b), 16);
176 }
177}