#[cfg(not(feature = "std"))]
extern crate alloc;
use super::welch::{Periodogram, WelchBuilder};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::iter::Sum;
use num::Float;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct FrequencyMetrics<T> {
pub lf: T,
pub hf: T,
pub vlf: T,
}
impl<
T: Float
+ Sum<T>
+ Copy
+ core::fmt::Debug
+ num::Signed
+ 'static
+ core::ops::AddAssign
+ core::marker::Send
+ core::marker::Sync
+ num::FromPrimitive,
> FrequencyMetrics<T>
{
fn trapezoidal(iter: impl Iterator<Item = (T, T)>) -> T {
let mut sum = T::zero();
let mut prev_x = None;
let mut prev_y = None;
for (x, y) in iter {
if let Some((prev_x_value, prev_y_value)) = prev_x.zip(prev_y) {
let dx = x - prev_x_value;
sum += T::from(0.5).unwrap() * (prev_y_value + y) * dx;
}
prev_x = Some(x);
prev_y = Some(y);
}
sum
}
fn interpolate(x: &[T], y: &[T], xp: impl Iterator<Item = T>) -> Vec<T> {
let m: Vec<T> = x
.windows(2)
.zip(y.windows(2))
.map(|(i, j)| (j[1] - j[0]) / (i[1] - i[0]))
.collect();
xp.map(|i| {
let xi = x.partition_point(|&p| p < i).saturating_sub(1);
y[xi] + m[xi] * (i - x[xi])
})
.collect()
}
pub fn compute_sampled(sampled_rr_intervals: &[T], rate: T) -> Self {
let mean = sampled_rr_intervals.iter().copied().sum::<T>()
/ T::from_usize(sampled_rr_intervals.len()).unwrap();
let sampled_rr_intervals: Vec<T> = sampled_rr_intervals.iter().map(|&i| i - mean).collect();
let welch = WelchBuilder::new(sampled_rr_intervals)
.with_fs(rate)
.with_overlap_size(128)
.with_segment_size(256)
.build();
let lf = welch
.frequencies()
.zip(welch.periodogram())
.filter_map(|(f, psd_value)| {
if f >= T::from(0.004).unwrap() && f < T::from(0.15).unwrap() {
Some((f, psd_value))
} else {
None
}
});
let hf = welch
.frequencies()
.zip(welch.periodogram())
.filter_map(|(f, psd_value)| {
if f >= T::from(0.15).unwrap() && f < T::from(0.4).unwrap() {
Some((f, psd_value))
} else {
None
}
});
let vlf = welch
.frequencies()
.zip(welch.periodogram())
.filter_map(|(f, psd_value)| {
if f >= T::from(0.003).unwrap() && f < T::from(0.04).unwrap() {
Some((f, psd_value))
} else {
None
}
});
let lf = Self::trapezoidal(lf);
let hf = Self::trapezoidal(hf);
let vlf = Self::trapezoidal(vlf);
Self {
lf: lf * T::from(2).unwrap(),
hf: hf * T::from(2).unwrap(),
vlf: vlf * T::from(2).unwrap(),
}
}
pub fn compute(rr_intervals: &[T], rate: T) -> Self {
let x = rr_intervals
.iter()
.scan(T::from(0).unwrap(), |s, &i| {
*s += i;
Some(*s)
})
.map(|i| (i - rr_intervals[0]) / T::from(1_000).unwrap())
.collect::<Vec<T>>();
let step = *x.last().unwrap() * rate;
let xp = (0..step.to_f64().unwrap() as u64).map(|i| T::from(i).unwrap() / rate);
let sampled_rr_intervals = Self::interpolate(&x, rr_intervals, xp);
Self::compute_sampled(&sampled_rr_intervals, rate)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_data::RR_INTERVALS;
use approx::{AbsDiffEq, RelativeEq, UlpsEq, assert_relative_eq};
impl<T: AbsDiffEq> AbsDiffEq for FrequencyMetrics<T>
where
T::Epsilon: Copy,
{
type Epsilon = T::Epsilon;
fn default_epsilon() -> T::Epsilon {
T::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: T::Epsilon) -> bool {
T::abs_diff_eq(&self.lf, &other.lf, epsilon)
&& T::abs_diff_eq(&self.hf, &other.hf, epsilon)
&& T::abs_diff_eq(&self.vlf, &other.vlf, epsilon)
}
}
impl<T: RelativeEq> RelativeEq for FrequencyMetrics<T>
where
T::Epsilon: Copy,
{
fn default_max_relative() -> T::Epsilon {
T::default_max_relative()
}
fn relative_eq(&self, other: &Self, epsilon: T::Epsilon, max_relative: T::Epsilon) -> bool {
T::relative_eq(&self.lf, &other.lf, epsilon, max_relative)
&& T::relative_eq(&self.hf, &other.hf, epsilon, max_relative)
&& T::relative_eq(&self.vlf, &other.vlf, epsilon, max_relative)
}
}
impl<T: UlpsEq> UlpsEq for FrequencyMetrics<T>
where
T::Epsilon: Copy,
{
fn default_max_ulps() -> u32 {
T::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: T::Epsilon, max_ulps: u32) -> bool {
T::ulps_eq(&self.lf, &other.lf, epsilon, max_ulps)
&& T::ulps_eq(&self.hf, &other.hf, epsilon, max_ulps)
&& T::ulps_eq(&self.vlf, &other.vlf, epsilon, max_ulps)
}
}
#[test]
fn test_frequency_metrics_with_resampling() {
let freq_params = FrequencyMetrics::compute(RR_INTERVALS, 4.);
assert_relative_eq!(
FrequencyMetrics {
lf: 3134.3763575489256,
hf: 300.3896422597976,
vlf: 430.1935931595116,
},
freq_params,
max_relative = 0.15,
);
}
#[test]
fn test_frequency_metrics_no_resampling() {
let freq_params = FrequencyMetrics::compute_sampled(&RR_INTERVALS.repeat(2), 4.);
assert_relative_eq!(
FrequencyMetrics {
lf: 23.036923299930482,
hf: 3676.2170268774908,
vlf: 0.0735921788931728,
},
freq_params,
max_relative = 0.03,
);
}
}