|
25 | 25 | ;; |
26 | 26 | ;; ### What We'll Explore |
27 | 27 | ;; |
28 | | -;; In this notebook, we'll walk through a [signal processing](https://en.wikipedia.org/wiki/Signal_processing) pipeline for analyzing Camera PPG data: |
| 28 | +;; **Our goal is to extract heart rate from smartphone camera video and validate it against clinical measurements.** |
| 29 | +;; |
| 30 | +;; The MTHS dataset provides both the raw camera data (RGB signals) and ground truth heart rate measurements from medical-grade equipment. This allows us to: |
| 31 | +;; |
| 32 | +;; 1. Develop and test signal processing algorithms |
| 33 | +;; 2. Validate our extracted heart rates against the ground truth |
| 34 | +;; 3. Calibrate parameters (filter cutoffs, window sizes, etc.) for optimal accuracy |
| 35 | +;; |
| 36 | +;; In this notebook, we'll walk through the complete pipeline: |
29 | 37 | ;; |
30 | 38 | ;; 1. **Loading and visualizing** raw [RGB](https://en.wikipedia.org/wiki/RGB_color_model) signals from multiple subjects |
31 | 39 | ;; 2. **Signal preprocessing**: removing [DC offset](https://en.wikipedia.org/wiki/DC_bias), [bandpass filtering](https://en.wikipedia.org/wiki/Band-pass_filter) to isolate heart rate frequencies (0.5-5 Hz, corresponding to 30-300 bpm), and [standardization](https://en.wikipedia.org/wiki/Standard_score) |
32 | | -;; 3. **Power spectrum analysis**: using windowed [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) to identify the dominant frequency (heart rate) |
| 40 | +;; 3. **Power spectrum analysis**: using windowed [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) to identify the dominant frequency |
| 41 | +;; 4. **Heart rate extraction**: finding the peak frequency and converting to bpm |
| 42 | +;; 5. **Validation**: comparing our estimates with ground truth measurements |
33 | 43 | ;; |
34 | 44 | ;; This exploration demonstrates how Clojure's ecosystem—combining tablecloth for data wrangling, dtype-next for efficient numerical computation, jdsp for signal processing, and tableplot for visualization—provides powerful tools for biomedical signal analysis. |
35 | 45 |
|
|
131 | 141 | ;; NumPy arrays can be inspected using dtype-next: |
132 | 142 |
|
133 | 143 | (dtype/shape (raw-data [:signal 23])) ; 30Hz signal → [n-samples, 3] array |
134 | | -(dtype/shape (raw-data [:label 23])) ; 1Hz labels → [n-samples, 2] array |
| 144 | +(dtype/shape (raw-data [:label 23])) ; 1Hz labels → [n-samples, 2] array (heart rate + SpO2) |
135 | 145 |
|
136 | 146 | ;; The [sampling rate](https://en.wikipedia.org/wiki/Sampling_(signal_processing)) is 30 Hz (30 samples per second) |
137 | 147 | (def sampling-rate 30) |
138 | 148 |
|
| 149 | +;; ## Ground Truth Data |
| 150 | +;; |
| 151 | +;; Before we dive into signal processing, let's look at the ground truth labels. |
| 152 | +;; These provide the reference heart rate values we'll use to validate our algorithms. |
| 153 | + |
| 154 | +(defn labels [i] |
| 155 | + (some-> [:label i] |
| 156 | + raw-data |
| 157 | + ds-tensor/tensor->dataset |
| 158 | + (tc/rename-columns [:heart-rate :spo2]))) |
| 159 | + |
| 160 | +;; For example, subject 23's ground truth measurements: |
| 161 | + |
| 162 | +(labels 23) |
| 163 | + |
139 | 164 | ;; Let's create a helper function to convert a subject's raw signal into a tablecloth dataset |
140 | 165 | ;; with properly named columns (:R, :G, :B) and a time column (:t in seconds): |
141 | 166 |
|
|
253 | 278 | :high-cutoff high-cutoff}) |
254 | 279 | :standardize stats/standardize})) |
255 | 280 |
|
256 | | -;; ## Power Spectrum Analysis |
| 281 | +;; ## Power Spectrum Analysis: Extracting Heart Rate |
257 | 282 | ;; |
258 | | -;; To extract the heart rate from our cleaned PPG signal, we'll use **[frequency domain](https://en.wikipedia.org/wiki/Frequency_domain) analysis**. |
| 283 | +;; Now comes the key step: **extracting heart rate from the camera signal**. |
| 284 | +;; |
| 285 | +;; To do this, we'll use **[frequency domain](https://en.wikipedia.org/wiki/Frequency_domain) analysis**. |
259 | 286 | ;; The idea is simple: a heartbeat creates a periodic oscillation in the signal, and we can find |
260 | 287 | ;; the dominant frequency of that oscillation using the [Fast Fourier Transform (FFT)](https://en.wikipedia.org/wiki/Fast_Fourier_transform). |
261 | 288 | ;; |
| 289 | +;; Once we identify the peak frequency, we can: |
| 290 | +;; |
| 291 | +;; 1. Convert it to beats per minute (bpm) |
| 292 | +;; 2. Compare it with the ground truth heart rate |
| 293 | +;; 3. Evaluate our algorithm's accuracy |
| 294 | +;; |
262 | 295 | ;; ### Windowing and Overlap |
263 | 296 | ;; |
264 | 297 | ;; Rather than analyzing the entire signal at once, we'll use **[windowed analysis](https://en.wikipedia.org/wiki/Window_function)**: |
|
0 commit comments