1pub mod subtractor;
2
3pub use subtractor::BackgroundSubtractor;
4
5use crate::core::types::Rect;
6use crate::error::{IrisError, Result};
7use crate::image::Image;
8use burn::tensor::backend::Backend;
9
10pub enum TrackerType {
12 KCF,
13 CSRT,
14 MOSSE,
15}
16
17struct MosseState {
22 filter_freq: Vec<f64>,
24 learning_rate: f64,
26 target_size: (usize, usize),
28 prev_gray: Option<Vec<f32>>,
30 prev_bbox: Option<Rect<usize>>,
32 search_w: usize,
34 search_h: usize,
36}
37
38impl MosseState {
39 fn new() -> Self {
40 Self {
41 filter_freq: Vec::new(),
42 learning_rate: 0.125,
43 target_size: (0, 0),
44 prev_gray: None,
45 prev_bbox: None,
46 search_w: 0,
47 search_h: 0,
48 }
49 }
50
51 fn init(&mut self, gray_data: &[f32], img_w: usize, bbox: Rect<usize>) {
52 let cy = bbox.y + bbox.height / 2;
53 let cx = bbox.x + bbox.width / 2;
54 let patch_h = bbox.height.max(4);
55 let patch_w = bbox.width.max(4);
56
57 let size = patch_h.max(patch_w);
59 self.target_size = (size, size);
60 self.search_w = size * 2;
61 self.search_h = size * 2;
62
63 let patch = extract_patch(gray_data, img_w, img_w, cx, cy, size, size);
65
66 let patch_freq = simple_dft_2d(&patch, size, size);
68
69 let mut target = vec![0.0f64; size * size];
71 let center = size as f64 / 2.0;
72 let sigma = size as f64 / 4.0;
73 for y in 0..size {
74 for x in 0..size {
75 let dx = x as f64 - center;
76 let dy = y as f64 - center;
77 target[y * size + x] = (-(dx * dx + dy * dy) / (2.0 * sigma * sigma)).exp();
78 }
79 }
80 let target_freq = simple_dft_2d(&target, size, size);
81
82 let mut filter = Vec::with_capacity(size * size);
84 let epsilon = 1e-4;
85 for i in 0..size * size {
86 let a_re = patch_freq[i * 2];
87 let a_im = patch_freq[i * 2 + 1];
88 let g_re = target_freq[i * 2];
89 let g_im = target_freq[i * 2 + 1];
90 let denom = a_re * a_re + a_im * a_im + epsilon;
91 filter.push((g_re * a_re + g_im * a_im) / denom);
92 filter.push((g_im * a_re - g_re * a_im) / denom);
93 }
94
95 self.filter_freq = filter;
96 self.prev_gray = Some(gray_data.to_vec());
97 self.prev_bbox = Some(bbox);
98 }
99
100 fn update(&mut self, gray_data: &[f32], img_w: usize) -> Option<Rect<usize>> {
101 let prev_bbox = self.prev_bbox?;
102 let (size, _) = self.target_size;
103
104 let cy = prev_bbox.y + prev_bbox.height / 2;
105 let cx = prev_bbox.x + prev_bbox.width / 2;
106
107 let search_cx = cx;
109 let search_cy = cy;
110
111 let patch = extract_patch(gray_data, img_w, img_w, search_cx, search_cy, size, size);
112 let patch_freq = simple_dft_2d(&patch, size, size);
113
114 let mut response = vec![0.0f64; size * size];
116 let mut max_val = f64::NEG_INFINITY;
117 let mut max_idx = 0;
118
119 for i in 0..size * size {
120 let p_re = patch_freq[i * 2];
121 let p_im = patch_freq[i * 2 + 1];
122 let h_re = self.filter_freq[i * 2];
123 let h_im = self.filter_freq[i * 2 + 1];
124 let re = h_re * p_re + h_im * p_im;
126 let _im = h_im * p_re - h_re * p_im;
127 response[i] = re; if re > max_val {
129 max_val = re;
130 max_idx = i;
131 }
132 }
133
134 let peak_y = max_idx / size;
136 let peak_x = max_idx % size;
137 let dy = peak_y as i32 - size as i32 / 2;
138 let dx = peak_x as i32 - size as i32 / 2;
139
140 let new_cx = (cx as i32 + dx).max(0) as usize;
141 let new_cy = (cy as i32 + dy).max(0) as usize;
142
143 let new_patch = extract_patch(gray_data, img_w, img_w, new_cx, new_cy, size, size);
145 let new_freq = simple_dft_2d(&new_patch, size, size);
146
147 let alpha = self.learning_rate;
148 for i in 0..size * size {
149 self.filter_freq[i * 2] =
150 self.filter_freq[i * 2] * (1.0 - alpha) + new_freq[i * 2] * alpha;
151 self.filter_freq[i * 2 + 1] =
152 self.filter_freq[i * 2 + 1] * (1.0 - alpha) + new_freq[i * 2 + 1] * alpha;
153 }
154
155 let new_bbox = Rect::new(
156 new_cx.saturating_sub(prev_bbox.width / 2),
157 new_cy.saturating_sub(prev_bbox.height / 2),
158 prev_bbox.width,
159 prev_bbox.height,
160 );
161
162 self.prev_gray = Some(gray_data.to_vec());
163 self.prev_bbox = Some(new_bbox);
164
165 Some(new_bbox)
166 }
167}
168
169fn extract_patch(
171 data: &[f32],
172 img_w: usize,
173 img_h: usize,
174 cx: usize,
175 cy: usize,
176 patch_w: usize,
177 patch_h: usize,
178) -> Vec<f64> {
179 let mut patch = vec![0.0f64; patch_h * patch_w];
180 let half_w = patch_w / 2;
181 let half_h = patch_h / 2;
182
183 for py in 0..patch_h {
184 for px in 0..patch_w {
185 let sx = cx as i32 + px as i32 - half_w as i32;
186 let sy = cy as i32 + py as i32 - half_h as i32;
187 if sx >= 0 && sx < img_w as i32 && sy >= 0 && sy < img_h as i32 {
188 patch[py * patch_w + px] = data[sy as usize * img_w + sx as usize] as f64;
189 }
190 }
191 }
192 patch
193}
194
195fn simple_dft_2d(data: &[f64], w: usize, h: usize) -> Vec<f64> {
198 let mut result = vec![0.0f64; w * h * 2];
199 let n = w * h;
200
201 for u in 0..h {
202 for v in 0..w {
203 let mut sum_re = 0.0f64;
204 let mut sum_im = 0.0f64;
205
206 for y in 0..h {
207 for x in 0..w {
208 let angle = -2.0
209 * std::f64::consts::PI
210 * ((u * y) as f64 / h as f64 + (v * x) as f64 / w as f64);
211 let val = data[y * w + x];
212 sum_re += val * angle.cos();
213 sum_im += val * angle.sin();
214 }
215 }
216
217 result[(u * w + v) * 2] = sum_re / (n as f64).sqrt();
218 result[(u * w + v) * 2 + 1] = sum_im / (n as f64).sqrt();
219 }
220 }
221
222 result
223}
224
225pub struct MeanShiftTracker {
236 bbox: Option<Rect<usize>>,
238 model_hist: Vec<f32>,
240 bins: usize,
242 spatial_radius: f32,
244 max_iter: usize,
246 epsilon: f32,
248 last_w: usize,
250 last_h: usize,
252}
253
254impl Default for MeanShiftTracker {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl MeanShiftTracker {
261 #[must_use]
263 pub fn new() -> Self {
264 Self {
265 bbox: None,
266 model_hist: Vec::new(),
267 bins: 16,
268 spatial_radius: 0.0,
269 max_iter: 30,
270 epsilon: 0.5,
271 last_w: 0,
272 last_h: 0,
273 }
274 }
275
276 #[must_use]
278 pub fn with_bins(mut self, bins: usize) -> Self {
279 self.bins = bins;
280 self
281 }
282
283 #[must_use]
285 pub fn with_max_iter(mut self, max_iter: usize) -> Self {
286 self.max_iter = max_iter;
287 self
288 }
289
290 pub fn init<B: Backend>(&mut self, image: &Image<B>, roi: Rect<usize>) -> Result<()> {
292 let dims = image.tensor.dims();
293 let c = dims[0];
294 let h = dims[1];
295 let w = dims[2];
296
297 if c < 3 {
298 return Err(IrisError::InvalidParameter(
299 "MeanShift requires at least a 3-channel image".into(),
300 ));
301 }
302
303 let tensor_data = image.tensor.clone().into_data();
304 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
305
306 let cx = roi.x + roi.width / 2;
307 let cy = roi.y + roi.height / 2;
308 self.spatial_radius =
309 ((roi.width as f32).powi(2) + (roi.height as f32).powi(2)).sqrt() / 2.0;
310
311 let total_bins = self.bins * self.bins * self.bins;
314 let mut hist = vec![0.0f32; total_bins];
315
316 for y in roi.y..(roi.y + roi.height).min(h) {
317 for x in roi.x..(roi.x + roi.width).min(w) {
318 let idx = (y * w + x) * 3;
319 let r = flat_vals[idx];
320 let g = flat_vals[idx + 1];
321 let b = flat_vals[idx + 2];
322
323 let ri = ((r * self.bins as f32) as usize).min(self.bins - 1);
324 let gi = ((g * self.bins as f32) as usize).min(self.bins - 1);
325 let bi = ((b * self.bins as f32) as usize).min(self.bins - 1);
326 let bin = ri * self.bins * self.bins + gi * self.bins + bi;
327
328 let dx = x as f32 - cx as f32;
330 let dy = y as f32 - cy as f32;
331 let dist = (dx * dx + dy * dy).sqrt();
332 let weight = if dist <= self.spatial_radius {
333 1.0 - (dist / self.spatial_radius).powi(2)
334 } else {
335 0.0
336 };
337 hist[bin] += weight;
338 }
339 }
340
341 let sum: f32 = hist.iter().sum();
343 if sum > 1e-10 {
344 for v in hist.iter_mut() {
345 *v /= sum;
346 }
347 }
348
349 self.model_hist = hist;
350 self.bbox = Some(roi);
351 self.last_w = w;
352 self.last_h = h;
353
354 Ok(())
355 }
356
357 pub fn update<B: Backend>(&mut self, image: &Image<B>) -> Result<Rect<usize>> {
359 let dims = image.tensor.dims();
360 let c = dims[0];
361 let h = dims[1];
362 let w = dims[2];
363
364 let cur_bbox = self.bbox.ok_or_else(|| {
365 IrisError::Generic("MeanShiftTracker not initialised. Call init first.".into())
366 })?;
367
368 if c < 3 {
369 return Err(IrisError::InvalidParameter(
370 "MeanShift requires at least a 3-channel image".into(),
371 ));
372 }
373
374 let tensor_data = image.tensor.clone().into_data();
375 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
376
377 let mut cx = cur_bbox.x as f32 + cur_bbox.width as f32 / 2.0;
378 let mut cy = cur_bbox.y as f32 + cur_bbox.height as f32 / 2.0;
379 let hw = cur_bbox.width as f32 / 2.0;
380 let hh = cur_bbox.height as f32 / 2.0;
381
382 for _ in 0..self.max_iter {
383 let mut sum_wx = 0.0f64;
385 let mut sum_wy = 0.0f64;
386 let mut sum_w = 0.0f64;
387
388 let y_min = (cy - hh).max(0.0) as usize;
389 let y_max = (cy + hh).min(h as f32 - 1.0) as usize;
390 let x_min = (cx - hw).max(0.0) as usize;
391 let x_max = (cx + hw).min(w as f32 - 1.0) as usize;
392
393 for y in y_min..=y_max {
394 for x in x_min..=x_max {
395 let idx = (y * w + x) * 3;
396 let r = flat_vals[idx];
397 let g = flat_vals[idx + 1];
398 let b = flat_vals[idx + 2];
399
400 let ri = ((r * self.bins as f32) as usize).min(self.bins - 1);
401 let gi = ((g * self.bins as f32) as usize).min(self.bins - 1);
402 let bi = ((b * self.bins as f32) as usize).min(self.bins - 1);
403 let bin = ri * self.bins * self.bins + gi * self.bins + bi;
404
405 let model_val = self.model_hist[bin];
406 if model_val < 1e-10 {
407 continue;
408 }
409
410 let dx = x as f32 - cx;
412 let dy = y as f32 - cy;
413 let dist = (dx * dx + dy * dy).sqrt();
414 let kernel_weight = if dist <= self.spatial_radius {
415 1.0 - (dist / self.spatial_radius).powi(2)
416 } else {
417 0.0
418 };
419
420 let w_i = model_val * kernel_weight;
421 sum_wx += w_i as f64 * x as f64;
422 sum_wy += w_i as f64 * y as f64;
423 sum_w += w_i as f64;
424 }
425 }
426
427 if sum_w < 1e-10 {
428 break;
429 }
430
431 let new_cx = (sum_wx / sum_w) as f32;
432 let new_cy = (sum_wy / sum_w) as f32;
433 let shift_x = new_cx - cx;
434 let shift_y = new_cy - cy;
435
436 cx = new_cx;
437 cy = new_cy;
438
439 if (shift_x * shift_x + shift_y * shift_y).sqrt() < self.epsilon {
440 break;
441 }
442 }
443
444 let new_bbox = Rect::new(
445 (cx - hw).round().max(0.0) as usize,
446 (cy - hh).round().max(0.0) as usize,
447 cur_bbox.width,
448 cur_bbox.height,
449 );
450
451 self.bbox = Some(new_bbox);
452 self.last_w = w;
453 self.last_h = h;
454
455 Ok(new_bbox)
456 }
457}
458
459pub struct Tracker<B: Backend> {
461 pub tracker_type: TrackerType,
462 pub bbox: Option<Rect<usize>>,
463 mosse_state: MosseState,
464 _marker: std::marker::PhantomData<B>,
465}
466
467impl<B: Backend> Tracker<B> {
468 #[must_use]
470 pub fn new(tracker_type: TrackerType) -> Self {
471 Self {
472 tracker_type,
473 bbox: None,
474 mosse_state: MosseState::new(),
475 _marker: std::marker::PhantomData,
476 }
477 }
478
479 pub fn init(&mut self, image: &Image<B>, bbox: Rect<usize>) -> Result<()> {
481 self.bbox = Some(bbox);
482
483 let gray = image.grayscale()?;
485 let dims = gray.tensor.dims();
486 let _h = dims[1];
487 let w = dims[2];
488 let tensor_data = gray.tensor.clone().into_data();
489 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
490 self.mosse_state.init(&flat_vals, w, bbox);
491
492 Ok(())
493 }
494
495 pub fn update(&mut self, image: &Image<B>) -> Result<Rect<usize>> {
497 let current = self.bbox.ok_or_else(|| {
498 crate::error::IrisError::Generic("Tracker not initialized. Call init first.".into())
499 })?;
500
501 let gray = image.grayscale()?;
503 let dims = gray.tensor.dims();
504 let _h = dims[1];
505 let w = dims[2];
506 let tensor_data = gray.tensor.clone().into_data();
507 let flat_vals: Vec<f32> = tensor_data.iter::<f32>().collect();
508
509 if let Some(new_bbox) = self.mosse_state.update(&flat_vals, w) {
510 self.bbox = Some(new_bbox);
511 return Ok(new_bbox);
512 }
513
514 Ok(current)
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use crate::test_helpers::{TestBackend, test_device};
523 use burn::tensor::{Tensor, TensorData};
524
525 #[test]
526 fn test_mosse_tracker() {
527 let device = test_device();
528 let flat_data = vec![0.5f32; 3 * 32 * 32];
529 let tensor =
530 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 32, 32]), &device);
531 let img = Image::new(tensor);
532
533 let mut tracker = Tracker::new(TrackerType::MOSSE);
534 let init_bbox = Rect::new(8, 8, 16, 16);
535 tracker.init(&img, init_bbox).unwrap();
536
537 let updated = tracker.update(&img).unwrap();
538 assert!(updated.width > 0);
540 assert!(updated.height > 0);
541 }
542
543 #[test]
544 fn test_meanshift_tracker_init_and_update() {
545 let device = test_device();
546
547 let mut flat_data = vec![0.2f32; 3 * 32 * 32];
550 for y in 12..20 {
552 for x in 12..20 {
553 let idx = (y * 32 + x) * 3;
554 flat_data[idx] = 1.0; flat_data[idx + 1] = 0.0; flat_data[idx + 2] = 0.0; }
558 }
559 let tensor =
560 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 32, 32]), &device);
561 let img = Image::new(tensor);
562
563 let mut tracker = MeanShiftTracker::new().with_bins(8).with_max_iter(10);
564 let roi = Rect::new(10, 10, 12, 12);
565 tracker.init(&img, roi).unwrap();
566
567 let updated = tracker.update(&img).unwrap();
569 assert!(updated.width > 0);
570 assert!(updated.height > 0);
571 let ucx = updated.x + updated.width / 2;
573 let ucy = updated.y + updated.height / 2;
574 assert!(
575 (ucx as isize - 16).unsigned_abs() <= 2 && (ucy as isize - 16).unsigned_abs() <= 2,
576 "Expected centre near (16,16), got ({ucx},{ucy})"
577 );
578 }
579
580 #[test]
581 fn test_tracker_api() {
582 let device = test_device();
583 let flat_data = vec![0.5f32; 3 * 16 * 16];
584 let tensor =
585 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 16, 16]), &device);
586 let img = Image::new(tensor);
587
588 let mut tracker = Tracker::new(TrackerType::KCF);
589 let init_bbox = Rect::new(2, 2, 8, 8);
590 tracker.init(&img, init_bbox).unwrap();
591
592 let updated = tracker.update(&img).unwrap();
593 assert_eq!(updated.width, 8);
594 assert_eq!(updated.height, 8);
595 }
596}