#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::iter::Sum;
use num::Float;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct TimeMetrics<T> {
pub rmssd: T,
pub sdnn: T,
pub pnn50: T,
pub mean_hr: T,
pub sdsd: T,
pub avnn: T,
pub cvsd: T,
}
impl<T: Float + Sum<T> + Copy + core::fmt::Debug> TimeMetrics<T> {
pub fn compute(rr_intervals: &[T]) -> Self {
let rr_diffs: Vec<T> = rr_intervals.windows(2).map(|i| i[1] - i[0]).collect();
let rr_intervals_mean: T =
rr_intervals.iter().copied().sum::<T>() / T::from(rr_intervals.len()).unwrap();
let rr_diffs_mean: T =
rr_diffs.iter().copied().sum::<T>() / T::from(rr_diffs.len()).unwrap();
let variance = rr_intervals
.iter()
.map(|&x| (x - rr_intervals_mean) * (x - rr_intervals_mean))
.sum::<T>()
/ T::from(rr_diffs.len()).unwrap();
let sdnn = variance.sqrt();
let rmssd =
(rr_diffs.iter().map(|&x| x * x).sum::<T>() / T::from(rr_diffs.len()).unwrap()).sqrt();
let sdsd = (rr_diffs
.iter()
.map(|&x| (x - rr_diffs_mean) * (x - rr_diffs_mean))
.sum::<T>()
/ T::from(rr_diffs.len()).unwrap())
.sqrt();
let pnn50 = T::from(
rr_diffs
.iter()
.filter(|&&x| x.abs() > T::from(50.0).unwrap())
.count(),
)
.unwrap()
/ T::from(rr_diffs.len()).unwrap();
let mean_hr = rr_intervals
.iter()
.map(|&i| T::from(60_000).unwrap() / i)
.sum::<T>()
/ T::from(rr_intervals.len()).unwrap();
Self {
sdnn,
rmssd,
pnn50,
mean_hr,
sdsd,
avnn: rr_intervals_mean,
cvsd: rmssd / rr_intervals_mean,
}
}
}
#[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 TimeMetrics<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.rmssd, &other.rmssd, epsilon)
&& T::abs_diff_eq(&self.sdnn, &other.sdnn, epsilon)
&& T::abs_diff_eq(&self.pnn50, &other.pnn50, epsilon)
&& T::abs_diff_eq(&self.mean_hr, &other.mean_hr, epsilon)
&& T::abs_diff_eq(&self.sdsd, &other.sdsd, epsilon)
&& T::abs_diff_eq(&self.cvsd, &other.cvsd, epsilon)
}
}
impl<T: RelativeEq> RelativeEq for TimeMetrics<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.rmssd, &other.rmssd, epsilon, max_relative)
&& T::relative_eq(&self.sdnn, &other.sdnn, epsilon, max_relative)
&& T::relative_eq(&self.pnn50, &other.pnn50, epsilon, max_relative)
&& T::relative_eq(&self.mean_hr, &other.mean_hr, epsilon, max_relative)
&& T::relative_eq(&self.sdsd, &other.sdsd, epsilon, max_relative)
&& T::relative_eq(&self.cvsd, &other.cvsd, epsilon, max_relative)
}
}
impl<T: UlpsEq> UlpsEq for TimeMetrics<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.rmssd, &other.rmssd, epsilon, max_ulps)
&& T::ulps_eq(&self.sdnn, &other.sdnn, epsilon, max_ulps)
&& T::ulps_eq(&self.pnn50, &other.pnn50, epsilon, max_ulps)
&& T::ulps_eq(&self.mean_hr, &other.mean_hr, epsilon, max_ulps)
&& T::ulps_eq(&self.sdsd, &other.sdsd, epsilon, max_ulps)
&& T::ulps_eq(&self.cvsd, &other.cvsd, epsilon, max_ulps)
}
}
#[test]
fn test_metrics() {
let time_params = TimeMetrics::compute(RR_INTERVALS);
assert_relative_eq!(
TimeMetrics {
rmssd: 59.99807873965272,
sdnn: 69.54843274772418,
pnn50: 0.414985590778098,
mean_hr: 70.46146532393496,
sdsd: 59.99789159342577,
cvsd: 0.06999675282912315,
avnn: 857.1551724137931,
},
time_params,
epsilon = 1e-14
);
}
}