#![allow(clippy::needless_range_loop)]
use std::f64::consts::PI;
pub fn filter_by_distance(peaks: &[usize], data: &[f64], min_distance: usize) -> Vec<usize> {
if peaks.is_empty() {
return vec![];
}
let mut sorted: Vec<(usize, f64)> = peaks.iter().map(|&i| (i, data[i])).collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut keep = vec![true; peaks.len()];
let mut result = Vec::new();
for (i, &(idx, _)) in sorted.iter().enumerate() {
if keep[i] {
result.push(idx);
for (j, &(other_idx, _)) in sorted.iter().enumerate() {
if j != i && keep[j] && idx.abs_diff(other_idx) < min_distance {
keep[j] = false;
}
}
}
}
result.sort();
result
}
pub fn compute_prominences(peaks: &[usize], data: &[f64]) -> Vec<f64> {
let n = data.len();
let mut prominences = Vec::with_capacity(peaks.len());
for &peak in peaks {
let peak_height = data[peak];
let mut left_min = peak_height;
for i in (0..peak).rev() {
if data[i] > peak_height {
break;
}
left_min = left_min.min(data[i]);
}
let mut right_min = peak_height;
for i in peak + 1..n {
if data[i] > peak_height {
break;
}
right_min = right_min.min(data[i]);
}
let base = left_min.max(right_min);
prominences.push(peak_height - base);
}
prominences
}
pub fn compute_savgol_coeffs(window_length: usize, polyorder: usize, deriv: usize) -> Vec<f64> {
let half = window_length / 2;
let m = polyorder + 1;
let mut a = vec![vec![0.0; m]; window_length];
for i in 0..window_length {
let x = i as f64 - half as f64;
let mut xi = 1.0;
for j in 0..m {
a[i][j] = xi;
xi *= x;
}
}
let mut ata = vec![vec![0.0; m]; m];
for i in 0..m {
for j in 0..m {
for k in 0..window_length {
ata[i][j] += a[k][i] * a[k][j];
}
}
}
let mut at_e = vec![0.0; m];
let factorial: f64 = (1..=deriv).map(|x| x as f64).product();
for k in 0..window_length {
at_e[deriv] += a[k][deriv];
}
at_e[deriv] = factorial;
let mut augmented = vec![vec![0.0; m + 1]; m];
for i in 0..m {
for j in 0..m {
augmented[i][j] = ata[i][j];
}
augmented[i][m] = at_e[i];
}
for i in 0..m {
let mut max_row = i;
for k in i + 1..m {
if augmented[k][i].abs() > augmented[max_row][i].abs() {
max_row = k;
}
}
augmented.swap(i, max_row);
let pivot = augmented[i][i];
if pivot.abs() < 1e-30 {
continue;
}
for j in i..=m {
augmented[i][j] /= pivot;
}
for k in 0..m {
if k != i {
let factor = augmented[k][i];
for j in i..=m {
augmented[k][j] -= factor * augmented[i][j];
}
}
}
}
let x: Vec<f64> = augmented.iter().map(|row| row[m]).collect();
let mut coeffs = vec![0.0; window_length];
for i in 0..window_length {
for j in 0..m {
coeffs[i] += x[j] * a[i][j];
}
}
coeffs
}
pub fn apply_butter_lowpass(data: &[f64], cutoff: f64, order: usize, zero_phase: bool) -> Vec<f64> {
let alpha = 2.0 * PI * cutoff / (2.0 + 2.0 * PI * cutoff);
let mut filtered = data.to_vec();
for _ in 0..order {
let mut y_prev = filtered[0];
for i in 0..filtered.len() {
let y = alpha * filtered[i] + (1.0 - alpha) * y_prev;
filtered[i] = y;
y_prev = y;
}
if zero_phase {
y_prev = filtered[filtered.len() - 1];
for i in (0..filtered.len()).rev() {
let y = alpha * filtered[i] + (1.0 - alpha) * y_prev;
filtered[i] = y;
y_prev = y;
}
}
}
filtered
}
pub fn apply_fir_lowpass(data: &[f64], cutoff: f64, filter_len: usize) -> Vec<f64> {
let half = filter_len / 2;
let mut h = Vec::with_capacity(filter_len);
for i in 0..filter_len {
let n = i as f64 - half as f64;
let sinc = if n.abs() < 1e-10 {
2.0 * cutoff
} else {
(2.0 * PI * cutoff * n).sin() / (PI * n)
};
let window = 0.54 - 0.46 * (2.0 * PI * i as f64 / (filter_len - 1) as f64).cos();
h.push(sinc * window);
}
let sum: f64 = h.iter().sum();
if sum.abs() > 1e-10 {
for c in &mut h {
*c /= sum;
}
}
let n = data.len();
let mut result = vec![0.0; n];
for i in 0..n {
for (j, &hj) in h.iter().enumerate() {
let k = i as isize + j as isize - half as isize;
if k >= 0 && (k as usize) < n {
result[i] += data[k as usize] * hj;
}
}
}
result
}