Skip to content

Commit fbc4074

Browse files
committed
mths - wip
1 parent c807b18 commit fbc4074

1 file changed

Lines changed: 253 additions & 0 deletions

File tree

src/dsp/mths.clj

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
^{:kindly/hide-code true
2+
:clay {:title "DSP Study Group - Camera PPG data"
3+
:quarto {:author [:onbreath :daslu]
4+
:description "Analysing the MTHS dataset of Camera PPG data"
5+
:category :clojure
6+
:type :post
7+
:date "2025-11-21"
8+
:tags [:dsp :math :biometrics]}}}
9+
(ns dsp.mths)
10+
11+
;; ## Intro
12+
13+
;; ## Setup
14+
15+
(ns dsp.mths
16+
(:require [libpython-clj2.require :refer [require-python]]
17+
[libpython-clj2.python :refer [py. py.. py.-] :as py]
18+
[tech.v3.datatype :as dtype]
19+
[tech.v3.datatype.functional :as dfn]
20+
[libpython-clj2.python.np-array]
21+
[clojure.java.io :as io]
22+
[babashka.fs :as fs]
23+
[clojure.string :as str]
24+
[tablecloth.api :as tc]
25+
[tech.v3.dataset.tensor :as ds-tensor]
26+
[scicloj.tableplot.v1.plotly :as plotly]
27+
[tablecloth.column.api :as tcc]
28+
[fastmath.stats :as stats]
29+
[scicloj.kindly.v4.kind :as kind]
30+
[tech.v3.parallel.for :as pfor]
31+
[tech.v3.tensor :as tensor]
32+
[tech.v3.dataset :as ds])
33+
(:import [com.github.psambit9791.jdsp.filter Butterworth Chebyshev]
34+
[com.github.psambit9791.jdsp.signal Detrend]
35+
[com.github.psambit9791.jdsp.signal.peaks FindPeak Peak]
36+
[com.github.psambit9791.jdsp.transform DiscreteFourier FastFourier]
37+
[com.github.psambit9791.jdsp.windows Hanning]))
38+
39+
(require-python '[numpy :as np])
40+
41+
^:kindly/hide-code
42+
(kind/hiccup
43+
[:style
44+
".clay-dataset {
45+
max-height:400px;
46+
overflow-y: auto;
47+
}
48+
"])
49+
50+
;; ## Reading data
51+
52+
;; We assume you have downloaded the MEDVSE repo alongside your Clojure project repo.
53+
54+
(def data-base-path
55+
"../MEDVSE/MTHS/Data/")
56+
57+
;; Let us see how we read one file as a Numpy data structure.
58+
59+
(np/load (str data-base-path "/signal_12.npy"))
60+
61+
;; Now, let us read all data and organise it in one data structure.
62+
63+
(def raw-data
64+
(-> data-base-path
65+
fs/list-dir
66+
(->> (map (fn [path]
67+
(let [nam (-> path
68+
fs/file-name
69+
(str/replace #"\.npy" ""))]
70+
[[(keyword (re-find #"signal|label" nam))
71+
(-> nam
72+
(str/split #"_")
73+
last
74+
Integer/parseInt)]
75+
(-> path
76+
str
77+
np/load)])))
78+
(into {}))))
79+
80+
;; Numpy arrays can be inspected using dtype-next:
81+
82+
(dtype/shape (raw-data [:signal 23])) ; 30Hz signal
83+
(dtype/shape (raw-data [:label 23])) ; 1Hz labels
84+
85+
86+
(def sampling-rate 30)
87+
88+
89+
;; A function to read the signal of the i'th subject:
90+
91+
(defn signal [i]
92+
(some-> [:signal i]
93+
raw-data
94+
ds-tensor/tensor->dataset
95+
(tc/rename-columns [:R :G :B])
96+
(tc/add-column :t (tcc// (range) 30.0))))
97+
98+
;; For example:
99+
100+
(signal 23)
101+
102+
;; ## Plotting
103+
104+
(defn plot-signal [s]
105+
(-> s
106+
(plotly/base {:=x :t
107+
:=mark-opacity 0.7})
108+
(plotly/layer-line {:=y :R
109+
:=mark-color "red"})
110+
(plotly/layer-line {:=y :G
111+
:=mark-color "green"})
112+
(plotly/layer-line {:=y :B
113+
:=mark-color "blue"})))
114+
115+
116+
(-> 23
117+
signal
118+
plot-signal)
119+
120+
121+
;; ## Taking a signal through a pipeline of transformations
122+
123+
(defn plot-signals-with-transformations [transformations]
124+
(kind/table
125+
{:row-vectors (->> (range 2 62)
126+
(pfor/pmap (fn [i]
127+
(->> transformations
128+
vals
129+
(reductions (fn [ds t]
130+
(tc/update-columns
131+
ds [:R :G :B] t))
132+
(signal i))
133+
(map plot-signal)
134+
(cons (kind/md (str "### " i))))))
135+
136+
kind/table)
137+
:column-names (concat ["subject" "raw"]
138+
(keys transformations))}))
139+
140+
141+
(defn remove-dc [signal]
142+
(-> signal
143+
double-array
144+
(Detrend. "constant")
145+
.detrendSignal))
146+
147+
148+
;; Bandpass filter (0.5-5 Hz for heart rate range)
149+
(defn bandpass-filter [signal {:keys [fs order low-cutoff high-cutoff]}]
150+
(let [flt (Butterworth. fs)
151+
result (.bandPassFilter flt
152+
(double-array signal)
153+
order
154+
low-cutoff
155+
high-cutoff)]
156+
(vec result)))
157+
158+
159+
160+
(let [[low-cutoff high-cutoff] [0.5 5]
161+
window-size 10
162+
window-samples (* sampling-rate window-size)
163+
overlap-fraction 0.5
164+
hop (* sampling-rate window-samples)
165+
windows-starts (range 0 )]
166+
(plot-signals-with-transformations
167+
{:remove-dc remove-dc
168+
:bandpass #(bandpass-filter % {:fs sampling-rate
169+
:order 4
170+
:low-cutoff low-cutoff
171+
:high-cutoff high-cutoff})
172+
:standardize stats/standardize}))
173+
174+
175+
176+
177+
;; ## Power spectry (WIP draft, incomplete)
178+
179+
180+
(let [[low-cutoff high-cutoff] [0.65 4]
181+
window-size 10
182+
window-samples (* sampling-rate window-size)
183+
hanning (-> window-samples
184+
Hanning.
185+
.getWindow
186+
dtype/as-reader)
187+
overlap-fraction 0.05
188+
hop (int (* overlap-fraction window-samples))
189+
subject 51
190+
sig (signal subject)
191+
n-samples (tc/row-count sig)
192+
std (-> sig
193+
(tc/update-columns [:R :G :B]
194+
#(-> %
195+
remove-dc
196+
(bandpass-filter {:fs sampling-rate
197+
:order 4
198+
:low-cutoff low-cutoff
199+
:high-cutoff high-cutoff})
200+
stats/standardize))
201+
(tc/select-columns [:R :G :B])
202+
ds-tensor/dataset->tensor)
203+
n-windows (-> n-samples
204+
(- window-samples)
205+
(quot hop))
206+
windows-shape [window-samples
207+
n-windows
208+
3]
209+
windows (tensor/compute-tensor
210+
windows-shape
211+
(fn [i j k]
212+
(std (+ (* j hop) i)
213+
k))
214+
:float32)
215+
hanninged-windows (tensor/compute-tensor
216+
windows-shape
217+
(fn [i j k]
218+
(* (windows i j k)
219+
(hanning i)))
220+
:float32)
221+
power-spectrum (-> hanninged-windows
222+
(tensor/transpose [1 2 0])
223+
(tensor/slice 2)
224+
(->> (mapv (fn [window]
225+
(let [fft (-> window
226+
double-array
227+
DiscreteFourier.)]
228+
(.transform fft)
229+
(.getMagnitude fft true)))))
230+
tensor/->tensor
231+
(#(tensor/reshape %
232+
[n-windows
233+
3
234+
(-> % first count)]))
235+
(tensor/transpose [2 0 1]))]
236+
[(-> windows
237+
(dfn/* 100)
238+
plotly/imshow)
239+
(-> hanninged-windows
240+
(dfn/* 100)
241+
plotly/imshow)
242+
(-> power-spectrum
243+
plotly/imshow)
244+
(-> power-spectrum
245+
(tensor/slice 1)
246+
(->> (map (fn [s]
247+
(-> s
248+
ds-tensor/tensor->dataset
249+
(tc/rename-columns [:R :G :B])
250+
plot-signal)))))])
251+
252+
253+

0 commit comments

Comments
 (0)