paradigma 1.0.3__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,48 +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
69
92
  self.resampling_frequency = 100
93
+ self.tolerance = 3 * 1 / self.sampling_frequency
70
94
  self.lower_cutoff_frequency = 0.2
71
95
  self.upper_cutoff_frequency = 3.5
72
96
  self.filter_order = 4
@@ -74,30 +98,36 @@ class IMUConfig(BaseConfig):
74
98
 
75
99
  class PPGConfig(BaseConfig):
76
100
 
77
- def __init__(self) -> None:
101
+ def __init__(self, column_mapping: dict[str, str] | None = None) -> None:
78
102
  super().__init__()
79
103
 
80
- self.set_filenames('PPG')
104
+ self.set_filenames("PPG")
81
105
 
82
- 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"]
83
112
 
84
113
  self.sampling_frequency = 30
114
+ self.resampling_frequency = 30
115
+ self.tolerance = 3 * 1 / self.sampling_frequency
85
116
  self.lower_cutoff_frequency = 0.4
86
117
  self.upper_cutoff_frequency = 3.5
87
118
  self.filter_order = 4
88
119
 
89
- self.d_channels_ppg = {
90
- DataColumns.PPG: DataUnits.NONE
91
- }
120
+ self.d_channels_ppg = {self.ppg_colname: DataUnits.NONE}
92
121
 
93
122
 
94
123
  # Domain base configs
95
124
  class GaitConfig(IMUConfig):
96
125
 
97
- def __init__(self, step) -> None:
98
- 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)
99
129
 
100
- self.set_sensor('accelerometer')
130
+ self.set_sensor("accelerometer")
101
131
 
102
132
  # ----------
103
133
  # Segmenting
@@ -105,7 +135,7 @@ class GaitConfig(IMUConfig):
105
135
  self.max_segment_gap_s = 1.5
106
136
  self.min_segment_length_s = 1.5
107
137
 
108
- if step == 'gait':
138
+ if step == "gait":
109
139
  self.window_length_s: float = 6
110
140
  self.window_step_length_s: float = 1
111
141
  else:
@@ -166,11 +196,15 @@ class GaitConfig(IMUConfig):
166
196
  }
167
197
 
168
198
  for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
169
- 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
+ )
170
202
 
171
- if step == 'arm_activity':
203
+ if step == "arm_activity":
172
204
  for mfcc_coef in range(1, self.mfcc_n_coefficients + 1):
173
- 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
+ )
174
208
 
175
209
 
176
210
  class TremorConfig(IMUConfig):
@@ -184,7 +218,7 @@ class TremorConfig(IMUConfig):
184
218
  """
185
219
  super().__init__()
186
220
 
187
- self.set_sensor('gyroscope')
221
+ self.set_sensor("gyroscope")
188
222
 
189
223
  # ----------
190
224
  # Segmenting
@@ -195,12 +229,12 @@ class TremorConfig(IMUConfig):
195
229
  # -----------------
196
230
  # Feature extraction
197
231
  # -----------------
198
- self.window_type = 'hann'
232
+ self.window_type = "hann"
199
233
  self.overlap_fraction: float = 0.8
200
234
  self.segment_length_psd_s: float = 3
201
235
  self.segment_length_spectrogram_s: float = 2
202
236
  self.spectral_resolution: float = 0.25
203
-
237
+
204
238
  # PSD based features
205
239
  self.fmin_peak_search: float = 1
206
240
  self.fmax_peak_search: float = 25
@@ -223,13 +257,13 @@ class TremorConfig(IMUConfig):
223
257
  # -----------
224
258
  # Aggregation
225
259
  # -----------
226
- self.aggregates_tremor_power: List[str] = ['mode_binned', 'median', '90p']
227
- self.evaluation_points_tremor_power: np.ndarray = np.linspace(0, 6, 301)
260
+ self.aggregates_tremor_power: List[str] = ["mode_binned", "median", "90p"]
261
+ self.evaluation_points_tremor_power: np.ndarray = np.linspace(0, 6, 301)
228
262
 
229
263
  # -----------------
230
264
  # TSDF data storage
231
265
  # -----------------
232
- if step == 'features':
266
+ if step == "features":
233
267
  self.d_channels_values: Dict[str, str] = {}
234
268
  for mfcc_coef in range(1, self.n_coefficients_mfcc + 1):
235
269
  self.d_channels_values[f"mfcc_{mfcc_coef}"] = "unitless"
@@ -237,81 +271,102 @@ class TremorConfig(IMUConfig):
237
271
  self.d_channels_values["freq_peak"] = "Hz"
238
272
  self.d_channels_values["below_tremor_power"] = "(deg/s)^2"
239
273
  self.d_channels_values["tremor_power"] = "(deg/s)^2"
240
- elif step == 'classification':
274
+ elif step == "classification":
241
275
  self.d_channels_values = {
242
276
  DataColumns.PRED_TREMOR_PROBA: "probability",
243
277
  DataColumns.PRED_TREMOR_LOGREG: "boolean",
244
278
  DataColumns.PRED_TREMOR_CHECKED: "boolean",
245
- DataColumns.PRED_ARM_AT_REST: "boolean"
279
+ DataColumns.PRED_ARM_AT_REST: "boolean",
246
280
  }
247
281
 
248
-
282
+
249
283
  class PulseRateConfig(PPGConfig):
250
- 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:
251
292
  super().__init__()
252
293
 
253
- # ----------
254
- # Segmenting
255
- # ----------
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
256
306
  self.window_length_s: int = 6
257
307
  self.window_step_length_s: int = 1
258
308
  self.window_overlap_s = self.window_length_s - self.window_step_length_s
259
309
 
260
- self.accelerometer_cols = IMUConfig().accelerometer_cols
310
+ self.accelerometer_colnames = accelerometer_colnames
261
311
 
262
- # -----------------------
263
- # Signal quality analysis
264
- # -----------------------
265
- self.freq_band_physio = [0.75, 3] # Hz
266
- self.bandwidth = 0.2 # Hz
267
- 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
268
316
 
269
- # ---------------------
270
- # Pulse rate estimation
271
- # ---------------------
272
- self.set_tfd_length(min_window_length_s) # Set tfd length to default of 30 seconds
317
+ # Pulse rate estimation parameters
273
318
  self.threshold_sqa = 0.5
274
319
  self.threshold_sqa_accelerometer = 0.10
275
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
+
276
351
  pr_est_length = 2 # pulse rate estimation length in seconds
277
- self.pr_est_samples = pr_est_length * self.sampling_frequency
352
+ self.pr_est_samples = pr_est_length * self.ppg_sampling_frequency
278
353
 
279
354
  # Time-frequency distribution parameters
280
- self.kern_type = 'sep'
281
- win_type_doppler = 'hamm'
282
- win_type_lag = 'hamm'
355
+ win_type_doppler = "hamm"
356
+ win_type_lag = "hamm"
283
357
  win_length_doppler = 8
284
358
  win_length_lag = 1
285
- doppler_samples = self.sampling_frequency * win_length_doppler
286
- 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"
287
362
  self.kern_params = {
288
- 'doppler': {
289
- 'win_length': doppler_samples,
290
- 'win_type': win_type_doppler,
291
- },
292
- 'lag': {
293
- 'win_length': lag_samples,
294
- 'win_type': win_type_lag,
295
- }
363
+ "doppler": {"win_length": doppler_samples, "win_type": win_type_doppler},
364
+ "lag": {"win_length": lag_samples, "win_type": win_type_lag},
296
365
  }
297
366
 
298
- self.set_sensor(sensor)
299
-
300
- def set_tfd_length(self, tfd_length: int):
301
- self.tfd_length = tfd_length
302
- self.min_pr_samples = int(round(self.tfd_length * self.sampling_frequency))
303
-
304
- def set_sensor(self, sensor):
305
- self.sensor = sensor
306
-
307
- if sensor not in ['ppg', 'imu']:
308
- raise ValueError(f"Invalid sensor type: {sensor}")
309
-
310
- if sensor == 'imu':
311
- self.sampling_frequency = IMUConfig().sampling_frequency
312
- else:
313
- self.sampling_frequency = PPGConfig().sampling_frequency
314
-
367
+ # --- Welch / FFT parameters based on current sensor frequency ---
315
368
  self.window_length_welch = 3 * self.sampling_frequency
316
369
  self.overlap_welch_window = self.window_length_welch // 2
317
- 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