paradigma 1.0.2__py3-none-any.whl → 1.0.4__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.
@@ -1,14 +1,19 @@
1
- import numpy as np
2
1
  import pickle
3
-
4
2
  from pathlib import Path
5
- from sklearn.base import BaseEstimator
6
3
  from typing import Any, Optional
7
4
 
5
+ import numpy as np
6
+ from sklearn.base import BaseEstimator
7
+ from sklearn.preprocessing import StandardScaler
8
+
9
+
8
10
  class ClassifierPackage:
9
- def __init__(self, classifier: Optional[BaseEstimator] = None,
10
- threshold: Optional[float] = None,
11
- scaler: Optional[Any] = None):
11
+ def __init__(
12
+ self,
13
+ classifier: Optional[BaseEstimator] = None,
14
+ threshold: Optional[float] = None,
15
+ scaler: Optional[Any] = None,
16
+ ):
12
17
  """
13
18
  Initialize the ClassifierPackage with a classifier, threshold, and scaler.
14
19
 
@@ -43,6 +48,18 @@ class ClassifierPackage:
43
48
  return X
44
49
  return self.scaler.transform(X)
45
50
 
51
+ def update_scaler(self, x_train: np.ndarray) -> None:
52
+ """
53
+ Update the scaler used for feature transformation.
54
+
55
+ Parameters
56
+ ----------
57
+ x_train : np.ndarray
58
+ The training data to fit the scaler.
59
+ """
60
+ scaler = StandardScaler()
61
+ self.scaler = scaler.fit(x_train)
62
+
46
63
  def predict_proba(self, X) -> float:
47
64
  """
48
65
  Make predictions using the classifier and apply the threshold.
@@ -61,7 +78,7 @@ class ClassifierPackage:
61
78
  if not self.classifier:
62
79
  raise ValueError("Classifier is not loaded.")
63
80
  return self.classifier.predict_proba(X)[:, 1]
64
-
81
+
65
82
  def predict(self, X) -> int:
66
83
  """
67
84
  Make predictions using the classifier and apply the threshold.
@@ -80,7 +97,7 @@ class ClassifierPackage:
80
97
  if not self.classifier:
81
98
  raise ValueError("Classifier is not loaded.")
82
99
  return int(self.predict_proba(X) >= self.threshold)
83
-
100
+
84
101
  def save(self, filepath: str | Path) -> None:
85
102
  """
86
103
  Save the ClassifierPackage to a file.
@@ -90,7 +107,7 @@ class ClassifierPackage:
90
107
  filepath : str
91
108
  The path to the file.
92
109
  """
93
- with open(filepath, 'wb') as f:
110
+ with open(filepath, "wb") as f:
94
111
  pickle.dump(self, f)
95
112
 
96
113
  @classmethod
@@ -109,7 +126,7 @@ class ClassifierPackage:
109
126
  The loaded classifier package.
110
127
  """
111
128
  try:
112
- with open(filepath, 'rb') as f:
129
+ with open(filepath, "rb") as f:
113
130
  return pickle.load(f)
114
131
  except Exception as e:
115
- raise ValueError(f"Failed to load classifier package: {e}") from e
132
+ raise ValueError(f"Failed to load classifier package: {e}") from e
paradigma/config.py CHANGED
@@ -1,12 +1,17 @@
1
+ import warnings
2
+ from dataclasses import asdict
1
3
  from typing import Dict, List
2
- from paradigma.constants import DataColumns, DataUnits
4
+
3
5
  import numpy as np
4
6
 
7
+ from paradigma.constants import DataColumns, DataUnits
8
+
9
+
5
10
  class BaseConfig:
6
11
  def __init__(self) -> None:
7
- self.meta_filename = ''
8
- self.values_filename = ''
9
- self.time_filename = ''
12
+ self.meta_filename = ""
13
+ self.values_filename = ""
14
+ self.time_filename = ""
10
15
 
11
16
  def set_sensor(self, sensor: str) -> None:
12
17
  """Sets the sensor and derived filenames"""
@@ -15,7 +20,7 @@ class BaseConfig:
15
20
 
16
21
  def set_filenames(self, prefix: str) -> None:
17
22
  """Sets the filenames based on the prefix. This method is duplicated from `gaits_analysis_config.py`.
18
-
23
+
19
24
  Parameters
20
25
  ----------
21
26
  prefix : str
@@ -25,47 +30,67 @@ class BaseConfig:
25
30
  self.time_filename = f"{prefix}_time.bin"
26
31
  self.values_filename = f"{prefix}_values.bin"
27
32
 
33
+
28
34
  class IMUConfig(BaseConfig):
35
+ """
36
+ IMU configuration that uses DataColumns() to dynamically map available channels.
37
+ Works even if only accelerometer or only gyroscope data is present.
38
+ """
29
39
 
30
- def __init__(self) -> None:
40
+ def __init__(self, column_mapping: dict[str, str] | None = None) -> None:
31
41
  super().__init__()
32
-
33
- self.set_filenames('IMU')
42
+ self.set_filenames("IMU")
34
43
 
35
44
  self.acceleration_units = DataUnits.ACCELERATION
36
45
  self.rotation_units = DataUnits.ROTATION
37
-
38
46
  self.axes = ["x", "y", "z"]
39
47
 
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,
48
+ # Generate a default mapping or override with user-provided mapping
49
+ default_mapping = asdict(DataColumns())
50
+ self.column_mapping = {**default_mapping, **(column_mapping or {})}
51
+
52
+ self.time_colname = self.column_mapping["TIME"]
53
+
54
+ self.accelerometer_colnames: List[str] = []
55
+ self.gyroscope_colnames: List[str] = []
56
+ self.gravity_colnames: List[str] = []
57
+
58
+ self.d_channels_accelerometer: Dict[str, str] = {}
59
+ self.d_channels_gyroscope: Dict[str, str] = {}
60
+
61
+ accel_keys = ["ACCELEROMETER_X", "ACCELEROMETER_Y", "ACCELEROMETER_Z"]
62
+ grav_keys = [
63
+ "GRAV_ACCELEROMETER_X",
64
+ "GRAV_ACCELEROMETER_Y",
65
+ "GRAV_ACCELEROMETER_Z",
54
66
  ]
67
+ gyro_keys = ["GYROSCOPE_X", "GYROSCOPE_Y", "GYROSCOPE_Z"]
55
68
 
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,
69
+ if all(k in self.column_mapping for k in accel_keys):
70
+ self.accelerometer_colnames = [self.column_mapping[k] for k in accel_keys]
71
+
72
+ if all(k in self.column_mapping for k in grav_keys):
73
+ self.gravity_colnames = [self.column_mapping[k] for k in grav_keys]
74
+
75
+ self.d_channels_accelerometer = {
76
+ c: self.acceleration_units for c in self.accelerometer_colnames
77
+ }
78
+
79
+ if all(k in self.column_mapping for k in gyro_keys):
80
+ self.gyroscope_colnames = [self.column_mapping[k] for k in gyro_keys]
81
+
82
+ self.d_channels_gyroscope = {
83
+ c: self.rotation_units for c in self.gyroscope_colnames
84
+ }
85
+
86
+ self.d_channels_imu: Dict[str, str] = {
87
+ **self.d_channels_accelerometer,
88
+ **self.d_channels_gyroscope,
65
89
  }
66
- self.d_channels_imu = {**self.d_channels_accelerometer, **self.d_channels_gyroscope}
67
90
 
68
91
  self.sampling_frequency = 100
92
+ self.resampling_frequency = 100
93
+ self.tolerance = 3 * 1 / self.sampling_frequency
69
94
  self.lower_cutoff_frequency = 0.2
70
95
  self.upper_cutoff_frequency = 3.5
71
96
  self.filter_order = 4
@@ -73,30 +98,36 @@ class IMUConfig(BaseConfig):
73
98
 
74
99
  class PPGConfig(BaseConfig):
75
100
 
76
- def __init__(self) -> None:
101
+ def __init__(self, column_mapping: dict[str, str] | None = None) -> None:
77
102
  super().__init__()
78
103
 
79
- self.set_filenames('PPG')
104
+ self.set_filenames("PPG")
80
105
 
81
- self.ppg_colname = DataColumns.PPG
106
+ # Generate a default mapping or override with user-provided mapping
107
+ default_mapping = asdict(DataColumns())
108
+ self.column_mapping = {**default_mapping, **(column_mapping or {})}
109
+
110
+ self.time_colname = self.column_mapping["TIME"]
111
+ self.ppg_colname = self.column_mapping["PPG"]
82
112
 
83
113
  self.sampling_frequency = 30
114
+ self.resampling_frequency = 30
115
+ self.tolerance = 3 * 1 / self.sampling_frequency
84
116
  self.lower_cutoff_frequency = 0.4
85
117
  self.upper_cutoff_frequency = 3.5
86
118
  self.filter_order = 4
87
119
 
88
- self.d_channels_ppg = {
89
- DataColumns.PPG: DataUnits.NONE
90
- }
120
+ self.d_channels_ppg = {self.ppg_colname: DataUnits.NONE}
91
121
 
92
122
 
93
123
  # Domain base configs
94
124
  class GaitConfig(IMUConfig):
95
125
 
96
- def __init__(self, step) -> None:
97
- super().__init__()
126
+ def __init__(self, step, column_mapping: dict[str, str] | None = None) -> None:
127
+ # Pass column_mapping through to IMUConfig
128
+ super().__init__(column_mapping=column_mapping)
98
129
 
99
- self.set_sensor('accelerometer')
130
+ self.set_sensor("accelerometer")
100
131
 
101
132
  # ----------
102
133
  # Segmenting
@@ -104,7 +135,7 @@ class GaitConfig(IMUConfig):
104
135
  self.max_segment_gap_s = 1.5
105
136
  self.min_segment_length_s = 1.5
106
137
 
107
- if step == 'gait':
138
+ if step == "gait":
108
139
  self.window_length_s: float = 6
109
140
  self.window_step_length_s: float = 1
110
141
  else:
@@ -165,11 +196,15 @@ class GaitConfig(IMUConfig):
165
196
  }
166
197
 
167
198
  for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
168
- self.d_channels_values[f"accelerometer_mfcc_{mfcc_coef}"] = DataUnits.GRAVITY
199
+ self.d_channels_values[f"accelerometer_mfcc_{mfcc_coef}"] = (
200
+ DataUnits.GRAVITY
201
+ )
169
202
 
170
- if step == 'arm_activity':
203
+ if step == "arm_activity":
171
204
  for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
172
- self.d_channels_values[f"gyroscope_mfcc_{mfcc_coef}"] = DataUnits.GRAVITY
205
+ self.d_channels_values[f"gyroscope_mfcc_{mfcc_coef}"] = (
206
+ DataUnits.GRAVITY
207
+ )
173
208
 
174
209
 
175
210
  class TremorConfig(IMUConfig):
@@ -183,7 +218,7 @@ class TremorConfig(IMUConfig):
183
218
  """
184
219
  super().__init__()
185
220
 
186
- self.set_sensor('gyroscope')
221
+ self.set_sensor("gyroscope")
187
222
 
188
223
  # ----------
189
224
  # Segmenting
@@ -194,12 +229,12 @@ class TremorConfig(IMUConfig):
194
229
  # -----------------
195
230
  # Feature extraction
196
231
  # -----------------
197
- self.window_type = 'hann'
232
+ self.window_type = "hann"
198
233
  self.overlap_fraction: float = 0.8
199
234
  self.segment_length_psd_s: float = 3
200
235
  self.segment_length_spectrogram_s: float = 2
201
236
  self.spectral_resolution: float = 0.25
202
-
237
+
203
238
  # PSD based features
204
239
  self.fmin_peak_search: float = 1
205
240
  self.fmax_peak_search: float = 25
@@ -222,12 +257,13 @@ class TremorConfig(IMUConfig):
222
257
  # -----------
223
258
  # Aggregation
224
259
  # -----------
225
- self.aggregates_tremor_power: List[str] = ['mode', 'median', '90p']
260
+ self.aggregates_tremor_power: List[str] = ["mode_binned", "median", "90p"]
261
+ self.evaluation_points_tremor_power: np.ndarray = np.linspace(0, 6, 301)
226
262
 
227
263
  # -----------------
228
264
  # TSDF data storage
229
265
  # -----------------
230
- if step == 'features':
266
+ if step == "features":
231
267
  self.d_channels_values: Dict[str, str] = {}
232
268
  for mfcc_coef in range(1, self.n_coefficients_mfcc + 1):
233
269
  self.d_channels_values[f"mfcc_{mfcc_coef}"] = "unitless"
@@ -235,81 +271,102 @@ class TremorConfig(IMUConfig):
235
271
  self.d_channels_values["freq_peak"] = "Hz"
236
272
  self.d_channels_values["below_tremor_power"] = "(deg/s)^2"
237
273
  self.d_channels_values["tremor_power"] = "(deg/s)^2"
238
- elif step == 'classification':
274
+ elif step == "classification":
239
275
  self.d_channels_values = {
240
276
  DataColumns.PRED_TREMOR_PROBA: "probability",
241
277
  DataColumns.PRED_TREMOR_LOGREG: "boolean",
242
278
  DataColumns.PRED_TREMOR_CHECKED: "boolean",
243
- DataColumns.PRED_ARM_AT_REST: "boolean"
279
+ DataColumns.PRED_ARM_AT_REST: "boolean",
244
280
  }
245
281
 
246
-
282
+
247
283
  class PulseRateConfig(PPGConfig):
248
- def __init__(self, sensor: str = 'ppg', min_window_length_s: int = 30) -> None:
284
+ def __init__(
285
+ self,
286
+ sensor: str = "ppg",
287
+ ppg_sampling_frequency: int = 30,
288
+ imu_sampling_frequency: int | None = None,
289
+ min_window_length_s: int = 30,
290
+ accelerometer_colnames: list[str] | None = None,
291
+ ) -> None:
249
292
  super().__init__()
250
293
 
251
- # ----------
252
- # Segmenting
253
- # ----------
294
+ self.ppg_sampling_frequency = ppg_sampling_frequency
295
+
296
+ if sensor == "imu":
297
+ if imu_sampling_frequency is not None:
298
+ self.imu_sampling_frequency = imu_sampling_frequency
299
+ else:
300
+ self.imu_sampling_frequency = IMUConfig().sampling_frequency
301
+ warnings.warn(
302
+ f"imu_sampling_frequency not provided, using default of {self.imu_sampling_frequency} Hz"
303
+ )
304
+
305
+ # Windowing parameters
254
306
  self.window_length_s: int = 6
255
307
  self.window_step_length_s: int = 1
256
308
  self.window_overlap_s = self.window_length_s - self.window_step_length_s
257
309
 
258
- self.accelerometer_cols = IMUConfig().accelerometer_cols
310
+ self.accelerometer_colnames = accelerometer_colnames
259
311
 
260
- # -----------------------
261
- # Signal quality analysis
262
- # -----------------------
263
- self.freq_band_physio = [0.75, 3] # Hz
264
- self.bandwidth = 0.2 # Hz
265
- self.freq_bin_resolution = 0.05 # Hz
312
+ # Signal quality analysis parameters
313
+ self.freq_band_physio = [0.75, 3] # Hz
314
+ self.bandwidth = 0.2 # Hz
315
+ self.freq_bin_resolution = 0.05 # Hz
266
316
 
267
- # ---------------------
268
- # Pulse rate estimation
269
- # ---------------------
270
- self.set_tfd_length(min_window_length_s) # Set tfd length to default of 30 seconds
317
+ # Pulse rate estimation parameters
271
318
  self.threshold_sqa = 0.5
272
319
  self.threshold_sqa_accelerometer = 0.10
273
320
 
321
+ # Set initial sensor and update sampling-dependent params
322
+ self.set_sensor(sensor, min_window_length_s)
323
+
324
+ def set_sensor(self, sensor: str, min_window_length_s: int | None = None) -> None:
325
+ """Sets the active sensor and recomputes sampling-dependent parameters."""
326
+ if sensor not in ["ppg", "imu"]:
327
+ raise ValueError(f"Invalid sensor type: {sensor}")
328
+ self.sensor = sensor
329
+
330
+ # Decide which frequency to use
331
+ self.sampling_frequency = (
332
+ self.imu_sampling_frequency
333
+ if sensor == "imu"
334
+ else self.ppg_sampling_frequency
335
+ )
336
+
337
+ # Update all frequency-dependent parameters
338
+ if min_window_length_s is not None:
339
+ self._update_sampling_dependent_params(min_window_length_s)
340
+ else:
341
+ # Reuse previous tfd_length if it exists, else fallback to 30
342
+ self._update_sampling_dependent_params(getattr(self, "tfd_length", 30))
343
+
344
+ def _update_sampling_dependent_params(self, tfd_length: int):
345
+ """Compute attributes that depend on sampling frequency."""
346
+
347
+ # --- PPG-dependent parameters ---
348
+ self.tfd_length = tfd_length
349
+ self.min_pr_samples = int(round(self.tfd_length * self.ppg_sampling_frequency))
350
+
274
351
  pr_est_length = 2 # pulse rate estimation length in seconds
275
- self.pr_est_samples = pr_est_length * self.sampling_frequency
352
+ self.pr_est_samples = pr_est_length * self.ppg_sampling_frequency
276
353
 
277
354
  # Time-frequency distribution parameters
278
- self.kern_type = 'sep'
279
- win_type_doppler = 'hamm'
280
- win_type_lag = 'hamm'
355
+ win_type_doppler = "hamm"
356
+ win_type_lag = "hamm"
281
357
  win_length_doppler = 8
282
358
  win_length_lag = 1
283
- doppler_samples = self.sampling_frequency * win_length_doppler
284
- lag_samples = win_length_lag * self.sampling_frequency
359
+ doppler_samples = self.ppg_sampling_frequency * win_length_doppler
360
+ lag_samples = win_length_lag * self.ppg_sampling_frequency
361
+ self.kern_type = "sep"
285
362
  self.kern_params = {
286
- 'doppler': {
287
- 'win_length': doppler_samples,
288
- 'win_type': win_type_doppler,
289
- },
290
- 'lag': {
291
- 'win_length': lag_samples,
292
- 'win_type': win_type_lag,
293
- }
363
+ "doppler": {"win_length": doppler_samples, "win_type": win_type_doppler},
364
+ "lag": {"win_length": lag_samples, "win_type": win_type_lag},
294
365
  }
295
366
 
296
- self.set_sensor(sensor)
297
-
298
- def set_tfd_length(self, tfd_length: int):
299
- self.tfd_length = tfd_length
300
- self.min_pr_samples = int(round(self.tfd_length * self.sampling_frequency))
301
-
302
- def set_sensor(self, sensor):
303
- self.sensor = sensor
304
-
305
- if sensor not in ['ppg', 'imu']:
306
- raise ValueError(f"Invalid sensor type: {sensor}")
307
-
308
- if sensor == 'imu':
309
- self.sampling_frequency = IMUConfig().sampling_frequency
310
- else:
311
- self.sampling_frequency = PPGConfig().sampling_frequency
312
-
367
+ # --- Welch / FFT parameters based on current sensor frequency ---
313
368
  self.window_length_welch = 3 * self.sampling_frequency
314
369
  self.overlap_welch_window = self.window_length_welch // 2
315
- self.nfft = len(np.arange(0, self.sampling_frequency/2, self.freq_bin_resolution))*2
370
+ self.nfft = (
371
+ len(np.arange(0, self.sampling_frequency / 2, self.freq_bin_resolution)) * 2
372
+ )
paradigma/constants.py CHANGED
@@ -2,39 +2,40 @@ from dataclasses import dataclass
2
2
 
3
3
 
4
4
  @dataclass(frozen=True)
5
- class DataColumns():
5
+ class DataColumns:
6
6
  """
7
7
  Class containing the data channels in `tsdf`.
8
8
  """
9
- ACCELEROMETER_X : str = "accelerometer_x"
10
- ACCELEROMETER_Y : str = "accelerometer_y"
11
- ACCELEROMETER_Z : str = "accelerometer_z"
12
- GYROSCOPE_X : str = "gyroscope_x"
13
- GYROSCOPE_Y : str = "gyroscope_y"
14
- GYROSCOPE_Z : str = "gyroscope_z"
15
- PPG : str = "green"
16
- TIME : str = "time"
17
- SEGMENT_NR : str = "segment_nr"
9
+
10
+ ACCELEROMETER_X: str = "accelerometer_x"
11
+ ACCELEROMETER_Y: str = "accelerometer_y"
12
+ ACCELEROMETER_Z: str = "accelerometer_z"
13
+ GYROSCOPE_X: str = "gyroscope_x"
14
+ GYROSCOPE_Y: str = "gyroscope_y"
15
+ GYROSCOPE_Z: str = "gyroscope_z"
16
+ PPG: str = "green"
17
+ TIME: str = "time"
18
+ SEGMENT_NR: str = "segment_nr"
18
19
  SEGMENT_CAT: str = "segment_category"
19
20
 
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"
21
+ # Gait
22
+ GRAV_ACCELEROMETER_X: str = "accelerometer_x_grav"
23
+ GRAV_ACCELEROMETER_Y: str = "accelerometer_y_grav"
24
+ GRAV_ACCELEROMETER_Z: str = "accelerometer_z_grav"
24
25
  PRED_GAIT_PROBA: str = "pred_gait_proba"
25
- PRED_GAIT : str = "pred_gait"
26
+ PRED_GAIT: str = "pred_gait"
26
27
  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"
28
- ANGLE : str = "angle"
29
- VELOCITY : str = "velocity"
28
+ PRED_NO_OTHER_ARM_ACTIVITY: str = "pred_no_other_arm_activity"
29
+ ANGLE: str = "angle"
30
+ VELOCITY: str = "velocity"
30
31
  DOMINANT_FREQUENCY: str = "dominant_frequency"
31
32
  RANGE_OF_MOTION: str = "range_of_motion"
32
33
  PEAK_VELOCITY: str = "peak_velocity"
33
34
 
34
35
  # The following are used in tremor analysis
35
36
  PRED_TREMOR_PROBA: str = "pred_tremor_proba"
36
- PRED_TREMOR_LOGREG : str = "pred_tremor_logreg"
37
- PRED_TREMOR_CHECKED : str = "pred_tremor_checked"
37
+ PRED_TREMOR_LOGREG: str = "pred_tremor_logreg"
38
+ PRED_TREMOR_CHECKED: str = "pred_tremor_checked"
38
39
  PRED_ARM_AT_REST: str = "pred_arm_at_rest"
39
40
 
40
41
  # Constants for PPG features
@@ -60,47 +61,51 @@ class DataColumns():
60
61
 
61
62
  # Constants for pulse rate
62
63
  PULSE_RATE: str = "pulse_rate"
63
-
64
+
65
+
64
66
  @dataclass(frozen=True)
65
- class DataUnits():
67
+ class DataUnits:
66
68
  """
67
69
  Class containing the data channel unit types in `tsdf`.
68
70
  """
71
+
69
72
  ACCELERATION: str = "m/s^2"
70
73
  """ The acceleration is in m/s^2. """
71
-
74
+
72
75
  ROTATION: str = "deg/s"
73
76
  """ The rotation is in degrees per second. """
74
-
77
+
75
78
  GRAVITY: str = "g"
76
79
  """ The acceleration due to gravity is in g. """
77
-
80
+
78
81
  POWER_SPECTRAL_DENSITY: str = "g^2/Hz"
79
82
  """ The power spectral density is in g^2/Hz. """
80
-
83
+
81
84
  FREQUENCY: str = "Hz"
82
85
  """ The frequency is in Hz. """
83
86
 
84
87
  NONE: str = "none"
85
88
  """ The data channel has no unit. """
86
-
89
+
87
90
 
88
91
  @dataclass(frozen=True)
89
- class TimeUnit():
92
+ class TimeUnit:
90
93
  """
91
94
  Class containing the `time` channel unit types in `tsdf`.
92
95
  """
93
- RELATIVE_MS : str = "relative_ms"
96
+
97
+ RELATIVE_MS: str = "relative_ms"
94
98
  """ The time is relative to the start time in milliseconds. """
95
- RELATIVE_S : str = "relative_s"
99
+ RELATIVE_S: str = "relative_s"
96
100
  """ The time is relative to the start time in seconds. """
97
- ABSOLUTE_MS : str = "absolute_ms"
101
+ ABSOLUTE_MS: str = "absolute_ms"
98
102
  """ The time is absolute in milliseconds. """
99
- ABSOLUTE_S : str = "absolute_s"
103
+ ABSOLUTE_S: str = "absolute_s"
100
104
  """ The time is absolute in seconds. """
101
- DIFFERENCE_MS : str = "difference_ms"
105
+ DIFFERENCE_MS: str = "difference_ms"
102
106
  """ The time is the difference between consecutive samples in milliseconds. """
103
- DIFFERENCE_S : str = "difference_s"
107
+ DIFFERENCE_S: str = "difference_s"
104
108
  """ The time is the difference between consecutive samples in seconds. """
105
109
 
110
+
106
111
  UNIX_TICKS_MS: int = 1000