use crate::float::Float;
pub enum PeakCorrection {
Quadratic,
None,
}
struct PeaksIter<'a, T> {
index: usize,
data: &'a [T],
}
impl<'a, T: Float> PeaksIter<'a, T> {
fn new(arr: &'a [T]) -> Self {
Self {
data: arr,
index: 0,
}
}
}
impl<'a, T: Float> Iterator for PeaksIter<'a, T> {
type Item = (usize, T);
fn next(&mut self) -> Option<(usize, T)> {
let mut idx = self.index;
let mut max = -T::infinity();
let mut max_index = idx;
if idx >= self.data.len() {
return None;
}
if idx == 0 {
idx += self
.data
.iter()
.take_while(|val| !val.is_sign_negative())
.count();
}
idx += self.data[idx..]
.iter()
.take_while(|val| val.is_sign_negative())
.count();
for val in &self.data[idx..] {
if val.is_sign_negative() {
break;
}
if *val > max {
max = *val;
max_index = idx;
}
idx += 1;
}
self.index = idx;
if max == -T::infinity() || idx == self.data.len() {
return None;
}
Some((max_index, max))
}
}
pub fn detect_peaks<'a, T: Float>(arr: &'a [T]) -> impl Iterator<Item = (usize, T)> + 'a {
PeaksIter::new(arr)
}
pub fn choose_peak<I: Iterator<Item = (usize, T)>, T: Float>(
mut peaks: I,
threshold: T,
) -> Option<(usize, T)> {
peaks.find(|p| p.1 > threshold)
}
pub fn correct_peak<T: Float>(peak: (usize, T), data: &[T], correction: PeakCorrection) -> (T, T) {
match correction {
PeakCorrection::Quadratic => {
let idx = peak.0;
let (x, y) = find_quadratic_peak(data[idx - 1], data[idx], data[idx + 1]);
(x + T::from_usize(idx).unwrap(), y)
}
PeakCorrection::None => (T::from_usize(peak.0).unwrap(), peak.1),
}
}
fn find_quadratic_peak<T: Float>(y0: T, y1: T, y2: T) -> (T, T) {
let two = T::from_f64(2.).unwrap();
let four = T::from_f64(4.).unwrap();
let a = (y0 + y2) / two - y1;
let b = (y2 - y0) / two;
let c = y1;
if a > T::zero() {
if y0 > y2 {
return (-T::one(), y0);
}
return (T::one(), y2);
}
(-b / (two * a), -b * b / (four * a) + c)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn peak_correction() {
fn quad1(x: f64) -> f64 {
-x * x + 4.0
}
let (x, y) = find_quadratic_peak(quad1(-1.5), quad1(-0.5), quad1(0.5));
assert_eq!(x - 0.5, 0.0);
assert_eq!(y, 4.0);
let (x, y) = find_quadratic_peak(quad1(-3.5), quad1(-2.5), quad1(-1.5));
assert_eq!(x - 2.5, 0.0);
assert_eq!(y, 4.0);
fn quad2(x: f64) -> f64 {
-2. * x * x + 3. * x - 2.5
}
let (x, y) = find_quadratic_peak(quad2(-1.), quad2(0.), quad2(1.));
assert_eq!(x, 0.75);
assert_eq!(y, -1.375);
fn quad3(x: f64) -> f64 {
x * x + 2.0
}
let (x, y) = find_quadratic_peak(quad3(0.), quad3(1.), quad3(2.));
assert_eq!(x + 1., 2.);
assert_eq!(y, 6.);
let (x, y) = find_quadratic_peak(quad3(-2.), quad3(-1.), quad3(0.));
assert_eq!(x - 1., -2.);
assert_eq!(y, 6.);
}
#[test]
fn detect_peaks_test() {
let v = vec![-2., -1., 1., 2., 1., -1., 4., -3., -2., 1., 1., -1.];
let peaks: Vec<(usize, f64)> = detect_peaks(v.as_slice()).collect();
assert_eq!(peaks, [(3, 2.), (6, 4.), (9, 1.)]);
let v = vec![1., 2., 1., -1., 2., -3., -2., 1., 1., -1.];
let peaks: Vec<(usize, f64)> = detect_peaks(v.as_slice()).collect();
assert_eq!(peaks, [(4, 2.), (7, 1.)]);
let v = vec![1., 2., 1., -1., 2., -3., -2., 1., 1.];
let peaks: Vec<(usize, f64)> = detect_peaks(v.as_slice()).collect();
assert_eq!(peaks, [(4, 2.)]);
}
}