paradigma 0.3.2__py3-none-any.whl → 0.4.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.
- paradigma/assets/gait_detection_clf_package.pkl +0 -0
- paradigma/assets/gait_filtering_clf_package.pkl +0 -0
- paradigma/assets/ppg_quality_clf_package.pkl +0 -0
- paradigma/assets/tremor_detection_clf_package.pkl +0 -0
- paradigma/classification.py +115 -0
- paradigma/config.py +314 -0
- paradigma/constants.py +48 -7
- paradigma/feature_extraction.py +811 -547
- paradigma/pipelines/__init__.py +0 -0
- paradigma/pipelines/gait_pipeline.py +727 -0
- paradigma/pipelines/heart_rate_pipeline.py +426 -0
- paradigma/pipelines/heart_rate_utils.py +780 -0
- paradigma/pipelines/tremor_pipeline.py +299 -0
- paradigma/preprocessing.py +363 -0
- paradigma/segmenting.py +396 -0
- paradigma/testing.py +416 -0
- paradigma/util.py +393 -16
- {paradigma-0.3.2.dist-info → paradigma-0.4.0.dist-info}/METADATA +58 -14
- paradigma-0.4.0.dist-info/RECORD +22 -0
- {paradigma-0.3.2.dist-info → paradigma-0.4.0.dist-info}/WHEEL +1 -1
- paradigma/gait_analysis.py +0 -415
- paradigma/gait_analysis_config.py +0 -266
- paradigma/heart_rate_analysis.py +0 -127
- paradigma/heart_rate_analysis_config.py +0 -9
- paradigma/heart_rate_util.py +0 -173
- paradigma/imu_preprocessing.py +0 -232
- paradigma/ppg/classifier/LR_PPG_quality.pkl +0 -0
- paradigma/ppg/classifier/LR_model.mat +0 -0
- paradigma/ppg/feat_extraction/acc_feature.m +0 -20
- paradigma/ppg/feat_extraction/peakdet.m +0 -64
- paradigma/ppg/feat_extraction/ppg_features.m +0 -53
- paradigma/ppg/glob_functions/extract_hr_segments.m +0 -37
- paradigma/ppg/glob_functions/extract_overlapping_segments.m +0 -23
- paradigma/ppg/glob_functions/jsonlab/AUTHORS.txt +0 -41
- paradigma/ppg/glob_functions/jsonlab/ChangeLog.txt +0 -74
- paradigma/ppg/glob_functions/jsonlab/LICENSE_BSD.txt +0 -25
- paradigma/ppg/glob_functions/jsonlab/LICENSE_GPLv3.txt +0 -699
- paradigma/ppg/glob_functions/jsonlab/README.txt +0 -394
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/entries +0 -368
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_jsonlab_basic.m.svn-base +0 -180
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_ubjson_basic.m.svn-base +0 -180
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example1.json.svn-base +0 -23
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example2.json.svn-base +0 -22
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example3.json.svn-base +0 -11
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example4.json.svn-base +0 -34
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_basictest.matlab.svn-base +0 -662
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.m.svn-base +0 -27
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.matlab.svn-base +0 -144
- paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_speedtest.m.svn-base +0 -21
- paradigma/ppg/glob_functions/jsonlab/examples/demo_jsonlab_basic.m +0 -180
- paradigma/ppg/glob_functions/jsonlab/examples/demo_ubjson_basic.m +0 -180
- paradigma/ppg/glob_functions/jsonlab/examples/example1.json +0 -23
- paradigma/ppg/glob_functions/jsonlab/examples/example2.json +0 -22
- paradigma/ppg/glob_functions/jsonlab/examples/example3.json +0 -11
- paradigma/ppg/glob_functions/jsonlab/examples/example4.json +0 -34
- paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_basictest.matlab +0 -662
- paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.m +0 -27
- paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.matlab +0 -144
- paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_speedtest.m +0 -21
- paradigma/ppg/glob_functions/jsonlab/jsonopt.m +0 -32
- paradigma/ppg/glob_functions/jsonlab/loadjson.m +0 -566
- paradigma/ppg/glob_functions/jsonlab/loadubjson.m +0 -528
- paradigma/ppg/glob_functions/jsonlab/mergestruct.m +0 -33
- paradigma/ppg/glob_functions/jsonlab/savejson.m +0 -475
- paradigma/ppg/glob_functions/jsonlab/saveubjson.m +0 -504
- paradigma/ppg/glob_functions/jsonlab/varargin2struct.m +0 -40
- paradigma/ppg/glob_functions/sample_prob_final.m +0 -49
- paradigma/ppg/glob_functions/synchronization.m +0 -76
- paradigma/ppg/glob_functions/tsdf_scan_meta.m +0 -22
- paradigma/ppg/hr_functions/Long_TFD_JOT.m +0 -37
- paradigma/ppg/hr_functions/PPG_TFD_HR.m +0 -59
- paradigma/ppg/hr_functions/TFD toolbox JOT/.gitignore +0 -4
- paradigma/ppg/hr_functions/TFD toolbox JOT/CHANGELOG.md +0 -23
- paradigma/ppg/hr_functions/TFD toolbox JOT/LICENCE.md +0 -27
- paradigma/ppg/hr_functions/TFD toolbox JOT/README.md +0 -251
- paradigma/ppg/hr_functions/TFD toolbox JOT/README.pdf +0 -0
- paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_kern.m +0 -142
- paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_lag_kern.m +0 -314
- paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_lag_kern.m +0 -123
- paradigma/ppg/hr_functions/TFD toolbox JOT/dec_tfd.m +0 -154
- paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_di_gdtfd.m +0 -194
- paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_li_gdtfd.m +0 -200
- paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_nonsep_gdtfd.m +0 -229
- paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_sep_gdtfd.m +0 -241
- paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/di_gdtfd.m +0 -157
- paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/li_gdtfd.m +0 -190
- paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/nonsep_gdtfd.m +0 -196
- paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/sep_gdtfd.m +0 -199
- paradigma/ppg/hr_functions/TFD toolbox JOT/full_tfd.m +0 -144
- paradigma/ppg/hr_functions/TFD toolbox JOT/load_curdir.m +0 -13
- paradigma/ppg/hr_functions/TFD toolbox JOT/pics/decimated_TFDs_examples.png +0 -0
- paradigma/ppg/hr_functions/TFD toolbox JOT/pics/full_TFDs_examples.png +0 -0
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/check_dec_params_seq.m +0 -79
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispEE.m +0 -9
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispVars.m +0 -26
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/disp_bytes.m +0 -25
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_full.m +0 -40
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_half.m +0 -34
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/gen_LFM.m +0 -29
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_analytic_signal.m +0 -76
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_window.m +0 -176
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/isreal_fn.m +0 -11
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/padWin.m +0 -97
- paradigma/ppg/hr_functions/TFD toolbox JOT/utils/vtfd.m +0 -149
- paradigma/ppg/preprocessing/preprocessing_imu.m +0 -15
- paradigma/ppg/preprocessing/preprocessing_ppg.m +0 -13
- paradigma/ppg_preprocessing.py +0 -313
- paradigma/preprocessing_config.py +0 -69
- paradigma/quantification.py +0 -58
- paradigma/tremor/TremorFeaturesAndClassification.m +0 -345
- paradigma/tremor/feat_extraction/DerivativesExtract.m +0 -22
- paradigma/tremor/feat_extraction/ExtractBandSignalsRMS.m +0 -72
- paradigma/tremor/feat_extraction/MFCCExtract.m +0 -100
- paradigma/tremor/feat_extraction/PSDBandPower.m +0 -52
- paradigma/tremor/feat_extraction/PSDEst.m +0 -63
- paradigma/tremor/feat_extraction/PSDExtrAxis.m +0 -88
- paradigma/tremor/feat_extraction/PSDExtrOpt.m +0 -95
- paradigma/tremor/preprocessing/InterpData.m +0 -32
- paradigma/tremor/weekly_aggregates/WeeklyAggregates.m +0 -295
- paradigma/windowing.py +0 -219
- paradigma-0.3.2.dist-info/RECORD +0 -108
- {paradigma-0.3.2.dist-info → paradigma-0.4.0.dist-info}/LICENSE +0 -0
paradigma/ppg_preprocessing.py
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
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, Union
|
|
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: Union[str, Path], 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 = TimeUnit.DIFFERENCE_MS,
|
|
57
|
-
output_unit_type = 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 = TimeUnit.DIFFERENCE_MS,
|
|
65
|
-
output_unit_type = 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=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=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=TimeUnit.ABSOLUTE_MS,
|
|
129
|
-
output_unit_type=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=TimeUnit.ABSOLUTE_MS,
|
|
137
|
-
output_unit_type=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
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from paradigma.constants import DataColumns, DataUnits
|
|
2
|
-
from paradigma.gait_analysis_config import IMUConfig
|
|
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 = DataUnits.ACCELERATION
|
|
13
|
-
self.rotation_units = DataUnits.ROTATION
|
|
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
|
-
def set_filenames(self, prefix: str) -> None:
|
|
27
|
-
"""Sets the filenames based on the prefix. This method is duplicated from `gaits_analysis_config.py`.
|
|
28
|
-
|
|
29
|
-
Parameters
|
|
30
|
-
----------
|
|
31
|
-
prefix : str
|
|
32
|
-
The prefix for the filenames.
|
|
33
|
-
"""
|
|
34
|
-
self.meta_filename = f"{prefix}_meta.json"
|
|
35
|
-
self.time_filename = f"{prefix}_time.bin"
|
|
36
|
-
self.values_filename = f"{prefix}_samples.bin"
|
|
37
|
-
|
|
38
|
-
class IMUPreprocessingConfig(BasePreprocessingConfig):
|
|
39
|
-
|
|
40
|
-
def __init__(self) -> None:
|
|
41
|
-
super().__init__()
|
|
42
|
-
|
|
43
|
-
self.set_filenames('IMU')
|
|
44
|
-
self.acceleration_units = DataUnits.ACCELERATION
|
|
45
|
-
self.rotation_units = DataUnits.ROTATION
|
|
46
|
-
|
|
47
|
-
self.d_channels_accelerometer = {
|
|
48
|
-
DataColumns.ACCELEROMETER_X: self.acceleration_units,
|
|
49
|
-
DataColumns.ACCELEROMETER_Y: self.acceleration_units,
|
|
50
|
-
DataColumns.ACCELEROMETER_Z: self.acceleration_units,
|
|
51
|
-
}
|
|
52
|
-
self.d_channels_gyroscope = {
|
|
53
|
-
DataColumns.GYROSCOPE_X: self.rotation_units,
|
|
54
|
-
DataColumns.GYROSCOPE_Y: self.rotation_units,
|
|
55
|
-
DataColumns.GYROSCOPE_Z: self.rotation_units,
|
|
56
|
-
}
|
|
57
|
-
self.d_channels_imu = {**self.d_channels_accelerometer, **self.d_channels_gyroscope}
|
|
58
|
-
|
|
59
|
-
class PPGPreprocessingConfig(BasePreprocessingConfig):
|
|
60
|
-
|
|
61
|
-
def __init__(self) -> None:
|
|
62
|
-
super().__init__()
|
|
63
|
-
|
|
64
|
-
self.set_filenames('PPG')
|
|
65
|
-
self.d_channels_ppg = {
|
|
66
|
-
DataColumns.PPG: DataUnits.NONE
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
self.sampling_frequency = 30
|
paradigma/quantification.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
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
|