cardio_rs/analysis/live_analysis.rs
1//! A module providing the `TimeQueue` struct, which manages a fixed size time series of RR intervals
2//! and allows for real-time processing of heart rate variability (HRV) metrics. It utilizes
3//! a sliding window of RR intervals and processes them using a user-defined or default
4//! pipeline for HRV calculation.
5//!
6//! # Examples
7//!
8//! Basic usage with the default pipeline:
9//! ```
10//! use cardio_rs::utils::test_data::RR_INTERVALS;
11//! use cardio_rs::live_analysis::TimeQueue;
12//! let mut queue = TimeQueue::new(60_000);
13//! let rr_intervals = RR_INTERVALS.to_vec();
14//! for interval in rr_intervals {
15//! queue.push(interval);
16//! }
17//! let hrv = queue.get_hrv();
18//! println!("Calculated HRV: {:?}", hrv);
19//! ```
20//!
21//! Custom pipeline usage:
22//! ```
23//! use cardio_rs::{live_analysis::TimeQueue, processing_utils::AnalysisPipeline, HrvMetrics};
24//! struct CustomPipeline;
25//! impl AnalysisPipeline<f64> for CustomPipeline {
26//! fn process(&self, data: Vec<f64>) -> HrvMetrics<f64> {
27//! HrvMetrics::default()
28//! }
29//! }
30//! use cardio_rs::utils::test_data::RR_INTERVALS;
31//! let mut queue = TimeQueue::new(60_000);
32//! queue.set_pipeline(Box::new(CustomPipeline));
33//! let rr_intervals = RR_INTERVALS.to_vec();
34//! for interval in rr_intervals {
35//! queue.push(interval);
36//! }
37//! let hrv = queue.get_hrv();
38//! println!("Calculated HRV with custom pipeline: {:?}", hrv);
39//! ```
40use crate::processing_utils::AnalysisPipeline;
41use num::Float;
42#[cfg(feature = "std")]
43use std::collections::VecDeque;
44#[cfg(not(feature = "std"))]
45extern crate alloc;
46#[cfg(not(feature = "std"))]
47use alloc::{boxed::Box, collections::vec_deque::VecDeque};
48
49/// A struct to manage a sliding window of RR intervals and process heart rate variability (HRV) metrics.
50/// It stores a collection of RR intervals in `data` and calculates HRV using a custom or default pipeline.
51pub struct TimeQueue<T> {
52 /// A `VecDeque` that stores the current RR intervals in the sliding window.
53 data: VecDeque<T>,
54
55 /// The total window time (in ms). Once this time is exceeded, the oldest RR interval is removed.
56 time: T,
57
58 /// The accumulated time that tracks how much time (in ms) has passed in the current window.
59 current_time: T,
60
61 /// The pipeline used to process the RR intervals and compute HRV metrics. It can be set to a custom pipeline.
62 pipeline: Box<dyn AnalysisPipeline<T>>,
63}
64
65impl<
66 T: Float
67 + core::iter::Sum<T>
68 + Copy
69 + 'static
70 + core::fmt::Debug
71 + num::Signed
72 + core::ops::AddAssign
73 + core::marker::Send
74 + core::marker::Sync
75 + core::ops::SubAssign
76 + Into<f64>
77 + num::FromPrimitive,
78> TimeQueue<T>
79where
80 Box<dyn AnalysisPipeline<T>>: Default,
81{
82 /// Creates a new `TimeQueue` with a sliding window of size `time`. A default pipeline is used.
83 ///
84 /// # Arguments
85 /// * `time` - The length of the time window (in ms).
86 pub fn new(time: usize) -> Self {
87 Self {
88 data: VecDeque::with_capacity(4 * time), // At most 250 bpm
89 time: T::from(time).unwrap(),
90 current_time: T::from(0).unwrap(),
91 pipeline: Default::default(),
92 }
93 }
94
95 /// Sets a custom pipeline to be used for HRV calculation.
96 ///
97 /// # Arguments
98 /// * `pipeline` - A boxed `AnalysisPipeline` that implements the HRV processing logic.
99 pub fn set_pipeline(&mut self, pipeline: Box<dyn AnalysisPipeline<T>>) {
100 self.pipeline = pipeline;
101 }
102
103 /// Pushes a new RR interval to the queue. The queue maintains the sliding window of RR intervals.
104 /// Once the current time exceeds the defined window time, the oldest interval is removed.
105 pub fn push(&mut self, rr_interval: T) {
106 self.current_time += rr_interval;
107 self.data.push_back(rr_interval);
108 if self.current_time >= self.time {
109 if let Some(deleted) = self.data.pop_front() {
110 self.current_time -= deleted;
111 }
112 }
113 }
114
115 /// Returns the current slice of the stored RR intervals.
116 pub fn get(&self) -> &[T] {
117 self.data.as_slices().0
118 }
119
120 /// Processes the current RR intervals using the pipeline and returns the HRV metrics.
121 ///
122 /// This function calls the `process` method on the custom or default pipeline to calculate HRV.
123 pub fn get_hrv(&self) -> crate::HrvMetrics<T> {
124 self.pipeline.process(self.get().to_vec())
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::{
132 frequency_domain::FrequencyMetrics, geometric_domain::GeometricMetrics,
133 non_linear::NonLinearMetrics, processing_utils::RRIntervals, time_domain::TimeMetrics,
134 utils::test_data::RR_INTERVALS,
135 };
136 #[cfg(not(feature = "std"))]
137 use alloc::vec::Vec;
138
139 #[test]
140 fn test_queue() {
141 let mut data = RR_INTERVALS.to_vec();
142 data.extend_from_within(..);
143 let mut queue = TimeQueue::new(298_500usize);
144 let mut win_0: crate::HrvMetrics<f64> = Default::default();
145 for (i, j) in data.iter().enumerate() {
146 queue.push(*j);
147 if i == data.len() / 2 - 1 {
148 win_0 = queue.get_hrv();
149 }
150 }
151
152 let win_1 = queue.get_hrv();
153 assert_eq!(win_0, win_1);
154 }
155 #[test]
156 fn test_queue_custom_pipeline() {
157 let mut data = RR_INTERVALS.to_vec();
158 data.extend_from_within(..);
159 let mut queue = TimeQueue::new(298_500usize);
160
161 struct Pipeline();
162 impl AnalysisPipeline<f64> for Pipeline {
163 fn process(&self, data: Vec<f64>) -> crate::HrvMetrics<f64> {
164 let rr_intervals = RRIntervals::new(data);
165
166 let time = TimeMetrics::compute(rr_intervals.as_slice());
167 let frequency = FrequencyMetrics::compute(rr_intervals.as_slice(), 4.);
168 let geometric = GeometricMetrics::compute(rr_intervals.as_slice());
169 let non_linear = NonLinearMetrics::compute_default(rr_intervals.as_slice());
170
171 crate::HrvMetrics {
172 time,
173 frequency,
174 geometric,
175 non_linear,
176 }
177 }
178 }
179 queue.set_pipeline(Box::new(Pipeline()));
180
181 let mut win_0: crate::HrvMetrics<f64> = Default::default();
182 for (i, j) in data.iter().enumerate() {
183 queue.push(*j);
184 if i == data.len() / 2 - 1 {
185 win_0 = queue.get_hrv();
186 }
187 }
188
189 let win_1 = queue.get_hrv();
190 assert_eq!(win_0, win_1);
191 }
192}