paradigma 1.0.4__py3-none-any.whl → 1.1.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/__init__.py +10 -1
- paradigma/classification.py +14 -14
- paradigma/config.py +38 -29
- paradigma/constants.py +10 -2
- paradigma/feature_extraction.py +106 -75
- paradigma/load.py +476 -0
- paradigma/orchestrator.py +670 -0
- paradigma/pipelines/gait_pipeline.py +488 -97
- paradigma/pipelines/pulse_rate_pipeline.py +278 -46
- paradigma/pipelines/pulse_rate_utils.py +176 -137
- paradigma/pipelines/tremor_pipeline.py +292 -72
- paradigma/prepare_data.py +409 -0
- paradigma/preprocessing.py +345 -77
- paradigma/segmenting.py +57 -42
- paradigma/testing.py +14 -9
- paradigma/util.py +36 -22
- paradigma-1.1.0.dist-info/METADATA +229 -0
- paradigma-1.1.0.dist-info/RECORD +26 -0
- {paradigma-1.0.4.dist-info → paradigma-1.1.0.dist-info}/WHEEL +1 -1
- paradigma-1.0.4.dist-info/METADATA +0 -140
- paradigma-1.0.4.dist-info/RECORD +0 -23
- {paradigma-1.0.4.dist-info → paradigma-1.1.0.dist-info}/entry_points.txt +0 -0
- {paradigma-1.0.4.dist-info → paradigma-1.1.0.dist-info}/licenses/LICENSE +0 -0
paradigma/__init__.py
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ParaDigMa: Parkinson Digital Biomarker Analysis Toolbox
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
# read version from installed package
|
|
2
6
|
from importlib.metadata import version
|
|
3
7
|
|
|
4
8
|
__version__ = version("paradigma")
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
# Import main pipeline functions for easy access
|
|
11
|
+
from .orchestrator import run_paradigma
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"run_paradigma",
|
|
15
|
+
]
|
paradigma/classification.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pickle
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from sklearn.base import BaseEstimator
|
|
@@ -10,9 +10,9 @@ from sklearn.preprocessing import StandardScaler
|
|
|
10
10
|
class ClassifierPackage:
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
|
-
classifier:
|
|
14
|
-
threshold:
|
|
15
|
-
scaler:
|
|
13
|
+
classifier: BaseEstimator | None = None,
|
|
14
|
+
threshold: float | None = None,
|
|
15
|
+
scaler: Any | None = None,
|
|
16
16
|
):
|
|
17
17
|
"""
|
|
18
18
|
Initialize the ClassifierPackage with a classifier, threshold, and scaler.
|
|
@@ -30,13 +30,13 @@ class ClassifierPackage:
|
|
|
30
30
|
self.threshold = threshold
|
|
31
31
|
self.scaler = scaler
|
|
32
32
|
|
|
33
|
-
def transform_features(self,
|
|
33
|
+
def transform_features(self, x) -> np.ndarray:
|
|
34
34
|
"""
|
|
35
35
|
Transform the input features using the scaler.
|
|
36
36
|
|
|
37
37
|
Parameters
|
|
38
38
|
----------
|
|
39
|
-
|
|
39
|
+
x : np.ndarray
|
|
40
40
|
The input features.
|
|
41
41
|
|
|
42
42
|
Return
|
|
@@ -45,8 +45,8 @@ class ClassifierPackage:
|
|
|
45
45
|
The transformed features.
|
|
46
46
|
"""
|
|
47
47
|
if not self.scaler:
|
|
48
|
-
return
|
|
49
|
-
return self.scaler.transform(
|
|
48
|
+
return x
|
|
49
|
+
return self.scaler.transform(x)
|
|
50
50
|
|
|
51
51
|
def update_scaler(self, x_train: np.ndarray) -> None:
|
|
52
52
|
"""
|
|
@@ -60,13 +60,13 @@ class ClassifierPackage:
|
|
|
60
60
|
scaler = StandardScaler()
|
|
61
61
|
self.scaler = scaler.fit(x_train)
|
|
62
62
|
|
|
63
|
-
def predict_proba(self,
|
|
63
|
+
def predict_proba(self, x) -> float:
|
|
64
64
|
"""
|
|
65
65
|
Make predictions using the classifier and apply the threshold.
|
|
66
66
|
|
|
67
67
|
Parameters
|
|
68
68
|
----------
|
|
69
|
-
|
|
69
|
+
x : np.ndarray
|
|
70
70
|
The input features.
|
|
71
71
|
|
|
72
72
|
Return
|
|
@@ -77,15 +77,15 @@ class ClassifierPackage:
|
|
|
77
77
|
"""
|
|
78
78
|
if not self.classifier:
|
|
79
79
|
raise ValueError("Classifier is not loaded.")
|
|
80
|
-
return self.classifier.predict_proba(
|
|
80
|
+
return self.classifier.predict_proba(x)[:, 1]
|
|
81
81
|
|
|
82
|
-
def predict(self,
|
|
82
|
+
def predict(self, x) -> int:
|
|
83
83
|
"""
|
|
84
84
|
Make predictions using the classifier and apply the threshold.
|
|
85
85
|
|
|
86
86
|
Parameters
|
|
87
87
|
----------
|
|
88
|
-
|
|
88
|
+
x : np.ndarray
|
|
89
89
|
The input features.
|
|
90
90
|
|
|
91
91
|
Return
|
|
@@ -96,7 +96,7 @@ class ClassifierPackage:
|
|
|
96
96
|
"""
|
|
97
97
|
if not self.classifier:
|
|
98
98
|
raise ValueError("Classifier is not loaded.")
|
|
99
|
-
return int(self.predict_proba(
|
|
99
|
+
return int(self.predict_proba(x) >= self.threshold)
|
|
100
100
|
|
|
101
101
|
def save(self, filepath: str | Path) -> None:
|
|
102
102
|
"""
|
paradigma/config.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from dataclasses import asdict
|
|
3
|
-
from typing import Dict, List
|
|
4
3
|
|
|
5
4
|
import numpy as np
|
|
6
5
|
|
|
@@ -19,7 +18,9 @@ class BaseConfig:
|
|
|
19
18
|
self.set_filenames(sensor)
|
|
20
19
|
|
|
21
20
|
def set_filenames(self, prefix: str) -> None:
|
|
22
|
-
"""Sets the filenames based on the prefix.
|
|
21
|
+
"""Sets the filenames based on the prefix.
|
|
22
|
+
|
|
23
|
+
This method is duplicated from `gaits_analysis_config.py`.
|
|
23
24
|
|
|
24
25
|
Parameters
|
|
25
26
|
----------
|
|
@@ -51,12 +52,12 @@ class IMUConfig(BaseConfig):
|
|
|
51
52
|
|
|
52
53
|
self.time_colname = self.column_mapping["TIME"]
|
|
53
54
|
|
|
54
|
-
self.accelerometer_colnames:
|
|
55
|
-
self.gyroscope_colnames:
|
|
56
|
-
self.gravity_colnames:
|
|
55
|
+
self.accelerometer_colnames: list[str] = []
|
|
56
|
+
self.gyroscope_colnames: list[str] = []
|
|
57
|
+
self.gravity_colnames: list[str] = []
|
|
57
58
|
|
|
58
|
-
self.d_channels_accelerometer:
|
|
59
|
-
self.d_channels_gyroscope:
|
|
59
|
+
self.d_channels_accelerometer: dict[str, str] = {}
|
|
60
|
+
self.d_channels_gyroscope: dict[str, str] = {}
|
|
60
61
|
|
|
61
62
|
accel_keys = ["ACCELEROMETER_X", "ACCELEROMETER_Y", "ACCELEROMETER_Z"]
|
|
62
63
|
grav_keys = [
|
|
@@ -83,7 +84,7 @@ class IMUConfig(BaseConfig):
|
|
|
83
84
|
c: self.rotation_units for c in self.gyroscope_colnames
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
self.d_channels_imu:
|
|
87
|
+
self.d_channels_imu: dict[str, str] = {
|
|
87
88
|
**self.d_channels_accelerometer,
|
|
88
89
|
**self.d_channels_gyroscope,
|
|
89
90
|
}
|
|
@@ -95,6 +96,10 @@ class IMUConfig(BaseConfig):
|
|
|
95
96
|
self.upper_cutoff_frequency = 3.5
|
|
96
97
|
self.filter_order = 4
|
|
97
98
|
|
|
99
|
+
# Segmentation parameters for handling non-contiguous data
|
|
100
|
+
self.max_segment_gap_s = 1.5
|
|
101
|
+
self.min_segment_length_s = 1.5
|
|
102
|
+
|
|
98
103
|
|
|
99
104
|
class PPGConfig(BaseConfig):
|
|
100
105
|
|
|
@@ -150,7 +155,7 @@ class GaitConfig(IMUConfig):
|
|
|
150
155
|
self.spectrum_high_frequency: int = int(self.sampling_frequency / 2)
|
|
151
156
|
|
|
152
157
|
# Power in specified frequency bands
|
|
153
|
-
self.d_frequency_bandwidths:
|
|
158
|
+
self.d_frequency_bandwidths: dict[str, list[float]] = {
|
|
154
159
|
"power_below_gait": [0.2, 0.7],
|
|
155
160
|
"power_gait": [0.7, 3.5],
|
|
156
161
|
"power_tremor": [3.5, 8],
|
|
@@ -170,7 +175,7 @@ class GaitConfig(IMUConfig):
|
|
|
170
175
|
# -----------------
|
|
171
176
|
# TSDF data storage
|
|
172
177
|
# -----------------
|
|
173
|
-
self.d_channels_values:
|
|
178
|
+
self.d_channels_values: dict[str, str] = {
|
|
174
179
|
"accelerometer_std_norm": DataUnits.GRAVITY,
|
|
175
180
|
"accelerometer_x_grav_mean": DataUnits.GRAVITY,
|
|
176
181
|
"accelerometer_y_grav_mean": DataUnits.GRAVITY,
|
|
@@ -178,18 +183,18 @@ class GaitConfig(IMUConfig):
|
|
|
178
183
|
"accelerometer_x_grav_std": DataUnits.GRAVITY,
|
|
179
184
|
"accelerometer_y_grav_std": DataUnits.GRAVITY,
|
|
180
185
|
"accelerometer_z_grav_std": DataUnits.GRAVITY,
|
|
181
|
-
"accelerometer_x_power_below_gait": DataUnits.
|
|
182
|
-
"accelerometer_y_power_below_gait": DataUnits.
|
|
183
|
-
"accelerometer_z_power_below_gait": DataUnits.
|
|
184
|
-
"accelerometer_x_power_gait": DataUnits.
|
|
185
|
-
"accelerometer_y_power_gait": DataUnits.
|
|
186
|
-
"accelerometer_z_power_gait": DataUnits.
|
|
187
|
-
"accelerometer_x_power_tremor": DataUnits.
|
|
188
|
-
"accelerometer_y_power_tremor": DataUnits.
|
|
189
|
-
"accelerometer_z_power_tremor": DataUnits.
|
|
190
|
-
"accelerometer_x_power_above_tremor": DataUnits.
|
|
191
|
-
"accelerometer_y_power_above_tremor": DataUnits.
|
|
192
|
-
"accelerometer_z_power_above_tremor": DataUnits.
|
|
186
|
+
"accelerometer_x_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
187
|
+
"accelerometer_y_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
188
|
+
"accelerometer_z_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
189
|
+
"accelerometer_x_power_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
190
|
+
"accelerometer_y_power_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
191
|
+
"accelerometer_z_power_gait": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
192
|
+
"accelerometer_x_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
193
|
+
"accelerometer_y_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
194
|
+
"accelerometer_z_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
195
|
+
"accelerometer_x_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
196
|
+
"accelerometer_y_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
197
|
+
"accelerometer_z_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY_ACC,
|
|
193
198
|
"accelerometer_x_dominant_frequency": DataUnits.FREQUENCY,
|
|
194
199
|
"accelerometer_y_dominant_frequency": DataUnits.FREQUENCY,
|
|
195
200
|
"accelerometer_z_dominant_frequency": DataUnits.FREQUENCY,
|
|
@@ -257,20 +262,23 @@ class TremorConfig(IMUConfig):
|
|
|
257
262
|
# -----------
|
|
258
263
|
# Aggregation
|
|
259
264
|
# -----------
|
|
260
|
-
self.aggregates_tremor_power:
|
|
265
|
+
self.aggregates_tremor_power: list[str] = ["mode_binned", "median", "90p"]
|
|
261
266
|
self.evaluation_points_tremor_power: np.ndarray = np.linspace(0, 6, 301)
|
|
262
267
|
|
|
263
268
|
# -----------------
|
|
264
269
|
# TSDF data storage
|
|
265
270
|
# -----------------
|
|
266
271
|
if step == "features":
|
|
267
|
-
self.d_channels_values:
|
|
272
|
+
self.d_channels_values: dict[str, str] = {}
|
|
268
273
|
for mfcc_coef in range(1, self.n_coefficients_mfcc + 1):
|
|
269
|
-
self.d_channels_values[f"mfcc_{mfcc_coef}"] =
|
|
274
|
+
self.d_channels_values[f"mfcc_{mfcc_coef}"] = DataUnits.NONE
|
|
275
|
+
|
|
276
|
+
self.d_channels_values[DataColumns.FREQ_PEAK] = DataUnits.FREQUENCY
|
|
277
|
+
self.d_channels_values[DataColumns.BELOW_TREMOR_POWER] = (
|
|
278
|
+
DataUnits.POWER_ROTATION
|
|
279
|
+
)
|
|
280
|
+
self.d_channels_values[DataColumns.TREMOR_POWER] = DataUnits.POWER_ROTATION
|
|
270
281
|
|
|
271
|
-
self.d_channels_values["freq_peak"] = "Hz"
|
|
272
|
-
self.d_channels_values["below_tremor_power"] = "(deg/s)^2"
|
|
273
|
-
self.d_channels_values["tremor_power"] = "(deg/s)^2"
|
|
274
282
|
elif step == "classification":
|
|
275
283
|
self.d_channels_values = {
|
|
276
284
|
DataColumns.PRED_TREMOR_PROBA: "probability",
|
|
@@ -299,7 +307,8 @@ class PulseRateConfig(PPGConfig):
|
|
|
299
307
|
else:
|
|
300
308
|
self.imu_sampling_frequency = IMUConfig().sampling_frequency
|
|
301
309
|
warnings.warn(
|
|
302
|
-
f"imu_sampling_frequency not provided, using default
|
|
310
|
+
f"imu_sampling_frequency not provided, using default "
|
|
311
|
+
f"of {self.imu_sampling_frequency} Hz"
|
|
303
312
|
)
|
|
304
313
|
|
|
305
314
|
# Windowing parameters
|
paradigma/constants.py
CHANGED
|
@@ -15,7 +15,9 @@ class DataColumns:
|
|
|
15
15
|
GYROSCOPE_Z: str = "gyroscope_z"
|
|
16
16
|
PPG: str = "green"
|
|
17
17
|
TIME: str = "time"
|
|
18
|
-
|
|
18
|
+
GAIT_SEGMENT_NR: str = "gait_segment_nr"
|
|
19
|
+
SEGMENT_NR: str = "gait_segment_nr" # Deprecated: Use GAIT_SEGMENT_NR instead
|
|
20
|
+
DATA_SEGMENT_NR: str = "data_segment_nr"
|
|
19
21
|
SEGMENT_CAT: str = "segment_category"
|
|
20
22
|
|
|
21
23
|
# Gait
|
|
@@ -37,6 +39,9 @@ class DataColumns:
|
|
|
37
39
|
PRED_TREMOR_LOGREG: str = "pred_tremor_logreg"
|
|
38
40
|
PRED_TREMOR_CHECKED: str = "pred_tremor_checked"
|
|
39
41
|
PRED_ARM_AT_REST: str = "pred_arm_at_rest"
|
|
42
|
+
TREMOR_POWER: str = "tremor_power"
|
|
43
|
+
BELOW_TREMOR_POWER: str = "below_tremor_power"
|
|
44
|
+
FREQ_PEAK: str = "freq_peak"
|
|
40
45
|
|
|
41
46
|
# Constants for PPG features
|
|
42
47
|
VARIANCE: str = "variance"
|
|
@@ -78,9 +83,12 @@ class DataUnits:
|
|
|
78
83
|
GRAVITY: str = "g"
|
|
79
84
|
""" The acceleration due to gravity is in g. """
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
POWER_SPECTRAL_DENSITY_ACC: str = "g^2/Hz"
|
|
82
87
|
""" The power spectral density is in g^2/Hz. """
|
|
83
88
|
|
|
89
|
+
POWER_ROTATION: str = "(deg/s)^2"
|
|
90
|
+
""" The power is in "(deg/s)^2. """
|
|
91
|
+
|
|
84
92
|
FREQUENCY: str = "Hz"
|
|
85
93
|
""" The frequency is in Hz. """
|
|
86
94
|
|