1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//! This crate provides a realtime ECG QRS detector.
//!
//! The implementation is based on [this article](https://biomedical-engineering-online.biomedcentral.com/articles/10.1186/1475-925X-3-28).
//!
//! `QrsDetector` does not use dynamic memory allocation. Instead, this crate relies on
//! [`generic_array`](../generic_array/index.html),
//! which means there's a bit of setup necessary to set the size of internal buffers.
//!
//! For more information, see [`QrsDetector`](./struct.QrsDetector.html).
#![cfg_attr(not(test), no_std)]

mod internal {
    pub fn max(a: f32, b: f32) -> f32 {
        if a > b {
            a
        } else {
            b
        }
    }
}

use if_chain::if_chain;
use sliding_window::Size;

pub use sliding_window::typenum;

mod algorithms {
    use sliding_window::{typenum::consts::U5, Size, SlidingWindow};

    use crate::internal::max;
    use crate::sampling::*;

    #[derive(Copy, Clone, Debug)]
    pub enum MState {
        Init(u32, f32),
        Disallow(u32, f32),
        Decreasing(u32, f32, f32),
        ConstantLow(f32),
    }

    pub struct M {
        state: MState,
        mm: SlidingWindow<f32, U5>,
        fs: SamplingFrequency,
        pub current_decrement: f32,
    }

    impl M {
        pub fn new(fs: SamplingFrequency) -> Self {
            Self {
                fs,
                mm: SlidingWindow::new(),
                // Initially M = 0.6*max(Y) is set for the first 3 s [originally 5s] of the signal
                state: MState::Init(fs.s_to_samples(3.0), 0.0),
                current_decrement: 0.0,
            }
        }

        fn m(&self) -> f32 {
            // M is calculated as an average value of MM.
            // Divide by 5 was done while calculating the individual Mx values
            self.mm.iter_unordered().sum()
        }

        pub fn update(&mut self, sample: f32) -> Option<f32> {
            self.state = match self.state {
                MState::Init(0, m) => {
                    // Initially M = 0.6*max(Y) is set for the first 3 s [originally 5s] of the signal
                    // A buffer with 5 steep-slope threshold values is preset:
                    // MM = [M1 M2 M3 M4 M5],
                    // where M1 ÷ M5 are equal to M
                    let m = 0.6 * max(m, sample);

                    for _ in 0..5 {
                        self.mm.insert(m / 5.0);
                    }

                    // It is not clear in the article what to do initially:
                    // - wait for a QRS detection and then start the M algorithm with Disallow state
                    // - decrease using "low slope" immediately
                    // This implementation uses the second option

                    // M is decreased in an interval 225 to 1225 ms [originally 200 to 1200 ms]
                    // following the last QRS detection at a low slope, reaching 60 % of its
                    // refreshed value at 1225 ms [originally 1200 ms].
                    let n_samples = self.fs.s_to_samples(1.0);
                    let decrement = m * 0.4 / n_samples as f32;
                    self.current_decrement = decrement;

                    MState::Decreasing(n_samples, m, decrement)
                }

                // Initially M = 0.6*max(Y) is set for the first 3 s [originally 5s] of the signal
                // Collect maximum value while in Init state
                MState::Init(samples, m) => MState::Init(samples - 1, max(m, sample)),

                MState::Disallow(0, m) => {
                    // In the interval QRS ÷ QRS+200ms a new value of M5 is calculated:
                    // newM 5 = 0.6*max(Yi)
                    let m = 0.6 * max(m, sample) / 5.0; // divide by 5 for averaging

                    // The estimated newM 5 value can become quite high, if steep slope premature
                    // ventricular contraction or artifact appeared, and for that reason it is
                    // limited to newM5 = 1.1* M5 if newM 5 > 1.5* M5.
                    let prev_m = self.mm[4];
                    if m > prev_m * 1.5 {
                        self.mm.insert(1.1 * prev_m);
                    } else {
                        self.mm.insert(m);
                    }

                    let m = self.m();

                    // M is decreased in an interval 225 to 1225 ms [originally 200 to 1200 ms]
                    // following the last QRS detection at a low slope, reaching 60 % of its
                    // refreshed value at 1225 ms [originally 1200 ms].
                    let n_samples = self.fs.s_to_samples(1.0);
                    let decrement = m * 0.4 / n_samples as f32;
                    self.current_decrement = decrement;

                    MState::Decreasing(n_samples, m, decrement)
                }

                // In the interval QRS ÷ QRS+200ms a new value of M5 is calculated:
                // newM 5 = 0.6*max(Yi)
                // Collect maximum value while in Disallow state
                MState::Disallow(samples, m) => MState::Disallow(samples - 1, max(m, sample)),

                // After 1225 ms [originally 1200 ms] M remains unchanged.
                MState::Decreasing(0, m, _) => MState::ConstantLow(m),

                // M is decreased in an interval 225 to 1225 ms [originally 200 to 1200 ms]
                // following the last QRS detection at a low slope, reaching 60 % of its
                // refreshed value at 1225 ms [originally 1200 ms].
                MState::Decreasing(samples, m, decrease_amount) => {
                    // Linear decrease using precomputed decrement value
                    MState::Decreasing(samples - 1, m - decrease_amount, decrease_amount)
                }

                // After 1225 ms [originally 1200 ms] M remains unchanged.
                MState::ConstantLow(m) => MState::ConstantLow(m),
            };

            match self.state {
                MState::Init(_, _) | MState::Disallow(_, _) => None,
                MState::Decreasing(_, m, _) | MState::ConstantLow(m) => Some(m),
            }
        }

        pub fn detection_event(&mut self, sample: f32) {
            // No detection is allowed 225 ms [originally 200 ms] after the current one.
            self.state = MState::Disallow(self.fs.s_to_samples(0.225), sample);
        }
    }

    #[derive(Copy, Clone, Debug)]
    pub enum FState {
        Ignore(u32),
        Init(u32, f32),
        Integrate(f32),
    }

    pub struct F<FMW, FB>
    where
        FMW: Size<f32>,
        FB: Size<f32>,
    {
        /// F should be initialized at the same time as M is, skip earlier samples
        state: FState,
        /// 350ms of the individual max samples of the 50ms buffer
        f_max_window: SlidingWindow<f32, FMW>,
        /// 50ms window of the signal
        f_buffer: SlidingWindow<f32, FB>,
    }

    impl<FMW, FB> F<FMW, FB>
    where
        FMW: Size<f32>,
        FB: Size<f32>,
    {
        pub fn new(fs: SamplingFrequency) -> Self {
            // sanity check buffer sizes
            debug_assert_eq!(
                FMW::to_u32(),
                fs.ms_to_samples(300.0),
                "Incorrect type parameters, must be <U{}, U{}>",
                fs.ms_to_samples(300.0),
                fs.ms_to_samples(50.0)
            );
            debug_assert_eq!(
                FB::to_u32(),
                fs.ms_to_samples(50.0),
                "Incorrect type parameters, must be <U{}, U{}>",
                fs.ms_to_samples(300.0),
                fs.ms_to_samples(50.0)
            );

            Self {
                state: FState::Ignore(fs.s_to_samples(2.65)),
                f_max_window: SlidingWindow::new(),
                f_buffer: SlidingWindow::new(),
            }
        }

        fn update_f_buffers(&mut self, sample: f32) -> (Option<f32>, f32) {
            // TODO: there are some special cases where the max search can be skipped
            self.f_buffer.insert(sample);

            // Calculate maximum value in the latest 50ms window
            let max = *self
                .f_buffer
                .iter_unordered()
                .max_by(|a, b| a.partial_cmp(b).unwrap())
                .unwrap();

            // Keep the 50ms maximum values for each sample in latest 300ms window
            // The oldest sample corresponds to the oldest 50ms in the latest 350ms window
            // TODO FIXME: off by some error :)
            let old = self.f_max_window.insert(max);

            (old, max)
        }

        pub fn update(&mut self, sample: f32) -> Option<f32> {
            self.state = match self.state {
                FState::Ignore(1) => FState::Init(FMW::to_u32() - 1, 0.0),
                FState::Ignore(n) => FState::Ignore(n - 1),
                FState::Init(n, favg) => {
                    let favg = favg + sample;
                    self.update_f_buffers(sample);

                    if n == 0 {
                        FState::Integrate(favg / (FMW::to_u32() as f32))
                    } else {
                        FState::Init(n - 1, favg)
                    }
                }
                FState::Integrate(f) => {
                    let (oldest_max, max) = self.update_f_buffers(sample);
                    FState::Integrate(f + (max - oldest_max.unwrap()) / 150.0)
                }
            };

            if let FState::Integrate(f) = self.state {
                Some(f)
            } else {
                None
            }
        }
    }

    #[derive(Copy, Clone, Debug)]
    pub enum RState {
        Ignore,
        InitBuffer,
        NoDecrease(u32, u32),    // samples remaining, average rr interval
        Decrease(u32, f32, f32), // samples remaining, value, decrement
        Constant(f32),
    }

    pub struct R {
        state: RState,
        rr: SlidingWindow<u32, U5>,
        prev_idx: u32, // no need to make it an Option
    }

    impl R {
        pub fn new() -> Self {
            Self {
                state: RState::Ignore,
                rr: SlidingWindow::new(),
                prev_idx: 0,
            }
        }

        fn enter_no_decrease(&mut self) {
            let rr_sum: u32 = self.rr.iter().sum();
            let rr_avg = rr_sum / 5;
            self.state = RState::NoDecrease(rr_avg * 2 / 3, rr_avg);
        }

        pub fn update(&mut self, m_decrement: f32) -> f32 {
            self.state = match self.state {
                RState::NoDecrease(0, rr_avg) => {
                    RState::Decrease(rr_avg / 3, 0.0, m_decrement / 1.4)
                }
                RState::NoDecrease(samples, rr_avg) => RState::NoDecrease(samples - 1, rr_avg),
                RState::Decrease(0, r, _) => RState::Constant(r),
                RState::Decrease(samples, r, decrement) => {
                    RState::Decrease(samples - 1, r - decrement, decrement)
                }
                o => o,
            };

            match self.state {
                RState::Ignore | RState::InitBuffer | RState::NoDecrease(_, _) => 0.0,
                RState::Constant(r) | RState::Decrease(_, r, _) => r,
            }
        }

        pub fn detection_event(&mut self, idx: u32) {
            match self.state {
                RState::Ignore => self.state = RState::InitBuffer,
                RState::InitBuffer => {
                    self.rr.insert(idx.wrapping_sub(self.prev_idx));
                    if self.rr.is_full() {
                        self.enter_no_decrease();
                    }
                }
                _ => {
                    self.rr.insert(idx.wrapping_sub(self.prev_idx));
                    self.enter_no_decrease();
                }
            };

            self.prev_idx = idx;
        }
    }
}

/// Helpers for working with sampling frequencies and sample numbers.
pub mod sampling;
use sampling::SamplingFrequency;

use algorithms::{F, M, R};

/// Find QRS complex in real-time sampled ECG signal.
///
/// # Type parameters:
///
/// The `QrsDetector` is built upon [`generic_array`](../generic_array/index.html).
/// This has an unfortunate implementation detail where the internal buffer sizes must be set on
/// the type level.
///
/// - `FMW` - number of samples representing 350ms, as [`typenum::U*`](../typenum/consts/index.html)
/// - `FB` - number of samples representing 50ms, as [`typenum::U*`](../typenum/consts/index.html)
///
/// These type parameters are checked at runtime and if incorrect and the error message will contain
/// the correct sizes.
pub struct QrsDetector<FMW, FB>
where
    FMW: Size<f32>,
    FB: Size<f32>,
{
    fs: SamplingFrequency,
    total_samples: u32,
    m: M,
    f: F<FMW, FB>,
    r: R,
}

impl<FMW, FB> QrsDetector<FMW, FB>
where
    FMW: Size<f32>,
    FB: Size<f32>,
{
    /// Returns a new QRS detector for signals sampled at `fs` sampling frequency.
    ///
    /// # Arguments
    /// * `fs` - The sampling frequency of the processed signal. For more information see
    /// [`SamplingFrequencyExt`](./sampling/trait.SamplingFrequencyExt.html).
    ///
    /// # Example
    /// ```rust
    /// use qrs_detector::sampling::*;
    /// use qrs_detector::QrsDetector;
    /// use qrs_detector::typenum::{U150, U25};
    ///
    /// // Assuming 500 samples per second
    /// // Type parameters must be 300ms and 50ms in number of samples
    /// let detector: QrsDetector<U150, U25> = QrsDetector::new(500.sps());
    /// ```
    pub fn new(fs: SamplingFrequency) -> Self {
        Self {
            fs,
            total_samples: 0,
            m: M::new(fs),
            f: F::new(fs),
            r: R::new(),
        }
    }

    /// Reset the internal state of the detector
    pub fn clear(&mut self) {
        *self = Self::new(self.fs);
    }

    /// Process a sample. Returns Some sample index if a QRS complex is detected.
    pub fn update(&mut self, sample: f32) -> Option<u32> {
        let m = self.m.update(sample);
        let f = self.f.update(sample);
        let r = self.r.update(self.m.current_decrement);

        let result = if_chain! {
            if let Some(m) = m;
            if let Some(f) = f;
            if sample > m + f + r;
            then {
                self.m.detection_event(sample);
                self.r.detection_event(self.total_samples);
                Some(self.total_samples)
            } else {
                None
            }
        };

        self.total_samples += 1;
        result
    }
}