use crate::error::{CvError, CvResult};
use crate::stabilize::MotionParameters;
use std::f64::consts::PI;
pub trait MotionSmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters>;
}
#[derive(Debug, Clone)]
pub struct GaussianSmoother {
window_size: usize,
sigma: f64,
kernel: Vec<f64>,
}
impl GaussianSmoother {
#[must_use]
pub fn new(window_size: usize, sigma: f64) -> Self {
let kernel = Self::compute_gaussian_kernel(window_size, sigma);
Self {
window_size,
sigma,
kernel,
}
}
#[must_use]
pub fn with_window_size(mut self, size: usize) -> Self {
self.window_size = size;
self.kernel = Self::compute_gaussian_kernel(size, self.sigma);
self
}
#[must_use]
pub fn with_sigma(mut self, sigma: f64) -> Self {
self.sigma = sigma;
self.kernel = Self::compute_gaussian_kernel(self.window_size, sigma);
self
}
fn compute_gaussian_kernel(size: usize, sigma: f64) -> Vec<f64> {
let half = (size / 2) as i32;
let mut kernel = Vec::with_capacity(size);
let mut sum = 0.0;
for i in -half..=half {
let x = i as f64;
let value = (-x * x / (2.0 * sigma * sigma)).exp();
kernel.push(value);
sum += value;
}
for value in &mut kernel {
*value /= sum;
}
kernel
}
fn smooth_signal(&self, signal: &[f64]) -> Vec<f64> {
let len = signal.len();
let mut smoothed = vec![0.0; len];
let half = (self.window_size / 2) as i32;
for i in 0..len {
let mut sum = 0.0;
let mut weight_sum = 0.0;
for (k, &kernel_value) in self.kernel.iter().enumerate() {
let offset = k as i32 - half;
let idx = i as i32 + offset;
if idx >= 0 && idx < len as i32 {
sum += signal[idx as usize] * kernel_value;
weight_sum += kernel_value;
}
}
smoothed[i] = if weight_sum > 0.0 {
sum / weight_sum
} else {
signal[i]
};
}
smoothed
}
}
impl MotionSmoother for GaussianSmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.smooth_signal(¶ms.dx),
dy: self.smooth_signal(¶ms.dy),
da: self.smooth_signal(¶ms.da),
ds: self.smooth_signal(¶ms.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct LowPassFilter {
cutoff: f64,
alpha: f64,
}
impl LowPassFilter {
#[must_use]
pub fn new(cutoff: f64) -> Self {
let alpha = Self::compute_alpha(cutoff);
Self { cutoff, alpha }
}
#[must_use]
pub fn with_cutoff(mut self, cutoff: f64) -> Self {
self.cutoff = cutoff.clamp(0.0, 1.0);
self.alpha = Self::compute_alpha(self.cutoff);
self
}
fn compute_alpha(cutoff: f64) -> f64 {
let rc = 1.0 / (2.0 * PI * cutoff);
let dt = 1.0;
dt / (rc + dt)
}
fn filter_signal(&self, signal: &[f64]) -> Vec<f64> {
if signal.is_empty() {
return Vec::new();
}
let mut filtered = Vec::with_capacity(signal.len());
filtered.push(signal[0]);
for i in 1..signal.len() {
let value = self.alpha * signal[i] + (1.0 - self.alpha) * filtered[i - 1];
filtered.push(value);
}
filtered
}
}
impl MotionSmoother for LowPassFilter {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.filter_signal(¶ms.dx),
dy: self.filter_signal(¶ms.dy),
da: self.filter_signal(¶ms.da),
ds: self.filter_signal(¶ms.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct AdaptiveSmoother {
base_window: usize,
magnitude_threshold: f64,
gaussian_smoother: GaussianSmoother,
}
impl AdaptiveSmoother {
#[must_use]
pub fn new(window_size: usize, magnitude_threshold: f64) -> Self {
Self {
base_window: window_size,
magnitude_threshold,
gaussian_smoother: GaussianSmoother::new(window_size, 3.0),
}
}
#[must_use]
pub fn with_window_size(mut self, size: usize) -> Self {
self.base_window = size;
self.gaussian_smoother = self.gaussian_smoother.with_window_size(size);
self
}
#[must_use]
pub fn with_magnitude_threshold(mut self, threshold: f64) -> Self {
self.magnitude_threshold = threshold;
self
}
fn compute_magnitudes(&self, params: &MotionParameters) -> Vec<f64> {
let len = params.dx.len();
let mut magnitudes = Vec::with_capacity(len);
for i in 0..len {
let mag = (params.dx[i] * params.dx[i] + params.dy[i] * params.dy[i]).sqrt();
magnitudes.push(mag);
}
magnitudes
}
fn compute_adaptive_weights(&self, magnitudes: &[f64]) -> Vec<f64> {
magnitudes
.iter()
.map(|&mag| {
let normalized = mag / self.magnitude_threshold;
1.0 / (1.0 + (-5.0 * (normalized - 1.0)).exp())
})
.collect()
}
fn smooth_signal_adaptive(
&self,
signal: &[f64],
weights: &[f64],
smoothed: &[f64],
) -> Vec<f64> {
signal
.iter()
.zip(smoothed.iter())
.zip(weights.iter())
.map(|((&original, &smooth), &weight)| {
original * weight + smooth * (1.0 - weight)
})
.collect()
}
pub fn smooth(
&mut self,
params: &MotionParameters,
max_magnitude: f64,
) -> CvResult<MotionParameters> {
self.magnitude_threshold = max_magnitude;
let magnitudes = self.compute_magnitudes(params);
let weights = self.compute_adaptive_weights(&magnitudes);
let gaussian_smoothed = self.gaussian_smoother.smooth(params)?;
Ok(MotionParameters {
dx: self.smooth_signal_adaptive(¶ms.dx, &weights, &gaussian_smoothed.dx),
dy: self.smooth_signal_adaptive(¶ms.dy, &weights, &gaussian_smoothed.dy),
da: self.smooth_signal_adaptive(¶ms.da, &weights, &gaussian_smoothed.da),
ds: self.smooth_signal_adaptive(¶ms.ds, &weights, &gaussian_smoothed.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct MovingAverageSmoother {
window_size: usize,
}
impl MovingAverageSmoother {
#[must_use]
pub const fn new(window_size: usize) -> Self {
Self { window_size }
}
#[must_use]
pub const fn with_window_size(mut self, size: usize) -> Self {
self.window_size = size;
self
}
fn smooth_signal(&self, signal: &[f64]) -> Vec<f64> {
let len = signal.len();
let mut smoothed = Vec::with_capacity(len);
let half = (self.window_size / 2) as i32;
for i in 0..len {
let mut sum = 0.0;
let mut count = 0;
let start = (i as i32 - half).max(0) as usize;
let end = (i as i32 + half + 1).min(len as i32) as usize;
for j in start..end {
sum += signal[j];
count += 1;
}
smoothed.push(sum / count as f64);
}
smoothed
}
}
impl MotionSmoother for MovingAverageSmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.smooth_signal(¶ms.dx),
dy: self.smooth_signal(¶ms.dy),
da: self.smooth_signal(¶ms.da),
ds: self.smooth_signal(¶ms.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct MedianFilterSmoother {
window_size: usize,
}
impl MedianFilterSmoother {
#[must_use]
pub const fn new(window_size: usize) -> Self {
Self { window_size }
}
#[must_use]
pub const fn with_window_size(mut self, size: usize) -> Self {
self.window_size = size;
self
}
fn smooth_signal(&self, signal: &[f64]) -> Vec<f64> {
let len = signal.len();
let mut smoothed = Vec::with_capacity(len);
let half = (self.window_size / 2) as i32;
for i in 0..len {
let mut window = Vec::new();
let start = (i as i32 - half).max(0) as usize;
let end = (i as i32 + half + 1).min(len as i32) as usize;
for j in start..end {
window.push(signal[j]);
}
window.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median = window[window.len() / 2];
smoothed.push(median);
}
smoothed
}
}
impl MotionSmoother for MedianFilterSmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.smooth_signal(¶ms.dx),
dy: self.smooth_signal(¶ms.dy),
da: self.smooth_signal(¶ms.da),
ds: self.smooth_signal(¶ms.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct ExponentialSmoother {
alpha: f64,
}
impl ExponentialSmoother {
#[must_use]
pub fn new(alpha: f64) -> Self {
Self {
alpha: alpha.clamp(0.0, 1.0),
}
}
#[must_use]
pub fn with_alpha(mut self, alpha: f64) -> Self {
self.alpha = alpha.clamp(0.0, 1.0);
self
}
fn smooth_signal(&self, signal: &[f64]) -> Vec<f64> {
if signal.is_empty() {
return Vec::new();
}
let mut smoothed = Vec::with_capacity(signal.len());
smoothed.push(signal[0]);
for i in 1..signal.len() {
let value = self.alpha * signal[i] + (1.0 - self.alpha) * smoothed[i - 1];
smoothed.push(value);
}
smoothed
}
}
impl MotionSmoother for ExponentialSmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.smooth_signal(¶ms.dx),
dy: self.smooth_signal(¶ms.dy),
da: self.smooth_signal(¶ms.da),
ds: self.smooth_signal(¶ms.ds),
})
}
}
#[derive(Debug, Clone)]
pub struct SavitzkyGolaySmoother {
window_size: usize,
polynomial_order: usize,
}
impl SavitzkyGolaySmoother {
#[must_use]
pub const fn new(window_size: usize, polynomial_order: usize) -> Self {
Self {
window_size,
polynomial_order,
}
}
fn smooth_signal(&self, signal: &[f64]) -> Vec<f64> {
let len = signal.len();
let mut smoothed = Vec::with_capacity(len);
let half = (self.window_size / 2) as i32;
for i in 0..len {
let mut sum = 0.0;
let mut count = 0;
let start = (i as i32 - half).max(0) as usize;
let end = (i as i32 + half + 1).min(len as i32) as usize;
for j in start..end {
sum += signal[j];
count += 1;
}
smoothed.push(sum / count as f64);
}
smoothed
}
}
impl MotionSmoother for SavitzkyGolaySmoother {
fn smooth(&mut self, params: &MotionParameters) -> CvResult<MotionParameters> {
Ok(MotionParameters {
dx: self.smooth_signal(¶ms.dx),
dy: self.smooth_signal(¶ms.dy),
da: self.smooth_signal(¶ms.da),
ds: self.smooth_signal(¶ms.ds),
})
}
}