1use crate::kernels::geometry::js_hypot;
10use crate::kernels::pool::SyncPtr;
11
12pub struct FieldMotion {
13 pub x: f64,
14 pub y: f64,
15 pub support: u32,
16 pub confidence: f64,
17}
18
19pub struct Field<'a> {
21 pub motions: &'a [FieldMotion],
22 pub labels: &'a [u8],
23 pub confidence: &'a [u8],
24 pub dynamic: &'a [u8],
25 pub cols: usize,
26 pub rows: usize,
27 pub difference: f64,
28 pub unknown: bool,
29}
30
31#[inline]
32fn norm(m: &FieldMotion) -> f64 {
33 js_hypot(m.x, m.y)
34}
35
36pub const MOVING_COLUMN: f64 = 1.0;
41
42pub fn informative(field: &Field<'_>) -> bool {
43 !field.unknown
44 && field.difference >= 0.2
45 && field
46 .motions
47 .iter()
48 .any(|m| norm(m) > 1.0 && m.confidence > 0.3)
49}
50
51pub struct Learner {
52 pub width: usize,
53 pub height: usize,
54 pub cell: usize,
55 pub cols: usize,
56 pub rows: usize,
57 pub split: Vec<f64>,
58 pub evidence: Vec<f64>,
59 pub activity: Vec<f64>,
60 pub observations: Vec<f64>,
61 pub row_fixed: Vec<f64>,
62 pub row_moving: Vec<f64>,
63 pub pixel_fixed: Vec<f64>,
67 pub pixel_moving: Vec<f64>,
68 pub informative_frames: u32,
69 pub row_change: Vec<f64>,
70 pub col_change: Vec<f64>,
71 pub col_moving: Vec<f64>,
75 pub col_mean: Vec<f64>,
76 pub col_gain: Vec<f64>,
77 pub horizontal_gain: Vec<f64>,
78 pub native_row_change: Vec<f64>,
80 pub native_col_change: Vec<f64>,
81 pub native_col_mean: Vec<f64>,
82 pub native_frames: u32,
83 pub native_width: usize,
84 pub native_height: usize,
85}
86
87impl Learner {
88 pub fn new(width: usize, height: usize) -> Learner {
89 let cell = 24;
90 let cols = width.div_ceil(cell);
91 let rows = height.div_ceil(cell);
92 let n = cols * rows;
93 Learner {
94 width,
95 height,
96 cell,
97 cols,
98 rows,
99 split: vec![0.0; n * 2],
100 evidence: vec![0.0; n * 2],
101 activity: vec![0.0; n],
102 observations: vec![0.0; n],
103 row_fixed: vec![0.0; height],
104 row_moving: vec![0.0; height],
105 pixel_fixed: vec![0.0; width * height],
106 pixel_moving: vec![0.0; width * height],
107 informative_frames: 0,
108 row_change: vec![0.0; height],
109 col_change: vec![0.0; width],
110 col_moving: vec![0.0; width],
111 col_mean: vec![0.0; width],
112 col_gain: vec![0.0; cols],
113 horizontal_gain: vec![0.0; rows],
114 native_row_change: Vec::new(),
115 native_col_change: Vec::new(),
116 native_col_mean: Vec::new(),
117 native_frames: 0,
118 native_width: 0,
119 native_height: 0,
120 }
121 }
122
123 pub fn add(
127 &mut self,
128 field: &Field<'_>,
129 prev: &[u8],
130 current: &[u8],
131 native: Option<(&[u8], &[u8], usize, usize)>,
132 ) -> bool {
133 if !informative(field) {
134 return false;
135 }
136 self.informative_frames += 1;
137 if let Some((a, b, w, h)) = native {
138 self.add_native(a, b, w, h);
139 }
140 let (width, height, cols, rows) = (self.width, self.height, self.cols, self.rows);
141 for y in 0..height {
142 let (mut sum, mut n) = (0.0f64, 0u32);
143 let mut x = 0;
144 while x < width {
145 let i = y * width + x;
146 sum += (prev[i] as i32 - current[i] as i32).abs() as f64;
147 n += 1;
148 x += 3;
149 }
150 self.row_change[y] += sum / (n.max(1) as f64);
151 }
152 let (mut change, mut mean) = (vec![0.0f64; width], vec![0.0f64; width]);
155 let mut n = 0u32;
156 let mut y = 0;
157 while y < height {
158 let (p, c) = (
159 &prev[y * width..(y + 1) * width],
160 ¤t[y * width..(y + 1) * width],
161 );
162 for x in 0..width {
163 change[x] += (p[x] as i32 - c[x] as i32).abs() as f64;
164 mean[x] += c[x] as f64;
165 }
166 n += 1;
167 y += 3;
168 }
169 for x in 0..width {
170 self.col_change[x] += change[x] / n as f64;
171 if change[x] / n as f64 > MOVING_COLUMN {
172 self.col_moving[x] += 1.0;
173 }
174 self.col_mean[x] += mean[x] / n as f64;
175 }
176 let models = field.motions.len();
177 let mut columns = vec![0.0f64; cols * models];
178 let mut lines = vec![0.0f64; rows * models];
179 let informative_frames = self.informative_frames as f64;
180 for y in 0..rows {
181 for x in 0..cols {
182 let i = y * cols + x;
183 let py = (height - 1).min(((y as f64 + 0.5) * self.cell as f64).floor() as usize);
184 let label = field.labels[i] as usize;
187 if field.confidence[i] <= 110
188 || field.dynamic[i] != 0
189 || norm(&field.motions[label]) <= 1.0
190 || self.row_change[py] / informative_frames < 0.9
191 {
192 continue;
193 }
194 let w = field.confidence[i] as f64 / 255.0;
195 columns[x * models + label] += w;
196 lines[y * models + label] += w;
197 }
198 }
199 gain(&columns, models, &mut self.col_gain);
200 gain(&lines, models, &mut self.horizontal_gain);
201 for y in 0..rows {
202 for x in 0..cols {
203 let i = y * cols + x;
204 let m = &field.motions[field.labels[i] as usize];
205 let c = field.confidence[i] as f64 / 255.0;
206 if c > 0.25 && field.dynamic[i] == 0 {
207 self.activity[i] += norm(m) * c;
208 self.observations[i] += c;
209 }
210 let neighbours = [
211 if x + 1 < cols { Some(i + 1) } else { None },
212 if y + 1 < rows { Some(i + cols) } else { None },
213 ];
214 for (k, j) in neighbours.iter().enumerate() {
215 let Some(j) = *j else {
216 continue;
217 };
218 if field.dynamic[i] != 0 || field.dynamic[j] != 0 {
219 continue;
220 }
221 let weight = c.min(field.confidence[j] as f64 / 255.0);
222 if weight < 0.25 {
223 continue;
224 }
225 let other = &field.motions[field.labels[j] as usize];
226 let disagreement = js_hypot(m.x - other.x, m.y - other.y);
227 self.evidence[i * 2 + k] += weight;
228 if disagreement > 1.8 {
229 self.split[i * 2 + k] += weight;
230 }
231 }
232 }
233 }
234 let mut moving: Option<&FieldMotion> = None;
237 for m in field.motions {
238 if norm(m) > 1.0 && m.support >= 4 && moving.is_none_or(|best| m.support > best.support)
239 {
240 moving = Some(m);
241 }
242 }
243 if let Some(moving) = moving {
244 let dx = crate::kernels::geometry::js_round(moving.x) as i64;
245 let dy = crate::kernels::geometry::js_round(moving.y) as i64;
246 let (w, h) = (width as i64, height as i64);
247 for y in 2..(h - 2).max(2) {
248 let mut x = 2i64;
249 while x < w - 2 {
250 if x + dx < 1 || x + dx >= w - 1 || y + dy < 1 || y + dy >= h - 1 {
251 x += 4;
252 continue;
253 }
254 let i = (y * w + x) as usize;
255 let stationary = (prev[i] as i32 - current[i] as i32).abs();
256 let motion =
257 (prev[((y + dy) * w + x + dx) as usize] as i32 - current[i] as i32).abs();
258 if stationary + 6 < motion {
259 self.row_fixed[y as usize] += 1.0;
260 self.pixel_fixed[i] += 1.0;
261 }
262 if motion + 6 < stationary {
263 self.row_moving[y as usize] += 1.0;
264 self.pixel_moving[i] += 1.0;
265 }
266 x += 4;
267 }
268 }
269 }
270 true
271 }
272
273 fn add_native(&mut self, a: &[u8], b: &[u8], w: usize, h: usize) {
274 if self.native_row_change.is_empty() {
275 self.native_row_change = vec![0.0; h];
276 self.native_col_change = vec![0.0; w];
277 self.native_col_mean = vec![0.0; w];
278 self.native_width = w;
279 self.native_height = h;
280 }
281 if w != self.native_width || h != self.native_height {
282 return;
284 }
285 self.native_frames += 1;
286 let step = (w / 480).max(1);
287 let vstep = (h / 300).max(1);
288 let sample = |i: usize| {
291 ((a[i] as i32 - b[i] as i32).abs()
292 + (a[i + 1] as i32 - b[i + 1] as i32).abs()
293 + (a[i + 2] as i32 - b[i + 2] as i32).abs()) as f64
294 / 3.0
295 };
296 let rows_out = SyncPtr(self.native_row_change.as_mut_ptr());
297 let chunks = crate::kernels::pool::chunks_for(h * w.div_ceil(step), 32 * 1024);
298 crate::kernels::pool::par_for(chunks, |c| {
299 for y in crate::kernels::pool::split(h, chunks, c)
300 ..crate::kernels::pool::split(h, chunks, c + 1)
301 {
302 let (mut sum, mut n) = (0.0f64, 0u32);
303 let mut x = 0;
304 while x < w {
305 sum += sample((y * w + x) * 4);
306 n += 1;
307 x += step;
308 }
309 unsafe { *rows_out.get().add(y) += sum / (n.max(1) as f64) };
311 }
312 });
313 let samples = h.div_ceil(vstep);
314 let (mut change, mut mean) = (vec![0.0f64; w], vec![0.0f64; w]);
315 let (change_out, mean_out) = (SyncPtr(change.as_mut_ptr()), SyncPtr(mean.as_mut_ptr()));
316 let chunks = crate::kernels::pool::chunks_for(w * samples, 32 * 1024);
317 crate::kernels::pool::par_for(chunks, |c| {
318 let (x0, x1) = (
319 crate::kernels::pool::split(w, chunks, c),
320 crate::kernels::pool::split(w, chunks, c + 1),
321 );
322 let (change, mean) = unsafe {
324 (
325 std::slice::from_raw_parts_mut(change_out.get().add(x0), x1 - x0),
326 std::slice::from_raw_parts_mut(mean_out.get().add(x0), x1 - x0),
327 )
328 };
329 let mut y = 0;
330 while y < h {
331 for x in x0..x1 {
332 let i = (y * w + x) * 4;
333 change[x - x0] += sample(i);
334 mean[x - x0] += (b[i] as f64 + b[i + 1] as f64 + b[i + 2] as f64) / 3.0;
335 }
336 y += vstep;
337 }
338 });
339 let n = samples.max(1) as f64;
340 for x in 0..w {
341 self.native_col_change[x] += change[x] / n;
342 self.native_col_mean[x] += mean[x] / n;
343 }
344 }
345}
346
347fn gain(bins: &[f64], models: usize, output: &mut [f64]) {
350 let count = output.len();
351 let mut total = vec![0.0f64; models];
352 let mut left = vec![0.0f64; models];
353 let mut weight = 0.0f64;
354 for k in 0..count {
355 for m in 0..models {
356 total[m] += bins[k * models + m];
357 weight += bins[k * models + m];
358 }
359 }
360 if weight < 10.0 {
361 return;
362 }
363 let all = total.iter().copied().fold(f64::NEG_INFINITY, f64::max);
365 for k in 1..count {
366 let (mut a, mut b) = (0.0f64, 0.0f64);
367 for m in 0..models {
368 left[m] += bins[(k - 1) * models + m];
369 a = a.max(left[m]);
370 b = b.max(total[m] - left[m]);
371 }
372 output[k] += (a + b - all) / weight;
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn uninformative_fields_leave_every_accumulator_untouched() {
382 let mut learner = Learner::new(48, 24);
383 let motions = [FieldMotion {
384 x: 0.0,
385 y: 0.0,
386 support: 10,
387 confidence: 1.0,
388 }];
389 let cells = learner.cols * learner.rows;
390 let field = Field {
391 motions: &motions,
392 labels: &vec![0u8; cells],
393 confidence: &vec![255u8; cells],
394 dynamic: &vec![0u8; cells],
395 cols: learner.cols,
396 rows: learner.rows,
397 difference: 1.0,
398 unknown: false,
399 };
400 let gray = vec![7u8; 48 * 24];
401 assert!(!learner.add(&field, &gray, &gray, None));
402 assert_eq!(learner.informative_frames, 0);
403 assert!(learner.row_change.iter().all(|&v| v == 0.0));
404 }
405}