biomechzoo 0.4.11__py3-none-any.whl → 0.5.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.

Potentially problematic release.


This version of biomechzoo might be problematic. Click here for more details.

@@ -0,0 +1,2 @@
1
+ def movement_onset():
2
+ raise NotImplementedError
@@ -1,4 +1,5 @@
1
1
  import warnings
2
+ import copy
2
3
  from biomechzoo.biomech_ops.normalize_line import normalize_line
3
4
 
4
5
 
@@ -14,14 +15,14 @@ def normalize_data(data, nlength=101):
14
15
  """
15
16
 
16
17
  # normalize channel length
17
- data_new = data.copy()
18
+ data_new = copy.deepcopy(data)
18
19
  for ch_name, ch_data in data_new.items():
19
20
  if ch_name != 'zoosystem':
20
21
  ch_data_line = ch_data['line']
21
22
  ch_data_event = ch_data['event']
22
23
  ch_data_normalized = normalize_line(ch_data_line, nlength)
23
- data_new[ch_name]['line'] = ch_data_normalized
24
- data_new[ch_name]['event'] = ch_data_event
24
+ data_new[ch_name]['line'] = ch_data_normalized
25
+ data_new[ch_name]['event'] = ch_data_event
25
26
  warnings.warn('event data have not been normalized')
26
27
 
27
28
  # update zoosystem
biomechzoo/biomechzoo.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import inspect
3
3
  import time
4
+
5
+ from biomechzoo.imu.tilt_algoirthm import tilt_algorithm_data
4
6
  from biomechzoo.utils.engine import engine # assumes this returns .zoo files in folder
5
7
  from biomechzoo.utils.zload import zload
6
8
  from biomechzoo.utils.zsave import zsave
@@ -12,6 +14,7 @@ from biomechzoo.conversion.table2zoo_data import table2zoo_data
12
14
  from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
13
15
  from biomechzoo.processing.removechannel_data import removechannel_data
14
16
  from biomechzoo.processing.renamechannel_data import renamechannel_data
17
+ from biomechzoo.processing.removeevent_data import removeevent_data
15
18
  from biomechzoo.processing.explodechannel_data import explodechannel_data
16
19
  from biomechzoo.processing.addevent_data import addevent_data
17
20
  from biomechzoo.processing.partition_data import partition_data
@@ -62,6 +65,8 @@ class BiomechZoo:
62
65
  in_folder_path = os.path.dirname(in_folder)
63
66
  self.in_folder = os.path.join(in_folder_path, out_folder)
64
67
 
68
+ batchdisp('all files saved to: {}'.format(self.in_folder ), level=1, verbose=self.verbose)
69
+
65
70
  def mvnx2zoo(self, out_folder=None, inplace=False):
66
71
  """ Converts all .mvnx files in the folder to .zoo format """
67
72
  start_time = time.time()
@@ -76,7 +81,7 @@ class BiomechZoo:
76
81
  f_zoo = f.replace('.mvnx', '.zoo')
77
82
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
78
83
  method_name = inspect.currentframe().f_code.co_name
79
- batchdisp('{} conversion complete for {} files'.format(method_name, len(fl)), level=1, verbose=verbose)
84
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
80
85
  # Update self.folder after processing
81
86
  self._update_folder(out_folder, inplace, in_folder)
82
87
 
@@ -100,17 +105,21 @@ class BiomechZoo:
100
105
  # Update self.folder after processing
101
106
  self._update_folder(out_folder, inplace, in_folder)
102
107
 
103
- def table2zoo(self, out_folder=None, inplace=None, skip_rows=0, extension='csv'):
108
+ def table2zoo(self, extension, out_folder=None, inplace=None, skip_rows=0, freq=None):
104
109
  """ Converts generic .csv file in the folder to .zoo format """
105
110
  start_time = time.time()
106
111
  verbose = self.verbose
107
112
  in_folder = self.in_folder
113
+
114
+ if extension.startswith('.'):
115
+ extension = extension[1:]
116
+
108
117
  if inplace is None:
109
118
  inplace = self.inplace
110
119
  fl = engine(in_folder, extension=extension, name_contains=self.name_contains, subfolders=self.subfolders)
111
120
  for f in fl:
112
121
  batchdisp('converting {} to zoo for {}'.format(extension, f), level=2, verbose=verbose)
113
- data = table2zoo_data(f, type=extension, skip_rows=skip_rows)
122
+ data = table2zoo_data(f, extension=extension, skip_rows=skip_rows, freq=freq)
114
123
  f_zoo = f.replace(extension, '.zoo')
115
124
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
116
125
  method_name = inspect.currentframe().f_code.co_name
@@ -119,8 +128,33 @@ class BiomechZoo:
119
128
  self._update_folder(out_folder, inplace, in_folder)
120
129
 
121
130
  def xls2zoo(self, out_folder=None, inplace=None):
122
- """ Converts generic .xls file in the folder to .zoo format """
123
- raise NotImplementedError
131
+ raise NotImplementedError('Use table2zoo instead')
132
+
133
+ def csv2zoo(self, out_folder=None, inplace=None):
134
+ raise NotImplementedError('Use table2zoo instead')
135
+
136
+ def parquet2zoo(self, out_folder=None, inplace=None):
137
+ raise NotImplementedError('Use table2zoo instead')
138
+
139
+ def tilt_algorithm(self, chname_avert, chname_medlat, chname_antpost, out_folder=None, inplace=False):
140
+ """ tilt correction for acceleration data """
141
+ start_time = time.time()
142
+ verbose = self.verbose
143
+ in_folder = self.in_folder
144
+ if inplace is None:
145
+ inplace = self.inplace
146
+ fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
147
+ for f in fl:
148
+ batchdisp('tilt correction of acceleration channels for {}'.format(f), level=2, verbose=verbose)
149
+ data = zload(f)
150
+ data = tilt_algorithm_data(data, chname_avert, chname_medlat, chname_antpost)
151
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
152
+ method_name = inspect.currentframe().f_code.co_name
153
+ batchdisp(
154
+ '{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
155
+ level=1, verbose=verbose)
156
+ # Update self.folder after processing
157
+ self._update_folder(out_folder, inplace, in_folder)
124
158
 
125
159
  def phase_angle(self, ch, out_folder=None, inplace=None):
126
160
  """ computes phase angles"""
@@ -185,7 +219,8 @@ class BiomechZoo:
185
219
  batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
186
220
  verbose=verbose)
187
221
  data_new = split_trial(data, start, end)
188
- zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
222
+ if data_new is not None:
223
+ zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
189
224
  method_name = inspect.currentframe().f_code.co_name
190
225
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
191
226
 
@@ -267,6 +302,27 @@ class BiomechZoo:
267
302
  # Update self.folder after processing
268
303
  self._update_folder(out_folder, inplace, in_folder)
269
304
 
305
+
306
+ def removeevent(self, events, mode='remove', out_folder=None, inplace=None):
307
+ """ removes channels from zoo files """
308
+ start_time = time.time()
309
+ verbose = self.verbose
310
+ in_folder = self.in_folder
311
+ if inplace is None:
312
+ inplace = self.inplace
313
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
314
+ for f in fl:
315
+ batchdisp('removing events {} for {}'.format(events, f), level=2, verbose=verbose)
316
+ data = zload(f)
317
+ data = removeevent_data(data, events, mode)
318
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
319
+ method_name = inspect.currentframe().f_code.co_name
320
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
321
+
322
+ # Update self.folder after processing
323
+ self._update_folder(out_folder, inplace, in_folder)
324
+
325
+
270
326
  def explodechannel(self, out_folder=None, inplace=None):
271
327
  """ explodes all channels in a zoo file """
272
328
  start_time = time.time()
@@ -307,7 +363,7 @@ class BiomechZoo:
307
363
  # Update self.folder after processing
308
364
  self._update_folder(out_folder, inplace, in_folder)
309
365
 
310
- def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
366
+ def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None, constant=None):
311
367
  """ adds events of type evt_type with name evt_name to channel ch """
312
368
  start_time = time.time()
313
369
  verbose = self.verbose
@@ -319,7 +375,7 @@ class BiomechZoo:
319
375
  if verbose:
320
376
  batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
321
377
  data = zload(f)
322
- data = addevent_data(data, ch, event_type, event_name)
378
+ data = addevent_data(data, ch, event_type, event_name, constant)
323
379
  zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
324
380
  method_name = inspect.currentframe().f_code.co_name
325
381
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
@@ -9,9 +9,7 @@ def mvnx2zoo_data(fl):
9
9
  mvnx_file = load_mvnx(fl)
10
10
 
11
11
  # create zoo data dict
12
- data = {}
13
- data['zoosystem'] = set_zoosystem()
14
-
12
+ data = {'zoosystem': set_zoosystem()}
15
13
  # extract joint angle data (All JOINTS may not exist in a given dataset)
16
14
  for key, val in JOINTS.items():
17
15
  try:
@@ -6,69 +6,76 @@ from biomechzoo.utils.set_zoosystem import set_zoosystem
6
6
  from biomechzoo.utils.compute_sampling_rate_from_time import compute_sampling_rate_from_time
7
7
 
8
8
 
9
- def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
10
- # todo: check calculation for sampling rate
9
+ def table2zoo_data(fl, extension, skip_rows=0, freq=None):
11
10
 
12
- if type not in ['csv', 'parquet']:
13
- raise NotImplementedError('Only csv and parquet currently supported')
11
+ if extension == 'csv':
12
+ df, metadata, freq = _csv2zoo(fl, skip_rows=skip_rows, freq=freq)
14
13
 
15
- # Read header lines until 'endheader'
16
- metadata = {}
17
- if type == 'csv' and skip_rows > 0:
18
- header_lines = []
19
- with open(csv_path, 'r') as f:
20
- for line in f:
21
- header_lines.append(line.strip())
22
- if line.strip().lower() == 'endheader':
23
- break
24
- # Parse metadata
25
- metadata = _parse_metadata(header_lines)
26
-
27
- if type == 'csv':
28
- df = pd.read_csv(csv_path, skiprows=skip_rows)
29
- elif type =='parquet':
30
- df = pd.read_parquet(csv_path)
14
+ elif extension == 'parquet':
15
+ df, metadata, freq = _parquet2zoo(fl, skip_rows=skip_rows, freq=freq)
31
16
  else:
32
- raise ValueError('type must be csv or parquet')
33
-
34
- # Use all columns
35
- df_data = df.iloc[:, 0:]
17
+ raise ValueError('extension {} not implemented'.format(extension))
36
18
 
37
19
  # assemble zoo data
38
- data = {}
39
- data['zoosystem'] = set_zoosystem()
40
-
41
- for ch in df_data.columns:
20
+ data = {'zoosystem': set_zoosystem()}
21
+ for ch in df.columns:
42
22
  data[ch] = {
43
- 'line': df_data[ch].values,
23
+ 'line': df[ch].values,
44
24
  'event': []
45
25
  }
46
26
 
47
- # try to find frequency in metadata
48
- if freq is None:
49
- if 'freq' in metadata:
50
- freq = metadata['freq']
51
- elif 'sampling_rate' in metadata:
52
- freq = metadata['sampling_rate']
53
- else:
54
- freq = None # or raise an error
55
27
 
56
28
  # now try to calculate freq from a time column
57
29
  if freq is None:
58
30
  time_col = [col for col in df.columns if 'time' in col.lower()]
59
- if time_col is not None:
31
+ if time_col is not None and len(time_col) > 0:
60
32
  time_data = df[time_col].to_numpy()[:, 0]
61
33
  freq = compute_sampling_rate_from_time(time_data)
62
-
34
+ else:
35
+ raise ValueError('Unable to compute sampling rate for time column, please specify a sampling frequency'
36
+ )
63
37
  # add metadata
64
38
  data['zoosystem']['Video']['Freq'] = freq
65
39
  data['zoosystem']['Analog']['Freq'] = 'None'
66
- if 'version' in metadata:
67
- data['zoosystem']['collection_system_version'] = metadata['version']
40
+
41
+ if metadata is not None:
42
+ data['zoosystem']['Other'] = metadata
68
43
 
69
44
  return data
70
45
 
71
46
 
47
+ def _parquet2zoo(fl, skip_rows=0, freq=None):
48
+ df = pd.read_parquet(fl)
49
+ metadata = None
50
+ return df, metadata, freq
51
+
52
+ def _csv2zoo(fl, skip_rows=0, freq=None):
53
+ header_lines = []
54
+ with open(fl, 'r') as f:
55
+ for line in f:
56
+ header_lines.append(line.strip())
57
+ if line.strip().lower() == 'endheader':
58
+ break
59
+ # Parse metadata
60
+ metadata = _parse_metadata(header_lines)
61
+
62
+ # try to find frequency in metadata
63
+ if freq is None:
64
+ if 'freq' in metadata:
65
+ freq = metadata['freq']
66
+ elif 'sampling_rate' in metadata:
67
+ freq = metadata['sampling_rate']
68
+ else:
69
+ freq = None # or raise an error
70
+
71
+ # read csv
72
+ df = pd.read_csv(fl, skiprows=skip_rows)
73
+
74
+ return df, metadata, freq
75
+
76
+
77
+
78
+
72
79
  def _parse_metadata(header_lines):
73
80
  metadata = {}
74
81
  for line in header_lines:
@@ -96,6 +103,8 @@ def _parse_metadata(header_lines):
96
103
  return metadata
97
104
 
98
105
 
106
+
107
+
99
108
  if __name__ == '__main__':
100
109
  """ for unit testing"""
101
110
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -0,0 +1,112 @@
1
+ import numpy as np
2
+ import math
3
+ import pandas as pd
4
+ from biomechzoo.processing.add_channel_data import add_channel_data
5
+
6
+ def tilt_algorithm_data(data,ch_vert, ch_medlat, ch_antpost, plot_or_not=None):
7
+
8
+ # extract channels from data
9
+ avert = data[ch_vert]['line']
10
+ amedlat = data[ch_medlat]['line']
11
+ aantpost = data[ch_antpost]['line']
12
+
13
+ _, avert_corr, amedlat_corr, aantpost_corr = tilt_algorithm_line(avert, amedlat, aantpost)
14
+
15
+ data = add_channel_data(data, ch_vert + '_tilt_corr', avert_corr)
16
+ data = add_channel_data(data, ch_medlat + '_tilt_corr', amedlat_corr)
17
+ data = add_channel_data(data, ch_antpost + '_tilt_corr', aantpost_corr)
18
+
19
+ return data
20
+
21
+
22
+ def tilt_algorithm_line(avert, amedlat, aantpost, plot_or_not=None):
23
+ """
24
+ TiltAlgorithm - to account for gravity and improper tilt alignment of a tri-axial trunk accelerometer.
25
+ Step 1: Extract raw measured (mean) accelerations
26
+ Step 2: Calculate tilt angles
27
+ Step 3: Calculate horizontal dynamic accelerations vectors
28
+ Step 4: Calculate estimated provisional vertical vector
29
+ Step 5: Calculate vertical dynamic vector
30
+ step 6.1: Calculate the contribution of static components
31
+ step 6.2 Transpose static component matrices
32
+ step 7: Remove the static components from the templates of pre and post
33
+
34
+ :param avert: signal predominantly in vertical direction
35
+ :param amedlat: signal predominantly in medio-lateral direction
36
+ :param aantpost: signal predominantly in anterior-posterior direction
37
+ :param plot_or_not: whether to plot the results
38
+ :return: dataframe of the tilt corrected and gravity subtracted vertical, medio-lateral and anterior-posterior
39
+ acceleration signals
40
+ """
41
+ #
42
+
43
+ a_vt = avert.mean()
44
+ a_ml = amedlat.mean()
45
+ a_ap = aantpost.mean()
46
+
47
+ # if avert is negative than turn the sensor around.
48
+ if a_vt < 0.5:
49
+ avert *= -1
50
+ amedlat *= -1
51
+ a_vt = avert.mean()
52
+ a_ml = amedlat.mean()
53
+
54
+ # Anterior tilt
55
+ TiltAngle_ap_rad = np.arcsin(a_ap)
56
+ TiltAngle_ap_deg = math.degrees(TiltAngle_ap_rad)
57
+
58
+ # mediolateral tilt
59
+ TiltAngle_ml_rad = np.arcsin(a_ml)
60
+ TiltAngle_ml_deg = math.degrees(TiltAngle_ml_rad)
61
+
62
+ # Anterior posterior
63
+ a_AP = (a_ap * np.cos(TiltAngle_ap_rad)) - (a_vt * np.sin(TiltAngle_ap_rad))
64
+ # AMediolateral
65
+ a_ML = (a_ml * np.cos(TiltAngle_ml_rad)) - (a_vt * np.sin(TiltAngle_ml_rad))
66
+
67
+ # a_vt_prov = a_ap*Sin(theta_ap) + a_vt*Cos(theta_ap)
68
+ a_vt_prov = (a_ap * np.sin(TiltAngle_ap_rad)) + (a_vt * np.cos(TiltAngle_ap_rad))
69
+
70
+ # a_VT = a_ml*sin(theta_ml) + a_vt_prov*cos(theta_ml) - 1
71
+ a_VT = (a_ml * np.sin(TiltAngle_ml_rad)) + (a_vt_prov * np.cos(TiltAngle_ml_rad)) - 1
72
+
73
+ a_AP_static = a_ap - a_AP
74
+ a_ML_static = a_ml - a_ML
75
+ a_VT_static = a_vt - a_VT
76
+
77
+ a_AP_static = np.transpose(a_AP_static)
78
+ a_ML_static = np.transpose(a_ML_static)
79
+ a_VT_static = np.transpose(a_VT_static)
80
+
81
+ amedlat2 = amedlat - a_ML_static
82
+ avert2 = avert - a_VT_static
83
+ aantpost2 = aantpost - a_AP_static
84
+
85
+ data = {'avert': avert2,
86
+ 'amedlat': amedlat2,
87
+ 'aantpost': aantpost2}
88
+ df_corrected = pd.DataFrame(data)
89
+
90
+ # if plot_or_not:
91
+ # f, ax = plt.subplots(nrows=3, ncols=1, sharex=True, dpi=300)
92
+ # sns.despine(offset=10)
93
+ # f.tight_layout()
94
+ # offset = 0.1
95
+ # f.subplots_adjust(left=0.15, top=0.95)
96
+ #
97
+ # sns.lineplot(avert, ax=ax[0], label='Raw')
98
+ # sns.lineplot(avert2, ax=ax[0], label='tilt corrected')
99
+ # ax[0].set_ylabel('vert acc (g)')
100
+ # ax[0].set_title('Vertical acceleration corrected with {}'.format(np.round(a_VT_static, 2)))
101
+ #
102
+ # sns.lineplot(amedlat, ax=ax[1], label='Raw')
103
+ # sns.lineplot(amedlat2, ax=ax[1], label='Tilt corrected')
104
+ # ax[1].set_ylabel('ml acc (g)')
105
+ # ax[1].set_title('Medio-lateral tilt angle corrected with {} degrees'.format(np.round(TiltAngle_ml_deg, 2)))
106
+ #
107
+ # sns.lineplot(aantpost, ax=ax[2], label='Raw')
108
+ # sns.lineplot(aantpost2, ax=ax[2], label='Tilt corrected')
109
+ # ax[2].set_ylabel('ap acc (g)')
110
+ # ax[2].set_title('Anterior-posterior tilt angle corrected with {} degrees'.format(np.round(TiltAngle_ap_deg, 2)))
111
+
112
+ return df_corrected, avert2, amedlat2, aantpost2
@@ -1,23 +1,28 @@
1
1
  import numpy as np
2
+ import copy
3
+ import warnings
4
+ from biomechzoo.utils.peak_sign import peak_sign
5
+ from biomechzoo.biomech_ops.movement_onset import movement_onset
2
6
 
7
+ def addevent_data(data, channels, ename, etype, constant=None):
3
8
 
4
- def addevent_data(data, ch, ename, etype):
5
- if isinstance(ch, str):
6
- ch = [ch]
9
+ data_new = copy.deepcopy(data)
7
10
 
8
- if len(ch) == 1 and ch[0].lower() == 'all':
9
- ch = [key for key in data if key != 'zoosystem']
11
+ if isinstance(channels, str):
12
+ channels = [channels]
10
13
 
11
- for channel in ch:
14
+ if len(channels) == 1 and channels[0].lower() == 'all':
15
+ channels = [key for key in data if key != 'zoosystem']
16
+
17
+ for channel in channels:
12
18
  if ename == '':
13
19
  data[channel]['event'] = {}
14
20
  continue
15
21
 
16
22
  if channel not in data:
17
- print(f'Channel {channel} does not exist')
18
- continue
23
+ raise KeyError('Channel {} does not exist'.format(channel))
19
24
 
20
- yd = data[channel]['line'] # 1D array
25
+ yd = data_new[channel]['line'] # 1D array
21
26
  etype = etype.lower()
22
27
  if etype == 'absmax':
23
28
  exd = int(np.argmax(np.abs(yd)))
@@ -42,13 +47,57 @@ def addevent_data(data, ch, ename, etype):
42
47
  exd = max_stance(yd)
43
48
  eyd = float(yd[exd])
44
49
  eyd = float(yd[exd])
50
+ elif etype == 'movement_onset':
51
+ exd = movement_onset(yd, constant)
52
+ eyd = yd[exd]
53
+ elif etype == 'movement_offset':
54
+ exd = movement_onset(yd,constant)
55
+ eyd = yd[exd]
56
+ elif etype in ['fs_fp', 'fo_fp']:
57
+ # --- Handle constant ---
58
+ if constant is None:
59
+ print('Warning: Force plate threshold not set, defaulting to 0.')
60
+ constant = 0.0
61
+
62
+ # --- Check sampling rates ---
63
+ AVR = data['zoosystem']['AVR']
64
+ if AVR != 1:
65
+ warnings.warn('Video and Analog channels must be at the same sampling rate or events will be incorrect.')
66
+
67
+ # --- Handle units ---
68
+ units = data['zoosystem']['Units']['Forces']
69
+ if units == 'N/kg':
70
+ m = data['zoosystem']['Anthro']['Bodymass']
71
+ else:
72
+ m = 1.0
73
+
74
+ # --- Extract force signal ---
75
+ if '_' not in channel:
76
+ yd = data_new[channel]['line'][:, 2] # looking for GRF Z
77
+ else:
78
+ yd = data_new[channel]['line']
79
+
80
+ # --- Determine peak sign ---
81
+ peak = peak_sign(yd) # user-defined function
82
+
83
+ # --- Find threshold crossing ---
84
+ threshold_signal = peak * yd * m
85
+ if 'fs' in etype:
86
+ exd_array = np.where(threshold_signal > constant)[0]
87
+ exd = exd_array[0] - 1 # MATLAB indexing correction
88
+ eyd = yd[exd]
89
+ else: # 'FO' type
90
+ exd_array = np.where(threshold_signal > constant)[0]
91
+ exd = exd_array[-1] + 1
92
+ eyd = yd[exd]
93
+
45
94
  else:
46
95
  raise ValueError(f'Unknown event type: {etype}')
47
96
 
48
97
  # Add event to the channel's event dict
49
- data[channel]['event'][ename] = [exd, eyd, 0]
98
+ data_new[channel]['event'][ename] = [exd, eyd, 0]
50
99
 
51
- return data
100
+ return data_new
52
101
 
53
102
  def max_stance(yd):
54
103
  """ extracts max from first 40% of the gait cycle"""
@@ -1,6 +1,7 @@
1
1
  from biomechzoo.utils.findfield import findfield
2
2
  import warnings
3
3
  import copy
4
+ import numpy as np
4
5
 
5
6
 
6
7
  def partition_data(data, evt_start, evt_end):
@@ -13,16 +14,19 @@ def partition_data(data, evt_start, evt_end):
13
14
  if e1 is None or e2 is None or len(e1) == 0 or len(e2) == 0:
14
15
  raise ValueError(f"Event not found: evt_start='{evt_start}' returned {e1}, evt_end='{evt_end}' returned {e2}")
15
16
 
17
+ # convert to int and get first value
18
+ e1 = int(e1[0])
19
+ e2 = int(e2[0])
20
+
16
21
  data_new = copy.deepcopy(data)
17
- for ch_name, ch_data in data_new.items():
22
+ for ch_name, ch_data in sorted(data_new.items()):
18
23
  if ch_name != 'zoosystem':
19
- print(ch_name)
20
- line = ch_data['line']
24
+ r = ch_data['line']
21
25
  try:
22
- if line.ndim == 1:
23
- data_new[ch_name]['line'] = line[e1[0]:e2[0]]
26
+ if r.ndim == 1:
27
+ data_new[ch_name]['line'] = r[e1:e2]
24
28
  else:
25
- data_new[ch_name]['line'] = line[e1[0]:e2[0], :]
29
+ data_new[ch_name]['line'] = r[e1:e2, :]
26
30
  except (IndexError, ValueError) as e:
27
31
  # IndexError: if e1[0]:e2[0] goes beyond the available indices
28
32
  # ValueError: less likely, but may arise with shape mismatches
@@ -31,29 +35,12 @@ def partition_data(data, evt_start, evt_end):
31
35
  # partition events
32
36
  events = ch_data['event']
33
37
  for event_name, value in events.items():
34
- original_frame = value[0]
38
+ original_frame = int(value[0])
35
39
  if original_frame == 999:
36
40
  continue # do not change outlier markers
37
41
  else:
38
- new_frame = original_frame - e1[0] + 1
39
- print(new_frame)
40
- data_new[ch_name]['event'][event_name][0] = new_frame
42
+ arr = np.array(data_new[ch_name]['event'][event_name], dtype=np.int32)
43
+ arr[0] = original_frame - e1
44
+ data_new[ch_name]['event'][event_name] = arr
41
45
 
42
46
  return data_new
43
-
44
-
45
- def _partition_line(arr, evt_start, evt_end):
46
- arr_new = arr[evt_start:evt_end, :]
47
- return arr_new
48
-
49
-
50
- def _partition_event(event_dict, evt_start, evt_end, arr_len):
51
- raise NotImplementedError
52
- # event_dict_new = {}
53
- # for event, event_val in event_dict:
54
- # event_val_new =
55
- # event_dict_new[event] =
56
- #
57
- #
58
- #
59
- # return event_dict_new
@@ -0,0 +1,57 @@
1
+ import copy
2
+ import warnings
3
+ from biomechzoo.utils.findfield import findfield
4
+
5
+ def removeevent_data(data, events, mode='remove'):
6
+ """
7
+ Remove or keep specified events in all channels of a zoo dictionary.
8
+
9
+ Parameters
10
+ ----------
11
+ data : dict
12
+ Zoo data loaded from a file
13
+ events : list of str
14
+ Events to remove or keep
15
+ mode : str
16
+ 'remove' or 'keep'
17
+
18
+ Returns
19
+ -------
20
+ dict
21
+ Modified zoo dictionary with events removed or kept
22
+ """
23
+ if mode not in ['remove', 'keep']:
24
+ raise ValueError("mode must be 'remove' or 'keep'.")
25
+
26
+ if isinstance(events, str):
27
+ events = [events]
28
+
29
+ # check if any events are not present
30
+ valid_events = []
31
+ for evt in events:
32
+ e, _ = findfield(data, evt)
33
+ if e is None:
34
+ warnings.warn('Could not find event {} in zoo file, skipping'.format(evt))
35
+ else:
36
+ valid_events.append(evt)
37
+ events = valid_events
38
+
39
+ data_new = copy.deepcopy(data)
40
+ channels = sorted([ch for ch in data_new if ch != 'zoosystem'])
41
+ for ch in channels:
42
+ event_dict = data_new[ch].get('event', {})
43
+ events_to_remove = []
44
+
45
+ for evt in list(event_dict.keys()):
46
+ if mode == 'remove' and evt in events:
47
+ events_to_remove.append(evt)
48
+ elif mode == 'keep' and evt not in events:
49
+ events_to_remove.append(evt)
50
+
51
+ for evt in events_to_remove:
52
+ event_dict.pop(evt, None)
53
+ # print('Removed event "{}" from channel "{}"'.format(evt, ch))
54
+
55
+ data_new[ch]['event'] = event_dict
56
+
57
+ return data_new
@@ -28,17 +28,11 @@ def renameevent_data(data, evt, nevt):
28
28
  # Get all data channels except 'zoosystem'
29
29
  channels = [ch for ch in data if ch != 'zoosystem']
30
30
  for old_name, new_name in zip(evt, nevt):
31
- eventsRenamed = False
32
31
  for ch in channels:
33
32
  events = data[ch].get('event', {})
34
33
  if old_name in events:
35
- print('Renaming event {} in channel {} to {}'.format(old_name, ch, new_name))
36
34
  data[ch]['event'][new_name] = events[old_name]
37
35
  del data[ch]['event'][old_name]
38
- eventsRenamed = True
39
-
40
- if not eventsRenamed:
41
- print('no event {} found in any channel'.format(old_name))
42
36
 
43
37
  return data
44
38
 
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import numpy as np
2
3
 
3
4
 
4
5
  def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, verbose=False):
@@ -66,10 +67,13 @@ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, v
66
67
  continue
67
68
  matched_files.append(full_path)
68
69
 
70
+ # sort list
71
+ matched_files = np.sort(matched_files)
72
+
69
73
  if verbose:
70
- print("Found {} {} files in subfolders named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
74
+ print("Found {} {} files in subfolder(s) named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
71
75
  for f in matched_files:
72
- print(" - {}".format(f))
76
+ print('{}'.format(f))
73
77
 
74
78
  return matched_files
75
79
 
@@ -79,6 +83,6 @@ if __name__ == '__main__':
79
83
  with extension .c3d in the sample study folder (data)"""
80
84
  # -------TESTING--------
81
85
  current_dir = os.path.dirname(os.path.abspath(__file__))
82
- project_root = os.path.dirname(current_dir)
86
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
83
87
  sample_dir = os.path.join(project_root, 'data', 'sample_study', 'raw c3d files')
84
88
  c3d_files = engine(sample_dir, '.c3d', subfolders=['Straight'], name_contains='HC03', verbose=True)
@@ -0,0 +1,24 @@
1
+ import numpy as np
2
+
3
+ def peak_sign(r):
4
+ """
5
+ Determine whether the largest absolute peak in the signal is positive or negative.
6
+
7
+ Parameters
8
+ ----------
9
+ r : array-like
10
+ Signal vector.
11
+
12
+ Returns
13
+ -------
14
+ sign : int
15
+ 1 if the maximum peak is positive, -1 if negative.
16
+ """
17
+ r = np.asarray(r)
18
+ max_val = np.max(r)
19
+ min_val = np.min(r)
20
+
21
+ if abs(max_val) > abs(min_val):
22
+ return 1
23
+ else:
24
+ return -1
@@ -1,23 +1,35 @@
1
- def split_trial(data, start_event_indx, end_event_indx):
1
+ import copy
2
+ from biomechzoo.utils.findfield import findfield
3
+
4
+
5
+ def split_trial(data, start_event, end_event):
2
6
  # todo check index problem compared to matlab start at 0 or 1
3
- data_new = data.copy()
7
+ data_new = copy.deepcopy(data)
8
+
9
+ start_event_indx, _ = findfield(data_new, start_event)
10
+ end_event_indx, _ = findfield(data_new, end_event)
4
11
 
5
12
  for key, value in data_new.items():
6
13
  if key == 'zoosystem':
7
14
  continue
8
15
 
9
16
  # Slice the line data
10
- data_new[key]['line'] = value['line'][start_event_indx:end_event_indx]
17
+ trial_length = len(data_new[key]['line'])
18
+ if trial_length > end_event_indx[0]:
19
+ data_new[key]['line'] = value['line'][start_event_indx[0]:end_event_indx[0]]
20
+ else:
21
+ print('skipping split trial since event is outside range of data')
22
+ return None
11
23
 
12
24
  # Update events if present
13
- if 'event' in value:
14
- new_events = {}
15
- for evt_name, evt_val in value['event'].items():
16
- event_frame = evt_val[0]
17
- # Check if event falls within the new window
18
- if start_event_indx <= event_frame < end_event_indx:
19
- # Adjust index relative to new start
20
- new_events[evt_name] = [event_frame - start_event_indx, 0, 0]
21
- data_new[key]['event'] = new_events
25
+ # if 'event' in value:
26
+ # new_events = {}
27
+ # for evt_name, evt_val in value['event'].items():
28
+ # event_frame = evt_val[0]
29
+ # # Check if event falls within the new window
30
+ # if start_event_indx <= event_frame < end_event_indx:
31
+ # # Adjust index relative to new start
32
+ # new_events[evt_name] = [event_frame - start_event_indx, 0, 0]
33
+ # data_new[key]['event'] = new_events
22
34
 
23
35
  return data_new
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomechzoo
3
- Version: 0.4.11
3
+ Version: 0.5.1
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
@@ -1,21 +1,23 @@
1
1
  __init__.py,sha256=Uy3ykqw4l_lZKiUWSUFPRZpkZajYUfZLBaQLVhKzxdI,772
2
2
  biomechzoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  biomechzoo/__main__.py,sha256=hSMHN1Rxn2367fSGTLHoOQ4_pocZw0IWI7HFxl-74oY,88
4
- biomechzoo/biomechzoo.py,sha256=N2qqUJrSvWueB6TEOJjONdqGjy1pv1PmNXA177mvjGs,19772
4
+ biomechzoo/biomechzoo.py,sha256=3PviTE-zv0TVp3pYayL2ZrMopj5LJtcSLjwxEsGMtxw,22515
5
5
  biomechzoo/biomech_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  biomechzoo/biomech_ops/continuous_relative_phase_data.py,sha256=RePbt6zyOI1iv74CWhxSrunIokTFYVfFmFnoW51781E,1300
7
7
  biomechzoo/biomech_ops/continuous_relative_phase_line.py,sha256=Fa1LFRuPlmGPLQLvln6HVnJy3zMSm9z5YeooHmTu0lc,1365
8
8
  biomechzoo/biomech_ops/filter_data.py,sha256=Q9knW23Ft_WeWVCBjaIQ5GkKU0NYV4SdGiDZV8Fm-hM,1805
9
9
  biomechzoo/biomech_ops/filter_line.py,sha256=XKUdRsxU5AO1gSldnwp3qNzsUUL8qpOpAMyQbEMo5uI,2600
10
- biomechzoo/biomech_ops/normalize_data.py,sha256=gpoUh6EpxpCprBdrSjmZ4UsAWgonJgD41XUJWD8Yo9Y,1299
10
+ biomechzoo/biomech_ops/movement_onset.py,sha256=3VAoOp01P08BKMaOZW3CwbMdoSMOufDaoMkWeYRcPmo,51
11
+ biomechzoo/biomech_ops/normalize_data.py,sha256=ESdXeUkKdd0WpjP6FW2EYrOAd7NKWkX1JWUNn1SzDB4,1335
11
12
  biomechzoo/biomech_ops/normalize_line.py,sha256=KUE8gEkIolA-VDFCdUuaskk-waO8jjJ20ZMZaS8Qha8,854
12
13
  biomechzoo/biomech_ops/phase_angle_data.py,sha256=_ekUBW2v3iC4UawcDL38ZZLYJmQsAmyqA61Q6_AMtmQ,1435
13
14
  biomechzoo/biomech_ops/phase_angle_line.py,sha256=p6osB17_3QQSyKLNojuc6nYhv-k0K6EUUH75EXu8ifc,1391
14
15
  biomechzoo/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
16
  biomechzoo/conversion/c3d2zoo_data.py,sha256=28JCj1Jpn7zsv2HjQdzH2F30jnGMwzmbBwYuRoWPJHo,4052
16
- biomechzoo/conversion/mvnx2zoo_data.py,sha256=fZBFFYHuTC0gsH4tjB0S8yszVtc5Ki7KosKN68-zTkU,3558
17
+ biomechzoo/conversion/mvnx2zoo_data.py,sha256=uMAZ4pNSSZ7NToW1WnawrXeVP8D-xE3dNDnooPvALE4,3545
17
18
  biomechzoo/conversion/opencap2zoo_data.py,sha256=n4bLsrJI0wTSzG5bgJcmxj1dp2plnUKFNRzcTIbmV1o,608
18
- biomechzoo/conversion/table2zoo_data.py,sha256=H_r6RNt_MGwkOr4JH-M844DT_ivScQx_byc6DIZa5go,3210
19
+ biomechzoo/conversion/table2zoo_data.py,sha256=rAP7OrKbVrb50NHJ6h8fgoMntV87-t9wT2xQ30AD96I,3330
20
+ biomechzoo/imu/tilt_algoirthm.py,sha256=PYVErXHeY-Wgi0aXCQxDodohTDzirRXo8BEW3_Pskqo,4312
19
21
  biomechzoo/mvn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
22
  biomechzoo/mvn/load_mvnx.py,sha256=B4VGuidQ-5G_5E1t5vpU51Nb_Lu85_EOS_FmGZYjfX8,22499
21
23
  biomechzoo/mvn/main_mvnx.py,sha256=e1LasJQ9icyzjWnRCEcAWQq_-L13-mIzf7PzYA3QYDg,2394
@@ -24,28 +26,30 @@ biomechzoo/mvn/mvnx_file_accessor.py,sha256=Gk2vKq9v_gPbnOS_12zgeJehjFz7v3ClTnN2
24
26
  biomechzoo/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
27
  biomechzoo/processing/add_channel_data.py,sha256=U1xLLBSnyJeeDWzgmHSxOz1hyguzuuDXxQCQ8IFoZkw,1955
26
28
  biomechzoo/processing/addchannel_data.py,sha256=rmnnlMRVkoMlQCR-nRg1jjh-hzMDt37Sx9fAmocrpqA,1953
27
- biomechzoo/processing/addevent_data.py,sha256=L9xaoP0yBvaGbseotxpGCVqjr-eSV-gpnQaeRTz4VKE,1631
29
+ biomechzoo/processing/addevent_data.py,sha256=Z7vsHqhsn8byw4d9atinjGpvglwj6tu38uqZfXVvluQ,3573
28
30
  biomechzoo/processing/explodechannel_data.py,sha256=ceyXfcCmeNqj4p0zCksVdrnmsYR4t-JHLIyv3JlfNpU,2484
29
- biomechzoo/processing/partition_data.py,sha256=XF8dSqvHGpqsT-Q4i6qpoOajAT4LYjP-PJ6KbpoCfFc,2018
31
+ biomechzoo/processing/partition_data.py,sha256=4wuKrnMmRMNobymYwZ0WuRvNGsvkhThZ2ZhoSkkhntg,1741
30
32
  biomechzoo/processing/removechannel_data.py,sha256=uO7jjuHapRr2CGLsrvYQ1eJLvb1y_8KR6Ct4M6TPALA,1740
33
+ biomechzoo/processing/removeevent_data.py,sha256=DC1vBIwuo_mk5Oz0UGLK7Nx3BVqAeVDfT29XgUM_1Rc,1631
31
34
  biomechzoo/processing/renamechannel_data.py,sha256=5lEnWJknAwnJYXDlwO6cFe7C8ct5o42tTHW0Y4GeIL4,2210
32
- biomechzoo/processing/renameevent_data.py,sha256=9w7C_fQOsQ8XbdTr_hrg_iQe51oDczq2Rj7yJLyYG0M,2215
35
+ biomechzoo/processing/renameevent_data.py,sha256=PRGHEs-t9qJMzDY1DCNgQz_h-aAOIII4ryQeCYBXwGo,1952
33
36
  biomechzoo/processing/split_trial_by_gait_cycle.py,sha256=maMUwumSlFLFc5LHdNdBO9JCoT06aaLbpdp43dgowDA,1684
34
37
  biomechzoo/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
38
  biomechzoo/utils/batchdisp.py,sha256=Ke7yeZ4cYQvyf8bmgsiLaRElUeQm49TYIYzAcPWan9s,839
36
39
  biomechzoo/utils/compute_sampling_rate_from_time.py,sha256=iyHbN734vYe5bXEoAsHScJN7Qw--2quUgII7judSQDk,579
37
- biomechzoo/utils/engine.py,sha256=D91ZhevBnoWml_NztZP-1lzFoANy5TyIAnJg0FFZyUc,3859
40
+ biomechzoo/utils/engine.py,sha256=tatGMBbgIe8DIHx2_z_JQwA9TJYCeVYqyWiKiMoRHw4,3971
38
41
  biomechzoo/utils/findfield.py,sha256=i7ewGQOMIiTC06tYFK-JctehHLCippkY9FoXIygx14w,381
39
42
  biomechzoo/utils/get_split_events.py,sha256=xNhEuG6Yqsr1bjWIBHLbepfX-fcqcCYIXZzS3eaDDHQ,911
43
+ biomechzoo/utils/peak_sign.py,sha256=4XNqVIrAmpLw9C9RmWwIKkI1rluBTzeCXJE4-AHCzC4,465
40
44
  biomechzoo/utils/set_zoosystem.py,sha256=oubEc3fy0x6y-AlqQWL3v7QYJA951jU9CRnlJ9ikwQo,1750
41
- biomechzoo/utils/split_trial.py,sha256=Fumz_ZukNBNtPauUhCge5EAHkg05dYDhA1_njQw0lHE,886
45
+ biomechzoo/utils/split_trial.py,sha256=0SNpKKkRoizRe9MCuMrQ6Ev6O5V51rN0D-e8FGlLlo4,1302
42
46
  biomechzoo/utils/version.py,sha256=JIXDUuOcaJiZv9ruMP6PtWvJBh4sP0D5kAvlqPiZK_I,130
43
47
  biomechzoo/utils/zload.py,sha256=_qmbQpiEwUNRcB86aS6dHiytOrz1ZXJVjYkk8h5fg8s,2039
44
48
  biomechzoo/utils/zplot.py,sha256=WVA8aCy1Pqy_bo_HXab9AmW-cBd8J8MPX2LAOd6dznU,1512
45
49
  biomechzoo/utils/zsave.py,sha256=wnRNuDxQc8bwCji4UrfoGjYrSZmka4eaDxQ5rMa78pE,1759
46
- biomechzoo-0.4.11.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
47
- biomechzoo-0.4.11.dist-info/METADATA,sha256=IAMgsVbJdayIURX7FwWfT5XNsBy4JfvOnqbTu-jlbYE,1580
48
- biomechzoo-0.4.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- biomechzoo-0.4.11.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
50
- biomechzoo-0.4.11.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
51
- biomechzoo-0.4.11.dist-info/RECORD,,
50
+ biomechzoo-0.5.1.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
51
+ biomechzoo-0.5.1.dist-info/METADATA,sha256=Jp2p2d6SsnirRpmQjUb91GyEX9sY5fRGG4jfVqRvwPA,1579
52
+ biomechzoo-0.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ biomechzoo-0.5.1.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
54
+ biomechzoo-0.5.1.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
55
+ biomechzoo-0.5.1.dist-info/RECORD,,