paradigma 0.3.2__py3-none-any.whl → 0.4.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.
Files changed (123) hide show
  1. paradigma/assets/gait_detection_clf_package.pkl +0 -0
  2. paradigma/assets/gait_filtering_clf_package.pkl +0 -0
  3. paradigma/assets/ppg_quality_clf_package.pkl +0 -0
  4. paradigma/assets/tremor_detection_clf_package.pkl +0 -0
  5. paradigma/classification.py +115 -0
  6. paradigma/config.py +314 -0
  7. paradigma/constants.py +48 -7
  8. paradigma/feature_extraction.py +811 -547
  9. paradigma/pipelines/__init__.py +0 -0
  10. paradigma/pipelines/gait_pipeline.py +727 -0
  11. paradigma/pipelines/heart_rate_pipeline.py +426 -0
  12. paradigma/pipelines/heart_rate_utils.py +780 -0
  13. paradigma/pipelines/tremor_pipeline.py +299 -0
  14. paradigma/preprocessing.py +363 -0
  15. paradigma/segmenting.py +396 -0
  16. paradigma/testing.py +416 -0
  17. paradigma/util.py +393 -16
  18. paradigma-0.4.1.dist-info/METADATA +138 -0
  19. paradigma-0.4.1.dist-info/RECORD +22 -0
  20. {paradigma-0.3.2.dist-info → paradigma-0.4.1.dist-info}/WHEEL +1 -1
  21. paradigma/gait_analysis.py +0 -415
  22. paradigma/gait_analysis_config.py +0 -266
  23. paradigma/heart_rate_analysis.py +0 -127
  24. paradigma/heart_rate_analysis_config.py +0 -9
  25. paradigma/heart_rate_util.py +0 -173
  26. paradigma/imu_preprocessing.py +0 -232
  27. paradigma/ppg/classifier/LR_PPG_quality.pkl +0 -0
  28. paradigma/ppg/classifier/LR_model.mat +0 -0
  29. paradigma/ppg/feat_extraction/acc_feature.m +0 -20
  30. paradigma/ppg/feat_extraction/peakdet.m +0 -64
  31. paradigma/ppg/feat_extraction/ppg_features.m +0 -53
  32. paradigma/ppg/glob_functions/extract_hr_segments.m +0 -37
  33. paradigma/ppg/glob_functions/extract_overlapping_segments.m +0 -23
  34. paradigma/ppg/glob_functions/jsonlab/AUTHORS.txt +0 -41
  35. paradigma/ppg/glob_functions/jsonlab/ChangeLog.txt +0 -74
  36. paradigma/ppg/glob_functions/jsonlab/LICENSE_BSD.txt +0 -25
  37. paradigma/ppg/glob_functions/jsonlab/LICENSE_GPLv3.txt +0 -699
  38. paradigma/ppg/glob_functions/jsonlab/README.txt +0 -394
  39. paradigma/ppg/glob_functions/jsonlab/examples/.svn/entries +0 -368
  40. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_jsonlab_basic.m.svn-base +0 -180
  41. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_ubjson_basic.m.svn-base +0 -180
  42. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example1.json.svn-base +0 -23
  43. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example2.json.svn-base +0 -22
  44. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example3.json.svn-base +0 -11
  45. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example4.json.svn-base +0 -34
  46. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_basictest.matlab.svn-base +0 -662
  47. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.m.svn-base +0 -27
  48. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.matlab.svn-base +0 -144
  49. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_speedtest.m.svn-base +0 -21
  50. paradigma/ppg/glob_functions/jsonlab/examples/demo_jsonlab_basic.m +0 -180
  51. paradigma/ppg/glob_functions/jsonlab/examples/demo_ubjson_basic.m +0 -180
  52. paradigma/ppg/glob_functions/jsonlab/examples/example1.json +0 -23
  53. paradigma/ppg/glob_functions/jsonlab/examples/example2.json +0 -22
  54. paradigma/ppg/glob_functions/jsonlab/examples/example3.json +0 -11
  55. paradigma/ppg/glob_functions/jsonlab/examples/example4.json +0 -34
  56. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_basictest.matlab +0 -662
  57. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.m +0 -27
  58. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.matlab +0 -144
  59. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_speedtest.m +0 -21
  60. paradigma/ppg/glob_functions/jsonlab/jsonopt.m +0 -32
  61. paradigma/ppg/glob_functions/jsonlab/loadjson.m +0 -566
  62. paradigma/ppg/glob_functions/jsonlab/loadubjson.m +0 -528
  63. paradigma/ppg/glob_functions/jsonlab/mergestruct.m +0 -33
  64. paradigma/ppg/glob_functions/jsonlab/savejson.m +0 -475
  65. paradigma/ppg/glob_functions/jsonlab/saveubjson.m +0 -504
  66. paradigma/ppg/glob_functions/jsonlab/varargin2struct.m +0 -40
  67. paradigma/ppg/glob_functions/sample_prob_final.m +0 -49
  68. paradigma/ppg/glob_functions/synchronization.m +0 -76
  69. paradigma/ppg/glob_functions/tsdf_scan_meta.m +0 -22
  70. paradigma/ppg/hr_functions/Long_TFD_JOT.m +0 -37
  71. paradigma/ppg/hr_functions/PPG_TFD_HR.m +0 -59
  72. paradigma/ppg/hr_functions/TFD toolbox JOT/.gitignore +0 -4
  73. paradigma/ppg/hr_functions/TFD toolbox JOT/CHANGELOG.md +0 -23
  74. paradigma/ppg/hr_functions/TFD toolbox JOT/LICENCE.md +0 -27
  75. paradigma/ppg/hr_functions/TFD toolbox JOT/README.md +0 -251
  76. paradigma/ppg/hr_functions/TFD toolbox JOT/README.pdf +0 -0
  77. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_kern.m +0 -142
  78. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_lag_kern.m +0 -314
  79. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_lag_kern.m +0 -123
  80. paradigma/ppg/hr_functions/TFD toolbox JOT/dec_tfd.m +0 -154
  81. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_di_gdtfd.m +0 -194
  82. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_li_gdtfd.m +0 -200
  83. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_nonsep_gdtfd.m +0 -229
  84. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_sep_gdtfd.m +0 -241
  85. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/di_gdtfd.m +0 -157
  86. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/li_gdtfd.m +0 -190
  87. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/nonsep_gdtfd.m +0 -196
  88. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/sep_gdtfd.m +0 -199
  89. paradigma/ppg/hr_functions/TFD toolbox JOT/full_tfd.m +0 -144
  90. paradigma/ppg/hr_functions/TFD toolbox JOT/load_curdir.m +0 -13
  91. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/decimated_TFDs_examples.png +0 -0
  92. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/full_TFDs_examples.png +0 -0
  93. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/check_dec_params_seq.m +0 -79
  94. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispEE.m +0 -9
  95. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispVars.m +0 -26
  96. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/disp_bytes.m +0 -25
  97. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_full.m +0 -40
  98. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_half.m +0 -34
  99. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/gen_LFM.m +0 -29
  100. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_analytic_signal.m +0 -76
  101. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_window.m +0 -176
  102. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/isreal_fn.m +0 -11
  103. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/padWin.m +0 -97
  104. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/vtfd.m +0 -149
  105. paradigma/ppg/preprocessing/preprocessing_imu.m +0 -15
  106. paradigma/ppg/preprocessing/preprocessing_ppg.m +0 -13
  107. paradigma/ppg_preprocessing.py +0 -313
  108. paradigma/preprocessing_config.py +0 -69
  109. paradigma/quantification.py +0 -58
  110. paradigma/tremor/TremorFeaturesAndClassification.m +0 -345
  111. paradigma/tremor/feat_extraction/DerivativesExtract.m +0 -22
  112. paradigma/tremor/feat_extraction/ExtractBandSignalsRMS.m +0 -72
  113. paradigma/tremor/feat_extraction/MFCCExtract.m +0 -100
  114. paradigma/tremor/feat_extraction/PSDBandPower.m +0 -52
  115. paradigma/tremor/feat_extraction/PSDEst.m +0 -63
  116. paradigma/tremor/feat_extraction/PSDExtrAxis.m +0 -88
  117. paradigma/tremor/feat_extraction/PSDExtrOpt.m +0 -95
  118. paradigma/tremor/preprocessing/InterpData.m +0 -32
  119. paradigma/tremor/weekly_aggregates/WeeklyAggregates.m +0 -295
  120. paradigma/windowing.py +0 -219
  121. paradigma-0.3.2.dist-info/METADATA +0 -79
  122. paradigma-0.3.2.dist-info/RECORD +0 -108
  123. {paradigma-0.3.2.dist-info → paradigma-0.4.1.dist-info}/LICENSE +0 -0
paradigma/util.py CHANGED
@@ -1,50 +1,427 @@
1
+ import json
1
2
  import os
2
3
  import numpy as np
3
4
  import pandas as pd
5
+ from pathlib import Path
4
6
  from datetime import timedelta
5
7
  from dateutil import parser
6
- from typing import Tuple
8
+ from typing import List, Tuple
7
9
 
8
10
  import tsdf
9
11
  from tsdf import TSDFMetadata
10
12
 
13
+ from paradigma.constants import DataColumns, TimeUnit
14
+
15
+
11
16
  def parse_iso8601_to_datetime(date_str):
12
17
  return parser.parse(date_str)
13
18
 
19
+
14
20
  def format_datetime_to_iso8601(datetime):
15
- return datetime.strftime('%Y-%m-%dT%H:%M:%S') + 'Z'
21
+ return datetime.strftime("%Y-%m-%dT%H:%M:%S") + "Z"
22
+
16
23
 
17
24
  def get_end_iso8601(start_iso8601, window_length_seconds):
18
25
  start_date = parser.parse(start_iso8601)
19
26
  end_date = start_date + timedelta(seconds=window_length_seconds)
20
27
  return format_datetime_to_iso8601(end_date)
21
28
 
22
- def write_data(metadata_time: TSDFMetadata, metadata_samples: TSDFMetadata,
23
- output_path: str, output_filename: str, df: pd.DataFrame):
29
+
30
+ def write_np_data(
31
+ metadata_time: TSDFMetadata,
32
+ np_array_time: np.ndarray,
33
+ metadata_values: TSDFMetadata,
34
+ np_array_values: np.ndarray,
35
+ output_path: str,
36
+ output_filename: str,
37
+ ):
38
+ """
39
+ Write the numpy arrays to binary files and store the metadata.
40
+
41
+ Parameters
42
+ ----------
43
+ metadata_time : TSDFMetadata
44
+ Metadata for the time column.
45
+ np_array_time : np.ndarray
46
+ The numpy array for the time column.
47
+ metadata_values : TSDFMetadata
48
+ Metadata for the samples columns.
49
+ np_array_values : np.ndarray
50
+ The numpy array for the samples columns.
51
+ output_path : str
52
+ The path where the files will be stored.
53
+ output_filename : str
54
+ The filename for the metadata.
55
+
56
+ """
57
+
58
+ if not os.path.exists(output_path):
59
+ os.makedirs(output_path)
60
+
61
+ # TODO: improve the way the metadata is stored at a different location
62
+ metadata_time.file_dir_path = output_path
63
+ metadata_values.file_dir_path = output_path
64
+
65
+ # store binaries and metadata
66
+ time_tsdf = tsdf.write_binary_file(file_dir=output_path, file_name=metadata_time.file_name, data=np_array_time, metadata=metadata_time.get_plain_tsdf_dict_copy())
67
+
68
+ samples_tsdf = tsdf.write_binary_file(file_dir=output_path, file_name=metadata_values.file_name, data=np_array_values, metadata=metadata_values.get_plain_tsdf_dict_copy())
69
+
70
+ tsdf.write_metadata([time_tsdf, samples_tsdf], output_filename)
71
+
72
+
73
+ def write_df_data(
74
+ metadata_time: TSDFMetadata,
75
+ metadata_values: TSDFMetadata,
76
+ output_path: str,
77
+ output_filename: str,
78
+ df: pd.DataFrame,
79
+ ):
80
+ """
81
+ Write the Pandas DataFrame to binary files and store the metadata.
82
+
83
+ Parameters
84
+ ----------
85
+ metadata_time : TSDFMetadata
86
+ Metadata for the time column.
87
+ metadata_values : TSDFMetadata
88
+ Metadata for the samples columns.
89
+ output_path : str
90
+ The path where the files will be stored.
91
+ output_filename : str
92
+ The filename for the metadata.
93
+ df : pd.DataFrame
94
+ The DataFrame to be stored.
95
+
96
+ """
24
97
  if not os.path.exists(output_path):
25
98
  os.makedirs(output_path)
26
99
 
27
100
  # Make sure the iso8601 format is correctly set
28
- #TODO: this should be properly validated in the tsdf library instead
101
+ # TODO: this should be properly validated in the tsdf library instead
29
102
  start_date = parser.parse(metadata_time.start_iso8601)
30
103
  metadata_time.start_iso8601 = format_datetime_to_iso8601(start_date)
31
104
  end_date = parser.parse(metadata_time.end_iso8601)
32
105
  metadata_time.end_iso8601 = format_datetime_to_iso8601(end_date)
33
- start_date = parser.parse(metadata_samples.start_iso8601)
34
- metadata_samples.start_iso8601 = format_datetime_to_iso8601(start_date)
35
- end_date = parser.parse(metadata_samples.end_iso8601)
36
- metadata_samples.end_iso8601 = format_datetime_to_iso8601(end_date)
106
+ start_date = parser.parse(metadata_values.start_iso8601)
107
+ metadata_values.start_iso8601 = format_datetime_to_iso8601(start_date)
108
+ end_date = parser.parse(metadata_values.end_iso8601)
109
+ metadata_values.end_iso8601 = format_datetime_to_iso8601(end_date)
37
110
 
38
111
  # TODO: improve the way the metadata is stored at a different location
39
112
  metadata_time.file_dir_path = output_path
40
- metadata_samples.file_dir_path = output_path
113
+ metadata_values.file_dir_path = output_path
41
114
 
42
115
  # store binaries and metadata
43
- tsdf.write_dataframe_to_binaries(output_path, df, [metadata_time, metadata_samples])
44
- tsdf.write_metadata([metadata_time, metadata_samples], output_filename)
116
+ tsdf.write_dataframe_to_binaries(output_path, df, [metadata_time, metadata_values])
117
+ tsdf.write_metadata([metadata_time, metadata_values], output_filename)
45
118
 
46
- def read_metadata(input_path: str, meta_filename: str, time_filename: str, values_filename: str) -> Tuple[TSDFMetadata, TSDFMetadata]:
47
- metadata_dict = tsdf.load_metadata_from_path(os.path.join(input_path, meta_filename))
119
+
120
+ def read_metadata(
121
+ input_path: str, meta_filename: str, time_filename: str, values_filename: str
122
+ ) -> Tuple[TSDFMetadata, TSDFMetadata]:
123
+ metadata_dict = tsdf.load_metadata_from_path(
124
+ os.path.join(input_path, meta_filename)
125
+ )
48
126
  metadata_time = metadata_dict[time_filename]
49
- metadata_samples = metadata_dict[values_filename]
50
- return metadata_time, metadata_samples
127
+ metadata_values = metadata_dict[values_filename]
128
+ return metadata_time, metadata_values
129
+
130
+
131
+ def load_tsdf_dataframe(path_to_data, prefix, meta_suffix='meta.json', time_suffix='time.bin', values_suffix='values.bin'):
132
+ meta_filename = f"{prefix}_{meta_suffix}"
133
+ time_filename = f"{prefix}_{time_suffix}"
134
+ values_filename = f"{prefix}_{values_suffix}"
135
+
136
+ metadata_time, metadata_values = read_metadata(path_to_data, meta_filename, time_filename, values_filename)
137
+ df = tsdf.load_dataframe_from_binaries([metadata_time, metadata_values], tsdf.constants.ConcatenationType.columns)
138
+
139
+ return df, metadata_time, metadata_values
140
+
141
+
142
+ def load_metadata_list(
143
+ dir_path: str, meta_filename: str, filenames: List[str]
144
+ ) -> List[TSDFMetadata]:
145
+ """
146
+ Load the metadata objects from a metadata file according to the specified binaries.
147
+
148
+ Parameters
149
+ ----------
150
+ dir_path : str
151
+ The dir path where the metadata file is stored.
152
+ meta_filename : str
153
+ The filename of the metadata file.
154
+ filenames : List[str]
155
+ The list of binary files of which the metadata files need to be loaded
156
+
157
+ """
158
+ metadata_dict = tsdf.load_metadata_from_path(
159
+ os.path.join(dir_path, meta_filename)
160
+ )
161
+ metadata_list = []
162
+ for filename in filenames:
163
+ metadata_list.append(metadata_dict[filename])
164
+
165
+ return metadata_list
166
+
167
+
168
+ def transform_time_array(
169
+ time_array: pd.Series,
170
+ input_unit_type: str,
171
+ output_unit_type: str,
172
+ start_time: float = 0.0,
173
+ ) -> np.ndarray:
174
+ """
175
+ Transforms the time array to relative time (when defined in delta time) and scales the values.
176
+
177
+ Parameters
178
+ ----------
179
+ time_array : pd.Series
180
+ The time array to transform.
181
+ input_unit_type : str
182
+ The time unit type of the input time array.
183
+ output_unit_type : str
184
+ The time unit type of the output time array. ParaDigMa expects `TimeUnit.RELATIVE_S`.
185
+ start_time : float, optional
186
+ The start time of the time array in UNIX seconds (default is 0.0)
187
+
188
+ Returns
189
+ -------
190
+ np.ndarray
191
+ The transformed time array in seconds, with the specified time unit type.
192
+
193
+ Notes
194
+ -----
195
+ - The function handles different time units (`TimeUnit.RELATIVE_MS`, `TimeUnit.RELATIVE_S`, `TimeUnit.ABSOLUTE_MS`, `TimeUnit.ABSOLUTE_S`, `TimeUnit.DIFFERENCE_MS`, `TimeUnit.DIFFERENCE_S`).
196
+ - The transformation allows for scaling of the time array, converting between time unit types (e.g., relative, absolute, or difference).
197
+ - When converting to `TimeUnit.RELATIVE_MS`, the function calculates the relative time starting from the provided or default start time.
198
+ """
199
+ input_units = input_unit_type.split('_')[-1].lower()
200
+ output_units = output_unit_type.split('_')[-1].lower()
201
+
202
+ if input_units == output_units:
203
+ scale_factor = 1
204
+ elif input_units == 's' and output_units == 'ms':
205
+ scale_factor = 1e3
206
+ elif input_units == 'ms' and output_units == 's':
207
+ scale_factor = 1 / 1e3
208
+ else:
209
+ raise ValueError(f"Unsupported time units conversion: {input_units} to {output_units}")
210
+
211
+ # Transform to relative time (`TimeUnit.RELATIVE_MS`)
212
+ if input_unit_type == TimeUnit.DIFFERENCE_MS or input_unit_type == TimeUnit.DIFFERENCE_S:
213
+ # Convert a series of differences into cumulative sum to reconstruct original time series.
214
+ time_array = np.cumsum(np.double(time_array))
215
+ elif input_unit_type == TimeUnit.ABSOLUTE_MS or input_unit_type == TimeUnit.ABSOLUTE_S:
216
+ # Set the start time if not provided.
217
+ if np.isclose(start_time, 0.0, rtol=1e-09, atol=1e-09):
218
+ start_time = time_array[0]
219
+ # Convert absolute time stamps into a time series relative to start_time.
220
+ time_array = (time_array - start_time)
221
+
222
+ # Transform the time array from `TimeUnit.RELATIVE_MS` to the specified time unit type
223
+ if output_unit_type == TimeUnit.ABSOLUTE_MS or output_unit_type == TimeUnit.ABSOLUTE_S:
224
+ # Converts time array to absolute time by adding the start time to each element.
225
+ time_array = time_array + start_time
226
+ elif output_unit_type == TimeUnit.DIFFERENCE_MS or output_unit_type == TimeUnit.DIFFERENCE_S:
227
+ # Creates a new array starting with 0, followed by the differences between consecutive elements.
228
+ time_array = np.diff(np.insert(time_array, 0, start_time))
229
+ elif output_unit_type == TimeUnit.RELATIVE_MS or output_unit_type == TimeUnit.RELATIVE_S:
230
+ # The array is already in relative format, do nothing.
231
+ pass
232
+
233
+ return time_array * scale_factor
234
+
235
+
236
+ def convert_units_accelerometer(data: np.ndarray, units: str) -> np.ndarray:
237
+ """
238
+ Convert acceleration data to g.
239
+
240
+ Parameters
241
+ ----------
242
+ data : np.ndarray
243
+ The acceleration data.
244
+
245
+ units : str
246
+ The unit of the data (currently supports g and m/s^2).
247
+
248
+ Returns
249
+ -------
250
+ np.ndarray
251
+ The acceleration data in g.
252
+
253
+ """
254
+ if units == "m/s^2":
255
+ return data / 9.81
256
+ elif units == "g":
257
+ return data
258
+ else:
259
+ raise ValueError(f"Unsupported unit: {units}")
260
+
261
+
262
+ def convert_units_gyroscope(data: np.ndarray, units: str) -> np.ndarray:
263
+ """
264
+ Convert gyroscope data to deg/s.
265
+
266
+ Parameters
267
+ ----------
268
+ data : np.ndarray
269
+ The gyroscope data.
270
+
271
+ units : str
272
+ The unit of the data (currently supports deg/s and rad/s).
273
+
274
+ Returns
275
+ -------
276
+ np.ndarray
277
+ The gyroscope data in deg/s.
278
+
279
+ """
280
+ if units == "deg/s":
281
+ return data
282
+ elif units == "rad/s":
283
+ return np.degrees(data)
284
+ else:
285
+ raise ValueError(f"Unsupported unit: {units}")
286
+
287
+
288
+ def invert_watch_side(df: pd.DataFrame, side: str) -> np.ndarray:
289
+ """
290
+ Invert the data based on the watch side.
291
+
292
+ Parameters
293
+ ----------
294
+ df : pd.DataFrame
295
+ The data.
296
+ side : str
297
+ The watch side (left or right).
298
+
299
+ Returns
300
+ -------
301
+ pd.DataFrame
302
+ The inverted data.
303
+
304
+ """
305
+ if side not in ["left", "right"]:
306
+ raise ValueError(f"Unsupported side: {side}")
307
+ elif side == "right":
308
+ df[DataColumns.GYROSCOPE_Y] *= -1
309
+ df[DataColumns.GYROSCOPE_Z] *= -1
310
+ df[DataColumns.ACCELEROMETER_X] *= -1
311
+
312
+ return df
313
+
314
+ def aggregate_parameter(parameter: np.ndarray, aggregate: str) -> np.ndarray:
315
+ """
316
+ Aggregate a parameter based on the specified method.
317
+
318
+ Parameters
319
+ ----------
320
+ parameter : np.ndarray
321
+ The parameter to aggregate.
322
+
323
+ aggregate : str
324
+ The aggregation method to apply.
325
+
326
+ Returns
327
+ -------
328
+ np.ndarray
329
+ The aggregated parameter.
330
+ """
331
+ if aggregate == 'mean':
332
+ return np.mean(parameter)
333
+ elif aggregate == 'median':
334
+ return np.median(parameter)
335
+ elif aggregate == 'mode':
336
+ unique_values, counts = np.unique(parameter, return_counts=True)
337
+ return unique_values[np.argmax(counts)]
338
+ elif aggregate == '90p':
339
+ return np.percentile(parameter, 90)
340
+ elif aggregate == '95p':
341
+ return np.percentile(parameter, 95)
342
+ elif aggregate == '99p':
343
+ return np.percentile(parameter, 99)
344
+ elif aggregate == 'std':
345
+ return np.std(parameter)
346
+ else:
347
+ raise ValueError(f"Invalid aggregation method: {aggregate}")
348
+
349
+ def merge_predictions_with_timestamps(
350
+ df_ts: pd.DataFrame,
351
+ df_predictions: pd.DataFrame,
352
+ pred_proba_colname: str,
353
+ window_length_s: float,
354
+ fs: int
355
+ ) -> pd.DataFrame:
356
+ """
357
+ Merges prediction probabilities with timestamps by expanding overlapping windows
358
+ into individual timestamps and averaging probabilities per unique timestamp.
359
+
360
+ Parameters:
361
+ ----------
362
+ df_ts : pd.DataFrame
363
+ DataFrame containing timestamps to be merged with predictions.
364
+ Must include the timestamp column specified in `DataColumns.TIME`.
365
+
366
+ df_predictions : pd.DataFrame
367
+ DataFrame containing prediction windows with start times and probabilities.
368
+ Must include:
369
+ - A column for window start times (defined by `DataColumns.TIME`).
370
+ - A column for prediction probabilities (defined by `DataColumns.PRED_GAIT_PROBA`).
371
+
372
+ pred_proba_colname : str
373
+ The column name for the prediction probabilities in `df_predictions`.
374
+
375
+ window_length_s : float
376
+ The length of the prediction window in seconds.
377
+
378
+ fs : int
379
+ The sampling frequency of the data.
380
+
381
+ Returns:
382
+ -------
383
+ pd.DataFrame
384
+ Updated `df_ts` with an additional column for averaged prediction probabilities.
385
+
386
+ Steps:
387
+ ------
388
+ 1. Expand prediction windows into individual timestamps using NumPy broadcasting.
389
+ 2. Flatten the timestamps and prediction probabilities into single arrays.
390
+ 3. Aggregate probabilities by unique timestamps using pandas `groupby`.
391
+ 4. Merge the aggregated probabilities with the input `df_ts`.
392
+
393
+ Notes:
394
+ ------
395
+ - Rounding is applied to timestamps to mitigate floating-point inaccuracies.
396
+ - Fully vectorized for speed and scalability, avoiding any row-wise operations.
397
+ """
398
+ # Step 1: Generate all timestamps for prediction windows using NumPy broadcasting
399
+ window_length = int(window_length_s * fs)
400
+ timestamps = (
401
+ df_predictions[DataColumns.TIME].values[:, None] +
402
+ np.arange(0, window_length) / fs
403
+ )
404
+
405
+ # Flatten timestamps and probabilities into a single array for efficient processing
406
+ flat_timestamps = timestamps.ravel()
407
+ flat_proba = np.repeat(
408
+ df_predictions[pred_proba_colname].values,
409
+ window_length
410
+ )
411
+
412
+ # Step 2: Create a DataFrame for expanded data
413
+ expanded_df = pd.DataFrame({
414
+ DataColumns.TIME: flat_timestamps,
415
+ pred_proba_colname: flat_proba
416
+ })
417
+
418
+ # Step 3: Round timestamps and aggregate probabilities
419
+ expanded_df[DataColumns.TIME] = expanded_df[DataColumns.TIME].round(2)
420
+ mean_proba = expanded_df.groupby(DataColumns.TIME, as_index=False).mean()
421
+
422
+ # Step 4: Round timestamps in `df_ts` and merge
423
+ df_ts[DataColumns.TIME] = df_ts[DataColumns.TIME].round(2)
424
+ df_ts = pd.merge(df_ts, mean_proba, how='left', on=DataColumns.TIME)
425
+ df_ts = df_ts.dropna(subset=[pred_proba_colname])
426
+
427
+ return df_ts
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.3
2
+ Name: paradigma
3
+ Version: 0.4.1
4
+ Summary: Paradigma - a toolbox for Digital Biomarkers for Parkinson's Disease
5
+ License: Apache-2.0
6
+ Author: Erik Post
7
+ Author-email: erik.post@radboudumc.nl
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: pandas (>=2.1.4,<3.0.0)
15
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
16
+ Requires-Dist: pytype (>=2024.4.11,<2025.0.0)
17
+ Requires-Dist: scikit-learn (>=1.3.2,<1.6.1)
18
+ Requires-Dist: tsdf (>=0.5.2,<0.6.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ <p align="center">
22
+ <img src="https://raw.githubusercontent.com/biomarkersParkinson/paradigma/main/docs/source/_static/img/paradigma-logo-banner.png" alt="ParaDigMa logo"/>
23
+ </p>
24
+
25
+ | Badges | |
26
+ |:----:|----|
27
+ | **Packages and Releases** | [![Latest release](https://img.shields.io/github/release/biomarkersparkinson/paradigma.svg)](https://github.com/biomarkersparkinson/paradigma/releases/latest) [![PyPI](https://img.shields.io/pypi/v/paradigma.svg)](https://pypi.python.org/pypi/paradigma/) [![Static Badge](https://img.shields.io/badge/RSD-paradigma-lib)](https://research-software-directory.org/software/paradigma) |
28
+ | **DOI** | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13838392.svg)](https://doi.org/10.5281/zenodo.13838392) |
29
+ | **Build Status** | [![](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![Build and test](https://github.com/biomarkersParkinson/paradigma/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/biomarkersParkinson/paradigma/actions/workflows/build-and-test.yml) [![pages-build-deployment](https://github.com/biomarkersParkinson/paradigma/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/biomarkersParkinson/paradigma/actions/workflows/pages/pages-build-deployment) |
30
+ | **License** | [![GitHub license](https://img.shields.io/github/license/biomarkersParkinson/paradigma)](https://github.com/biomarkersparkinson/paradigma/blob/main/LICENSE) |
31
+ <!-- | **Fairness** | [![fair-software.eu](https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F-green)](https://fair-software.eu) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/8083/badge)](https://www.bestpractices.dev/projects/8083) | -->
32
+
33
+ ## Introduction
34
+ The Parkinsons Disease Digital Markers (ParaDigMa) toolbox is a Python
35
+ software package designed for processing passively collected wrist
36
+ sensor data to extract digital measures of motor and non-motor signs
37
+ of Parkinson's disease (PD).
38
+
39
+ Specifically, the toolbox contains three data processing pipelines:
40
+ (1) arm swing during gait, (2) tremor, and (3) heart rate analysis.
41
+ Furthermore, the toolbox contains general functionalities for signal
42
+ processing and feature extraction, such as filtering, peak detection,
43
+ and spectral analysis. The toolbox is designed to be user-friendly and
44
+ modular, enabling researchers to easily extend the toolbox with new
45
+ algorithms and functionalities. The toolbox is accompanied by a set of
46
+ example scripts and notebooks for each domain that demonstrate how to use
47
+ the toolbox for processing sensor data and extracting digital measures.
48
+
49
+ It contains functionalities for processing the following sensor types:
50
+
51
+ - Inertial Measurement Units (accelerometer, gyroscope)
52
+ - Photoplethysmogram (PPG)
53
+
54
+ ## More about ParaDigMa
55
+ The components of ParaDigMa are visually shown in the diagram below.
56
+
57
+ <p align="center">
58
+ <img src="https://raw.githubusercontent.com/biomarkersParkinson/paradigma/main/docs/source/_static/img/pipeline-architecture.png" alt="Pipeline architeecture"/>
59
+ </p>
60
+
61
+ #### Processes
62
+ ParaDigMa can best be understood by categorizing the sequential processes:
63
+
64
+ | Process | Description |
65
+ | ---- | ---- |
66
+ | Preprocessing | Ensuring that the sensor data is ready for further processing |
67
+ | Feature extraction | Creating features based on windowed views of the timestamps |
68
+ | Classification | Making predictions using developed and validated classifiers |
69
+ | Quantification | Selecting specific features of interest |
70
+ | Aggregation | Aggregating the features at a specified time-level |
71
+
72
+ #### Domain requirements
73
+ ParaDigMa can be used to extract aggregations related to a single or multiple domain(s). Each domain has its specific data requirements. Strict requirements for the domain are marked by X, soft requirements (for some additional functionalities) are marked by O.
74
+
75
+ | | Gait | Tremor | Heart Rate |
76
+ |----------|:-----------:|:-----------:|:-----------:|
77
+ | **Accelerometer** | X | | O |
78
+ | **Gyroscope** | X | X | |
79
+ | **PPG** | | | X |
80
+
81
+
82
+
83
+ ## Installation
84
+
85
+ The package is available in PyPi and requires [Python 3.10](https://www.python.org/downloads/) or higher. It can be installed using:
86
+
87
+ ```bash
88
+ pip install paradigma
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ See our [extended documentation](https://biomarkersparkinson.github.io/paradigma/).
94
+
95
+ ## Development
96
+
97
+ ### Installation
98
+
99
+ The package requires Python 3.11 or higher. Use [Poetry](https://python-poetry.org/docs/#installation) to set up the environment and install the dependencies:
100
+
101
+ ```bash
102
+ poetry install
103
+ ```
104
+
105
+ ### Testing
106
+
107
+ ```bash
108
+ poetry run pytest
109
+ ```
110
+
111
+ ### Type checking
112
+
113
+ ```bash
114
+ poetry run pytype .
115
+ ```
116
+
117
+ ### Building documentation
118
+
119
+ ```bash
120
+ poetry run make html --directory docs/
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ Interested in contributing? Check out the contributing guidelines. Please note that this project is released with a Code of Conduct. By contributing to this project, you agree to abide by its terms.
126
+
127
+ ## License
128
+
129
+ The core team of ParaDigMa consists of Erik Post, Kars Veldkamp, Nienke Timmermans, Diogo Coutinho Soriano, Luc Evers,
130
+ Peter Kok and Vedran Kasalica. Advisors to the project are Max Little, Jordan Raykov, Twan van Laarhoven, Hayriye Cagnan, and Bas Bloem. It is licensed under the terms of the Apache License 2.0 license.
131
+
132
+ ## Credits
133
+
134
+ ParaDigMa was created with [`cookiecutter`](https://cookiecutter.readthedocs.io/en/latest/) and the `py-pkgs-cookiecutter` [template](https://github.com/py-pkgs/py-pkgs-cookiecutter).
135
+
136
+ ## Contact
137
+
138
+ For more information or questions about ParaDigMa, please reach out to erik.post@radboudumc.nl.
@@ -0,0 +1,22 @@
1
+ paradigma/__init__.py,sha256=vCLqo7vOEgcnYs10gUVYvEFfi8y-jBi7w1YKRoqn95k,127
2
+ paradigma/assets/gait_detection_clf_package.pkl,sha256=8jCbuM_4dkilSjOEk9ss7bJbSppgzXe72y0X4BCnzCU,11497247
3
+ paradigma/assets/gait_filtering_clf_package.pkl,sha256=lAaLyhmXdV4X_drmYt0EM6wGwSo80yhpxtncWGq4RfQ,3915
4
+ paradigma/assets/ppg_quality_clf_package.pkl,sha256=vUcM4v8gZwWAmDVK7E4UcHhVnhlEg27RSB71oPGloSc,1292
5
+ paradigma/assets/tremor_detection_clf_package.pkl,sha256=bZ7xrqTsCLg0CKBxVejgnnh8hnKSqNsf-bSQG7D2aqY,1613
6
+ paradigma/classification.py,sha256=sBJSePvwHZNPUQuLdx-pncfnDzMq-1naomsCxSJneWY,2921
7
+ paradigma/config.py,sha256=4F6FsY5HI6d-L_vB6zehyTAJK3X-U0F6IrLwxN9M-dw,11102
8
+ paradigma/constants.py,sha256=JlrD4Zx66g7myQALYAc4Gw_y6yW5EipZuvwj9_fjjpI,3543
9
+ paradigma/feature_extraction.py,sha256=pXfVoiuElrMzLCCZu1gj1uYT5v7vb3vULUjkxeJNWXQ,34566
10
+ paradigma/pipelines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ paradigma/pipelines/gait_pipeline.py,sha256=Go0iUmr82v4Vcv2_WBLp5BqRkxKAXImc2N1EHuFYp40,29058
12
+ paradigma/pipelines/heart_rate_pipeline.py,sha256=0-D9KcW9nwE5sgXsWHONkeKrsX6qZ5BYqjDttoffwL8,17726
13
+ paradigma/pipelines/heart_rate_utils.py,sha256=aV2mTMWrFWHZD0KpHqy3IIC1onZykbppyp7_OUWxFTU,26764
14
+ paradigma/pipelines/tremor_pipeline.py,sha256=hPqLf26MRperwlqpNgrwayByoP3aWwnWc-tQMSJNZGw,13314
15
+ paradigma/preprocessing.py,sha256=DQ-lgmta1tng0neuWftwmXG3yesW6dxOsYvjSV2OFRk,13498
16
+ paradigma/segmenting.py,sha256=Jrz2JQX5eSfR9jBfpBhc6QV0SFmPVT5O6T8MyL0sdSw,13874
17
+ paradigma/testing.py,sha256=DSbWeYl5HuZ-bNyOKwgwMHQGG8KlTabvGTR1Yzd-9CY,17955
18
+ paradigma/util.py,sha256=6G-z6OXv8T2PS8RFjkd6IfDywdzTjvSl1pfNwqnT8i0,14431
19
+ paradigma-0.4.1.dist-info/LICENSE,sha256=Lda8kIVC2kbmlSeYaUWwUwV75Q-q31idYvo18HUTfiw,9807
20
+ paradigma-0.4.1.dist-info/METADATA,sha256=ZEXlKrTQX67H7wM5acfEZIUFJZ9FOSuLVAq9yf6DK_A,6416
21
+ paradigma-0.4.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
22
+ paradigma-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any