#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use core::{
iter::Sum,
ops::{Deref, DerefMut},
};
use num::Float;
pub enum EctopicMethod {
Karlsson,
Acar,
}
#[derive(Debug)]
pub struct RRIntervals<T> {
rr_intervals: Vec<T>,
outliers: Option<Vec<usize>>,
ectopics: Option<Vec<usize>>,
}
pub trait DetectOutliers<T> {
fn detect_outliers(&mut self, lowest_rr: &T, highest_rr: &T);
fn detect_ectopics(&mut self, method: EctopicMethod);
}
impl<T: Float + Sum<T> + Copy + core::fmt::Debug + num::Signed + 'static + num::FromPrimitive>
DetectOutliers<T> for RRIntervals<T>
{
fn detect_outliers(&mut self, lowest_rr: &T, highest_rr: &T) {
let outliers: Vec<usize> = self
.iter()
.enumerate()
.filter_map(|(index, value)| {
if lowest_rr > value || value > highest_rr {
Some(index)
} else {
None
}
})
.collect();
self.outliers = if outliers.is_empty() {
None
} else {
Some(outliers)
};
}
fn detect_ectopics(&mut self, method: EctopicMethod) {
let ectopics: Vec<usize> = match method {
EctopicMethod::Karlsson => (0..self.len() - 2)
.filter_map(|i| {
let mean = (self[i] + self[i + 2]) / T::from(2).unwrap();
if (mean - self[i + 1]).abs() >= T::from(0.2).unwrap() * mean {
Some(i + 1)
} else {
None
}
})
.collect(),
EctopicMethod::Acar => (9..self.len())
.filter(|&i| {
let mean = (self[i - 9..i].iter().cloned().sum::<T>()) / T::from(9).unwrap();
(mean - self[i]).abs() >= T::from(0.2).unwrap() * mean
})
.collect(),
};
self.ectopics = if ectopics.is_empty() {
None
} else {
Some(ectopics)
};
}
}
impl<T> Deref for RRIntervals<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.rr_intervals
}
}
impl<T> DerefMut for RRIntervals<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.rr_intervals
}
}
impl<T: Float + Sum<T> + Copy + core::fmt::Debug + num::Signed + 'static + num::FromPrimitive>
RRIntervals<T>
{
pub fn new(rr_intervals: Vec<T>) -> Self {
Self {
rr_intervals,
outliers: None,
ectopics: None,
}
}
pub fn remove_outliers_ectopics(&mut self) {
self.rr_intervals = self
.iter()
.enumerate()
.filter_map(|(i, j)| {
if self.outliers.as_ref().unwrap_or(&vec![]).contains(&i)
|| self.ectopics.as_ref().unwrap_or(&vec![]).contains(&i)
{
None
} else {
Some(*j)
}
})
.collect::<Vec<T>>();
self.ectopics = None;
self.outliers = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_data::RR_INTERVALS;
#[test]
fn test_remove_none() {
let mut rr_intervals = RRIntervals::new(RR_INTERVALS.to_vec());
rr_intervals.remove_outliers_ectopics();
assert_eq!(RR_INTERVALS, *rr_intervals);
}
#[test]
fn test_remove_ectopics_karlsson() {
let mut rr_intervals = RRIntervals::new(vec![800., 850., 900., 600., 800., 820., 840.]);
rr_intervals.detect_ectopics(EctopicMethod::Karlsson);
assert_eq!(Some(vec![2, 3]), rr_intervals.ectopics);
rr_intervals.remove_outliers_ectopics();
assert_eq!(vec![800., 850., 800., 820., 840.], *rr_intervals);
}
#[test]
fn test_remove_ectopics_acar() {
let mut rr_intervals = RRIntervals::new(vec![
800., 850., 3000., 600., 800., 820., 240., 800., 850., 3000., 600., 800., 820., 240.,
]);
rr_intervals.detect_ectopics(EctopicMethod::Acar);
assert_eq!(Some(vec![9, 10, 11, 13]), rr_intervals.ectopics);
rr_intervals.remove_outliers_ectopics();
assert_eq!(
vec![800., 850., 3000., 600., 800., 820., 240., 800., 850., 820.],
*rr_intervals
);
}
#[test]
fn test_remove_outliers() {
let mut rr_intervals = RRIntervals::new(vec![800., 850., 3000., 600., 800., 820., 240.]);
rr_intervals.detect_outliers(&300., &2_000.);
assert_eq!(Some(vec![2, 6]), rr_intervals.outliers);
rr_intervals.remove_outliers_ectopics();
assert_eq!(vec![800., 850., 600., 800., 820.], *rr_intervals);
}
#[test]
fn test_remove_combined() {
let mut rr_intervals = RRIntervals::new(vec![800., 850., 3000., 600., 800., 820., 240.]);
rr_intervals.outliers = Some(vec![2, 3, 4]);
rr_intervals.ectopics = Some(vec![4, 5]);
rr_intervals.remove_outliers_ectopics();
assert_eq!(vec![800., 850., 240.], *rr_intervals);
}
}