biomechzoo 0.4.11__tar.gz → 0.5.0__tar.gz

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.

Files changed (60) hide show
  1. {biomechzoo-0.4.11/src/biomechzoo.egg-info → biomechzoo-0.5.0}/PKG-INFO +1 -1
  2. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/pyproject.toml +1 -1
  3. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/normalize_data.py +4 -3
  4. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomechzoo.py +42 -8
  5. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/conversion/mvnx2zoo_data.py +1 -3
  6. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/conversion/table2zoo_data.py +50 -41
  7. biomechzoo-0.5.0/src/biomechzoo/processing/addevent_data.py +98 -0
  8. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/partition_data.py +14 -27
  9. biomechzoo-0.5.0/src/biomechzoo/processing/removeevent_data.py +57 -0
  10. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/renameevent_data.py +0 -6
  11. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/engine.py +7 -3
  12. biomechzoo-0.5.0/src/biomechzoo/utils/peak_sign.py +24 -0
  13. biomechzoo-0.5.0/src/biomechzoo/utils/split_trial.py +35 -0
  14. {biomechzoo-0.4.11 → biomechzoo-0.5.0/src/biomechzoo.egg-info}/PKG-INFO +1 -1
  15. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/SOURCES.txt +2 -0
  16. biomechzoo-0.4.11/src/biomechzoo/processing/addevent_data.py +0 -56
  17. biomechzoo-0.4.11/src/biomechzoo/utils/split_trial.py +0 -23
  18. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/LICENSE +0 -0
  19. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/README.md +0 -0
  20. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/setup.cfg +0 -0
  21. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/__init__.py +0 -0
  22. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/__init__.py +0 -0
  23. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/__main__.py +0 -0
  24. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/__init__.py +0 -0
  25. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +0 -0
  26. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +0 -0
  27. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/filter_data.py +0 -0
  28. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/filter_line.py +0 -0
  29. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/normalize_line.py +0 -0
  30. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/phase_angle_data.py +0 -0
  31. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/phase_angle_line.py +0 -0
  32. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/conversion/__init__.py +0 -0
  33. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/conversion/c3d2zoo_data.py +0 -0
  34. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/conversion/opencap2zoo_data.py +0 -0
  35. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/mvn/__init__.py +0 -0
  36. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/mvn/load_mvnx.py +0 -0
  37. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/mvn/main_mvnx.py +0 -0
  38. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/mvn/mvn.py +0 -0
  39. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/mvn/mvnx_file_accessor.py +0 -0
  40. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/__init__.py +0 -0
  41. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/add_channel_data.py +0 -0
  42. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/addchannel_data.py +0 -0
  43. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/explodechannel_data.py +0 -0
  44. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/removechannel_data.py +0 -0
  45. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/renamechannel_data.py +0 -0
  46. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/processing/split_trial_by_gait_cycle.py +0 -0
  47. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/__init__.py +0 -0
  48. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/batchdisp.py +0 -0
  49. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/compute_sampling_rate_from_time.py +0 -0
  50. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/findfield.py +0 -0
  51. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/get_split_events.py +0 -0
  52. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/set_zoosystem.py +0 -0
  53. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/version.py +0 -0
  54. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/zload.py +0 -0
  55. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/zplot.py +0 -0
  56. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo/utils/zsave.py +0 -0
  57. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/dependency_links.txt +0 -0
  58. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/entry_points.txt +0 -0
  59. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/requires.txt +0 -0
  60. {biomechzoo-0.4.11 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomechzoo
3
- Version: 0.4.11
3
+ Version: 0.5.0
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "biomechzoo"
3
- version = "0.4.11"
3
+ version = "0.5.0"
4
4
  description = "Python implementation of the biomechZoo toolbox"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11,<3.12" # max version for opencap-process tools is 3.11
@@ -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
@@ -12,6 +12,7 @@ from biomechzoo.conversion.table2zoo_data import table2zoo_data
12
12
  from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
13
13
  from biomechzoo.processing.removechannel_data import removechannel_data
14
14
  from biomechzoo.processing.renamechannel_data import renamechannel_data
15
+ from biomechzoo.processing.removeevent_data import removeevent_data
15
16
  from biomechzoo.processing.explodechannel_data import explodechannel_data
16
17
  from biomechzoo.processing.addevent_data import addevent_data
17
18
  from biomechzoo.processing.partition_data import partition_data
@@ -62,6 +63,8 @@ class BiomechZoo:
62
63
  in_folder_path = os.path.dirname(in_folder)
63
64
  self.in_folder = os.path.join(in_folder_path, out_folder)
64
65
 
66
+ batchdisp('all files saved to: {}'.format(self.in_folder ), level=1, verbose=self.verbose)
67
+
65
68
  def mvnx2zoo(self, out_folder=None, inplace=False):
66
69
  """ Converts all .mvnx files in the folder to .zoo format """
67
70
  start_time = time.time()
@@ -76,7 +79,7 @@ class BiomechZoo:
76
79
  f_zoo = f.replace('.mvnx', '.zoo')
77
80
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
78
81
  method_name = inspect.currentframe().f_code.co_name
79
- batchdisp('{} conversion complete for {} files'.format(method_name, len(fl)), level=1, verbose=verbose)
82
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
80
83
  # Update self.folder after processing
81
84
  self._update_folder(out_folder, inplace, in_folder)
82
85
 
@@ -100,17 +103,21 @@ class BiomechZoo:
100
103
  # Update self.folder after processing
101
104
  self._update_folder(out_folder, inplace, in_folder)
102
105
 
103
- def table2zoo(self, out_folder=None, inplace=None, skip_rows=0, extension='csv'):
106
+ def table2zoo(self, extension, out_folder=None, inplace=None, skip_rows=0, freq=None):
104
107
  """ Converts generic .csv file in the folder to .zoo format """
105
108
  start_time = time.time()
106
109
  verbose = self.verbose
107
110
  in_folder = self.in_folder
111
+
112
+ if extension.startswith('.'):
113
+ extension = extension[1:]
114
+
108
115
  if inplace is None:
109
116
  inplace = self.inplace
110
117
  fl = engine(in_folder, extension=extension, name_contains=self.name_contains, subfolders=self.subfolders)
111
118
  for f in fl:
112
119
  batchdisp('converting {} to zoo for {}'.format(extension, f), level=2, verbose=verbose)
113
- data = table2zoo_data(f, type=extension, skip_rows=skip_rows)
120
+ data = table2zoo_data(f, extension=extension, skip_rows=skip_rows, freq=freq)
114
121
  f_zoo = f.replace(extension, '.zoo')
115
122
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
116
123
  method_name = inspect.currentframe().f_code.co_name
@@ -119,8 +126,13 @@ class BiomechZoo:
119
126
  self._update_folder(out_folder, inplace, in_folder)
120
127
 
121
128
  def xls2zoo(self, out_folder=None, inplace=None):
122
- """ Converts generic .xls file in the folder to .zoo format """
123
- raise NotImplementedError
129
+ raise NotImplementedError('Use table2zoo instead')
130
+
131
+ def csv2zoo(self, out_folder=None, inplace=None):
132
+ raise NotImplementedError('Use table2zoo instead')
133
+
134
+ def parquet2zoo(self, out_folder=None, inplace=None):
135
+ raise NotImplementedError('Use table2zoo instead')
124
136
 
125
137
  def phase_angle(self, ch, out_folder=None, inplace=None):
126
138
  """ computes phase angles"""
@@ -185,7 +197,8 @@ class BiomechZoo:
185
197
  batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
186
198
  verbose=verbose)
187
199
  data_new = split_trial(data, start, end)
188
- zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
200
+ if data_new is not None:
201
+ zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
189
202
  method_name = inspect.currentframe().f_code.co_name
190
203
  batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
191
204
 
@@ -267,6 +280,27 @@ class BiomechZoo:
267
280
  # Update self.folder after processing
268
281
  self._update_folder(out_folder, inplace, in_folder)
269
282
 
283
+
284
+ def removeevent(self, events, mode='remove', out_folder=None, inplace=None):
285
+ """ removes channels from zoo files """
286
+ start_time = time.time()
287
+ verbose = self.verbose
288
+ in_folder = self.in_folder
289
+ if inplace is None:
290
+ inplace = self.inplace
291
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
292
+ for f in fl:
293
+ batchdisp('removing events {} for {}'.format(events, f), level=2, verbose=verbose)
294
+ data = zload(f)
295
+ data = removeevent_data(data, events, mode)
296
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
297
+ method_name = inspect.currentframe().f_code.co_name
298
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
299
+
300
+ # Update self.folder after processing
301
+ self._update_folder(out_folder, inplace, in_folder)
302
+
303
+
270
304
  def explodechannel(self, out_folder=None, inplace=None):
271
305
  """ explodes all channels in a zoo file """
272
306
  start_time = time.time()
@@ -307,7 +341,7 @@ class BiomechZoo:
307
341
  # Update self.folder after processing
308
342
  self._update_folder(out_folder, inplace, in_folder)
309
343
 
310
- def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
344
+ def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None, constant=None):
311
345
  """ adds events of type evt_type with name evt_name to channel ch """
312
346
  start_time = time.time()
313
347
  verbose = self.verbose
@@ -319,7 +353,7 @@ class BiomechZoo:
319
353
  if verbose:
320
354
  batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
321
355
  data = zload(f)
322
- data = addevent_data(data, ch, event_type, event_name)
356
+ data = addevent_data(data, ch, event_type, event_name, constant)
323
357
  zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
324
358
  method_name = inspect.currentframe().f_code.co_name
325
359
  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,98 @@
1
+ import numpy as np
2
+ import copy
3
+ import warnings
4
+ from biomechzoo.utils.peak_sign import peak_sign
5
+
6
+ def addevent_data(data, channels, ename, etype, constant=None):
7
+
8
+ data_new = copy.deepcopy(data)
9
+
10
+ if isinstance(channels, str):
11
+ channels = [channels]
12
+
13
+ if len(channels) == 1 and channels[0].lower() == 'all':
14
+ channels = [key for key in data if key != 'zoosystem']
15
+
16
+ for channel in channels:
17
+ if ename == '':
18
+ data[channel]['event'] = {}
19
+ continue
20
+
21
+ if channel not in data:
22
+ raise KeyError('Channel {} does not exist'.format(channel))
23
+
24
+ yd = data_new[channel]['line'] # 1D array
25
+ etype = etype.lower()
26
+ if etype == 'absmax':
27
+ exd = int(np.argmax(np.abs(yd)))
28
+ eyd = float(yd[exd])
29
+ elif etype == 'first':
30
+ exd = 0
31
+ eyd = float(yd[exd])
32
+ elif etype == 'last':
33
+ exd = len(yd) - 1
34
+ eyd = float(yd[exd])
35
+ elif etype == 'max':
36
+ exd = int(np.argmax(yd))
37
+ eyd = float(yd[exd])
38
+ elif etype == 'min':
39
+ exd = int(np.argmin(yd))
40
+ eyd = float(yd[exd])
41
+ elif etype == 'rom':
42
+ eyd = float(np.max(yd) - np.min(yd))
43
+ exd = 0 # dummy index (like MATLAB version)
44
+ elif etype == 'max_stance':
45
+ # special event for gait and running
46
+ exd = max_stance(yd)
47
+ eyd = float(yd[exd])
48
+ eyd = float(yd[exd])
49
+ elif etype in ['fs_fp', 'fo_fp']:
50
+ # --- Handle constant ---
51
+ if constant is None:
52
+ print('Warning: Force plate threshold not set, defaulting to 0.')
53
+ constant = 0.0
54
+
55
+ # --- Check sampling rates ---
56
+ AVR = data['zoosystem']['AVR']
57
+ if AVR != 1:
58
+ warnings.warn('Video and Analog channels must be at the same sampling rate or events will be incorrect.')
59
+
60
+ # --- Handle units ---
61
+ units = data['zoosystem']['Units']['Forces']
62
+ if units == 'N/kg':
63
+ m = data['zoosystem']['Anthro']['Bodymass']
64
+ else:
65
+ m = 1.0
66
+
67
+ # --- Extract force signal ---
68
+ if '_' not in channel:
69
+ yd = data_new[channel]['line'][:, 2] # looking for GRF Z
70
+ else:
71
+ yd = data_new[channel]['line']
72
+
73
+ # --- Determine peak sign ---
74
+ peak = peak_sign(yd) # user-defined function
75
+
76
+ # --- Find threshold crossing ---
77
+ threshold_signal = peak * yd * m
78
+ if 'fs' in etype:
79
+ exd_array = np.where(threshold_signal > constant)[0]
80
+ exd = exd_array[0] - 1 # MATLAB indexing correction
81
+ eyd = yd[exd]
82
+ else: # 'FO' type
83
+ exd_array = np.where(threshold_signal > constant)[0]
84
+ exd = exd_array[-1] + 1
85
+ eyd = yd[exd]
86
+
87
+ else:
88
+ raise ValueError(f'Unknown event type: {etype}')
89
+
90
+ # Add event to the channel's event dict
91
+ data_new[channel]['event'][ename] = [exd, eyd, 0]
92
+
93
+ return data_new
94
+
95
+ def max_stance(yd):
96
+ """ extracts max from first 40% of the gait cycle"""
97
+ raise NotImplementedError
98
+ return exd
@@ -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
@@ -0,0 +1,35 @@
1
+ import copy
2
+ from biomechzoo.utils.findfield import findfield
3
+
4
+
5
+ def split_trial(data, start_event, end_event):
6
+ # todo check index problem compared to matlab start at 0 or 1
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)
11
+
12
+ for key, value in data_new.items():
13
+ if key == 'zoosystem':
14
+ continue
15
+
16
+ # Slice the line data
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
23
+
24
+ # Update events if present
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
34
+
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.0
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
@@ -37,6 +37,7 @@ src/biomechzoo/processing/addevent_data.py
37
37
  src/biomechzoo/processing/explodechannel_data.py
38
38
  src/biomechzoo/processing/partition_data.py
39
39
  src/biomechzoo/processing/removechannel_data.py
40
+ src/biomechzoo/processing/removeevent_data.py
40
41
  src/biomechzoo/processing/renamechannel_data.py
41
42
  src/biomechzoo/processing/renameevent_data.py
42
43
  src/biomechzoo/processing/split_trial_by_gait_cycle.py
@@ -46,6 +47,7 @@ src/biomechzoo/utils/compute_sampling_rate_from_time.py
46
47
  src/biomechzoo/utils/engine.py
47
48
  src/biomechzoo/utils/findfield.py
48
49
  src/biomechzoo/utils/get_split_events.py
50
+ src/biomechzoo/utils/peak_sign.py
49
51
  src/biomechzoo/utils/set_zoosystem.py
50
52
  src/biomechzoo/utils/split_trial.py
51
53
  src/biomechzoo/utils/version.py
@@ -1,56 +0,0 @@
1
- import numpy as np
2
-
3
-
4
- def addevent_data(data, ch, ename, etype):
5
- if isinstance(ch, str):
6
- ch = [ch]
7
-
8
- if len(ch) == 1 and ch[0].lower() == 'all':
9
- ch = [key for key in data if key != 'zoosystem']
10
-
11
- for channel in ch:
12
- if ename == '':
13
- data[channel]['event'] = {}
14
- continue
15
-
16
- if channel not in data:
17
- print(f'Channel {channel} does not exist')
18
- continue
19
-
20
- yd = data[channel]['line'] # 1D array
21
- etype = etype.lower()
22
- if etype == 'absmax':
23
- exd = int(np.argmax(np.abs(yd)))
24
- eyd = float(yd[exd])
25
- elif etype == 'first':
26
- exd = 0
27
- eyd = float(yd[exd])
28
- elif etype == 'last':
29
- exd = len(yd) - 1
30
- eyd = float(yd[exd])
31
- elif etype == 'max':
32
- exd = int(np.argmax(yd))
33
- eyd = float(yd[exd])
34
- elif etype == 'min':
35
- exd = int(np.argmin(yd))
36
- eyd = float(yd[exd])
37
- elif etype == 'rom':
38
- eyd = float(np.max(yd) - np.min(yd))
39
- exd = 0 # dummy index (like MATLAB version)
40
- elif etype == 'max_stance':
41
- # special event for gait and running
42
- exd = max_stance(yd)
43
- eyd = float(yd[exd])
44
- eyd = float(yd[exd])
45
- else:
46
- raise ValueError(f'Unknown event type: {etype}')
47
-
48
- # Add event to the channel's event dict
49
- data[channel]['event'][ename] = [exd, eyd, 0]
50
-
51
- return data
52
-
53
- def max_stance(yd):
54
- """ extracts max from first 40% of the gait cycle"""
55
- raise NotImplementedError
56
- return exd
@@ -1,23 +0,0 @@
1
- def split_trial(data, start_event_indx, end_event_indx):
2
- # todo check index problem compared to matlab start at 0 or 1
3
- data_new = data.copy()
4
-
5
- for key, value in data_new.items():
6
- if key == 'zoosystem':
7
- continue
8
-
9
- # Slice the line data
10
- data_new[key]['line'] = value['line'][start_event_indx:end_event_indx]
11
-
12
- # 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
22
-
23
- return data_new
File without changes
File without changes
File without changes
File without changes