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
paradigma/segmenting.py
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from typing import List
|
|
5
|
+
from paradigma.constants import DataColumns
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
def tabulate_windows(
|
|
10
|
+
df: pd.DataFrame,
|
|
11
|
+
columns: List[str],
|
|
12
|
+
window_length_s: float,
|
|
13
|
+
window_step_length_s: float,
|
|
14
|
+
fs: int,
|
|
15
|
+
) -> np.ndarray:
|
|
16
|
+
"""
|
|
17
|
+
Split the given DataFrame into overlapping windows of specified length and step size.
|
|
18
|
+
|
|
19
|
+
This function extracts windows of data from the specified columns of the DataFrame, based on
|
|
20
|
+
the window length and step size provided in the configuration. The windows are returned in
|
|
21
|
+
a 3D NumPy array, where the first dimension represents the window index, the second dimension
|
|
22
|
+
represents the time steps within the window, and the third dimension represents the columns
|
|
23
|
+
of the data.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
df : pd.DataFrame
|
|
28
|
+
The input DataFrame containing the data to be windowed.
|
|
29
|
+
columns : list of str
|
|
30
|
+
A list of column names from the DataFrame that will be used for windowing.
|
|
31
|
+
window_length_s : float
|
|
32
|
+
The length of each window in seconds.
|
|
33
|
+
window_step_length_s : float
|
|
34
|
+
The step size between consecutive windows in seconds.
|
|
35
|
+
fs : int
|
|
36
|
+
The sampling frequency of the data in Hz.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
np.ndarray
|
|
41
|
+
A 3D NumPy array of shape (n_windows, window_size, n_columns), where:
|
|
42
|
+
- `n_windows` is the number of windows that can be formed from the data.
|
|
43
|
+
- `window_size` is the length of each window in terms of the number of time steps.
|
|
44
|
+
- `n_columns` is the number of columns in the input DataFrame specified by `columns`.
|
|
45
|
+
|
|
46
|
+
If the length of the data is shorter than the specified window size, an empty array is returned.
|
|
47
|
+
|
|
48
|
+
Notes
|
|
49
|
+
-----
|
|
50
|
+
This function uses `np.lib.stride_tricks.sliding_window_view` to generate sliding windows of data.
|
|
51
|
+
The step size is applied to extract windows at intervals.
|
|
52
|
+
If the data is insufficient for at least one window, an empty array will be returned.
|
|
53
|
+
|
|
54
|
+
Example
|
|
55
|
+
-------
|
|
56
|
+
config = Config(window_length_s=5, window_step_length_s=1, sampling_frequency=100)
|
|
57
|
+
df = pd.DataFrame({'col1': np.random.randn(100), 'col2': np.random.randn(100)})
|
|
58
|
+
columns = ['col1', 'col2']
|
|
59
|
+
windows = tabulate_windows(config, df, columns)
|
|
60
|
+
"""
|
|
61
|
+
window_size = int(window_length_s * fs)
|
|
62
|
+
window_step_size = int(window_step_length_s * fs)
|
|
63
|
+
n_columns = len(columns)
|
|
64
|
+
|
|
65
|
+
data = df[columns].values
|
|
66
|
+
|
|
67
|
+
# Check if data length is sufficient
|
|
68
|
+
if len(data) < window_size:
|
|
69
|
+
return np.empty((0, window_size, n_columns)) # Return an empty array if insufficient data
|
|
70
|
+
|
|
71
|
+
windows = np.lib.stride_tricks.sliding_window_view(
|
|
72
|
+
data, window_shape=(window_size, n_columns)
|
|
73
|
+
)[::window_step_size].squeeze()
|
|
74
|
+
|
|
75
|
+
# Ensure 3D shape (n_windows, window_size, n_columns)
|
|
76
|
+
if windows.ndim == 2: # Single window case
|
|
77
|
+
windows = windows[np.newaxis, :, :] # Add a new axis at the start
|
|
78
|
+
|
|
79
|
+
return windows
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def tabulate_windows_legacy(config, df, agg_func='first'):
|
|
83
|
+
"""
|
|
84
|
+
Efficiently creates a windowed dataframe from the input dataframe using vectorized operations.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
df: The input dataframe, where each row represents a timestamp (0.01 sec).
|
|
88
|
+
window_length_s: The number of seconds per window.
|
|
89
|
+
window_step_length_s: The number of seconds to shift between windows.
|
|
90
|
+
single_value_cols: List of columns where a single value (e.g., mean) is needed.
|
|
91
|
+
list_value_cols: List of columns where all 600 values should be stored in a list.
|
|
92
|
+
agg_func: Aggregation function for single-value columns (e.g., 'mean', 'first').
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The windowed dataframe.
|
|
96
|
+
"""
|
|
97
|
+
# If single_value_cols or list_value_cols is None, default to an empty list
|
|
98
|
+
if config.single_value_cols is None:
|
|
99
|
+
config.single_value_cols = []
|
|
100
|
+
if config.list_value_cols is None:
|
|
101
|
+
config.list_value_cols = []
|
|
102
|
+
|
|
103
|
+
window_length = int(config.window_length_s * config.sampling_frequency)
|
|
104
|
+
window_step_size = int(config.window_step_length_s * config.sampling_frequency)
|
|
105
|
+
|
|
106
|
+
n_rows = len(df)
|
|
107
|
+
if window_length > n_rows:
|
|
108
|
+
raise ValueError(f"Window size ({window_length}) cannot be greater than the number of rows ({n_rows}) in the dataframe.")
|
|
109
|
+
|
|
110
|
+
# Create indices for window start positions
|
|
111
|
+
window_starts = np.arange(0, n_rows - window_length + 1, window_step_size)
|
|
112
|
+
|
|
113
|
+
# Prepare the result for the final DataFrame
|
|
114
|
+
result = []
|
|
115
|
+
|
|
116
|
+
# Handle single value columns with vectorized operations
|
|
117
|
+
agg_func_map = {
|
|
118
|
+
'mean': np.mean,
|
|
119
|
+
'first': lambda x: x[0],
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Check if agg_func is a callable (custom function) or get the function from the map
|
|
123
|
+
if callable(agg_func):
|
|
124
|
+
agg_func_np = agg_func
|
|
125
|
+
else:
|
|
126
|
+
agg_func_np = agg_func_map.get(agg_func, agg_func_map['mean']) # Default to 'mean' if agg_func is not recognized
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
for window_nr, start in enumerate(window_starts, 1):
|
|
130
|
+
end = start + window_length
|
|
131
|
+
window = df.iloc[start:end]
|
|
132
|
+
|
|
133
|
+
agg_data = {
|
|
134
|
+
'window_nr': window_nr,
|
|
135
|
+
'window_start': window[DataColumns.TIME].iloc[0],
|
|
136
|
+
'window_end': window[DataColumns.TIME].iloc[-1],
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Aggregate single-value columns
|
|
140
|
+
for col in config.single_value_cols:
|
|
141
|
+
if col in window.columns: # Only process columns that exist in the window
|
|
142
|
+
agg_data[col] = agg_func_np(window[col].values)
|
|
143
|
+
|
|
144
|
+
# Collect list-value columns efficiently using numpy slicing
|
|
145
|
+
for col in config.list_value_cols:
|
|
146
|
+
if col in window.columns: # Only process columns that exist in the window
|
|
147
|
+
agg_data[col] = window[col].values.tolist()
|
|
148
|
+
|
|
149
|
+
result.append(agg_data)
|
|
150
|
+
|
|
151
|
+
# Convert result list into a DataFrame
|
|
152
|
+
windowed_df = pd.DataFrame(result)
|
|
153
|
+
|
|
154
|
+
# Ensure the column order is as desired: window_nr, window_start, window_end, pre_or_post, and then the rest
|
|
155
|
+
desired_order = ['window_nr', 'window_start', 'window_end'] + config.single_value_cols + config.list_value_cols
|
|
156
|
+
|
|
157
|
+
return windowed_df[desired_order]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def create_segments(
|
|
161
|
+
time_array: np.ndarray,
|
|
162
|
+
max_segment_gap_s: float,
|
|
163
|
+
):
|
|
164
|
+
# Calculate the difference between consecutive time values
|
|
165
|
+
time_diff = np.diff(time_array, prepend=0.0)
|
|
166
|
+
|
|
167
|
+
# Create a boolean mask for where the gap exceeds the threshold
|
|
168
|
+
gap_exceeds = time_diff > max_segment_gap_s
|
|
169
|
+
|
|
170
|
+
# Create the segment number based on the cumulative sum of the gap_exceeds mask
|
|
171
|
+
segments = gap_exceeds.cumsum() + 1 # +1 to start enumeration from 1
|
|
172
|
+
|
|
173
|
+
return segments
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def discard_segments(
|
|
177
|
+
df: pd.DataFrame,
|
|
178
|
+
segment_nr_colname: str,
|
|
179
|
+
min_segment_length_s: float,
|
|
180
|
+
fs: int,
|
|
181
|
+
format: str='timestamps'
|
|
182
|
+
) -> pd.DataFrame:
|
|
183
|
+
"""
|
|
184
|
+
Remove segments smaller than a specified size and reset segment enumeration.
|
|
185
|
+
|
|
186
|
+
This function filters out segments from the DataFrame that are smaller than a
|
|
187
|
+
given minimum size, based on the configuration. After removing small segments,
|
|
188
|
+
the segment numbers are reset to start from 1.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
config : object
|
|
193
|
+
A configuration object containing:
|
|
194
|
+
- `min_segment_length_s`: The minimum segment length in seconds.
|
|
195
|
+
- `sampling_frequency`: The sampling frequency in Hz.
|
|
196
|
+
df : pd.DataFrame
|
|
197
|
+
The input DataFrame containing a segment column and time series data.
|
|
198
|
+
format : str, optional
|
|
199
|
+
The format of the input data, either 'timestamps' or 'windows'.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
pd.DataFrame
|
|
204
|
+
A filtered DataFrame where small segments have been removed and segment
|
|
205
|
+
numbers have been reset to start from 1.
|
|
206
|
+
|
|
207
|
+
Example
|
|
208
|
+
-------
|
|
209
|
+
config = Config(min_segment_length_s=2, sampling_frequency=100, segment_nr_colname='segment')
|
|
210
|
+
df = pd.DataFrame({
|
|
211
|
+
'segment': [1, 1, 2, 2, 2],
|
|
212
|
+
'time': [0, 1, 2, 3, 4]
|
|
213
|
+
})
|
|
214
|
+
df_filtered = discard_segments(config, df)
|
|
215
|
+
# Result:
|
|
216
|
+
# segment time
|
|
217
|
+
# 0 1 0
|
|
218
|
+
# 1 1 1
|
|
219
|
+
# 2 2 2
|
|
220
|
+
# 3 2 3
|
|
221
|
+
# 4 2 4
|
|
222
|
+
"""
|
|
223
|
+
# Minimum segment size in number of samples
|
|
224
|
+
if format == 'timestamps':
|
|
225
|
+
min_samples = min_segment_length_s * fs
|
|
226
|
+
elif format == 'windows':
|
|
227
|
+
min_samples = min_segment_length_s
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError("Invalid format. Must be 'timestamps' or 'windows'.")
|
|
230
|
+
|
|
231
|
+
# Group by segment and filter out small segments in one step
|
|
232
|
+
valid_segment_mask = (
|
|
233
|
+
df.groupby(segment_nr_colname)[segment_nr_colname]
|
|
234
|
+
.transform('size') >= min_samples
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
df = df[valid_segment_mask].copy()
|
|
238
|
+
|
|
239
|
+
# Reset segment numbers in a single step
|
|
240
|
+
unique_segments = pd.factorize(df[segment_nr_colname])[0] + 1
|
|
241
|
+
df[segment_nr_colname] = unique_segments
|
|
242
|
+
|
|
243
|
+
return df
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def categorize_segments(df, fs, format='timestamps', window_step_length_s=None):
|
|
247
|
+
"""
|
|
248
|
+
Categorize segments based on their duration.
|
|
249
|
+
|
|
250
|
+
This function categorizes segments into four categories based on their duration
|
|
251
|
+
in seconds. The categories are defined as:
|
|
252
|
+
- Category 1: Segments shorter than 5 seconds
|
|
253
|
+
- Category 2: Segments between 5 and 10 seconds
|
|
254
|
+
- Category 3: Segments between 10 and 20 seconds
|
|
255
|
+
- Category 4: Segments longer than 20 seconds
|
|
256
|
+
|
|
257
|
+
The duration of each segment is calculated based on the sampling frequency and
|
|
258
|
+
the number of rows (data points) in the segment.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
df : pd.DataFrame
|
|
263
|
+
The input DataFrame containing the segment column with segment numbers.
|
|
264
|
+
config : object
|
|
265
|
+
A configuration object containing `sampling_frequency`.
|
|
266
|
+
format : str, optional
|
|
267
|
+
The format of the input data, either 'timestamps' or 'windows'.
|
|
268
|
+
|
|
269
|
+
Returns
|
|
270
|
+
-------
|
|
271
|
+
pd.Series
|
|
272
|
+
A Series containing the category for each segment:
|
|
273
|
+
- 'short' for segments < 5 seconds
|
|
274
|
+
- 'moderately_long' for segments between 5 and 10 seconds
|
|
275
|
+
- 'long' for segments between 10 and 20 seconds
|
|
276
|
+
- 'very_long' for segments > 20 seconds
|
|
277
|
+
"""
|
|
278
|
+
if format == 'windows' and window_step_length_s is None:
|
|
279
|
+
raise ValueError("Window step length must be provided for 'windows' format.")
|
|
280
|
+
|
|
281
|
+
# Define duration thresholds in seconds
|
|
282
|
+
d_max_duration = {
|
|
283
|
+
'short': 5,
|
|
284
|
+
'moderately_long': 10,
|
|
285
|
+
'long': 20
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Convert thresholds to rows if format is 'timestamps'
|
|
289
|
+
if format == 'timestamps':
|
|
290
|
+
d_max_duration = {k: v * fs for k, v in d_max_duration.items()}
|
|
291
|
+
|
|
292
|
+
# Count rows per segment
|
|
293
|
+
segment_sizes = df[DataColumns.SEGMENT_NR].value_counts()
|
|
294
|
+
|
|
295
|
+
# Convert segment sizes to duration in seconds
|
|
296
|
+
if format == 'windows':
|
|
297
|
+
segment_sizes *= window_step_length_s
|
|
298
|
+
|
|
299
|
+
# Group by the segment column and apply the categorization
|
|
300
|
+
def categorize(segment_size):
|
|
301
|
+
if segment_size < d_max_duration['short']:
|
|
302
|
+
return 'short'
|
|
303
|
+
elif segment_size < d_max_duration['moderately_long']:
|
|
304
|
+
return 'moderately_long'
|
|
305
|
+
elif segment_size < d_max_duration['long']:
|
|
306
|
+
return 'long'
|
|
307
|
+
else:
|
|
308
|
+
return 'very_long'
|
|
309
|
+
|
|
310
|
+
# Apply categorization to the DataFrame
|
|
311
|
+
return df[DataColumns.SEGMENT_NR].map(segment_sizes).map(categorize).astype('category')
|
|
312
|
+
|
|
313
|
+
class WindowedDataExtractor:
|
|
314
|
+
"""
|
|
315
|
+
A utility class for extracting specific column indices and slices
|
|
316
|
+
from a list of windowed column names.
|
|
317
|
+
|
|
318
|
+
Attributes
|
|
319
|
+
----------
|
|
320
|
+
column_indices : dict
|
|
321
|
+
A dictionary mapping column names to their indices.
|
|
322
|
+
|
|
323
|
+
Methods
|
|
324
|
+
-------
|
|
325
|
+
get_index(col)
|
|
326
|
+
Returns the index of a specific column.
|
|
327
|
+
get_slice(cols)
|
|
328
|
+
Returns a slice object for a range of consecutive columns.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self, windowed_cols):
|
|
332
|
+
"""
|
|
333
|
+
Initialize the WindowedDataExtractor.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
windowed_cols : list of str
|
|
338
|
+
A list of column names in the windowed data.
|
|
339
|
+
|
|
340
|
+
Raises
|
|
341
|
+
------
|
|
342
|
+
ValueError
|
|
343
|
+
If the list of `windowed_cols` is empty.
|
|
344
|
+
"""
|
|
345
|
+
if not windowed_cols:
|
|
346
|
+
raise ValueError("The list of windowed columns cannot be empty.")
|
|
347
|
+
self.column_indices = {col: idx for idx, col in enumerate(windowed_cols)}
|
|
348
|
+
|
|
349
|
+
def get_index(self, col):
|
|
350
|
+
"""
|
|
351
|
+
Get the index of a specific column.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
col : str
|
|
356
|
+
The name of the column to retrieve the index for.
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
int
|
|
361
|
+
The index of the specified column.
|
|
362
|
+
|
|
363
|
+
Raises
|
|
364
|
+
------
|
|
365
|
+
ValueError
|
|
366
|
+
If the column is not found in the `windowed_cols` list.
|
|
367
|
+
"""
|
|
368
|
+
if col not in self.column_indices:
|
|
369
|
+
raise ValueError(f"Column '{col}' not found in windowed_cols.")
|
|
370
|
+
return self.column_indices[col]
|
|
371
|
+
|
|
372
|
+
def get_slice(self, cols):
|
|
373
|
+
"""
|
|
374
|
+
Get a slice object for a range of consecutive columns.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
cols : list of str
|
|
379
|
+
A list of consecutive column names to define the slice.
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
slice
|
|
384
|
+
A slice object spanning the indices of the given columns.
|
|
385
|
+
|
|
386
|
+
Raises
|
|
387
|
+
------
|
|
388
|
+
ValueError
|
|
389
|
+
If one or more columns in `cols` are not found in the `windowed_cols` list.
|
|
390
|
+
"""
|
|
391
|
+
if not all(col in self.column_indices for col in cols):
|
|
392
|
+
missing = [col for col in cols if col not in self.column_indices]
|
|
393
|
+
raise ValueError(f"The following columns are missing from windowed_cols: {missing}")
|
|
394
|
+
start_idx = self.column_indices[cols[0]]
|
|
395
|
+
end_idx = self.column_indices[cols[-1]] + 1
|
|
396
|
+
return slice(start_idx, end_idx)
|