paradigma 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. paradigma/__init__.py +1 -3
  2. paradigma/constants.py +35 -0
  3. paradigma/feature_extraction.py +678 -0
  4. paradigma/gait_analysis.py +413 -0
  5. paradigma/gait_analysis_config.py +244 -0
  6. paradigma/heart_rate_analysis.py +127 -0
  7. paradigma/heart_rate_analysis_config.py +9 -0
  8. paradigma/heart_rate_util.py +173 -0
  9. paradigma/imu_preprocessing.py +229 -0
  10. paradigma/ppg/classifier/LR_PPG_quality.pkl +0 -0
  11. paradigma/ppg/classifier/LR_model.mat +0 -0
  12. paradigma/ppg/feat_extraction/acc_feature.m +20 -0
  13. paradigma/ppg/feat_extraction/peakdet.m +64 -0
  14. paradigma/ppg/feat_extraction/ppg_features.m +53 -0
  15. paradigma/ppg/glob_functions/extract_hr_segments.m +37 -0
  16. paradigma/ppg/glob_functions/extract_overlapping_segments.m +23 -0
  17. paradigma/ppg/glob_functions/jsonlab/AUTHORS.txt +41 -0
  18. paradigma/ppg/glob_functions/jsonlab/ChangeLog.txt +74 -0
  19. paradigma/ppg/glob_functions/jsonlab/LICENSE_BSD.txt +25 -0
  20. paradigma/ppg/glob_functions/jsonlab/LICENSE_GPLv3.txt +699 -0
  21. paradigma/ppg/glob_functions/jsonlab/README.txt +394 -0
  22. paradigma/ppg/glob_functions/jsonlab/examples/.svn/entries +368 -0
  23. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_jsonlab_basic.m.svn-base +180 -0
  24. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_ubjson_basic.m.svn-base +180 -0
  25. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example1.json.svn-base +23 -0
  26. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example2.json.svn-base +22 -0
  27. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example3.json.svn-base +11 -0
  28. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example4.json.svn-base +34 -0
  29. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_basictest.matlab.svn-base +662 -0
  30. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.m.svn-base +27 -0
  31. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.matlab.svn-base +144 -0
  32. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_speedtest.m.svn-base +21 -0
  33. paradigma/ppg/glob_functions/jsonlab/examples/demo_jsonlab_basic.m +180 -0
  34. paradigma/ppg/glob_functions/jsonlab/examples/demo_ubjson_basic.m +180 -0
  35. paradigma/ppg/glob_functions/jsonlab/examples/example1.json +23 -0
  36. paradigma/ppg/glob_functions/jsonlab/examples/example2.json +22 -0
  37. paradigma/ppg/glob_functions/jsonlab/examples/example3.json +11 -0
  38. paradigma/ppg/glob_functions/jsonlab/examples/example4.json +34 -0
  39. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_basictest.matlab +662 -0
  40. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.m +27 -0
  41. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.matlab +144 -0
  42. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_speedtest.m +21 -0
  43. paradigma/ppg/glob_functions/jsonlab/jsonopt.m +32 -0
  44. paradigma/ppg/glob_functions/jsonlab/loadjson.m +566 -0
  45. paradigma/ppg/glob_functions/jsonlab/loadubjson.m +528 -0
  46. paradigma/ppg/glob_functions/jsonlab/mergestruct.m +33 -0
  47. paradigma/ppg/glob_functions/jsonlab/savejson.m +475 -0
  48. paradigma/ppg/glob_functions/jsonlab/saveubjson.m +504 -0
  49. paradigma/ppg/glob_functions/jsonlab/varargin2struct.m +40 -0
  50. paradigma/ppg/glob_functions/sample_prob_final.m +49 -0
  51. paradigma/ppg/glob_functions/synchronization.m +76 -0
  52. paradigma/ppg/glob_functions/tsdf_scan_meta.m +22 -0
  53. paradigma/ppg/hr_functions/Long_TFD_JOT.m +37 -0
  54. paradigma/ppg/hr_functions/PPG_TFD_HR.m +59 -0
  55. paradigma/ppg/hr_functions/TFD toolbox JOT/.gitignore +4 -0
  56. paradigma/ppg/hr_functions/TFD toolbox JOT/CHANGELOG.md +23 -0
  57. paradigma/ppg/hr_functions/TFD toolbox JOT/LICENCE.md +27 -0
  58. paradigma/ppg/hr_functions/TFD toolbox JOT/README.md +251 -0
  59. paradigma/ppg/hr_functions/TFD toolbox JOT/README.pdf +0 -0
  60. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_kern.m +142 -0
  61. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_lag_kern.m +314 -0
  62. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_lag_kern.m +123 -0
  63. paradigma/ppg/hr_functions/TFD toolbox JOT/dec_tfd.m +154 -0
  64. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_di_gdtfd.m +194 -0
  65. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_li_gdtfd.m +200 -0
  66. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_nonsep_gdtfd.m +229 -0
  67. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_sep_gdtfd.m +241 -0
  68. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/di_gdtfd.m +157 -0
  69. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/li_gdtfd.m +190 -0
  70. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/nonsep_gdtfd.m +196 -0
  71. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/sep_gdtfd.m +199 -0
  72. paradigma/ppg/hr_functions/TFD toolbox JOT/full_tfd.m +144 -0
  73. paradigma/ppg/hr_functions/TFD toolbox JOT/load_curdir.m +13 -0
  74. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/decimated_TFDs_examples.png +0 -0
  75. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/full_TFDs_examples.png +0 -0
  76. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/check_dec_params_seq.m +79 -0
  77. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispEE.m +9 -0
  78. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispVars.m +26 -0
  79. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/disp_bytes.m +25 -0
  80. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_full.m +40 -0
  81. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_half.m +34 -0
  82. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/gen_LFM.m +29 -0
  83. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_analytic_signal.m +76 -0
  84. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_window.m +176 -0
  85. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/isreal_fn.m +11 -0
  86. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/padWin.m +97 -0
  87. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/vtfd.m +149 -0
  88. paradigma/ppg/preprocessing/preprocessing_imu.m +15 -0
  89. paradigma/ppg/preprocessing/preprocessing_ppg.m +13 -0
  90. paradigma/ppg_preprocessing.py +313 -0
  91. paradigma/preprocessing_config.py +64 -0
  92. paradigma/quantification.py +58 -0
  93. paradigma/tremor/TremorFeaturesAndClassification.m +345 -0
  94. paradigma/tremor/feat_extraction/DerivativesExtract.m +22 -0
  95. paradigma/tremor/feat_extraction/ExtractBandSignalsRMS.m +72 -0
  96. paradigma/tremor/feat_extraction/MFCCExtract.m +100 -0
  97. paradigma/tremor/feat_extraction/PSDBandPower.m +52 -0
  98. paradigma/tremor/feat_extraction/PSDEst.m +63 -0
  99. paradigma/tremor/feat_extraction/PSDExtrAxis.m +88 -0
  100. paradigma/tremor/feat_extraction/PSDExtrOpt.m +95 -0
  101. paradigma/tremor/preprocessing/InterpData.m +32 -0
  102. paradigma/tremor/weekly_aggregates/WeeklyAggregates.m +295 -0
  103. paradigma/util.py +50 -0
  104. paradigma/windowing.py +217 -0
  105. paradigma-0.2.0.dist-info/LICENSE +192 -0
  106. paradigma-0.2.0.dist-info/METADATA +58 -0
  107. paradigma-0.2.0.dist-info/RECORD +108 -0
  108. paradigma/dummy.py +0 -3
  109. paradigma-0.1.2.dist-info/LICENSE +0 -201
  110. paradigma-0.1.2.dist-info/METADATA +0 -18
  111. paradigma-0.1.2.dist-info/RECORD +0 -6
  112. {paradigma-0.1.2.dist-info → paradigma-0.2.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,313 @@
1
+ import os
2
+ import json
3
+ import numpy as np
4
+ import pandas as pd
5
+ from pathlib import Path
6
+ from typing import List
7
+ from datetime import datetime, timedelta
8
+
9
+ import tsdf
10
+ from paradigma.constants import DataUnits, TimeUnit, DataColumns
11
+ from paradigma.preprocessing_config import PPGPreprocessingConfig, IMUPreprocessingConfig
12
+ from paradigma.util import parse_iso8601_to_datetime, write_data
13
+ import paradigma.imu_preprocessing
14
+
15
+
16
+ def scan_and_sync_segments(input_path_ppg, input_path_imu):
17
+
18
+ # Scan for available TSDF metadata files
19
+ meta_ppg = extract_meta_from_tsdf_files(input_path_ppg)
20
+ meta_imu = extract_meta_from_tsdf_files(input_path_imu)
21
+
22
+ # Synchronize PPG and IMU data segments
23
+ segments_ppg, segments_imu = synchronization(meta_ppg, meta_imu) # Define `synchronization`
24
+
25
+ assert len(segments_ppg) == len(segments_imu), 'Number of PPG and IMU segments do not match.'
26
+
27
+ # Load metadata for every synced segment pair
28
+ metadatas_ppg = [tsdf.load_metadata_from_path(meta_ppg[index]['tsdf_meta_fullpath']) for index in segments_ppg]
29
+ metadatas_imu = [tsdf.load_metadata_from_path(meta_imu[index]['tsdf_meta_fullpath']) for index in segments_imu]
30
+
31
+ return metadatas_ppg, metadatas_imu
32
+
33
+
34
+ def preprocess_ppg_data(tsdf_meta_ppg: tsdf.TSDFMetadata, tsdf_meta_imu: tsdf.TSDFMetadata, output_path: str, ppg_config: PPGPreprocessingConfig, imu_config: IMUPreprocessingConfig):
35
+
36
+ # Load PPG data
37
+ metadata_time_ppg = tsdf_meta_ppg[ppg_config.time_filename]
38
+ metadata_samples_ppg = tsdf_meta_ppg[ppg_config.values_filename]
39
+ df_ppg = tsdf.load_dataframe_from_binaries([metadata_time_ppg, metadata_samples_ppg], tsdf.constants.ConcatenationType.columns)
40
+
41
+ # Load IMU data
42
+ metadata_time_imu = tsdf_meta_imu[imu_config.time_filename]
43
+ metadata_samples_imu = tsdf_meta_imu[imu_config.values_filename]
44
+ df_imu = tsdf.load_dataframe_from_binaries([metadata_time_imu, metadata_samples_imu], tsdf.constants.ConcatenationType.columns)
45
+
46
+ # Drop the gyroscope columns from the IMU data
47
+ cols_to_drop = df_imu.filter(regex='^rotation_').columns
48
+ df_imu.drop(cols_to_drop, axis=1, inplace=True)
49
+ df_imu = df_imu.rename(columns={f'acceleration_{a}': f'accelerometer_{a}' for a in ['x', 'y', 'z']})
50
+
51
+ # Transform the time arrays to absolute milliseconds
52
+ start_time_ppg = parse_iso8601_to_datetime(metadata_time_ppg.start_iso8601).timestamp()
53
+ df_imu[DataColumns.TIME] = paradigma.imu_preprocessing.transform_time_array(
54
+ time_array=df_imu[DataColumns.TIME],
55
+ scale_factor=1000,
56
+ input_unit_type = paradigma.constants.TimeUnit.difference_ms,
57
+ output_unit_type = paradigma.constants.TimeUnit.absolute_ms,
58
+ start_time = start_time_ppg)
59
+
60
+ start_time_imu = parse_iso8601_to_datetime(metadata_time_imu.start_iso8601).timestamp()
61
+ df_ppg[DataColumns.TIME] = paradigma.imu_preprocessing.transform_time_array(
62
+ time_array=df_ppg[DataColumns.TIME],
63
+ scale_factor=1000,
64
+ input_unit_type = paradigma.constants.TimeUnit.difference_ms,
65
+ output_unit_type = paradigma.constants.TimeUnit.absolute_ms,
66
+ start_time = start_time_imu)
67
+
68
+ # Extract overlapping segments
69
+ print("Shape of the original data:", df_ppg.shape, df_imu.shape)
70
+ df_ppg_overlapping, df_imu_overlapping = extract_overlapping_segments(df_ppg, df_imu)
71
+ print("Shape of the overlapping segments:", df_ppg_overlapping.shape, df_imu_overlapping.shape)
72
+
73
+ # The following method is failing
74
+ df_imu_proc = paradigma.imu_preprocessing.resample_data(
75
+ df=df_imu_overlapping,
76
+ time_column=DataColumns.TIME,
77
+ time_unit_type=paradigma.constants.TimeUnit.absolute_ms,
78
+ unscaled_column_names = list(imu_config.d_channels_accelerometer.keys()),
79
+ resampling_frequency=imu_config.sampling_frequency,
80
+ scale_factors=metadata_samples_imu.scale_factors[0:3],
81
+ start_time=start_time_imu)
82
+
83
+ # metadata_samples_ppg.scale_factors - the data specifies 1, but it is not an obligatory tsdf field, maybe it should be optional parameter in `resample_data`
84
+ df_ppg_proc = paradigma.imu_preprocessing.resample_data(
85
+ df=df_ppg_overlapping,
86
+ time_column=DataColumns.TIME,
87
+ time_unit_type=paradigma.constants.TimeUnit.absolute_ms,
88
+ unscaled_column_names = list(ppg_config.d_channels_ppg.keys()),
89
+ scale_factors=metadata_samples_imu.scale_factors,
90
+ resampling_frequency=ppg_config.sampling_frequency,
91
+ start_time = start_time_imu
92
+ )
93
+
94
+ # apply Butterworth filter to accelerometer data
95
+ for col in imu_config.d_channels_accelerometer.keys():
96
+
97
+ # change to correct units [g]
98
+ if imu_config.acceleration_units == DataUnits.ACCELERATION:
99
+ df_imu_proc[col] /= 9.81
100
+
101
+ for result, side_pass in zip(['filt', 'grav'], ['hp', 'lp']):
102
+ df_imu_proc[f'{result}_{col}'] = paradigma.imu_preprocessing.butterworth_filter(
103
+ single_sensor_col=np.array(df_imu_proc[col]),
104
+ order=imu_config.filter_order,
105
+ cutoff_frequency=imu_config.lower_cutoff_frequency,
106
+ passband=side_pass,
107
+ sampling_frequency=imu_config.sampling_frequency,
108
+ )
109
+
110
+ df_imu_proc = df_imu_proc.drop(columns=[col])
111
+ df_imu_proc = df_imu_proc.rename(columns={f'filt_{col}': col})
112
+
113
+ for col in ppg_config.d_channels_ppg.keys():
114
+ df_ppg_proc[f'filt_{col}'] = paradigma.imu_preprocessing.butterworth_filter(
115
+ single_sensor_col=np.array(df_ppg_proc[col]),
116
+ order=ppg_config.filter_order,
117
+ cutoff_frequency=[ppg_config.lower_cutoff_frequency, ppg_config.upper_cutoff_frequency],
118
+ passband='band',
119
+ sampling_frequency=ppg_config.sampling_frequency,
120
+ )
121
+
122
+ df_ppg_proc = df_ppg_proc.drop(columns=[col])
123
+ df_ppg_proc = df_ppg_proc.rename(columns={f'filt_{col}': col})
124
+
125
+ df_imu_proc[DataColumns.TIME] = paradigma.imu_preprocessing.transform_time_array(
126
+ time_array=df_imu_proc[DataColumns.TIME],
127
+ scale_factor=1,
128
+ input_unit_type=paradigma.constants.TimeUnit.absolute_ms,
129
+ output_unit_type=paradigma.constants.TimeUnit.relative_ms,
130
+ start_time=start_time_ppg,
131
+ )
132
+
133
+ df_ppg_proc[DataColumns.TIME] = paradigma.imu_preprocessing.transform_time_array(
134
+ time_array=df_ppg_proc[DataColumns.TIME],
135
+ scale_factor=1,
136
+ input_unit_type=paradigma.constants.TimeUnit.absolute_ms,
137
+ output_unit_type=paradigma.constants.TimeUnit.relative_ms,
138
+ start_time=start_time_imu,
139
+ )
140
+
141
+ # Store data
142
+ metadata_samples_imu.channels = list(imu_config.d_channels_accelerometer.keys())
143
+ metadata_samples_imu.units = list(imu_config.d_channels_accelerometer.values())
144
+ metadata_samples_imu.file_name = 'accelerometer_samples.bin'
145
+ metadata_time_imu.units = [TimeUnit.absolute_ms]
146
+ metadata_time_imu.file_name = 'accelerometer_time.bin'
147
+ write_data(metadata_time_imu, metadata_samples_imu, output_path, 'accelerometer_meta.json', df_imu_proc)
148
+
149
+ metadata_samples_ppg.channels = list(ppg_config.d_channels_ppg.keys())
150
+ metadata_samples_ppg.units = list(ppg_config.d_channels_ppg.values())
151
+ metadata_samples_ppg.file_name = 'PPG_samples.bin'
152
+ metadata_time_ppg.units = [TimeUnit.absolute_ms]
153
+ metadata_time_ppg.file_name = 'PPG_time.bin'
154
+ write_data(metadata_time_ppg, metadata_samples_ppg, output_path, 'PPG_meta.json', df_ppg_proc)
155
+
156
+
157
+ # TODO: ideally something like this should be possible directly in the tsdf library
158
+ def extract_meta_from_tsdf_files(tsdf_data_dir : str) -> List[dict]:
159
+ """
160
+ For each given TSDF directory, transcribe TSDF metadata contents to a list of dictionaries.
161
+
162
+ Parameters
163
+ ----------
164
+ tsdf_data_dir : str
165
+ Path to the directory containing TSDF metadata files.
166
+
167
+ Returns
168
+ -------
169
+ List[Dict]
170
+ List of dictionaries with metadata from each JSON file in the directory.
171
+
172
+ Examples
173
+ --------
174
+ >>> extract_meta_from_tsdf_files('/path/to/tsdf_data')
175
+ [{'start_iso8601': '2021-06-27T16:52:20Z', 'end_iso8601': '2021-06-27T17:52:20Z'}, ...]
176
+ """
177
+ metas = []
178
+
179
+ # Collect all metadata JSON files in the specified directory
180
+ meta_list = list(Path(tsdf_data_dir).rglob('*_meta.json'))
181
+ for meta_file in meta_list:
182
+ with open(meta_file, 'r') as file:
183
+ json_obj = json.load(file)
184
+ meta_data = {
185
+ 'tsdf_meta_fullpath': str(meta_file),
186
+ 'subject_id': json_obj['subject_id'],
187
+ 'start_iso8601': json_obj['start_iso8601'],
188
+ 'end_iso8601': json_obj['end_iso8601']
189
+ }
190
+ metas.append(meta_data)
191
+
192
+ return metas
193
+
194
+
195
+ def synchronization(ppg_meta, imu_meta):
196
+ """
197
+ Synchronize PPG and IMU data segments based on their start and end times.
198
+
199
+ Parameters
200
+ ----------
201
+ ppg_meta : list of dict
202
+ List of dictionaries containing 'start_iso8601' and 'end_iso8601' keys for PPG data.
203
+ imu_meta : list of dict
204
+ List of dictionaries containing 'start_iso8601' and 'end_iso8601' keys for IMU data.
205
+
206
+ Returns
207
+ -------
208
+ segment_ppg_total : list of int
209
+ List of synchronized segment indices for PPG data.
210
+ segment_imu_total : list of int
211
+ List of synchronized segment indices for IMU data.
212
+ """
213
+ ppg_start_time = [parse_iso8601_to_datetime(t['start_iso8601']) for t in ppg_meta]
214
+ imu_start_time = [parse_iso8601_to_datetime(t['start_iso8601']) for t in imu_meta]
215
+ ppg_end_time = [parse_iso8601_to_datetime(t['end_iso8601']) for t in ppg_meta]
216
+ imu_end_time = [parse_iso8601_to_datetime(t['end_iso8601']) for t in imu_meta]
217
+
218
+ # Create a time vector covering the entire range
219
+ time_vector_total = []
220
+ current_time = min(min(ppg_start_time), min(imu_start_time))
221
+ end_time = max(max(ppg_end_time), max(imu_end_time))
222
+ while current_time <= end_time:
223
+ time_vector_total.append(current_time)
224
+ current_time += timedelta(seconds=1)
225
+
226
+ time_vector_total = np.array(time_vector_total)
227
+
228
+ # Initialize variables
229
+ data_presence_ppg = np.zeros(len(time_vector_total), dtype=int)
230
+ data_presence_ppg_idx = np.zeros(len(time_vector_total), dtype=int)
231
+ data_presence_imu = np.zeros(len(time_vector_total), dtype=int)
232
+ data_presence_imu_idx = np.zeros(len(time_vector_total), dtype=int)
233
+
234
+ # Mark the segments of PPG data with 1
235
+ for i, (start, end) in enumerate(zip(ppg_start_time, ppg_end_time)):
236
+ indices = np.where((time_vector_total >= start) & (time_vector_total < end))[0]
237
+ data_presence_ppg[indices] = 1
238
+ data_presence_ppg_idx[indices] = i
239
+
240
+ # Mark the segments of IMU data with 1
241
+ for i, (start, end) in enumerate(zip(imu_start_time, imu_end_time)):
242
+ indices = np.where((time_vector_total >= start) & (time_vector_total < end))[0]
243
+ data_presence_imu[indices] = 1
244
+ data_presence_imu_idx[indices] = i
245
+
246
+ # Find the indices where both PPG and IMU data are present
247
+ corr_indices = np.where((data_presence_ppg == 1) & (data_presence_imu == 1))[0]
248
+
249
+ # Find the start and end indices of each segment
250
+ corr_start_end = []
251
+ if len(corr_indices) > 0:
252
+ start_idx = corr_indices[0]
253
+ for i in range(1, len(corr_indices)):
254
+ if corr_indices[i] - corr_indices[i - 1] > 1:
255
+ end_idx = corr_indices[i - 1]
256
+ corr_start_end.append((start_idx, end_idx))
257
+ start_idx = corr_indices[i]
258
+ # Add the last segment
259
+ corr_start_end.append((start_idx, corr_indices[-1]))
260
+
261
+ # Extract the synchronized indices for each segment
262
+ segment_ppg_total = []
263
+ segment_imu_total = []
264
+ for start_idx, end_idx in corr_start_end:
265
+ segment_ppg = np.unique(data_presence_ppg_idx[start_idx:end_idx + 1])
266
+ segment_imu = np.unique(data_presence_imu_idx[start_idx:end_idx + 1])
267
+ if len(segment_ppg) > 1 and len(segment_imu) == 1:
268
+ segment_ppg_total.extend(segment_ppg)
269
+ segment_imu_total.extend([segment_imu[0]] * len(segment_ppg))
270
+ elif len(segment_ppg) == 1 and len(segment_imu) > 1:
271
+ segment_ppg_total.extend([segment_ppg[0]] * len(segment_imu))
272
+ segment_imu_total.extend(segment_imu)
273
+ elif len(segment_ppg) == len(segment_imu):
274
+ segment_ppg_total.extend(segment_ppg)
275
+ segment_imu_total.extend(segment_imu)
276
+ else:
277
+ continue
278
+
279
+ return segment_ppg_total, segment_imu_total
280
+
281
+ def extract_overlapping_segments(df_ppg, df_imu, time_column_ppg='time', time_column_imu='time'):
282
+ """
283
+ Extract DataFrames with overlapping data segments between IMU and PPG datasets based on their timestamps.
284
+
285
+ Parameters:
286
+ df_ppg (pd.DataFrame): DataFrame containing PPG data with a time column in UNIX milliseconds.
287
+ df_imu (pd.DataFrame): DataFrame containing IMU data with a time column in UNIX milliseconds.
288
+ time_column_ppg (str): Column name of the timestamp in the PPG DataFrame.
289
+ time_column_imu (str): Column name of the timestamp in the IMU DataFrame.
290
+
291
+ Returns:
292
+ tuple: Tuple containing two DataFrames (df_ppg_overlapping, df_imu_overlapping) that contain only the data
293
+ within the overlapping time segments.
294
+ """
295
+ # Convert UNIX milliseconds to seconds
296
+ ppg_time = df_ppg[time_column_ppg] / 1000 # Convert milliseconds to seconds
297
+ imu_time = df_imu[time_column_imu] / 1000 # Convert milliseconds to seconds
298
+
299
+ # Determine the overlapping time interval
300
+ start_time = max(ppg_time.iloc[0], imu_time.iloc[0])
301
+ end_time = min(ppg_time.iloc[-1], imu_time.iloc[-1])
302
+
303
+ # Extract indices for overlapping segments
304
+ ppg_start_index = np.searchsorted(ppg_time, start_time, 'left')
305
+ ppg_end_index = np.searchsorted(ppg_time, end_time, 'right') - 1
306
+ imu_start_index = np.searchsorted(imu_time, start_time, 'left')
307
+ imu_end_index = np.searchsorted(imu_time, end_time, 'right') - 1
308
+
309
+ # Extract overlapping segments from DataFrames
310
+ df_ppg_overlapping = df_ppg.iloc[ppg_start_index:ppg_end_index + 1]
311
+ df_imu_overlapping = df_imu.iloc[imu_start_index:imu_end_index + 1]
312
+
313
+ return df_ppg_overlapping, df_imu_overlapping
@@ -0,0 +1,64 @@
1
+ from paradigma.constants import DataColumns
2
+
3
+
4
+ class BasePreprocessingConfig:
5
+
6
+ def __init__(self) -> None:
7
+
8
+ self.meta_filename = ''
9
+ self.values_filename = ''
10
+ self.time_filename = ''
11
+
12
+ self.acceleration_units = 'm/s^2'
13
+ self.rotation_units = 'deg/s'
14
+
15
+ self.time_colname = DataColumns.TIME
16
+
17
+ # participant information
18
+ self.side_watch = 'right'
19
+
20
+ # filtering
21
+ self.sampling_frequency = 100
22
+ self.lower_cutoff_frequency = 0.2
23
+ self.upper_cutoff_frequency = 3.5
24
+ self.filter_order = 4
25
+
26
+
27
+ class IMUPreprocessingConfig(BasePreprocessingConfig):
28
+
29
+ def __init__(self) -> None:
30
+ super().__init__()
31
+
32
+ self.meta_filename = 'IMU_meta.json'
33
+ self.values_filename = 'IMU_samples.bin'
34
+ self.time_filename = 'IMU_time.bin'
35
+
36
+ self.acceleration_units = 'm/s^2'
37
+ self.rotation_units = 'deg/s'
38
+
39
+ self.d_channels_accelerometer = {
40
+ DataColumns.ACCELEROMETER_X: self.acceleration_units,
41
+ DataColumns.ACCELEROMETER_Y: self.acceleration_units,
42
+ DataColumns.ACCELEROMETER_Z: self.acceleration_units,
43
+ }
44
+ self.d_channels_gyroscope = {
45
+ DataColumns.GYROSCOPE_X: self.rotation_units,
46
+ DataColumns.GYROSCOPE_Y: self.rotation_units,
47
+ DataColumns.GYROSCOPE_Z: self.rotation_units,
48
+ }
49
+ self.d_channels_imu = {**self.d_channels_accelerometer, **self.d_channels_gyroscope}
50
+
51
+ class PPGPreprocessingConfig(BasePreprocessingConfig):
52
+
53
+ def __init__(self) -> None:
54
+ super().__init__()
55
+
56
+ self.meta_filename = 'PPG_meta.json'
57
+ self.values_filename = 'PPG_samples.bin'
58
+ self.time_filename = 'PPG_time.bin'
59
+
60
+ self.d_channels_ppg = {
61
+ DataColumns.PPG: 'none'
62
+ }
63
+
64
+ self.sampling_frequency = 30
@@ -0,0 +1,58 @@
1
+ import pandas as pd
2
+
3
+ def aggregate_segments(
4
+ df: pd.DataFrame,
5
+ time_colname: str,
6
+ segment_nr_colname: str,
7
+ window_step_size_s: float,
8
+ l_metrics: list,
9
+ l_aggregates: list,
10
+ l_quantiles: list=[],
11
+ )-> pd.DataFrame:
12
+ """Extract arm swing aggregations from segments of a dataframe
13
+
14
+ Parameters
15
+ ----------
16
+ df : pd.DataFrame
17
+ Dataframe containing windowed arm swing features
18
+ time_colname : str
19
+ Name of the column containing the start time of the window
20
+ segment_nr_colname : str
21
+ Name of the column containing the segment number
22
+ window_step_size_s : float
23
+ Duration of each window in seconds
24
+ l_metrics : list
25
+ List of metrics to aggregate
26
+ l_aggregates : list
27
+ List of aggregation functions to apply to the metrics
28
+ l_quantiles : list
29
+ List of quantiles to calculate
30
+
31
+ Returns
32
+ -------
33
+ pd.DataFrame
34
+ Dataframe of segments containing aggregated arm swing features for each segment
35
+ """
36
+ l_df_agg = []
37
+ for metric in l_metrics:
38
+ df_agg = df.groupby(segment_nr_colname)[metric].agg(l_aggregates).reset_index().rename(columns={x: f'{metric}_{x}' for x in l_aggregates})
39
+ df_qs = df.groupby(segment_nr_colname)[metric].quantile(l_quantiles).reset_index()
40
+
41
+
42
+ for quantile in l_quantiles:
43
+ df_agg[f"{metric}_quantile_{int(quantile*100)}"] = df_qs.loc[df_qs[f'level_1']==quantile, metric].reset_index(drop=True)
44
+
45
+ l_df_agg.append(df_agg)
46
+
47
+ for j in range(len(l_df_agg)):
48
+ if j == 0:
49
+ df_agg = l_df_agg[j]
50
+ else:
51
+ df_agg = pd.merge(left=df_agg, right=l_df_agg[j], how='left', on=segment_nr_colname)
52
+
53
+ df_segments_stats = df.groupby(segment_nr_colname)[time_colname].agg(time='min', segment_duration_s='count')
54
+ df_segments_stats['segment_duration_s'] *= window_step_size_s
55
+
56
+ df_agg = pd.merge(left=df_agg, right=df_segments_stats, how='left', on=segment_nr_colname)
57
+
58
+ return df_agg