paradigma 0.3.1__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.1.dist-info → paradigma-0.4.0.dist-info}/METADATA +58 -14
- paradigma-0.4.0.dist-info/RECORD +22 -0
- {paradigma-0.3.1.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.1.dist-info/RECORD +0 -108
- {paradigma-0.3.1.dist-info → paradigma-0.4.0.dist-info}/LICENSE +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pickle
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from sklearn.base import BaseEstimator
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
class ClassifierPackage:
|
|
9
|
+
def __init__(self, classifier: Optional[BaseEstimator] = None,
|
|
10
|
+
threshold: Optional[float] = None,
|
|
11
|
+
scaler: Optional[Any] = None):
|
|
12
|
+
"""
|
|
13
|
+
Initialize the ClassifierPackage with a classifier, threshold, and scaler.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
classifier
|
|
18
|
+
The trained classifier.
|
|
19
|
+
threshold : float
|
|
20
|
+
The classification threshold.
|
|
21
|
+
scaler
|
|
22
|
+
The trained scaler (e.g., StandardScaler or MinMaxScaler).
|
|
23
|
+
"""
|
|
24
|
+
self.classifier = classifier
|
|
25
|
+
self.threshold = threshold
|
|
26
|
+
self.scaler = scaler
|
|
27
|
+
|
|
28
|
+
def transform_features(self, X) -> np.ndarray:
|
|
29
|
+
"""
|
|
30
|
+
Transform the input features using the scaler.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
X : np.ndarray
|
|
35
|
+
The input features.
|
|
36
|
+
|
|
37
|
+
Return
|
|
38
|
+
------
|
|
39
|
+
np.ndarray
|
|
40
|
+
The transformed features.
|
|
41
|
+
"""
|
|
42
|
+
if not self.scaler:
|
|
43
|
+
return X
|
|
44
|
+
return self.scaler.transform(X)
|
|
45
|
+
|
|
46
|
+
def predict_proba(self, X) -> float:
|
|
47
|
+
"""
|
|
48
|
+
Make predictions using the classifier and apply the threshold.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
X : np.ndarray
|
|
53
|
+
The input features.
|
|
54
|
+
|
|
55
|
+
Return
|
|
56
|
+
------
|
|
57
|
+
float
|
|
58
|
+
The predicted probability.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
if not self.classifier:
|
|
62
|
+
raise ValueError("Classifier is not loaded.")
|
|
63
|
+
return self.classifier.predict_proba(X)[:, 1]
|
|
64
|
+
|
|
65
|
+
def predict(self, X) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Make predictions using the classifier and apply the threshold.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
X : np.ndarray
|
|
72
|
+
The input features.
|
|
73
|
+
|
|
74
|
+
Return
|
|
75
|
+
------
|
|
76
|
+
int
|
|
77
|
+
The predicted class.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
if not self.classifier:
|
|
81
|
+
raise ValueError("Classifier is not loaded.")
|
|
82
|
+
return int(self.predict_proba(X) >= self.threshold)
|
|
83
|
+
|
|
84
|
+
def save(self, filepath: str | Path) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Save the ClassifierPackage to a file.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
filepath : str
|
|
91
|
+
The path to the file.
|
|
92
|
+
"""
|
|
93
|
+
with open(filepath, 'wb') as f:
|
|
94
|
+
pickle.dump(self, f)
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def load(cls, filepath: str | Path):
|
|
98
|
+
"""
|
|
99
|
+
Load a ClassifierPackage from a file.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
filepath : str
|
|
104
|
+
The path to the file.
|
|
105
|
+
|
|
106
|
+
Return
|
|
107
|
+
------
|
|
108
|
+
ClassifierPackage
|
|
109
|
+
The loaded classifier package.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
with open(filepath, 'rb') as f:
|
|
113
|
+
return pickle.load(f)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
raise ValueError(f"Failed to load classifier package: {e}") from e
|
paradigma/config.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
from paradigma.constants import DataColumns, DataUnits
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
class BaseConfig:
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self.meta_filename = ''
|
|
8
|
+
self.values_filename = ''
|
|
9
|
+
self.time_filename = ''
|
|
10
|
+
|
|
11
|
+
def set_sensor(self, sensor: str) -> None:
|
|
12
|
+
"""Sets the sensor and derived filenames"""
|
|
13
|
+
self.sensor: str = sensor
|
|
14
|
+
self.set_filenames(sensor)
|
|
15
|
+
|
|
16
|
+
def set_filenames(self, prefix: str) -> None:
|
|
17
|
+
"""Sets the filenames based on the prefix. This method is duplicated from `gaits_analysis_config.py`.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
prefix : str
|
|
22
|
+
The prefix for the filenames.
|
|
23
|
+
"""
|
|
24
|
+
self.meta_filename = f"{prefix}_meta.json"
|
|
25
|
+
self.time_filename = f"{prefix}_time.bin"
|
|
26
|
+
self.values_filename = f"{prefix}_values.bin"
|
|
27
|
+
|
|
28
|
+
class IMUConfig(BaseConfig):
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
super().__init__()
|
|
32
|
+
|
|
33
|
+
self.set_filenames('IMU')
|
|
34
|
+
|
|
35
|
+
self.acceleration_units = DataUnits.ACCELERATION
|
|
36
|
+
self.rotation_units = DataUnits.ROTATION
|
|
37
|
+
|
|
38
|
+
self.axes = ["x", "y", "z"]
|
|
39
|
+
|
|
40
|
+
self.accelerometer_cols: List[str] = [
|
|
41
|
+
DataColumns.ACCELEROMETER_X,
|
|
42
|
+
DataColumns.ACCELEROMETER_Y,
|
|
43
|
+
DataColumns.ACCELEROMETER_Z,
|
|
44
|
+
]
|
|
45
|
+
self.gyroscope_cols: List[str] = [
|
|
46
|
+
DataColumns.GYROSCOPE_X,
|
|
47
|
+
DataColumns.GYROSCOPE_Y,
|
|
48
|
+
DataColumns.GYROSCOPE_Z,
|
|
49
|
+
]
|
|
50
|
+
self.gravity_cols: List[str] = [
|
|
51
|
+
DataColumns.GRAV_ACCELEROMETER_X,
|
|
52
|
+
DataColumns.GRAV_ACCELEROMETER_Y,
|
|
53
|
+
DataColumns.GRAV_ACCELEROMETER_Z,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
self.d_channels_accelerometer = {
|
|
57
|
+
DataColumns.ACCELEROMETER_X: self.acceleration_units,
|
|
58
|
+
DataColumns.ACCELEROMETER_Y: self.acceleration_units,
|
|
59
|
+
DataColumns.ACCELEROMETER_Z: self.acceleration_units,
|
|
60
|
+
}
|
|
61
|
+
self.d_channels_gyroscope = {
|
|
62
|
+
DataColumns.GYROSCOPE_X: self.rotation_units,
|
|
63
|
+
DataColumns.GYROSCOPE_Y: self.rotation_units,
|
|
64
|
+
DataColumns.GYROSCOPE_Z: self.rotation_units,
|
|
65
|
+
}
|
|
66
|
+
self.d_channels_imu = {**self.d_channels_accelerometer, **self.d_channels_gyroscope}
|
|
67
|
+
|
|
68
|
+
self.sampling_frequency = 100
|
|
69
|
+
self.lower_cutoff_frequency = 0.2
|
|
70
|
+
self.upper_cutoff_frequency = 3.5
|
|
71
|
+
self.filter_order = 4
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PPGConfig(BaseConfig):
|
|
75
|
+
|
|
76
|
+
def __init__(self) -> None:
|
|
77
|
+
super().__init__()
|
|
78
|
+
|
|
79
|
+
self.set_filenames('PPG')
|
|
80
|
+
|
|
81
|
+
self.ppg_colname = DataColumns.PPG
|
|
82
|
+
|
|
83
|
+
self.sampling_frequency = 30
|
|
84
|
+
self.lower_cutoff_frequency = 0.4
|
|
85
|
+
self.upper_cutoff_frequency = 3.5
|
|
86
|
+
self.filter_order = 4
|
|
87
|
+
|
|
88
|
+
self.d_channels_ppg = {
|
|
89
|
+
DataColumns.PPG: DataUnits.NONE
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Domain base configs
|
|
94
|
+
class GaitConfig(IMUConfig):
|
|
95
|
+
|
|
96
|
+
def __init__(self, step) -> None:
|
|
97
|
+
super().__init__()
|
|
98
|
+
|
|
99
|
+
self.set_sensor('accelerometer')
|
|
100
|
+
|
|
101
|
+
# ----------
|
|
102
|
+
# Segmenting
|
|
103
|
+
# ----------
|
|
104
|
+
self.max_segment_gap_s = 1.5
|
|
105
|
+
self.min_segment_length_s = 1.5
|
|
106
|
+
|
|
107
|
+
if step == 'gait':
|
|
108
|
+
self.window_length_s: float = 6
|
|
109
|
+
self.window_step_length_s: float = 1
|
|
110
|
+
else:
|
|
111
|
+
self.window_length_s: float = 3
|
|
112
|
+
self.window_step_length_s: float = self.window_length_s * 0.25
|
|
113
|
+
|
|
114
|
+
# -----------------
|
|
115
|
+
# Feature extraction
|
|
116
|
+
# -----------------
|
|
117
|
+
self.window_type: str = "hann"
|
|
118
|
+
self.spectrum_low_frequency: int = 0
|
|
119
|
+
self.spectrum_high_frequency: int = int(self.sampling_frequency / 2)
|
|
120
|
+
|
|
121
|
+
# Power in specified frequency bands
|
|
122
|
+
self.d_frequency_bandwidths: Dict[str, List[float]] = {
|
|
123
|
+
"power_below_gait": [0.2, 0.7],
|
|
124
|
+
"power_gait": [0.7, 3.5],
|
|
125
|
+
"power_tremor": [3.5, 8],
|
|
126
|
+
"power_above_tremor": [8, 25],
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Mel frequency cepstral coefficients
|
|
130
|
+
self.mfcc_low_frequency: int = 0
|
|
131
|
+
self.mfcc_high_frequency: int = 25
|
|
132
|
+
self.mfcc_n_dct_filters: int = 15
|
|
133
|
+
self.mfcc_n_coefficients: int = 12
|
|
134
|
+
|
|
135
|
+
# Dominant frequency of first harmonic of arm swing
|
|
136
|
+
self.angle_fmin: float = 0.5
|
|
137
|
+
self.angle_fmax: float = 1.5
|
|
138
|
+
|
|
139
|
+
# -----------------
|
|
140
|
+
# TSDF data storage
|
|
141
|
+
# -----------------
|
|
142
|
+
self.d_channels_values: Dict[str, str] = {
|
|
143
|
+
"accelerometer_std_norm": DataUnits.GRAVITY,
|
|
144
|
+
"accelerometer_x_grav_mean": DataUnits.GRAVITY,
|
|
145
|
+
"accelerometer_y_grav_mean": DataUnits.GRAVITY,
|
|
146
|
+
"accelerometer_z_grav_mean": DataUnits.GRAVITY,
|
|
147
|
+
"accelerometer_x_grav_std": DataUnits.GRAVITY,
|
|
148
|
+
"accelerometer_y_grav_std": DataUnits.GRAVITY,
|
|
149
|
+
"accelerometer_z_grav_std": DataUnits.GRAVITY,
|
|
150
|
+
"accelerometer_x_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
151
|
+
"accelerometer_y_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
152
|
+
"accelerometer_z_power_below_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
153
|
+
"accelerometer_x_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
154
|
+
"accelerometer_y_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
155
|
+
"accelerometer_z_power_gait": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
156
|
+
"accelerometer_x_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
157
|
+
"accelerometer_y_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
158
|
+
"accelerometer_z_power_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
159
|
+
"accelerometer_x_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
160
|
+
"accelerometer_y_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
161
|
+
"accelerometer_z_power_above_tremor": DataUnits.POWER_SPECTRAL_DENSITY,
|
|
162
|
+
"accelerometer_x_dominant_frequency": DataUnits.FREQUENCY,
|
|
163
|
+
"accelerometer_y_dominant_frequency": DataUnits.FREQUENCY,
|
|
164
|
+
"accelerometer_z_dominant_frequency": DataUnits.FREQUENCY,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
|
|
168
|
+
self.d_channels_values[f"accelerometer_mfcc_{mfcc_coef}"] = DataUnits.GRAVITY
|
|
169
|
+
|
|
170
|
+
if step == 'arm_activity':
|
|
171
|
+
for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
|
|
172
|
+
self.d_channels_values[f"gyroscope_mfcc_{mfcc_coef}"] = DataUnits.GRAVITY
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class TremorConfig(IMUConfig):
|
|
176
|
+
|
|
177
|
+
def __init__(self, step: str | None = None) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
step : str (optional)
|
|
182
|
+
The step of the tremor pipeline. Can be 'features' or 'classification'.
|
|
183
|
+
"""
|
|
184
|
+
super().__init__()
|
|
185
|
+
|
|
186
|
+
self.set_sensor('gyroscope')
|
|
187
|
+
|
|
188
|
+
# ----------
|
|
189
|
+
# Segmenting
|
|
190
|
+
# ----------
|
|
191
|
+
self.window_length_s: float = 4
|
|
192
|
+
self.window_step_length_s: float = 4
|
|
193
|
+
|
|
194
|
+
# -----------------
|
|
195
|
+
# Feature extraction
|
|
196
|
+
# -----------------
|
|
197
|
+
self.window_type = 'hann'
|
|
198
|
+
|
|
199
|
+
# Power spectral density
|
|
200
|
+
self.overlap_fraction: float = 0.8
|
|
201
|
+
self.segment_length_s: float = 3
|
|
202
|
+
self.spectral_resolution: float = 0.25
|
|
203
|
+
self.fmin_peak_search: float = 1
|
|
204
|
+
self.fmax_peak_search: float = 25
|
|
205
|
+
self.fmin_below_rest_tremor: float = 0.5
|
|
206
|
+
self.fmax_below_rest_tremor: float = 3
|
|
207
|
+
self.fmin_rest_tremor: float = 3
|
|
208
|
+
self.fmax_rest_tremor: float = 7
|
|
209
|
+
|
|
210
|
+
# Mel frequency cepstral coefficients
|
|
211
|
+
self.fmin_mfcc: float = 0
|
|
212
|
+
self.fmax_mfcc: float = 25
|
|
213
|
+
self.n_dct_filters_mfcc: int = 15
|
|
214
|
+
self.n_coefficients_mfcc: int = 12
|
|
215
|
+
|
|
216
|
+
# --------------
|
|
217
|
+
# Classification
|
|
218
|
+
# --------------
|
|
219
|
+
self.movement_threshold: float = 50
|
|
220
|
+
|
|
221
|
+
# -----------
|
|
222
|
+
# Aggregation
|
|
223
|
+
# -----------
|
|
224
|
+
self.aggregates_tremor_power: List[str] = ['mode', 'median', '90p']
|
|
225
|
+
|
|
226
|
+
# -----------------
|
|
227
|
+
# TSDF data storage
|
|
228
|
+
# -----------------
|
|
229
|
+
if step == 'features':
|
|
230
|
+
self.d_channels_values: Dict[str, str] = {}
|
|
231
|
+
for mfcc_coef in range(1, self.n_coefficients_mfcc + 1):
|
|
232
|
+
self.d_channels_values[f"mfcc_{mfcc_coef}"] = "unitless"
|
|
233
|
+
|
|
234
|
+
self.d_channels_values["freq_peak"] = "Hz"
|
|
235
|
+
self.d_channels_values["below_tremor_power"] = "(deg/s)^2"
|
|
236
|
+
self.d_channels_values["tremor_power"] = "(deg/s)^2"
|
|
237
|
+
elif step == 'classification':
|
|
238
|
+
self.d_channels_values = {
|
|
239
|
+
DataColumns.PRED_TREMOR_PROBA: "probability",
|
|
240
|
+
DataColumns.PRED_TREMOR_LOGREG: "boolean",
|
|
241
|
+
DataColumns.PRED_TREMOR_CHECKED: "boolean",
|
|
242
|
+
DataColumns.PRED_ARM_AT_REST: "boolean"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class HeartRateConfig(PPGConfig):
|
|
247
|
+
def __init__(self, sensor: str = 'ppg', min_window_length_s: int = 30) -> None:
|
|
248
|
+
super().__init__()
|
|
249
|
+
|
|
250
|
+
# ----------
|
|
251
|
+
# Segmenting
|
|
252
|
+
# ----------
|
|
253
|
+
self.window_length_s: int = 6
|
|
254
|
+
self.window_step_length_s: int = 1
|
|
255
|
+
self.window_overlap_s = self.window_length_s - self.window_step_length_s
|
|
256
|
+
|
|
257
|
+
self.accelerometer_cols = IMUConfig().accelerometer_cols
|
|
258
|
+
|
|
259
|
+
# -----------------------
|
|
260
|
+
# Signal quality analysis
|
|
261
|
+
# -----------------------
|
|
262
|
+
self.freq_band_physio = [0.75, 3] # Hz
|
|
263
|
+
self.bandwidth = 0.2 # Hz
|
|
264
|
+
self.freq_bin_resolution = 0.05 # Hz
|
|
265
|
+
|
|
266
|
+
# ---------------------
|
|
267
|
+
# Heart rate estimation
|
|
268
|
+
# ---------------------
|
|
269
|
+
self.set_tfd_length(min_window_length_s) # Set tfd length to default of 30 seconds
|
|
270
|
+
self.threshold_sqa = 0.5
|
|
271
|
+
self.threshold_sqa_accelerometer = 0.13
|
|
272
|
+
|
|
273
|
+
hr_est_length = 2
|
|
274
|
+
self.hr_est_samples = hr_est_length * self.sampling_frequency
|
|
275
|
+
|
|
276
|
+
# Time-frequency distribution parameters
|
|
277
|
+
self.kern_type = 'sep'
|
|
278
|
+
win_type_doppler = 'hamm'
|
|
279
|
+
win_type_lag = 'hamm'
|
|
280
|
+
win_length_doppler = 8
|
|
281
|
+
win_length_lag = 1
|
|
282
|
+
doppler_samples = self.sampling_frequency * win_length_doppler
|
|
283
|
+
lag_samples = win_length_lag * self.sampling_frequency
|
|
284
|
+
self.kern_params = {
|
|
285
|
+
'doppler': {
|
|
286
|
+
'win_length': doppler_samples,
|
|
287
|
+
'win_type': win_type_doppler,
|
|
288
|
+
},
|
|
289
|
+
'lag': {
|
|
290
|
+
'win_length': lag_samples,
|
|
291
|
+
'win_type': win_type_lag,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
self.set_sensor(sensor)
|
|
296
|
+
|
|
297
|
+
def set_tfd_length(self, tfd_length: int):
|
|
298
|
+
self.tfd_length = tfd_length
|
|
299
|
+
self.min_hr_samples = int(round(self.tfd_length * self.sampling_frequency))
|
|
300
|
+
|
|
301
|
+
def set_sensor(self, sensor):
|
|
302
|
+
self.sensor = sensor
|
|
303
|
+
|
|
304
|
+
if sensor not in ['ppg', 'imu']:
|
|
305
|
+
raise ValueError(f"Invalid sensor type: {sensor}")
|
|
306
|
+
|
|
307
|
+
if sensor == 'imu':
|
|
308
|
+
self.sampling_frequency = IMUConfig().sampling_frequency
|
|
309
|
+
else:
|
|
310
|
+
self.sampling_frequency = PPGConfig().sampling_frequency
|
|
311
|
+
|
|
312
|
+
self.window_length_welch = 3 * self.sampling_frequency
|
|
313
|
+
self.overlap_welch_window = self.window_length_welch // 2
|
|
314
|
+
self.nfft = len(np.arange(0, self.sampling_frequency/2, self.freq_bin_resolution))*2
|
paradigma/constants.py
CHANGED
|
@@ -14,17 +14,52 @@ class DataColumns():
|
|
|
14
14
|
GYROSCOPE_Z : str = "gyroscope_z"
|
|
15
15
|
PPG : str = "green"
|
|
16
16
|
TIME : str = "time"
|
|
17
|
+
SEGMENT_NR : str = "segment_nr"
|
|
18
|
+
SEGMENT_CAT: str = "segment_category"
|
|
17
19
|
|
|
18
|
-
#
|
|
19
|
-
GRAV_ACCELEROMETER_X : str = "
|
|
20
|
-
GRAV_ACCELEROMETER_Y : str = "
|
|
21
|
-
GRAV_ACCELEROMETER_Z : str = "
|
|
20
|
+
# Gait
|
|
21
|
+
GRAV_ACCELEROMETER_X : str = "accelerometer_x_grav"
|
|
22
|
+
GRAV_ACCELEROMETER_Y : str = "accelerometer_y_grav"
|
|
23
|
+
GRAV_ACCELEROMETER_Z : str = "accelerometer_z_grav"
|
|
24
|
+
PRED_GAIT_PROBA: str = "pred_gait_proba"
|
|
22
25
|
PRED_GAIT : str = "pred_gait"
|
|
23
|
-
|
|
26
|
+
PRED_NO_OTHER_ARM_ACTIVITY_PROBA: str = "pred_no_other_arm_activity_proba"
|
|
27
|
+
PRED_NO_OTHER_ARM_ACTIVITY : str = "pred_no_other_arm_activity"
|
|
24
28
|
ANGLE : str = "angle"
|
|
25
|
-
ANGLE_SMOOTH : str = "angle_smooth"
|
|
26
29
|
VELOCITY : str = "velocity"
|
|
27
|
-
|
|
30
|
+
DOMINANT_FREQUENCY: str = "dominant_frequency"
|
|
31
|
+
RANGE_OF_MOTION: str = "range_of_motion"
|
|
32
|
+
PEAK_VELOCITY: str = "peak_velocity"
|
|
33
|
+
|
|
34
|
+
# The following are used in tremor analysis
|
|
35
|
+
PRED_TREMOR_PROBA: str = "pred_tremor_proba"
|
|
36
|
+
PRED_TREMOR_LOGREG : str = "pred_tremor_logreg"
|
|
37
|
+
PRED_TREMOR_CHECKED : str = "pred_tremor_checked"
|
|
38
|
+
PRED_ARM_AT_REST: str = "pred_arm_at_rest"
|
|
39
|
+
|
|
40
|
+
# Constants for PPG features
|
|
41
|
+
VARIANCE: str = "variance"
|
|
42
|
+
MEAN: str = "mean"
|
|
43
|
+
MEDIAN: str = "median"
|
|
44
|
+
KURTOSIS: str = "kurtosis"
|
|
45
|
+
SKEWNESS: str = "skewness"
|
|
46
|
+
DOMINANT_FREQUENCY: str = "dominant_frequency"
|
|
47
|
+
RELATIVE_POWER: str = "relative_power"
|
|
48
|
+
SPECTRAL_ENTROPY: str = "spectral_entropy"
|
|
49
|
+
SIGNAL_NOISE_RATIO: str = "signal_noise_ratio"
|
|
50
|
+
SECOND_HIGHEST_PEAK: str = "second_highest_peak"
|
|
51
|
+
POWER_RATIO: str = "power_ratio"
|
|
52
|
+
|
|
53
|
+
# Constants for PPG SQA feature using accerometer data
|
|
54
|
+
ACC_POWER_RATIO: str = "acc_power_ratio"
|
|
55
|
+
|
|
56
|
+
# Constants for SQA classification
|
|
57
|
+
PRED_SQA_PROBA: str = "pred_sqa_proba"
|
|
58
|
+
PRED_SQA_ACC_LABEL: str = "pred_sqa_acc_label"
|
|
59
|
+
PRED_SQA: str = "pred_sqa"
|
|
60
|
+
|
|
61
|
+
# Constants for heart rate
|
|
62
|
+
HEART_RATE: str = "heart_rate"
|
|
28
63
|
|
|
29
64
|
@dataclass(frozen=True)
|
|
30
65
|
class DataUnits():
|
|
@@ -57,9 +92,15 @@ class TimeUnit():
|
|
|
57
92
|
"""
|
|
58
93
|
RELATIVE_MS : str = "relative_ms"
|
|
59
94
|
""" The time is relative to the start time in milliseconds. """
|
|
95
|
+
RELATIVE_S : str = "relative_s"
|
|
96
|
+
""" The time is relative to the start time in seconds. """
|
|
60
97
|
ABSOLUTE_MS : str = "absolute_ms"
|
|
61
98
|
""" The time is absolute in milliseconds. """
|
|
99
|
+
ABSOLUTE_S : str = "absolute_s"
|
|
100
|
+
""" The time is absolute in seconds. """
|
|
62
101
|
DIFFERENCE_MS : str = "difference_ms"
|
|
63
102
|
""" The time is the difference between consecutive samples in milliseconds. """
|
|
103
|
+
DIFFERENCE_S : str = "difference_s"
|
|
104
|
+
""" The time is the difference between consecutive samples in seconds. """
|
|
64
105
|
|
|
65
106
|
UNIX_TICKS_MS: int = 1000
|