cardio_rs/analysis/windows_analysis.rs
1//! A module providing functionality for performing window-based analysis of Heart Rate
2//! Variability (HRV) metrics over non-overlapping windows of RR intervals.
3//!
4//! The analysis is done over fixed-duration windows, where each window is processed
5//! independently, and the results for each window are stored. The user can customize the
6//! pipeline for HRV metric calculation by implementing the `AnalysisPipeline` trait, or
7//! they can use the default implementation that computes various HRV metrics.
8//!
9//! ## Key Components:
10//!
11//! - `WindowsAnalysisBuilder<T>`: A builder that allows you to configure and generate a
12//! `WindowsAnalysis` with user-defined data and pipeline.
13//! - `AnalysisPipeline<T>`: A trait that allows the user to define custom pipelines to
14//! process RR interval data and compute various HRV metrics.
15//! - `DefaultPipeline`: A default implementation of the `AnalysisPipeline` trait that computes
16//! HRV metrics using the `TimeMetrics`, `FrequencyMetrics`, and `GeometricMetrics` modules.
17//! - `WindowsAnalysis<T>`: A struct representing the result of the windowed HRV analysis,
18//! containing the computed HRV metrics for each window.
19//!
20//! ## Example Usage:
21//!
22//! ### Basic Example (Using Default Pipeline):
23//! ```rust
24//! use cardio_rs::utils::test_data::RR_INTERVALS;
25//! use cardio_rs::windows_analysis::WindowsAnalysisBuilder;
26//!
27//! let rr_intervals = RR_INTERVALS.to_vec();
28//! let window_size = 60_000.0; // 1-minute window size
29//!
30//! let windows_analysis = WindowsAnalysisBuilder::new(rr_intervals)
31//! .with_window_size(window_size)
32//! .build();
33//!
34//! for (i, hrv_metrics) in windows_analysis.metrics.iter().enumerate() {
35//! println!("Window {}: HRV Metrics: {:?}", i, hrv_metrics);
36//! }
37//! ```
38//!
39//! ### Custom Pipeline Example:
40//! You can define your own custom analysis pipeline by implementing the `AnalysisPipeline` trait.
41//! This allows you to customize how HRV metrics are computed over the RR intervals.
42//!
43//! ```rust
44//! use cardio_rs::utils::test_data::RR_INTERVALS;
45//! use cardio_rs::{windows_analysis::{WindowsAnalysisBuilder}, HrvMetrics, geometric_domain::GeometricMetrics, non_linear::NonLinearMetrics, processing_utils::{EctopicMethod, RRIntervals, DetectOutliers, AnalysisPipeline}, time_domain::TimeMetrics, frequency_domain::FrequencyMetrics};
46//!
47//! // Define a custom pipeline by implementing the AnalysisPipeline trait
48//! struct CustomPipeline;
49//!
50//! impl AnalysisPipeline<f64> for CustomPipeline
51//! {
52//! fn process(&self, data: Vec<f64>) -> HrvMetrics<f64> {
53//! let mut rr_intervals = RRIntervals::new(data);
54//! rr_intervals.detect_ectopics(EctopicMethod::Karlsson);
55//! rr_intervals.detect_outliers(&300., &2_000.);
56//! rr_intervals.remove_outliers_ectopics();
57//!
58//! let time = TimeMetrics::compute(rr_intervals.as_slice());
59//! let frequency = FrequencyMetrics::compute(rr_intervals.as_slice(), 10.);
60//! let geometric = GeometricMetrics::compute(rr_intervals.as_slice());
61//! let non_linear = NonLinearMetrics::compute_default(rr_intervals.as_slice());
62//!
63//! HrvMetrics {
64//! time,
65//! frequency,
66//! geometric,
67//! non_linear,
68//! }
69//! }
70//! }
71//!
72//! let rr_intervals = RR_INTERVALS.to_vec();
73//!
74//! // Create the window analysis with the custom pipeline and non-overlapping windows
75//! let custom_pipeline = Box::new(CustomPipeline);
76//! let windows_analysis = WindowsAnalysisBuilder::new(rr_intervals)
77//! .with_window_size(60_000.0) // Set the window size (1 minute)
78//! .with_pipeline(custom_pipeline) // Use the custom pipeline
79//! .build();
80//!
81//! // Print HRV metrics for each window
82//! for (i, hrv_metrics) in windows_analysis.metrics.iter().enumerate() {
83//! println!("Window {}: HRV Metrics: {:?}", i, hrv_metrics);
84//! }
85//! ```
86
87#[cfg(not(feature = "std"))]
88extern crate alloc;
89use crate::{HrvMetrics, processing_utils::AnalysisPipeline};
90#[cfg(not(feature = "std"))]
91use alloc::{boxed::Box, vec::Vec};
92use core::iter::Sum;
93use num::Float;
94
95/// A struct that holds the HRV metrics for each window in the analysis.
96///
97/// `WindowsAnalysis<T>` stores the HRV metrics computed for each window of RR intervals.
98/// The `metrics` vector contains the HRV results for all the non-overlapping windows
99/// that have been processed. Each window is defined by the specified `window_size`.
100pub struct WindowsAnalysis<T> {
101 /// The size of the sliding window used for the analysis.
102 pub window_size: T,
103
104 /// The list of HRV metrics computed for each window.
105 pub metrics: Vec<HrvMetrics<T>>,
106}
107
108/// A builder struct for configuring and constructing a `WindowsAnalysis`.
109///
110/// The `WindowsAnalysisBuilder` struct is used to configure the sliding window size, the analysis pipeline,
111/// and the data for HRV computation. After the builder is configured, the `build()` method is used to generate
112/// a `WindowsAnalysis` instance containing HRV metrics for each window.
113pub struct WindowsAnalysisBuilder<T> {
114 /// The RR intervals data used for the analysis.
115 data: Vec<T>,
116
117 /// The pipeline used to process the RR intervals and compute HRV metrics.
118 pipeline: Box<dyn AnalysisPipeline<T>>,
119
120 /// The size of the window used for the analysis.
121 window_size: T,
122}
123
124impl<
125 T: Float
126 + Sum<T>
127 + Copy
128 + core::fmt::Debug
129 + num::Signed
130 + 'static
131 + core::ops::AddAssign
132 + core::marker::Send
133 + core::marker::Sync
134 + num::FromPrimitive,
135> WindowsAnalysisBuilder<T>
136where
137 Box<dyn AnalysisPipeline<T>>: Default,
138{
139 /// Creates a new `WindowsAnalysisBuilder` with the provided data.
140 ///
141 /// # Arguments
142 /// * `data` - A vector of RR intervals to be analyzed.
143 ///
144 /// # Returns
145 /// Returns a new `WindowsAnalysisBuilder` instance.
146 pub fn new(data: Vec<T>) -> Self {
147 Self {
148 data,
149 window_size: T::from(60_000).unwrap(),
150 pipeline: Default::default(),
151 }
152 }
153
154 /// Sets the window size for the analysis.
155 ///
156 /// # Arguments
157 /// * `window_size` - The size of each window, in milliseconds.
158 ///
159 /// # Returns
160 /// Returns the builder instance for chaining.
161 pub fn with_window_size(mut self, window_size: T) -> Self {
162 self.window_size = window_size;
163 self
164 }
165
166 /// Sets the pipeline used to process the RR intervals and compute HRV metrics.
167 ///
168 /// # Arguments
169 /// * `pipeline` - The pipeline used to process the data.
170 ///
171 /// # Returns
172 /// Returns the builder instance for chaining.
173 pub fn with_pipeline(mut self, pipeline: Box<dyn AnalysisPipeline<T>>) -> Self {
174 self.pipeline = pipeline;
175 self
176 }
177
178 /// Builds the `WindowsAnalysis` struct based on the current configuration of the builder.
179 ///
180 /// This method processes the data into windows, computes the HRV metrics for each window,
181 /// and stores the results in a `WindowsAnalysis` instance.
182 ///
183 /// # Returns
184 /// Returns a `WindowsAnalysis` containing the HRV metrics for each window.
185 pub fn build(&self) -> WindowsAnalysis<T> {
186 let metrics = self
187 .data
188 .iter()
189 .enumerate()
190 .scan((T::from(0).unwrap(), 0), |s, (index, &i)| {
191 s.0 += i;
192 if index == self.data.len() - 1 {
193 Some(Some((s.1, self.data.len())))
194 } else if s.0 <= self.window_size {
195 Some(None)
196 } else {
197 s.0 = T::from(0).unwrap();
198 let prev = s.1;
199 s.1 = index;
200 Some(Some((prev, s.1)))
201 }
202 })
203 .flatten()
204 .map(|i| self.pipeline.process(self.data[i.0..i.1].to_vec()))
205 .collect();
206
207 WindowsAnalysis {
208 metrics,
209 window_size: self.window_size,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::utils::test_data::RR_INTERVALS;
218
219 #[test]
220 fn test_window_size() {
221 let data = RR_INTERVALS.to_vec();
222 let hrv = WindowsAnalysisBuilder::new(data.clone()).build();
223 assert_eq!(hrv.metrics.len(), 5);
224
225 let hrv = WindowsAnalysisBuilder::new(data.clone())
226 .with_window_size(30_000.)
227 .build();
228 assert_eq!(hrv.metrics.len(), 10);
229 }
230
231 #[test]
232 fn test_windows_analysis() {
233 let mut data = RR_INTERVALS.to_vec();
234 data.extend_from_within(..);
235 let hrv = WindowsAnalysisBuilder::new(data.clone())
236 .with_window_size(597_000. / 2.)
237 .build();
238 assert_eq!(hrv.metrics[0], hrv.metrics[1]);
239 }
240}