biomechzoo 0.4.0__tar.gz → 0.4.4__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 (53) hide show
  1. {biomechzoo-0.4.0/src/biomechzoo.egg-info → biomechzoo-0.4.4}/PKG-INFO +3 -2
  2. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/pyproject.toml +5 -4
  3. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomechzoo.py +104 -61
  4. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/conversion/csv2zoo_data.py +40 -20
  5. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/conversion/mvnx2zoo_data.py +18 -7
  6. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/mvn/load_mvnx.py +2 -2
  7. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/mvn/main_mvnx.py +1 -1
  8. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/mvn/mvnx_file_accessor.py +1 -1
  9. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/partition_data.py +4 -4
  10. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/engine.py +6 -1
  11. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/zsave.py +7 -3
  12. {biomechzoo-0.4.0 → biomechzoo-0.4.4/src/biomechzoo.egg-info}/PKG-INFO +3 -2
  13. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo.egg-info/requires.txt +1 -0
  14. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/LICENSE +0 -0
  15. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/README.md +0 -0
  16. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/setup.cfg +0 -0
  17. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/__init__.py +0 -0
  18. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/__main__.py +0 -0
  19. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/__init__.py +0 -0
  20. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +0 -0
  21. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +0 -0
  22. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/filter_data.py +0 -0
  23. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/filter_line.py +0 -0
  24. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/normalize_data.py +0 -0
  25. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/normalize_line.py +0 -0
  26. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/phase_angle_data.py +0 -0
  27. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/biomech_ops/phase_angle_line.py +0 -0
  28. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/conversion/__init__.py +0 -0
  29. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/conversion/c3d2zoo_data.py +0 -0
  30. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/conversion/opencap2zoo_data.py +0 -0
  31. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/mvn/__init__.py +0 -0
  32. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/mvn/mvn.py +0 -0
  33. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/__init__.py +0 -0
  34. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/add_channel_data.py +0 -0
  35. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/addchannel_data.py +0 -0
  36. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/addevent_data.py +0 -0
  37. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/explodechannel_data.py +0 -0
  38. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/removechannel_data.py +0 -0
  39. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/renamechannel_data.py +0 -0
  40. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/renameevent_data.py +0 -0
  41. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/processing/split_trial_by_gait_cycle.py +0 -0
  42. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/__init__.py +0 -0
  43. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/batchdisp.py +0 -0
  44. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/compute_sampling_rate_from_time.py +0 -0
  45. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/findfield.py +0 -0
  46. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/get_split_events.py +0 -0
  47. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/split_trial.py +0 -0
  48. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/zload.py +0 -0
  49. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo/utils/zplot.py +0 -0
  50. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo.egg-info/SOURCES.txt +0 -0
  51. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo.egg-info/dependency_links.txt +0 -0
  52. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo.egg-info/entry_points.txt +0 -0
  53. {biomechzoo-0.4.0 → biomechzoo-0.4.4}/src/biomechzoo.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomechzoo
3
- Version: 0.4.0
3
+ Version: 0.4.4
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
7
- Requires-Python: >=3.11
7
+ Requires-Python: <3.12,>=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: ezc3d>=1.5.19
@@ -12,6 +12,7 @@ Requires-Dist: matplotlib>=3.10.6
12
12
  Requires-Dist: numpy==2.2.6
13
13
  Requires-Dist: pandas>=2.3.2
14
14
  Requires-Dist: scipy>=1.16.2
15
+ Requires-Dist: pyarrow>=19.0.0
15
16
  Dynamic: license-file
16
17
 
17
18
  # BiomechZoo for Python
@@ -1,17 +1,18 @@
1
1
  [project]
2
2
  name = "biomechzoo"
3
- version = "0.4.0"
3
+ version = "0.4.4"
4
4
  description = "Python implementation of the biomechZoo toolbox"
5
5
  readme = "README.md"
6
- requires-python = ">=3.11"
6
+ requires-python = ">=3.11,<3.12" # max version for opencap-process tools is 3.11
7
7
  license = "MIT"
8
8
  license-files = ["LICEN[CS]E*"]
9
9
  dependencies = [
10
- "ezc3d>=1.5.19",
10
+ "ezc3d>=1.5.19", # for reading c3d files
11
11
  "matplotlib>=3.10.6",
12
- "numpy==2.2.6",
12
+ "numpy==2.2.6", # max version for opencap-process tools
13
13
  "pandas>=2.3.2",
14
14
  "scipy>=1.16.2",
15
+ "pyarrow>=19.0.0", # for parquet support
15
16
  ]
16
17
 
17
18
  [project.urls]
@@ -1,4 +1,6 @@
1
1
  import os
2
+ import inspect
3
+ import time
2
4
  from biomechzoo.utils.engine import engine # assumes this returns .zoo files in folder
3
5
  from biomechzoo.utils.zload import zload
4
6
  from biomechzoo.utils.zsave import zsave
@@ -20,15 +22,27 @@ from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_rel
20
22
 
21
23
 
22
24
  class BiomechZoo:
23
- def __init__(self, in_folder, inplace=False, verbose=0):
25
+ def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
24
26
  self.verbose = verbose
25
27
  self.in_folder = in_folder
26
28
  self.verbose = verbose
27
- self.inplace = inplace # choice to save processed files to new folder
29
+ self.inplace = inplace # choice to save processed files to new folder
30
+ self.subfolders = subfolders # only run processes on list in subfolder
31
+ self.name_contains = name_contains # only run processes on files with name_contains in file name
28
32
 
29
33
  batchdisp('BiomechZoo initialized', level=1, verbose=verbose)
30
34
  batchdisp('verbosity set to: {}'.format(verbose), level=1, verbose=verbose)
31
- batchdisp('processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
35
+ batchdisp('root processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
36
+ if name_contains is not None:
37
+ batchdisp('only include files containing name_contains string: {}'.format(self.name_contains), level=1, verbose=verbose)
38
+ if subfolders is not None:
39
+ if type(subfolders) is list:
40
+ batchdisp('only process files in subfolder(s):', level=1, verbose=verbose)
41
+ for subfolder in self.subfolders:
42
+ batchdisp('{}'.format(os.path.join(self.in_folder, subfolder)), level=1, verbose=verbose)
43
+ else:
44
+ batchdisp('only process files in subfolder(s): {}'.format(os.path.join(self.in_folder, self.subfolders)), level=1, verbose=verbose)
45
+
32
46
  if inplace:
33
47
  batchdisp('Processing mode: overwrite (inplace=True) (each step will be applied to same folder)', level=1, verbose=verbose)
34
48
  else:
@@ -50,57 +64,75 @@ class BiomechZoo:
50
64
 
51
65
  def mvnx2zoo(self, out_folder=None, inplace=False):
52
66
  """ Converts all .mvnx files in the folder to .zoo format """
67
+ start_time = time.time()
53
68
  verbose = self.verbose
54
69
  in_folder = self.in_folder
55
70
  if inplace is None:
56
71
  inplace = self.inplace
57
-
58
- fl = engine(in_folder, extension='.mvnx')
72
+ fl = engine(in_folder, extension='.mvnx', name_contains=self.name_contains, subfolders=self.subfolders)
59
73
  for f in fl:
60
74
  batchdisp('converting mvnx to zoo for {}'.format(f), level=2, verbose=verbose)
61
75
  data = mvnx2zoo_data(f)
62
76
  f_zoo = f.replace('.mvnx', '.zoo')
63
77
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
64
- batchdisp('mvnx to zoo conversion complete', level=1, verbose=verbose)
65
-
78
+ method_name = inspect.currentframe().f_code.co_name
79
+ batchdisp('{} conversion complete for {} files'.format(method_name, len(fl)), level=1, verbose=verbose)
66
80
  # Update self.folder after processing
67
81
  self._update_folder(out_folder, inplace, in_folder)
68
82
 
69
83
  def c3d2zoo(self, out_folder=None, inplace=None):
70
84
  """ Converts all .c3d files in the folder to .zoo format """
85
+ start_time = time.time()
71
86
  from ezc3d import c3d
72
87
  verbose = self.verbose
73
88
  in_folder = self.in_folder
74
89
  if inplace is None:
75
90
  inplace = self.inplace
76
-
77
- fl = engine(in_folder, extension='.c3d')
91
+ fl = engine(in_folder, extension='.c3d', name_contains=self.name_contains, subfolders=self.subfolders)
78
92
  for f in fl:
79
93
  batchdisp('converting c3d to zoo for {}'.format(f), level=2, verbose=verbose)
80
94
  c3d_obj = c3d(f)
81
95
  data = c3d2zoo_data(c3d_obj)
82
96
  f_zoo = f.replace('.c3d', '.zoo')
83
97
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
84
- batchdisp('c3d to zoo conversion complete', level=1, verbose=verbose)
85
-
98
+ method_name = inspect.currentframe().f_code.co_name
99
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
86
100
  # Update self.folder after processing
87
101
  self._update_folder(out_folder, inplace, in_folder)
88
102
 
89
- def csv2zoo(self, out_folder=None, inplace=None):
103
+ def csv2zoo(self, out_folder=None, inplace=None, skip_rows=0):
90
104
  """ Converts generic .csv file in the folder to .zoo format """
105
+ start_time = time.time()
91
106
  verbose = self.verbose
92
107
  in_folder = self.in_folder
93
108
  if inplace is None:
94
109
  inplace = self.inplace
95
-
96
- fl = engine(in_folder, extension='.csv')
110
+ fl = engine(in_folder, extension='.csv', name_contains=self.name_contains, subfolders=self.subfolders)
97
111
  for f in fl:
98
112
  batchdisp('converting csv to zoo for {}'.format(f), level=2, verbose=verbose)
99
- data = csv2zoo_data(f)
113
+ data = csv2zoo_data(f, type='csv', skip_rows=skip_rows)
100
114
  f_zoo = f.replace('.csv', '.zoo')
101
115
  zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
102
- batchdisp('csv to zoo conversion complete', level=1, verbose=verbose)
116
+ method_name = inspect.currentframe().f_code.co_name
117
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
118
+ # Update self.folder after processing
119
+ self._update_folder(out_folder, inplace, in_folder)
103
120
 
121
+ def parquet2zoo(self, out_folder=None, inplace=None):
122
+ """ Converts generic .csv file in the folder to .zoo format """
123
+ start_time = time.time()
124
+ verbose = self.verbose
125
+ in_folder = self.in_folder
126
+ if inplace is None:
127
+ inplace = self.inplace
128
+ fl = engine(in_folder, extension='.parquet', name_contains=self.name_contains, subfolders=self.subfolders)
129
+ for f in fl:
130
+ batchdisp('converting parquet to zoo for {}'.format(f), level=2, verbose=verbose)
131
+ data = csv2zoo_data(f, type='parquet')
132
+ f_zoo = f.replace('.parquet', '.zoo')
133
+ zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
134
+ method_name = inspect.currentframe().f_code.co_name
135
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
104
136
  # Update self.folder after processing
105
137
  self._update_folder(out_folder, inplace, in_folder)
106
138
 
@@ -110,67 +142,70 @@ class BiomechZoo:
110
142
 
111
143
  def phase_angle(self, ch, out_folder=None, inplace=None):
112
144
  """ computes phase angles"""
145
+ start_time = time.time()
113
146
  verbose = self.verbose
114
147
  in_folder = self.in_folder
115
148
  if inplace is None:
116
149
  inplace = self.inplace
117
-
118
- fl = engine(in_folder)
150
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
119
151
  for f in fl:
120
152
  if verbose:
121
153
  batchdisp('computing phase angles for {}'.format(f), level=2, verbose=verbose)
122
154
  data = zload(f)
123
155
  data = phase_angle_data(data, ch)
124
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
125
- batchdisp('phase angle computation complete', level=1, verbose=verbose)
156
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
157
+ method_name = inspect.currentframe().f_code.co_name
158
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
126
159
 
127
160
  # Update self.folder after processing
128
161
  self._update_folder(out_folder, inplace, in_folder)
129
162
 
130
163
  def continuous_relative_phase(self, ch_prox, ch_dist, out_folder=None, inplace=None):
131
164
  """ computes CRP angles"""
165
+ start_time = time.time()
132
166
  verbose = self.verbose
133
167
  in_folder = self.in_folder
134
168
  if inplace is None:
135
169
  inplace = self.inplace
136
-
137
- fl = engine(in_folder)
170
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
138
171
  for f in fl:
139
172
  if verbose:
140
173
  batchdisp('computing CRP angles between channel {} (prox) and {} (dist) for {}'.format(ch_prox, ch_dist, f), level=2, verbose=verbose)
141
174
  data = zload(f)
142
175
  data = continuous_relative_phase_data(data, ch_dist, ch_prox)
143
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
144
- batchdisp('CRP computation complete', level=1, verbose=verbose)
176
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
177
+ method_name = inspect.currentframe().f_code.co_name
178
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
145
179
 
146
180
  # Update self.folder after processing
147
181
  self._update_folder(out_folder, inplace, in_folder)
148
182
 
149
183
  def split_trial_by_gait_cycle(self, first_event_name, out_folder=None, inplace=None):
150
184
  """ split by gait cycle according to event_name"""
185
+ start_time = time.time()
151
186
  verbose = self.verbose
152
187
  in_folder = self.in_folder
153
188
  if inplace is None:
154
189
  inplace = self.inplace
155
-
156
- fl = engine(in_folder)
190
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
157
191
  for f in fl:
158
192
  f_name = os.path.splitext(os.path.basename(f))[0]
159
- batchdisp('splitting by gait cycle for {} by {}'.format(f, first_event_name), level=2, verbose=verbose)
160
193
  data = zload(f)
161
194
  split_events = get_split_events(data, first_event_name)
162
195
  if split_events is None:
163
196
  print('no event {} found, saving original file'.format(first_event_name))
164
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
197
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
165
198
  else:
166
199
  for i, _ in enumerate(split_events[0:-1]):
167
200
  fl_new = f.replace(f_name, f_name + '_' + str(i + 1))
168
201
  start = split_events[i]
169
202
  end = split_events[i + 1]
203
+ batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
204
+ verbose=verbose)
170
205
  data_new = split_trial(data, start, end)
171
- zsave(fl_new, data_new, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
172
-
173
- batchdisp('splitting by gait cycle complete', level=1, verbose=verbose)
206
+ zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
207
+ method_name = inspect.currentframe().f_code.co_name
208
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
174
209
 
175
210
  # Update self.folder after processing
176
211
  self._update_folder(out_folder, inplace, in_folder)
@@ -195,130 +230,137 @@ class BiomechZoo:
195
230
  # self._update_folder(out_folder, inplace, in_folder)
196
231
  def renameevent(self, evt, nevt, out_folder=None, inplace=None):
197
232
  """ renames event evt to nevt in all zoo files """
233
+ start_time = time.time()
198
234
  verbose = self.verbose
199
235
  in_folder = self.in_folder
200
236
  if inplace is None:
201
237
  inplace = self.inplace
202
-
203
- fl = engine(in_folder)
238
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
204
239
  for f in fl:
205
240
  batchdisp('renaming events from {} to {} for {}'.format(evt, nevt ,f), level=2, verbose=verbose)
206
241
  data = zload(f)
207
242
  data = renameevent_data(data, evt, nevt)
208
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
209
- batchdisp('rename event complete', level=1, verbose=verbose)
243
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
244
+ method_name = inspect.currentframe().f_code.co_name
245
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
210
246
 
211
247
  # Update self.folder after processing
212
248
  self._update_folder(out_folder, inplace, in_folder)
213
249
 
214
250
  def renamechannnel(self, ch, ch_new, out_folder=None, inplace=None):
215
251
  """ renames channels from ch to ch_new in all zoo files """
252
+ start_time = time.time()
216
253
  verbose = self.verbose
217
254
  in_folder = self.in_folder
218
255
  if inplace is None:
219
256
  inplace = self.inplace
220
-
221
- fl = engine(in_folder)
257
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
222
258
  for f in fl:
223
259
  batchdisp('renaming channels from {} to {} for {}'.format(ch, ch_new ,f), level=2, verbose=verbose)
224
260
  data = zload(f)
225
261
  data = renamechannel_data(data, ch, ch_new)
226
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
227
- batchdisp('rename channels complete', level=1, verbose=verbose)
262
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
263
+ method_name = inspect.currentframe().f_code.co_name
264
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
228
265
 
229
266
  # Update self.folder after processing
230
267
  self._update_folder(out_folder, inplace, in_folder)
231
268
 
232
269
  def removechannel(self, ch, mode='remove', out_folder=None, inplace=None):
233
270
  """ removes channels from zoo files """
271
+ start_time = time.time()
234
272
  verbose = self.verbose
235
273
  in_folder = self.in_folder
236
274
  if inplace is None:
237
275
  inplace = self.inplace
238
-
239
- fl = engine(in_folder)
276
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
240
277
  for f in fl:
241
278
  batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
242
279
  data = zload(f)
243
280
  data = removechannel_data(data, ch, mode)
244
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
245
- batchdisp('remove channel complete', level=1, verbose=verbose)
281
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
282
+ method_name = inspect.currentframe().f_code.co_name
283
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
246
284
 
247
285
  # Update self.folder after processing
248
286
  self._update_folder(out_folder, inplace, in_folder)
249
287
 
250
288
  def explodechannel(self, out_folder=None, inplace=None):
251
289
  """ explodes all channels in a zoo file """
290
+ start_time = time.time()
252
291
  verbose = self.verbose
253
292
  in_folder = self.in_folder
254
293
  if inplace is None:
255
294
  inplace = self.inplace
256
-
257
- fl = engine(in_folder)
295
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
258
296
  for f in fl:
259
297
  if verbose:
260
298
  batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
261
299
  data = zload(f)
262
300
  data = explodechannel_data(data)
263
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
264
- batchdisp('explode channel complete', level=1, verbose=verbose)
301
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
302
+ method_name = inspect.currentframe().f_code.co_name
303
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
265
304
 
266
305
  # Update self.folder after processing
267
306
  self._update_folder(out_folder, inplace, in_folder)
268
307
 
269
308
  def normalize(self, nlen=101, out_folder=None, inplace=None):
270
309
  """ time normalizes all channels to length nlen """
310
+ start_time = time.time()
271
311
  verbose = self.verbose
272
312
  in_folder = self.in_folder
273
313
  if inplace is None:
274
314
  inplace = self.inplace
275
-
276
- fl = engine(in_folder)
315
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
277
316
  for f in fl:
278
317
  if verbose:
279
318
  batchdisp('normalizing channels to length {} for {}'.format(nlen, f), level=2, verbose=verbose)
280
319
  data = zload(f)
281
320
  data = normalize_data(data, nlen)
282
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
283
- batchdisp('normalization complete', level=1, verbose=verbose)
321
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
322
+ method_name = inspect.currentframe().f_code.co_name
323
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
284
324
 
285
325
  # Update self.folder after processing
286
326
  self._update_folder(out_folder, inplace, in_folder)
287
327
 
288
328
  def addevent(self, ch, evt_type, evt_name, out_folder=None, inplace=None):
289
329
  """ adds events of type evt_type with name evt_name to channel ch """
330
+ start_time = time.time()
290
331
  verbose = self.verbose
291
332
  in_folder = self.in_folder
292
333
  if inplace is None:
293
334
  inplace = self.inplace
294
-
295
- fl = engine(in_folder)
335
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
296
336
  for f in fl:
297
337
  if verbose:
298
338
  batchdisp('adding event {} to channel {} for {}'.format(evt_type, ch, f), level=2, verbose=verbose)
299
339
  data = zload(f)
300
340
  data = addevent_data(data, ch, evt_type, evt_name)
301
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
302
- batchdisp('add event complete', level=1, verbose=verbose)
341
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
342
+ method_name = inspect.currentframe().f_code.co_name
343
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
303
344
 
304
345
  # Update self.folder after processing
305
346
  self._update_folder(out_folder, inplace, in_folder)
306
347
 
307
348
  def partition(self, evt_start, evt_end, out_folder=None, inplace=None):
308
349
  """ partitions data between events evt_start and evt_end """
350
+ start_time = time.time()
309
351
  verbose = self.verbose
310
352
  in_folder = self.in_folder
311
353
  if inplace is None:
312
354
  inplace = self.inplace
313
-
314
- fl = engine(in_folder)
355
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
315
356
  for f in fl:
316
357
  if verbose:
317
358
  batchdisp('partitioning data between events {} and {} for {}'.format(evt_start, evt_end, f), level=2, verbose=verbose)
318
359
  data = zload(f)
319
360
  data = partition_data(data, evt_start, evt_end)
320
- zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
321
- batchdisp('partition complete', level=1, verbose=verbose)
361
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
362
+ method_name = inspect.currentframe().f_code.co_name
363
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
322
364
  # Update self.folder after processing
323
365
  self._update_folder(out_folder, inplace, in_folder)
324
366
 
@@ -340,8 +382,9 @@ class BiomechZoo:
340
382
  # batchdisp('filtering data in channels {} for {}'.format(ch, f), level=2, verbose=verbose)
341
383
  # data = zload(f)
342
384
  # data = filter_data(data, ch, filt)
343
- # zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
344
- # batchdisp('filter data complete', level=1, verbose=verbose)
385
+ # zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder, verbose=verbose)
386
+ # method_name = inspect.currentframe().f_code.co_name
387
+ # batchdisp('{} computation complete for {} file(s)'.format(method_name, len(fl)), level=1, verbose=verbose)
345
388
  #
346
389
  # # Update self.folder after processing
347
390
  # self._update_folder(out_folder, inplace, in_folder)
@@ -5,25 +5,32 @@ import re
5
5
  from biomechzoo.utils.compute_sampling_rate_from_time import compute_sampling_rate_from_time
6
6
 
7
7
 
8
- def csv2zoo_data(csv_path, header_len=10):
8
+ def csv2zoo_data(csv_path, type='csv',skip_rows=0, freq=None):
9
+ # todo: check calculation for sampling rate
9
10
 
10
11
  # Read header lines until 'endheader'
11
- header_lines = []
12
- with open(csv_path, 'r') as f:
13
- for line in f:
14
- header_lines.append(line.strip())
15
- if line.strip().lower() == 'endheader':
16
- break
17
-
18
- # Parse metadata
19
- metadata = _parse_metadata(header_lines)
20
-
21
- # Step 3: Load data
22
- df = pd.read_csv(csv_path, skiprows=header_len)
23
- time = df.iloc[:, 0].values # first column is Time
24
- data = df.iloc[:, 1:]
25
-
26
- # S Assemble zoo data
12
+ metadata = {}
13
+ if type == 'csv' and skip_rows > 0:
14
+ header_lines = []
15
+ with open(csv_path, 'r') as f:
16
+ for line in f:
17
+ header_lines.append(line.strip())
18
+ if line.strip().lower() == 'endheader':
19
+ break
20
+ # Parse metadata
21
+ metadata = _parse_metadata(header_lines)
22
+
23
+ if type == 'csv':
24
+ df = pd.read_csv(csv_path, skiprows=skip_rows)
25
+ elif type =='parquet':
26
+ df = pd.read_parquet(csv_path)
27
+ else:
28
+ raise ValueError('type must be csv or parquet')
29
+
30
+ # Use all columns
31
+ data = df.iloc[:, 0:]
32
+
33
+ # assemble zoo data
27
34
  zoo_data = {}
28
35
  for ch in data.columns:
29
36
  zoo_data[ch] = {
@@ -31,13 +38,26 @@ def csv2zoo_data(csv_path, header_len=10):
31
38
  'event': []
32
39
  }
33
40
 
34
- # compute sampling rate
35
- fsamp = compute_sampling_rate_from_time(time)
41
+ # try to find frequency in metadata
42
+ if freq is None:
43
+ if 'freq' in metadata:
44
+ freq = metadata['freq']
45
+ elif 'sampling_rate' in metadata:
46
+ freq = metadata['sampling_rate']
47
+ else:
48
+ freq = None # or raise an error
49
+
50
+ # now try to calculate freq from a time column
51
+ if freq is None:
52
+ time_col = [col for col in df.columns if 'time' in col.lower()]
53
+ if time_col is not None:
54
+ time_data = df[time_col].to_numpy()[:,0]
55
+ freq = compute_sampling_rate_from_time(time_data)
36
56
 
37
57
  # add metadata
38
58
  # todo update zoosystem to match biomechzoo requirements
39
59
  zoo_data['zoosystem'] = metadata
40
- zoo_data['zoosystem']['Freq'] = fsamp
60
+ zoo_data['zoosystem']['Freq'] = freq
41
61
 
42
62
  return zoo_data
43
63
 
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
- from src.biomechzoo.mvn.load_mvnx import load_mvnx
3
- from src.biomechzoo.mvn.mvn import JOINTS, SEGMENTS
2
+ from biomechzoo.mvn.load_mvnx import load_mvnx
3
+ from biomechzoo.mvn.mvn import JOINTS, SEGMENTS
4
4
 
5
5
  def mvnx2zoo_data(fl):
6
6
  """ loads mvnx file from xsens"""
@@ -15,7 +15,7 @@ def mvnx2zoo_data(fl):
15
15
  try:
16
16
  r = mvnx_file.get_joint_angle(joint=key)
17
17
  data[val] = {
18
- 'line': r,
18
+ 'line': np.array(r),
19
19
  'event': {}
20
20
  }
21
21
  except KeyError:
@@ -25,8 +25,9 @@ def mvnx2zoo_data(fl):
25
25
  for key, val in SEGMENTS.items():
26
26
  try:
27
27
  r = mvnx_file.get_sensor_ori(segment=key)
28
+
28
29
  data[val] = {
29
- 'line': r,
30
+ 'line': np.array(r),
30
31
  'event': {}
31
32
  }
32
33
  except KeyError:
@@ -40,6 +41,17 @@ def mvnx2zoo_data(fl):
40
41
 
41
42
  return data
42
43
 
44
+ def is_valid_for_zoo(val):
45
+ """
46
+ Returns True if the value is valid for a MATLAB-compatible zoo structure.
47
+ """
48
+ if val is None:
49
+ return False
50
+ if isinstance(val, list) and len(val) == 0:
51
+ return False
52
+ if isinstance(val, np.ndarray) and val.size == 0:
53
+ return False
54
+ return True
43
55
 
44
56
  def _get_meta_info(mvnx_file, data):
45
57
  # todo: add more, see mvnx_file object
@@ -51,7 +63,6 @@ def _get_meta_info(mvnx_file, data):
51
63
  data['zoosystem']['recording_date'] = mvnx_file.recording_date
52
64
  data['zoosystem']['original_file_name'] = mvnx_file.original_file_name
53
65
  data['zoosystem']['frame_count'] = mvnx_file.frame_count
54
- data['zoosystem']['comments'] = mvnx_file.comments
55
66
  return data
56
67
 
57
68
 
@@ -75,7 +86,7 @@ def _get_foot_strike_events(mvnx_file, data):
75
86
  if LHeel[i - 1] == 0 and LHeel[i] == 1:
76
87
  hs_l.append(i)
77
88
 
78
- # add to event branch of any channel
89
+ # add to event branch of any channel
79
90
  if 'jL5S1' in data:
80
91
  ch = 'jL5S1'
81
92
  else:
@@ -97,6 +108,6 @@ if __name__ == '__main__':
97
108
  from src.biomechzoo.utils.zplot import zplot
98
109
  # -------TESTING--------
99
110
  project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
100
- fl = os.path.join(project_root, 'data', 'other', 'Flat-001.mvnx')
111
+ fl = os.path.join(project_root, 'data', 'other', 'Flat001.mvnx')
101
112
  data = mvnx2zoo_data(fl)
102
113
  zplot(data, 'jRightKnee')
@@ -6,8 +6,8 @@ Script to load an mvnx
6
6
  import xml.etree.ElementTree as ET
7
7
  import collections
8
8
  import numpy as np
9
- from src.biomechzoo.mvn.mvnx_file_accessor import MvnxFileAccessor
10
- from src.biomechzoo.mvn import mvn
9
+ from biomechzoo.mvn.mvnx_file_accessor import MvnxFileAccessor
10
+ from biomechzoo.mvn import mvn
11
11
 
12
12
  # Xml namespace for mvnx files
13
13
  ns = {'mvn': 'http://www.xsens.com/mvn/mvnx'}
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import argparse
3
3
  import matplotlib.pyplot as plt
4
- from src.biomechzoo.mvn.load_mvnx import load_mvnx
4
+ from biomechzoo.mvn.load_mvnx import load_mvnx
5
5
 
6
6
 
7
7
  # Convert mvnx file to python data
@@ -1,4 +1,4 @@
1
- from src.biomechzoo.mvn import mvn
1
+ from biomechzoo.mvn import mvn
2
2
  import warnings
3
3
 
4
4
 
@@ -6,14 +6,14 @@ def partition_data(data, evt_start, evt_end):
6
6
  """ partition data for all channels between events evt_start and evt_end"""
7
7
 
8
8
  # extract event values
9
- e1, _ = findfield(data,evt_start)
10
- e2, _ = findfield(data,evt_end)
9
+ e1, _ = findfield(data, evt_start)
10
+ e2, _ = findfield(data, evt_end)
11
11
 
12
12
  data_new = data.copy()
13
13
  for ch_name, ch_data in data_new.items():
14
14
  if ch_name != 'zoosystem':
15
15
  try:
16
- data_new[ch_name]['line'] = ch_data[e1[0]:e2[0], :]
16
+ data_new[ch_name]['line'] = ch_data[e1[0]:e2[0],]
17
17
  except (IndexError, ValueError) as e:
18
18
  # IndexError: if e1[0]:e2[0] goes beyond the available indices
19
19
  # ValueError: less likely, but may arise with shape mismatches
@@ -48,4 +48,4 @@ def _partition_event(event_dict, evt_start, evt_end, arr_len):
48
48
  #
49
49
  #
50
50
  #
51
- # return event_dict_new
51
+ # return event_dict_new
@@ -19,7 +19,7 @@ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, v
19
19
  Arguments:
20
20
  root_folder (str): The root directory path where the search begins.
21
21
  extension (str): File extension to search for (e.g., '.zoo', '.c3d'). Default .zoo
22
- subfolders (list of str, optional): List of folder names to restrict the search to.
22
+ subfolders (list or str, optional): List of folder names to restrict the search to.
23
23
  Only files inside these folders (or their subfolders) are included.
24
24
  If None, search all subfolders.
25
25
  name_contains (str, optional): Substring that must be present in the filename
@@ -28,6 +28,11 @@ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, v
28
28
  Returns:
29
29
  list of str: List of full file paths matching the criteria.
30
30
  """
31
+ # check format of subfolder (string or list)
32
+ if subfolders is not None:
33
+ if type(subfolders) is str:
34
+ subfolders = [subfolders]
35
+
31
36
  matched_files = []
32
37
 
33
38
  subfolders_set = set(subfolders) if subfolders else None
@@ -2,8 +2,10 @@ from scipy.io import savemat
2
2
  import inspect
3
3
  import os
4
4
 
5
+ from biomechzoo.utils.batchdisp import batchdisp
5
6
 
6
- def zsave(fl, data, inplace=True, out_folder=None, root_folder=None):
7
+
8
+ def zsave(fl, data, inplace=True, out_folder=None, root_folder=None, verbose=False):
7
9
  """
8
10
  Save zoo data to .zoo file (MAT format)
9
11
 
@@ -28,9 +30,9 @@ def zsave(fl, data, inplace=True, out_folder=None, root_folder=None):
28
30
 
29
31
  # Determine save path
30
32
  if inplace:
31
- save_path = fl
33
+ fl_new = fl
34
+ out_dir = os.path.dirname(fl)
32
35
  else:
33
- filename = os.path.basename(fl)
34
36
  if out_folder is None:
35
37
  out_folder = 'processed'
36
38
 
@@ -47,4 +49,6 @@ def zsave(fl, data, inplace=True, out_folder=None, root_folder=None):
47
49
 
48
50
  # Save the .zoo file
49
51
  savemat(fl_new, data)
52
+ batchdisp('all files saved to ' + out_dir, level=1, verbose=verbose)
53
+
50
54
 
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomechzoo
3
- Version: 0.4.0
3
+ Version: 0.4.4
4
4
  Summary: Python implementation of the biomechZoo toolbox
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
7
- Requires-Python: >=3.11
7
+ Requires-Python: <3.12,>=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: ezc3d>=1.5.19
@@ -12,6 +12,7 @@ Requires-Dist: matplotlib>=3.10.6
12
12
  Requires-Dist: numpy==2.2.6
13
13
  Requires-Dist: pandas>=2.3.2
14
14
  Requires-Dist: scipy>=1.16.2
15
+ Requires-Dist: pyarrow>=19.0.0
15
16
  Dynamic: license-file
16
17
 
17
18
  # BiomechZoo for Python
@@ -3,3 +3,4 @@ matplotlib>=3.10.6
3
3
  numpy==2.2.6
4
4
  pandas>=2.3.2
5
5
  scipy>=1.16.2
6
+ pyarrow>=19.0.0
File without changes
File without changes
File without changes