|
27 | 27 | [java-time.api :as jt])) |
28 | 28 |
|
29 | 29 |
|
30 | | -;; # Exploring HRV |
| 30 | +;; # Exploring HRV - DRAFT 🛠 |
31 | 31 |
|
32 | 32 | (ns data-analysis.heart-rate-variability.exploring-heart-rate-variability |
33 | 33 | (:require [tech.v3.datatype :as dtype] |
|
75 | 75 | (plotly/layer-bar {:=y :ppi}))) |
76 | 76 |
|
77 | 77 |
|
78 | | - |
79 | | -(def compute-spectrograms |
| 78 | +(def compute-measures |
80 | 79 | (fn [ppi-ds {:keys [sampling-rate |
81 | 80 | window-size-in-sec ]}] |
82 | 81 | (let [spline (interp/interpolation |
|
96 | 95 | hop-size 8 |
97 | 96 | n-windows (int (/ (- n window-size) |
98 | 97 | hop-size)) |
99 | | - spectrograms (->> (range n-windows) |
100 | | - (pfor/pmap (fn [w] |
101 | | - (let [start-idx (* w hop-size) |
102 | | - window (-> resampled-ppi |
103 | | - :ppi |
104 | | - (dtype/sub-buffer start-idx window-size)) |
105 | | - window-standardized (stats/standardize window) |
| 98 | + windows (->> (range n-windows) |
| 99 | + (map (fn [w] |
| 100 | + [w (let [start-idx (* w hop-size)] |
| 101 | + (-> resampled-ppi |
| 102 | + :ppi |
| 103 | + (dtype/sub-buffer start-idx window-size)))]))) |
| 104 | + rmssds (->> windows |
| 105 | + (map (fn [[w window]] |
| 106 | + (-> window |
| 107 | + (tcc/shift 1) |
| 108 | + (tcc/- window) |
| 109 | + tcc/sq |
| 110 | + tcc/mean |
| 111 | + tcc/sqrt)))) |
| 112 | + spectrograms (->> windows |
| 113 | + (pfor/pmap (fn [[w window]] |
| 114 | + (let [window-standardized (stats/standardize window) |
106 | 115 | window-filtered (.bandPassFilter bw |
107 | 116 | (double-array window-standardized) |
108 | 117 | 4 |
|
117 | 126 | vec)] |
118 | 127 | {:sampling-rate sampling-rate |
119 | 128 | :resampled-ppi resampled-ppi |
| 129 | + :rmssds rmssds |
120 | 130 | :spectrograms spectrograms}))) |
121 | 131 |
|
122 | 132 |
|
123 | 133 | (comment |
124 | | - (compute-spectrograms my-ppi |
125 | | - {:sampling-rate 10 |
126 | | - :window-size-in-sec 60})) |
127 | | - |
| 134 | + (compute-measures my-ppi |
| 135 | + {:sampling-rate 10 |
| 136 | + :window-size-in-sec 60})) |
128 | 137 |
|
129 | 138 |
|
130 | 139 | ;; [An Overview of Heart Rate Variability Metrics and Norms](https://pmc.ncbi.nlm.nih.gov/articles/PMC5624990/) |
|
145 | 154 | tcc/sum)))) |
146 | 155 |
|
147 | 156 |
|
148 | | - |
149 | | -(defn plot-with-power-spectrum [{:keys [sampling-rate |
150 | | - resampled-ppi |
151 | | - spectrograms]}] |
| 157 | +(defn plot-with-measures [{:keys [sampling-rate |
| 158 | + resampled-ppi |
| 159 | + rmssds |
| 160 | + spectrograms]}] |
152 | 161 | (when spectrograms |
153 | | - (kind/fragment |
154 | | - (let [n (-> spectrograms first :magnitude count) |
155 | | - Nyquist-freq (/ sampling-rate 2.0) |
156 | | - freq-resolution (/ Nyquist-freq n) |
157 | | - times (map (comp str :t) spectrograms) |
158 | | - freqs (tcc/* (range n) |
159 | | - freq-resolution)] |
160 | | - [(-> resampled-ppi |
161 | | - (plotly/base {:=height 300 :=width 700}) |
162 | | - (plotly/layer-bar (merge {:=x :t |
163 | | - :=y :ppi} |
164 | | - (when (:label resampled-ppi) |
165 | | - {:=color :label |
166 | | - :=color-type :nominal})))) |
167 | | - (kind/plotly |
168 | | - {:data [{:x times |
169 | | - :y freqs |
170 | | - :z (-> (mapv :magnitude spectrograms) |
171 | | - (tensor/transpose [1 0])) |
172 | | - :type :heatmap |
173 | | - :showscale false}] |
174 | | - :layout {:height 300 |
175 | | - :width 700 |
176 | | - :margin {:t 25} |
177 | | - :xaxis {:title {:text "t"}} |
178 | | - :yaxis {:title {:text "freq"}}}}) |
179 | | - (-> {:freq freqs |
180 | | - :mean-power (-> spectrograms |
181 | | - (->> (map :magnitude)) |
182 | | - tensor/->tensor |
183 | | - (tensor/reduce-axis dfn/mean 0))} |
184 | | - tc/dataset |
185 | | - (plotly/base {:=height 300 :=width 700}) |
186 | | - (plotly/layer-bar {:=x :freq |
187 | | - :=y :mean-power})) |
188 | | - (-> {:t times |
189 | | - :LF-to-HF (->> spectrograms |
190 | | - (map (partial LF-to-HF freqs)))} |
191 | | - tc/dataset |
192 | | - (plotly/base {:=height 300 :=width 700}) |
193 | | - (plotly/layer-line {:=x :t |
194 | | - :=y :LF-to-HF}) |
195 | | - plotly/plot |
196 | | - (assoc-in [:layout :yaxis :range] [0 4]) |
197 | | - (assoc-in [:layout :yaxis :title] {:text "LF/HF"}))])))) |
| 162 | + (let [n (-> spectrograms first :magnitude count) |
| 163 | + Nyquist-freq (/ sampling-rate 2.0) |
| 164 | + freq-resolution (/ Nyquist-freq n) |
| 165 | + times (map (comp str :t) spectrograms) |
| 166 | + freqs (tcc/* (range n) |
| 167 | + freq-resolution)] |
| 168 | + {:resampled-ppi (-> resampled-ppi |
| 169 | + (plotly/base {:=height 300 :=width 700}) |
| 170 | + (plotly/layer-bar (merge {:=x :t |
| 171 | + :=y :ppi} |
| 172 | + (when (:label resampled-ppi) |
| 173 | + {:=color :label |
| 174 | + :=color-type :nominal})))) |
| 175 | + :rmssd (-> {:t times |
| 176 | + :rmssd rmssds} |
| 177 | + tc/dataset |
| 178 | + (plotly/base {:=height 300 :=width 700}) |
| 179 | + (plotly/layer-line {:=x :t |
| 180 | + :=y :rmssd}) |
| 181 | + plotly/plot |
| 182 | + (assoc-in [:layout :yaxis :range] [0 0.1])) |
| 183 | + :power-spectrum (kind/plotly |
| 184 | + {:data [{:x times |
| 185 | + :y freqs |
| 186 | + :z (-> (mapv :magnitude spectrograms) |
| 187 | + (tensor/transpose [1 0])) |
| 188 | + :type :heatmap |
| 189 | + :showscale false}] |
| 190 | + :layout {:height 300 |
| 191 | + :width 700 |
| 192 | + :margin {:t 25} |
| 193 | + :xaxis {:title {:text "t"}} |
| 194 | + :yaxis {:title {:text "freq"}}}}) |
| 195 | + :mean-power-spectrum (-> {:freq freqs |
| 196 | + :mean-power (-> spectrograms |
| 197 | + (->> (map :magnitude)) |
| 198 | + tensor/->tensor |
| 199 | + (tensor/reduce-axis dfn/mean 0))} |
| 200 | + tc/dataset |
| 201 | + (plotly/base {:=height 300 :=width 700}) |
| 202 | + (plotly/layer-bar {:=x :freq |
| 203 | + :=y :mean-power})) |
| 204 | + :LF-to-HF-series (-> {:t times |
| 205 | + :LF-to-HF (->> spectrograms |
| 206 | + (map (partial LF-to-HF freqs)))} |
| 207 | + tc/dataset |
| 208 | + (plotly/base {:=height 300 :=width 700}) |
| 209 | + (plotly/layer-line {:=x :t |
| 210 | + :=y :LF-to-HF}) |
| 211 | + plotly/plot |
| 212 | + (assoc-in [:layout :yaxis :range] [0 4]) |
| 213 | + (assoc-in [:layout :yaxis :title] {:text "LF/HF"}))}))) |
198 | 214 |
|
199 | 215 |
|
200 | 216 | (delay |
201 | 217 | (-> my-ppi |
202 | | - (compute-spectrograms {:sampling-rate 10 |
203 | | - :window-size-in-sec 60}) |
204 | | - plot-with-power-spectrum)) |
| 218 | + (compute-measures {:sampling-rate 10 |
| 219 | + :window-size-in-sec 60}) |
| 220 | + plot-with-measures)) |
205 | 221 |
|
206 | 222 |
|
207 | 223 | ;; ## Analysing ECG data |
|
327 | 343 | (tcc/shift (:t %) 1))) |
328 | 344 | (tc/drop-rows [0])))) |
329 | 345 |
|
330 | | - |
331 | 346 | ;; ### Plotting the PPI |
332 | 347 |
|
333 | 348 | (delay |
|
341 | 356 | (plotly/layer-bar {:=x :t |
342 | 357 | :=y :ppi}))) |
343 | 358 |
|
344 | | -;; ## Spectrograms again |
| 359 | +;; ### Measures again |
345 | 360 |
|
346 | 361 | (def WESAD-spectrograms |
347 | 362 | (memoize |
348 | | - (fn [{:keys [ppi-params spectrogram-params]}] |
| 363 | + (fn [{:keys [ppi-params measures-params]}] |
349 | 364 | (-> ppi-params |
350 | 365 | extract-ppi |
351 | | - (compute-spectrograms spectrogram-params))))) |
| 366 | + (compute-measures measures-params))))) |
352 | 367 |
|
353 | 368 |
|
354 | 369 | (delay |
355 | 370 | (-> {:ppi-params {:subject-id 5 |
356 | 371 | :row-interval [0 1000000]} |
357 | | - :spectrogram-params {:sampling-rate 10 |
358 | | - :window-size-in-sec 120}} |
| 372 | + :measures-params {:sampling-rate 10 |
| 373 | + :window-size-in-sec 120}} |
359 | 374 | WESAD-spectrograms |
360 | | - plot-with-power-spectrum)) |
| 375 | + plot-with-measures)) |
| 376 | + |
| 377 | +;; ## A subject's journey |
| 378 | + |
| 379 | +(def id->label |
| 380 | + [:transient, :baseline, |
| 381 | + :stress, :amusement, :meditation, |
| 382 | + :ignore :ignore :ignore]) |
| 383 | + |
| 384 | +(def label-intervals |
| 385 | + (memoize |
| 386 | + (fn [subject] |
| 387 | + (-> (labelled-dataset subject) |
| 388 | + :label |
| 389 | + (->> (partition-by identity) |
| 390 | + (map (fn [part] |
| 391 | + [(-> part first int id->label) |
| 392 | + (count part)]))) |
| 393 | + tc/dataset |
| 394 | + (tc/rename-columns [:label :n]) |
| 395 | + (tc/add-column :offset #(cons 0 (reductions + (:n %)))) |
| 396 | + (tc/select-columns [:offset :n :label]))))) |
| 397 | + |
| 398 | + |
| 399 | +(delay |
| 400 | + (label-intervals 5)) |
361 | 401 |
|
362 | 402 |
|
| 403 | +(delay |
| 404 | + (let [subject 5] |
| 405 | + (kind/fragment |
| 406 | + (-> (label-intervals subject) |
| 407 | + (tc/select-rows #(not= (:label %) :ignore)) |
| 408 | + #_(tc/select-rows #(= (:label %) :meditation)) |
| 409 | + (tc/rows :as-maps) |
| 410 | + (->> (mapcat (fn [{:keys [offset n label]}] |
| 411 | + [label |
| 412 | + (try (-> {:ppi-params {:subject-id subject |
| 413 | + :row-interval [offset (+ offset n)]} |
| 414 | + :measures-params {:sampling-rate 10 |
| 415 | + :window-size-in-sec 120}} |
| 416 | + WESAD-spectrograms |
| 417 | + plot-with-measures) |
| 418 | + (catch Exception e 'unavailable))]))))))) |
0 commit comments