paradigma 0.2.0__py3-none-any.whl → 0.3.1__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/constants.py +49 -19
- paradigma/feature_extraction.py +42 -17
- paradigma/gait_analysis.py +20 -18
- paradigma/gait_analysis_config.py +171 -149
- paradigma/heart_rate_util.py +2 -2
- paradigma/imu_preprocessing.py +32 -29
- paradigma/ppg_preprocessing.py +14 -14
- paradigma/preprocessing_config.py +20 -15
- paradigma/util.py +4 -4
- paradigma/windowing.py +8 -6
- paradigma-0.3.1.dist-info/METADATA +79 -0
- {paradigma-0.2.0.dist-info → paradigma-0.3.1.dist-info}/RECORD +14 -14
- paradigma-0.2.0.dist-info/METADATA +0 -58
- {paradigma-0.2.0.dist-info → paradigma-0.3.1.dist-info}/LICENSE +0 -0
- {paradigma-0.2.0.dist-info → paradigma-0.3.1.dist-info}/WHEEL +0 -0
|
@@ -1,106 +1,150 @@
|
|
|
1
1
|
from typing import Dict, List
|
|
2
2
|
|
|
3
|
-
from paradigma.constants import DataColumns
|
|
3
|
+
from paradigma.constants import DataColumns, DataUnits
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
class IMUConfig:
|
|
8
|
+
"""
|
|
9
|
+
Base class for Gait feature extraction and Gait detection configurations, based on the IMU data (accelerometer, gyroscope).
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self):
|
|
12
|
+
|
|
13
|
+
self.time_colname = DataColumns.TIME
|
|
14
|
+
|
|
15
|
+
self.l_accelerometer_cols: List[str] = [
|
|
16
|
+
DataColumns.ACCELEROMETER_X,
|
|
17
|
+
DataColumns.ACCELEROMETER_Y,
|
|
18
|
+
DataColumns.ACCELEROMETER_Z,
|
|
19
|
+
]
|
|
20
|
+
self.l_gyroscope_cols: List[str] = [
|
|
21
|
+
DataColumns.GYROSCOPE_X,
|
|
22
|
+
DataColumns.GYROSCOPE_Y,
|
|
23
|
+
DataColumns.GYROSCOPE_Z,
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
self.l_gravity_cols: List[str] = [
|
|
27
|
+
DataColumns.GRAV_ACCELEROMETER_X,
|
|
28
|
+
DataColumns.GRAV_ACCELEROMETER_Y,
|
|
29
|
+
DataColumns.GRAV_ACCELEROMETER_Z,
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def set_sensor(self, sensor: str) -> None:
|
|
33
|
+
"""Sets the sensor and derived filenames"""
|
|
34
|
+
self.sensor: str = sensor
|
|
35
|
+
self.set_filenames(sensor)
|
|
36
|
+
|
|
37
|
+
def set_filenames(self, prefix: str) -> None:
|
|
38
|
+
"""Sets the filenames based on the prefix,
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
prefix : str
|
|
43
|
+
The prefix for the filenames.
|
|
44
|
+
"""
|
|
45
|
+
self.meta_filename = f"{prefix}_meta.json"
|
|
46
|
+
self.time_filename = f"{prefix}_time.bin"
|
|
47
|
+
self.values_filename = f"{prefix}_samples.bin"
|
|
48
|
+
|
|
49
|
+
def set_filenames_values(self, prefix: str) -> None:
|
|
50
|
+
"""Sets the filenames based on the prefix,
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
prefix : str
|
|
55
|
+
The prefix for the filenames.
|
|
56
|
+
"""
|
|
57
|
+
self.meta_filename = f"{prefix}_meta.json"
|
|
58
|
+
self.time_filename = f"{prefix}_time.bin"
|
|
59
|
+
self.values_filename = f"{prefix}_values.bin"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GaitFeatureExtractionConfig (IMUConfig):
|
|
7
63
|
|
|
8
64
|
def __init__(self) -> None:
|
|
9
|
-
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.set_sensor("accelerometer")
|
|
10
67
|
self.set_sampling_frequency(100)
|
|
11
68
|
|
|
12
|
-
self.window_type: str =
|
|
69
|
+
self.window_type: str = "hann"
|
|
13
70
|
self.verbose: int = 0
|
|
14
|
-
|
|
71
|
+
|
|
15
72
|
self.window_length_s: int = 6
|
|
16
73
|
self.window_step_size_s: int = 1
|
|
17
74
|
|
|
18
75
|
# cepstral coefficients
|
|
19
|
-
self.cc_low_frequency = 0
|
|
20
|
-
self.cc_high_frequency = 25
|
|
76
|
+
self.cc_low_frequency: int = 0
|
|
77
|
+
self.cc_high_frequency: int = 25
|
|
21
78
|
self.n_dct_filters_cc: int = 20
|
|
22
79
|
self.n_coefficients_cc: int = 12
|
|
23
|
-
|
|
80
|
+
|
|
24
81
|
self.d_frequency_bandwidths: Dict[str, List[float]] = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
82
|
+
"power_below_gait": [0.3, 0.7],
|
|
83
|
+
"power_gait": [0.7, 3.5],
|
|
84
|
+
"power_tremor": [3.5, 8],
|
|
85
|
+
"power_above_tremor": [8, self.sampling_frequency],
|
|
29
86
|
}
|
|
30
87
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
88
|
+
|
|
89
|
+
self.l_window_level_cols: List[str] = [
|
|
90
|
+
"id",
|
|
91
|
+
"window_nr",
|
|
92
|
+
"window_start",
|
|
93
|
+
"window_end",
|
|
37
94
|
]
|
|
95
|
+
self.l_data_point_level_cols: List[str] = (
|
|
96
|
+
self.l_accelerometer_cols + self.l_gravity_cols
|
|
97
|
+
)
|
|
38
98
|
|
|
39
|
-
self.l_gravity_cols: List[str] = [f'grav_{x}' for x in self.l_accelerometer_cols]
|
|
40
|
-
self.l_window_level_cols: List[str] = ['id', 'window_nr', 'window_start', 'window_end']
|
|
41
|
-
self.l_data_point_level_cols: List[str] = self.l_accelerometer_cols + self.l_gravity_cols
|
|
42
|
-
|
|
43
99
|
# TODO: generate this dictionary using object attributes (self.X) and parameters (e.g., n_dct_filters for cc)
|
|
44
100
|
self.d_channels_values: Dict[str, str] = {
|
|
45
|
-
f
|
|
46
|
-
f
|
|
47
|
-
f
|
|
48
|
-
f
|
|
49
|
-
f
|
|
50
|
-
f
|
|
51
|
-
f
|
|
52
|
-
f
|
|
53
|
-
f
|
|
54
|
-
f
|
|
55
|
-
f
|
|
56
|
-
f
|
|
57
|
-
f
|
|
58
|
-
f
|
|
59
|
-
f
|
|
60
|
-
f
|
|
61
|
-
f
|
|
62
|
-
f
|
|
63
|
-
f
|
|
64
|
-
f
|
|
65
|
-
f
|
|
66
|
-
|
|
101
|
+
f"grav_{self.sensor}_x_mean": DataUnits.GRAVITY,
|
|
102
|
+
f"grav_{self.sensor}_y_mean": DataUnits.GRAVITY,
|
|
103
|
+
f"grav_{self.sensor}_z_mean": DataUnits.GRAVITY,
|
|
104
|
+
f"grav_{self.sensor}_x_std": DataUnits.GRAVITY,
|
|
105
|
+
f"grav_{self.sensor}_y_std": DataUnits.GRAVITY,
|
|
106
|
+
f"grav_{self.sensor}_z_std": DataUnits.GRAVITY,
|
|
107
|
+
f"{self.sensor}_x_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
108
|
+
f"{self.sensor}_y_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
109
|
+
f"{self.sensor}_z_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
110
|
+
f"{self.sensor}_x_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
111
|
+
f"{self.sensor}_y_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
112
|
+
f"{self.sensor}_z_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
113
|
+
f"{self.sensor}_x_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
114
|
+
f"{self.sensor}_y_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
115
|
+
f"{self.sensor}_z_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
116
|
+
f"{self.sensor}_x_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
117
|
+
f"{self.sensor}_y_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
118
|
+
f"{self.sensor}_z_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
119
|
+
f"{self.sensor}_x_dominant_frequency": DataUnits.FREQUENCY,
|
|
120
|
+
f"{self.sensor}_y_dominant_frequency": DataUnits.FREQUENCY,
|
|
121
|
+
f"{self.sensor}_z_dominant_frequency": DataUnits.FREQUENCY,
|
|
122
|
+
"std_norm_acc": DataUnits.GRAVITY,
|
|
67
123
|
}
|
|
68
124
|
|
|
69
|
-
for cc_coef in range(1, self.n_coefficients_cc+1):
|
|
70
|
-
self.d_channels_values[f
|
|
71
|
-
|
|
72
|
-
# TODO: move to higher level config class (duplicate in armswing feature extraction)
|
|
73
|
-
def set_sensor(self, sensor: str) -> None:
|
|
74
|
-
""" Sets the sensor and derived filenames """
|
|
75
|
-
self.sensor: str = sensor
|
|
76
|
-
self.meta_filename: str = f'{self.sensor}_meta.json'
|
|
77
|
-
self.values_filename: str = f'{self.sensor}_samples.bin'
|
|
78
|
-
self.time_filename: str = f'{self.sensor}_time.bin'
|
|
125
|
+
for cc_coef in range(1, self.n_coefficients_cc + 1):
|
|
126
|
+
self.d_channels_values[f"cc_{cc_coef}_{self.sensor}"] = "g"
|
|
79
127
|
|
|
80
128
|
def set_sampling_frequency(self, sampling_frequency: int) -> None:
|
|
81
|
-
"""
|
|
129
|
+
"""Sets the sampling frequency and derived variables"""
|
|
82
130
|
self.sampling_frequency: int = sampling_frequency
|
|
83
131
|
self.spectrum_low_frequency: int = 0 # Hz
|
|
84
132
|
self.spectrum_high_frequency: int = int(self.sampling_frequency / 2) # Hz
|
|
85
133
|
self.filter_length: int = self.spectrum_high_frequency - 1
|
|
86
134
|
|
|
87
135
|
|
|
88
|
-
class GaitDetectionConfig:
|
|
136
|
+
class GaitDetectionConfig(IMUConfig):
|
|
89
137
|
|
|
90
138
|
def __init__(self) -> None:
|
|
91
|
-
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
self.meta_filename = 'gait_meta.json'
|
|
95
|
-
self.time_filename = 'gait_time.bin'
|
|
96
|
-
self.values_filename = 'gait_values.bin'
|
|
139
|
+
super().__init__()
|
|
140
|
+
self.classifier_file_name = "gd_classifier.pkl"
|
|
141
|
+
self.thresholds_file_name = "gd_threshold.txt"
|
|
97
142
|
|
|
98
|
-
self.
|
|
143
|
+
self.set_filenames_values("gait")
|
|
99
144
|
|
|
100
|
-
self.time_colname = 'time'
|
|
101
145
|
|
|
102
146
|
|
|
103
|
-
class ArmSwingFeatureExtractionConfig:
|
|
147
|
+
class ArmSwingFeatureExtractionConfig(IMUConfig):
|
|
104
148
|
|
|
105
149
|
def initialize_window_length_fields(self, window_length_s: int) -> None:
|
|
106
150
|
self.window_length_s = window_length_s
|
|
@@ -117,10 +161,10 @@ class ArmSwingFeatureExtractionConfig:
|
|
|
117
161
|
self.spectrum_high_frequency = int(sampling_frequency / 2)
|
|
118
162
|
|
|
119
163
|
self.d_frequency_bandwidths = {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
"power_below_gait": [0.3, 0.7],
|
|
165
|
+
"power_gait": [0.7, 3.5],
|
|
166
|
+
"power_tremor": [3.5, 8],
|
|
167
|
+
"power_above_tremor": [8, sampling_frequency],
|
|
124
168
|
}
|
|
125
169
|
|
|
126
170
|
# cepstral coefficients
|
|
@@ -129,41 +173,32 @@ class ArmSwingFeatureExtractionConfig:
|
|
|
129
173
|
self.n_dct_filters_cc: int = 20
|
|
130
174
|
self.n_coefficients_cc: int = 12
|
|
131
175
|
|
|
132
|
-
def initialize_column_names(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
self.time_colname = time_colname
|
|
136
|
-
self.pred_gait_colname = pred_gait_colname
|
|
137
|
-
self.angle_smooth_colname = angle_smooth_colname
|
|
138
|
-
self.angle_colname = angle_colname
|
|
139
|
-
self.velocity_colname = velocity_colname
|
|
140
|
-
self.segment_nr_colname = segment_nr_colname
|
|
176
|
+
def initialize_column_names(
|
|
177
|
+
self
|
|
178
|
+
) -> None:
|
|
141
179
|
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
180
|
+
self.pred_gait_colname=DataColumns.PRED_GAIT
|
|
181
|
+
self.angle_smooth_colname: str = DataColumns.ANGLE_SMOOTH
|
|
182
|
+
self.angle_colname=DataColumns.ANGLE
|
|
183
|
+
self.velocity_colname=DataColumns.VELOCITY
|
|
184
|
+
self.segment_nr_colname=DataColumns.SEGMENT_NR
|
|
147
185
|
|
|
148
|
-
self.l_gyroscope_cols: List[str] = [
|
|
149
|
-
DataColumns.GYROSCOPE_X,
|
|
150
|
-
DataColumns.GYROSCOPE_Y,
|
|
151
|
-
DataColumns.GYROSCOPE_Z
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
self.l_gravity_cols: List[str] = [f'grav_{x}' for x in self.l_accelerometer_cols]
|
|
155
186
|
|
|
156
|
-
self.l_data_point_level_cols =
|
|
157
|
-
|
|
158
|
-
|
|
187
|
+
self.l_data_point_level_cols: List[str] = (
|
|
188
|
+
self.l_accelerometer_cols
|
|
189
|
+
+ self.l_gyroscope_cols
|
|
190
|
+
+ self.l_gravity_cols
|
|
191
|
+
+ [self.angle_smooth_colname, self.velocity_colname]
|
|
192
|
+
)
|
|
159
193
|
|
|
160
194
|
def __init__(self) -> None:
|
|
195
|
+
super().__init__()
|
|
161
196
|
# general
|
|
162
|
-
self.sensor =
|
|
163
|
-
self.units =
|
|
197
|
+
self.sensor = "IMU"
|
|
198
|
+
self.units = "degrees"
|
|
164
199
|
|
|
165
200
|
# windowing
|
|
166
|
-
self.window_type =
|
|
201
|
+
self.window_type = "hann"
|
|
167
202
|
self.initialize_window_length_fields(3)
|
|
168
203
|
|
|
169
204
|
self.initialize_sampling_frequency_fields(100)
|
|
@@ -171,72 +206,59 @@ class ArmSwingFeatureExtractionConfig:
|
|
|
171
206
|
self.initialize_column_names()
|
|
172
207
|
|
|
173
208
|
self.d_channels_values = {
|
|
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
|
-
|
|
209
|
+
"angle_perc_power": "proportion",
|
|
210
|
+
"range_of_motion": "deg",
|
|
211
|
+
"forward_peak_ang_vel_mean": DataUnits.ROTATION,
|
|
212
|
+
"forward_peak_ang_vel_std": DataUnits.ROTATION,
|
|
213
|
+
"backward_peak_ang_vel_mean": DataUnits.ROTATION,
|
|
214
|
+
"backward_peak_ang_vel_std": DataUnits.ROTATION,
|
|
215
|
+
"std_norm_acc": DataUnits.GRAVITY,
|
|
216
|
+
"grav_accelerometer_x_mean": DataUnits.GRAVITY,
|
|
217
|
+
"grav_accelerometer_x_std": DataUnits.GRAVITY,
|
|
218
|
+
"grav_accelerometer_y_mean": DataUnits.GRAVITY,
|
|
219
|
+
"grav_accelerometer_y_std": DataUnits.GRAVITY,
|
|
220
|
+
"grav_accelerometer_z_mean": DataUnits.GRAVITY,
|
|
221
|
+
"grav_accelerometer_z_std": DataUnits.GRAVITY,
|
|
222
|
+
"accelerometer_x_power_below_gait": "X",
|
|
223
|
+
"accelerometer_x_power_gait": "X",
|
|
224
|
+
"accelerometer_x_power_tremor": "X",
|
|
225
|
+
"accelerometer_x_power_above_tremor": "X",
|
|
226
|
+
"accelerometer_x_dominant_frequency": DataUnits.FREQUENCY,
|
|
227
|
+
"accelerometer_y_power_below_gait": "X",
|
|
228
|
+
"accelerometer_y_power_gait": "X",
|
|
229
|
+
"accelerometer_y_power_tremor": "X",
|
|
230
|
+
"accelerometer_y_power_above_tremor": "X",
|
|
231
|
+
"accelerometer_y_dominant_frequency": DataUnits.FREQUENCY,
|
|
232
|
+
"accelerometer_z_power_below_gait": "X",
|
|
233
|
+
"accelerometer_z_power_gait": "X",
|
|
234
|
+
"accelerometer_z_power_tremor": "X",
|
|
235
|
+
"accelerometer_z_power_above_tremor": "X",
|
|
236
|
+
"accelerometer_z_dominant_frequency": DataUnits.FREQUENCY,
|
|
237
|
+
"angle_dominant_frequency": DataUnits.FREQUENCY,
|
|
203
238
|
}
|
|
204
239
|
|
|
205
|
-
for sensor in [
|
|
206
|
-
for cc_coef in range(1, self.n_coefficients_cc+1):
|
|
207
|
-
self.d_channels_values[f
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
# TODO: move to higher level config class (duplicate in gait feature extraction)
|
|
211
|
-
def set_sensor(self, sensor: str) -> None:
|
|
212
|
-
""" Sets the sensor and derived filenames """
|
|
213
|
-
self.sensor: str = sensor
|
|
214
|
-
self.meta_filename: str = f'{self.sensor}_meta.json'
|
|
215
|
-
self.values_filename: str = f'{self.sensor}_samples.bin'
|
|
216
|
-
self.time_filename: str = f'{self.sensor}_time.bin'
|
|
240
|
+
for sensor in ["accelerometer", "gyroscope"]:
|
|
241
|
+
for cc_coef in range(1, self.n_coefficients_cc + 1):
|
|
242
|
+
self.d_channels_values[f"cc_{cc_coef}_{sensor}"] = DataUnits.GRAVITY
|
|
217
243
|
|
|
218
244
|
|
|
219
|
-
class ArmSwingDetectionConfig:
|
|
245
|
+
class ArmSwingDetectionConfig(IMUConfig):
|
|
220
246
|
|
|
221
247
|
def __init__(self) -> None:
|
|
222
|
-
|
|
248
|
+
super().__init__()
|
|
249
|
+
self.classifier_file_name = "asd_classifier.pkl"
|
|
223
250
|
|
|
224
|
-
self.
|
|
225
|
-
self.time_filename = 'arm_swing_time.bin'
|
|
226
|
-
self.values_filename = 'arm_swing_values.bin'
|
|
251
|
+
self.set_filenames_values("arm_swing")
|
|
227
252
|
|
|
228
|
-
self.l_accel_cols = [DataColumns.ACCELEROMETER_X, DataColumns.ACCELEROMETER_Y, DataColumns.ACCELEROMETER_Z]
|
|
229
|
-
self.l_gyro_cols = [DataColumns.GYROSCOPE_X, DataColumns.GYROSCOPE_Y, DataColumns.GYROSCOPE_Z]
|
|
230
253
|
|
|
231
254
|
|
|
232
|
-
class ArmSwingQuantificationConfig:
|
|
255
|
+
class ArmSwingQuantificationConfig(IMUConfig):
|
|
233
256
|
|
|
234
257
|
def __init__(self) -> None:
|
|
235
|
-
|
|
236
|
-
self.
|
|
237
|
-
self.values_filename = 'arm_swing_values.bin'
|
|
258
|
+
super().__init__()
|
|
259
|
+
self.set_filenames_values("arm_swing")
|
|
238
260
|
|
|
239
|
-
self.pred_arm_swing_colname =
|
|
261
|
+
self.pred_arm_swing_colname = DataColumns.PRED_ARM_SWING
|
|
240
262
|
|
|
241
263
|
self.window_length_s = 3
|
|
242
264
|
self.window_step_size = 0.75
|
paradigma/heart_rate_util.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
1
|
+
from typing import List, Tuple, Union
|
|
2
2
|
import pickle
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import numpy as np
|
|
@@ -72,7 +72,7 @@ def extract_ppg_features(arr_ppg: np.ndarray, sampling_frequency: int) -> np.nda
|
|
|
72
72
|
# print(features_df)
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def peakdet(v: np.ndarray, delta, x: np.ndarray=None) -> Tuple[List[Tuple[int, float]], List[Tuple[int, float]]]:
|
|
75
|
+
def peakdet(v: np.ndarray, delta, x: Union[np.ndarray, None]=None) -> Tuple[List[Tuple[int, float]], List[Tuple[int, float]]]:
|
|
76
76
|
"""
|
|
77
77
|
Detect peaks in a vector.
|
|
78
78
|
|
paradigma/imu_preprocessing.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
from typing import List, Union
|
|
2
3
|
import numpy as np
|
|
3
4
|
import pandas as pd
|
|
@@ -10,10 +11,11 @@ from paradigma.util import write_data, read_metadata
|
|
|
10
11
|
from paradigma.preprocessing_config import IMUPreprocessingConfig
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
def preprocess_imu_data(input_path: str, output_path: str, config: IMUPreprocessingConfig) -> None:
|
|
14
|
+
def preprocess_imu_data(input_path: Union[str, Path], output_path: Union[str, Path], config: IMUPreprocessingConfig) -> None:
|
|
14
15
|
|
|
15
16
|
# Load data
|
|
16
|
-
metadata_time, metadata_samples = read_metadata(input_path, config.meta_filename,
|
|
17
|
+
metadata_time, metadata_samples = read_metadata(str(input_path), str(config.meta_filename),
|
|
18
|
+
str(config.time_filename), str(config.values_filename))
|
|
17
19
|
df = tsdf.load_dataframe_from_binaries([metadata_time, metadata_samples], tsdf.constants.ConcatenationType.columns)
|
|
18
20
|
|
|
19
21
|
# Rename columns
|
|
@@ -24,14 +26,14 @@ def preprocess_imu_data(input_path: str, output_path: str, config: IMUPreprocess
|
|
|
24
26
|
df[config.time_colname] = transform_time_array(
|
|
25
27
|
time_array=df[config.time_colname],
|
|
26
28
|
scale_factor=1000,
|
|
27
|
-
input_unit_type = TimeUnit.
|
|
28
|
-
output_unit_type = TimeUnit.
|
|
29
|
+
input_unit_type = TimeUnit.DIFFERENCE_MS,
|
|
30
|
+
output_unit_type = TimeUnit.RELATIVE_MS)
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
df = resample_data(
|
|
32
34
|
df=df,
|
|
33
35
|
time_column=config.time_colname,
|
|
34
|
-
time_unit_type=TimeUnit.
|
|
36
|
+
time_unit_type=TimeUnit.RELATIVE_MS,
|
|
35
37
|
unscaled_column_names = list(config.d_channels_imu.keys()),
|
|
36
38
|
scale_factors=metadata_samples.scale_factors,
|
|
37
39
|
resampling_frequency=config.sampling_frequency)
|
|
@@ -71,10 +73,10 @@ def preprocess_imu_data(input_path: str, output_path: str, config: IMUPreprocess
|
|
|
71
73
|
write_data(metadata_time, metadata_samples, output_path, f'{sensor}_meta.json', df_sensor)
|
|
72
74
|
|
|
73
75
|
def transform_time_array(
|
|
74
|
-
time_array:
|
|
76
|
+
time_array: pd.Series,
|
|
75
77
|
scale_factor: float,
|
|
76
|
-
input_unit_type:
|
|
77
|
-
output_unit_type:
|
|
78
|
+
input_unit_type: str,
|
|
79
|
+
output_unit_type: str,
|
|
78
80
|
start_time: float = 0.0,
|
|
79
81
|
) -> np.ndarray:
|
|
80
82
|
"""
|
|
@@ -82,14 +84,14 @@ def transform_time_array(
|
|
|
82
84
|
|
|
83
85
|
Parameters
|
|
84
86
|
----------
|
|
85
|
-
time_array :
|
|
87
|
+
time_array : pd.Series
|
|
86
88
|
The time array in milliseconds to transform.
|
|
87
89
|
scale_factor : float
|
|
88
90
|
The scale factor to apply to the time array.
|
|
89
|
-
input_unit_type :
|
|
90
|
-
The time unit type of the input time array. Raw PPP data was in `TimeUnit.
|
|
91
|
-
output_unit_type :
|
|
92
|
-
The time unit type of the output time array. The processing is often done in `TimeUnit.
|
|
91
|
+
input_unit_type : str
|
|
92
|
+
The time unit type of the input time array. Raw PPP data was in `TimeUnit.DIFFERENCE_MS`.
|
|
93
|
+
output_unit_type : str
|
|
94
|
+
The time unit type of the output time array. The processing is often done in `TimeUnit.RELATIVE_MS`.
|
|
93
95
|
start_time : float, optional
|
|
94
96
|
The start time of the time array in UNIX milliseconds (default is 0.0)
|
|
95
97
|
|
|
@@ -98,28 +100,28 @@ def transform_time_array(
|
|
|
98
100
|
time_array
|
|
99
101
|
The transformed time array in milliseconds, with the specified time unit type.
|
|
100
102
|
"""
|
|
101
|
-
# Scale time array and transform to relative time (`TimeUnit.
|
|
102
|
-
if input_unit_type == TimeUnit.
|
|
103
|
+
# Scale time array and transform to relative time (`TimeUnit.RELATIVE_MS`)
|
|
104
|
+
if input_unit_type == TimeUnit.DIFFERENCE_MS:
|
|
103
105
|
# Convert a series of differences into cumulative sum to reconstruct original time series.
|
|
104
106
|
time_array = np.cumsum(np.double(time_array)) / scale_factor
|
|
105
|
-
elif input_unit_type == TimeUnit.
|
|
107
|
+
elif input_unit_type == TimeUnit.ABSOLUTE_MS:
|
|
106
108
|
# Set the start time if not provided.
|
|
107
109
|
if np.isclose(start_time, 0.0, rtol=1e-09, atol=1e-09):
|
|
108
110
|
start_time = time_array[0]
|
|
109
111
|
# Convert absolute time stamps into a time series relative to start_time.
|
|
110
112
|
time_array = (time_array - start_time) / scale_factor
|
|
111
|
-
elif input_unit_type == TimeUnit.
|
|
113
|
+
elif input_unit_type == TimeUnit.RELATIVE_MS:
|
|
112
114
|
# Scale the relative time series as per the scale_factor.
|
|
113
115
|
time_array = time_array / scale_factor
|
|
114
116
|
|
|
115
|
-
# Transform the time array from `TimeUnit.
|
|
116
|
-
if output_unit_type == TimeUnit.
|
|
117
|
+
# Transform the time array from `TimeUnit.RELATIVE_MS` to the specified time unit type
|
|
118
|
+
if output_unit_type == TimeUnit.ABSOLUTE_MS:
|
|
117
119
|
# Converts time array to absolute time by adding the start time to each element.
|
|
118
120
|
time_array = time_array + start_time
|
|
119
|
-
elif output_unit_type == TimeUnit.
|
|
121
|
+
elif output_unit_type == TimeUnit.DIFFERENCE_MS:
|
|
120
122
|
# Creates a new array starting with 0, followed by the differences between consecutive elements.
|
|
121
123
|
time_array = np.diff(np.insert(time_array, 0, start_time))
|
|
122
|
-
elif output_unit_type == TimeUnit.
|
|
124
|
+
elif output_unit_type == TimeUnit.RELATIVE_MS:
|
|
123
125
|
# The array is already in relative format, do nothing.
|
|
124
126
|
pass
|
|
125
127
|
return time_array
|
|
@@ -127,11 +129,11 @@ def transform_time_array(
|
|
|
127
129
|
|
|
128
130
|
def resample_data(
|
|
129
131
|
df: pd.DataFrame,
|
|
130
|
-
time_column :
|
|
131
|
-
time_unit_type:
|
|
132
|
-
unscaled_column_names:
|
|
132
|
+
time_column : str,
|
|
133
|
+
time_unit_type: str,
|
|
134
|
+
unscaled_column_names: List[str],
|
|
133
135
|
resampling_frequency: int,
|
|
134
|
-
scale_factors:
|
|
136
|
+
scale_factors: List[float] = [],
|
|
135
137
|
start_time: float = 0.0,
|
|
136
138
|
) -> pd.DataFrame:
|
|
137
139
|
"""
|
|
@@ -143,9 +145,9 @@ def resample_data(
|
|
|
143
145
|
The data to resample.
|
|
144
146
|
time_column : str
|
|
145
147
|
The name of the time column.
|
|
146
|
-
time_unit_type :
|
|
147
|
-
The time unit type of the time array. The method currently works only for `TimeUnit.
|
|
148
|
-
unscaled_column_names :
|
|
148
|
+
time_unit_type : str
|
|
149
|
+
The time unit type of the time array. The method currently works only for `TimeUnit.RELATIVE_MS`.
|
|
150
|
+
unscaled_column_names : List[str]
|
|
149
151
|
The names of the columns to resample.
|
|
150
152
|
resampling_frequency : int
|
|
151
153
|
The frequency to resample the data to.
|
|
@@ -160,7 +162,7 @@ def resample_data(
|
|
|
160
162
|
The resampled data.
|
|
161
163
|
"""
|
|
162
164
|
# We need a start_time if the time is in absolute time format
|
|
163
|
-
if time_unit_type == TimeUnit.
|
|
165
|
+
if time_unit_type == TimeUnit.ABSOLUTE_MS and start_time == 0.0:
|
|
164
166
|
raise ValueError("start_time is required for absolute time format")
|
|
165
167
|
|
|
166
168
|
# get time and values
|
|
@@ -183,6 +185,7 @@ def resample_data(
|
|
|
183
185
|
raise ValueError("time_abs_array is not strictly increasing")
|
|
184
186
|
|
|
185
187
|
cs = CubicSpline(time_abs_array, scaled_values.T[j])
|
|
188
|
+
#TODO: isn't sensor_col of type DataColumns?
|
|
186
189
|
df[sensor_col] = cs(df[time_column])
|
|
187
190
|
|
|
188
191
|
return df
|