-
Notifications
You must be signed in to change notification settings - Fork 0
/
biometrics.py
327 lines (251 loc) · 12.1 KB
/
biometrics.py
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
"""
This module contains functions for biometrics calculations and comparison between the true and estimated biometrics.
"""
import numpy as np
from sklearn.metrics import mean_absolute_error
def calculate_ohv1_mae(signals_for_biometrics: dict, on_normalized: bool = False) -> float:
"""
Calculates the mean absolute error (MAE) between true and estimated Orthostatic Hypovolemia 1 (OHV1) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: MAE of the OHV1.
"""
signal_name = 'ppg'
if on_normalized:
signal_name = 'ppg_normalized'
ohv1_true_array, ohv1_pred_array = [], []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['true_labels']) == 2)
min_standing = min(signal[signal_name][mask])
ohv1_true = abs(min_standing - min_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal[signal_name][mask]) == 0:
continue
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['predicted_labels']) == 2)
if len(signal[signal_name][mask]) == 0:
continue
min_standing = min(signal[signal_name][mask])
ohv1_true_array.append(ohv1_true)
ohv1_pred_array.append(abs(min_standing - min_supine))
except ValueError:
continue
if len(ohv1_true_array) == 0 or len(ohv1_pred_array) == 0:
return np.nan
return mean_absolute_error(ohv1_true_array, ohv1_pred_array)
def calculate_ohv1_percentage_error(signals_for_biometrics: dict) -> float:
"""
Calculates the percentage error between true and estimated Orthostatic Hypovolemia 1 (OHV1) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: percentage error of the OHV1.
"""
signal_name = 'ppg'
ohv1_errors = []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['true_labels']) == 2)
min_standing = min(signal[signal_name][mask])
ohv1_true = abs(min_standing - min_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal[signal_name][mask]) == 0:
continue
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['predicted_labels']) == 2)
if len(signal[signal_name][mask]) == 0:
continue
min_standing = min(signal[signal_name][mask])
ohv1_pred = abs(min_standing - min_supine)
percentage_error = abs(ohv1_pred - ohv1_true) / ohv1_true
ohv1_errors.append(percentage_error)
except ValueError:
continue
if len(ohv1_errors) == 0:
return np.nan
return np.mean(ohv1_errors)
def calculate_ohv2_mae(signals_for_biometrics: dict, on_normalized: bool = False) -> float:
"""
Calculates the mean absolute error (MAE) between true and estimated Orthostatic Hypovolemia 2 (OHV2) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: MAE of the OHV2.
"""
signal_name = 'ppg'
if on_normalized:
signal_name = 'ppg_normalized'
ohv2_true_array, ohv2_pred_array = [], []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['true_labels']) == 3)
min_standing = min(signal[signal_name][mask])
ohv2_true = abs(min_standing - min_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal[signal_name][mask]) == 0:
continue
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['predicted_labels']) == 3)
if len(signal[signal_name][mask]) == 0:
continue
min_standing = min(signal[signal_name][mask])
ohv2_true_array.append(ohv2_true)
ohv2_pred_array.append(abs(min_standing - min_supine))
except ValueError:
continue
if len(ohv2_true_array) == 0 or len(ohv2_pred_array) == 0:
return np.nan
return mean_absolute_error(ohv2_true_array, ohv2_pred_array)
def calculate_ohv2_percentage_error(signals_for_biometrics: dict) -> float:
"""
Calculates the percentage error between true and estimated Orthostatic Hypovolemia 2 (OHV2) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: percentage error of the OHV2.
"""
signal_name = 'ppg'
ohv2_errors = []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['true_labels']) == 3)
min_standing = min(signal[signal_name][mask])
ohv2_true = abs(min_standing - min_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal[signal_name][mask]) == 0:
continue
min_supine = min(signal[signal_name][mask])
mask = np.where(np.array(signal['predicted_labels']) == 3)
if len(signal[signal_name][mask]) == 0:
continue
min_standing = min(signal[signal_name][mask])
ohv2_pred = abs(min_standing - min_supine)
percentage_error = abs(ohv2_pred - ohv2_true) / ohv2_true
ohv2_errors.append(percentage_error)
except ValueError:
continue
if len(ohv2_errors) == 0:
return np.nan
return np.mean(ohv2_errors)
def calculate_otc_mae(signals_for_biometrics: dict, sampling_rate: int = 50) -> float:
"""
Calculates the mean absolute error (MAE) between true and estimated
Orthostatic Time Constraint (OTC) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:param sampling_rate: The sampling rate of the signals. Defaults to 50.
:return: MAE of the OTC.
"""
otc_true_array, otc_pred_array = [], []
for signal in signals_for_biometrics:
try:
start_transition = signal['true_labels'].index(1)
orthostatis_archieved_at = signal['true_labels'].index(2)
otc_true = abs(orthostatis_archieved_at - start_transition) / sampling_rate
start_transition = signal['predicted_labels'].index(1)
orthostatis_archieved_at = signal['predicted_labels'].index(2)
otc_true_array.append(otc_true)
otc_pred_array.append(abs(orthostatis_archieved_at - start_transition) / sampling_rate)
except ValueError:
continue
if len(otc_true_array) == 0 or len(otc_pred_array) == 0:
return np.nan
return mean_absolute_error(otc_true_array, otc_pred_array)
def calculate_otc_percentage_error(signals_for_biometrics: dict, sampling_rate: int = 50) -> float:
"""
Calculates the percentage error between true and estimated
Orthostatic Time Constraint (OTC) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:param sampling_rate: The sampling rate of the signals. Defaults to 50.
:return: Percentage error of the OTC.
"""
otc_errors = []
for signal in signals_for_biometrics:
try:
start_transition = signal['true_labels'].index(1)
orthostatis_archieved_at = signal['true_labels'].index(2)
otc_true = abs(orthostatis_archieved_at - start_transition) / sampling_rate
start_transition = signal['predicted_labels'].index(1)
orthostatis_archieved_at = signal['predicted_labels'].index(2)
otc_pred = abs(orthostatis_archieved_at - start_transition) / sampling_rate
percentage_error = abs(otc_pred - otc_true) / otc_true
otc_errors.append(percentage_error)
except ValueError:
continue
if len(otc_errors) == 0:
return np.nan
return np.mean(otc_errors)
def calculate_pot_mae(signals_for_biometrics):
"""
Calculates the mean absolute error (MAE) between true and estimated
Postural Orthostatic Tachycardia (POT) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: MAE of the POT.
"""
pot_true_array, pot_pred_array = [], []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
avg_supine = np.mean(signal['hr'][mask])
mask = np.where((np.array(signal['true_labels']) == 2) | (np.array(signal['true_labels']) == 3))
max_standing_orthostatis = max(signal['hr'][mask])
pot_true = abs(max_standing_orthostatis - avg_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal['hr'][mask]) == 0:
continue
avg_supine = np.mean(signal['hr'][mask])
mask = np.where((np.array(signal['predicted_labels']) == 2) | (np.array(signal['predicted_labels']) == 3))
if len(signal['hr'][mask]) == 0:
continue
max_standing_orthostatis = max(signal['hr'][mask])
pot_true_array.append(pot_true)
pot_pred_array.append(abs(max_standing_orthostatis - avg_supine))
except:
continue
if len(pot_true_array) == 0 or len(pot_pred_array) == 0:
return np.nan
return mean_absolute_error(pot_true_array, pot_pred_array)
def calculate_pot_percentage_error(signals_for_biometrics):
"""
Calculates the percentage error between true and estimated
Postural Orthostatic Tachycardia (POT) biometric.
:param signals_for_biometrics: A dictionary containing signals, true and estimated stages labels.
:return: percentage error of the POT.
"""
pot_errors = []
for signal in signals_for_biometrics:
try:
mask = np.where(np.array(signal['true_labels']) == 0)
avg_supine = np.mean(signal['hr'][mask])
mask = np.where((np.array(signal['true_labels']) == 2) | (np.array(signal['true_labels']) == 3))
max_standing_orthostatis = max(signal['hr'][mask])
pot_true = abs(max_standing_orthostatis - avg_supine)
mask = np.where(np.array(signal['predicted_labels']) == 0)
if len(signal['hr'][mask]) == 0:
continue
avg_supine = np.mean(signal['hr'][mask])
mask = np.where((np.array(signal['predicted_labels']) == 2) | (np.array(signal['predicted_labels']) == 3))
if len(signal['hr'][mask]) == 0:
continue
max_standing_orthostatis = max(signal['hr'][mask])
pot_pred = abs(max_standing_orthostatis - avg_supine)
percentage_error = abs(pot_pred - pot_true) / pot_true
pot_errors.append(percentage_error)
except:
continue
if len(pot_errors) == 0:
return np.nan
return np.mean(pot_errors)
def calculate_mean_absolute_error_all(signals):
ohv1 = calculate_ohv1_mae(signals)
ohv2 = calculate_ohv2_mae(signals)
otc = calculate_otc_mae(signals)
pot = calculate_pot_mae(signals)
return ohv1, ohv2, otc, pot
def calculate_percentage_error_all(signals):
ohv1 = calculate_ohv1_percentage_error(signals) * 100
ohv2 = calculate_ohv2_percentage_error(signals) * 100
otc = calculate_otc_percentage_error(signals) * 100
pot = calculate_pot_percentage_error(signals) * 100
return ohv1, ohv2, otc, pot