biomechzoo 0.4.1__py3-none-any.whl → 0.4.5__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.

__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ BiomechZoo: A Python toolbox for processing and analyzing human movement data.
3
+
4
+ This package provides functions for converting, processing, analyzing,
5
+ and visualizing biomechanical data (e.g., motion capture, EMG, kinetics).
6
+
7
+ Example:
8
+ from biomechzoo import BiomechZoo
9
+ from biomechzoo.conversion import c3d2zoo
10
+
11
+ zoo = BiomechZoo()
12
+ zoo.conversion.c3d2zoo('path/to/data')
13
+ """
14
+
15
+ # Import main class or entry point
16
+ from .biomechzo import BiomechZoo
17
+
18
+ # Import commonly used submodules
19
+ from . import conversion
20
+ from . import processing
21
+ from . import plotting
22
+ from . import utils
23
+
24
+ # Define what gets exposed with "from biomechzoo import *"
25
+ __all__ = [
26
+ "BiomechZoo",
27
+ "conversion",
28
+ "processing",
29
+ "plotting",
30
+ "utils",
31
+ ]
32
+
33
+ __version__ = "0.4.4"
biomechzoo/biomechzoo.py CHANGED
@@ -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
@@ -18,17 +20,28 @@ from biomechzoo.biomech_ops.normalize_data import normalize_data
18
20
  from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
19
21
  from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
20
22
 
21
-
22
23
  class BiomechZoo:
23
- def __init__(self, in_folder, inplace=False, verbose=0):
24
+ def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
24
25
  self.verbose = verbose
25
26
  self.in_folder = in_folder
26
27
  self.verbose = verbose
27
- self.inplace = inplace # choice to save processed files to new folder
28
+ self.inplace = inplace # choice to save processed files to new folder
29
+ self.subfolders = subfolders # only run processes on list in subfolder
30
+ self.name_contains = name_contains # only run processes on files with name_contains in file name
28
31
 
29
32
  batchdisp('BiomechZoo initialized', level=1, verbose=verbose)
30
33
  batchdisp('verbosity set to: {}'.format(verbose), level=1, verbose=verbose)
31
- batchdisp('processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
34
+ batchdisp('root processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
35
+ if name_contains is not None:
36
+ batchdisp('only include files containing name_contains string: {}'.format(self.name_contains), level=1, verbose=verbose)
37
+ if subfolders is not None:
38
+ if type(subfolders) is list:
39
+ batchdisp('only process files in subfolder(s):', level=1, verbose=verbose)
40
+ for subfolder in self.subfolders:
41
+ batchdisp('{}'.format(os.path.join(self.in_folder, subfolder)), level=1, verbose=verbose)
42
+ else:
43
+ batchdisp('only process files in subfolder(s): {}'.format(os.path.join(self.in_folder, self.subfolders)), level=1, verbose=verbose)
44
+
32
45
  if inplace:
33
46
  batchdisp('Processing mode: overwrite (inplace=True) (each step will be applied to same folder)', level=1, verbose=verbose)
34
47
  else:
@@ -50,57 +63,75 @@ class BiomechZoo:
50
63
 
51
64
  def mvnx2zoo(self, out_folder=None, inplace=False):
52
65
  """ Converts all .mvnx files in the folder to .zoo format """
66
+ start_time = time.time()
53
67
  verbose = self.verbose
54
68
  in_folder = self.in_folder
55
69
  if inplace is None:
56
70
  inplace = self.inplace
57
-
58
- fl = engine(in_folder, extension='.mvnx')
71
+ fl = engine(in_folder, extension='.mvnx', name_contains=self.name_contains, subfolders=self.subfolders)
59
72
  for f in fl:
60
73
  batchdisp('converting mvnx to zoo for {}'.format(f), level=2, verbose=verbose)
61
74
  data = mvnx2zoo_data(f)
62
75
  f_zoo = f.replace('.mvnx', '.zoo')
63
76
  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
-
77
+ method_name = inspect.currentframe().f_code.co_name
78
+ batchdisp('{} conversion complete for {} files'.format(method_name, len(fl)), level=1, verbose=verbose)
66
79
  # Update self.folder after processing
67
80
  self._update_folder(out_folder, inplace, in_folder)
68
81
 
69
82
  def c3d2zoo(self, out_folder=None, inplace=None):
70
83
  """ Converts all .c3d files in the folder to .zoo format """
84
+ start_time = time.time()
71
85
  from ezc3d import c3d
72
86
  verbose = self.verbose
73
87
  in_folder = self.in_folder
74
88
  if inplace is None:
75
89
  inplace = self.inplace
76
-
77
- fl = engine(in_folder, extension='.c3d')
90
+ fl = engine(in_folder, extension='.c3d', name_contains=self.name_contains, subfolders=self.subfolders)
78
91
  for f in fl:
79
92
  batchdisp('converting c3d to zoo for {}'.format(f), level=2, verbose=verbose)
80
93
  c3d_obj = c3d(f)
81
94
  data = c3d2zoo_data(c3d_obj)
82
95
  f_zoo = f.replace('.c3d', '.zoo')
83
96
  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
-
97
+ method_name = inspect.currentframe().f_code.co_name
98
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
86
99
  # Update self.folder after processing
87
100
  self._update_folder(out_folder, inplace, in_folder)
88
101
 
89
- def csv2zoo(self, out_folder=None, inplace=None):
102
+ def csv2zoo(self, out_folder=None, inplace=None, skip_rows=0):
90
103
  """ Converts generic .csv file in the folder to .zoo format """
104
+ start_time = time.time()
91
105
  verbose = self.verbose
92
106
  in_folder = self.in_folder
93
107
  if inplace is None:
94
108
  inplace = self.inplace
95
-
96
- fl = engine(in_folder, extension='.csv')
109
+ fl = engine(in_folder, extension='.csv', name_contains=self.name_contains, subfolders=self.subfolders)
97
110
  for f in fl:
98
111
  batchdisp('converting csv to zoo for {}'.format(f), level=2, verbose=verbose)
99
- data = csv2zoo_data(f)
112
+ data = csv2zoo_data(f, type='csv', skip_rows=skip_rows)
100
113
  f_zoo = f.replace('.csv', '.zoo')
101
114
  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)
115
+ method_name = inspect.currentframe().f_code.co_name
116
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
117
+ # Update self.folder after processing
118
+ self._update_folder(out_folder, inplace, in_folder)
103
119
 
120
+ def parquet2zoo(self, out_folder=None, inplace=None):
121
+ """ Converts generic .csv file in the folder to .zoo format """
122
+ start_time = time.time()
123
+ verbose = self.verbose
124
+ in_folder = self.in_folder
125
+ if inplace is None:
126
+ inplace = self.inplace
127
+ fl = engine(in_folder, extension='.parquet', name_contains=self.name_contains, subfolders=self.subfolders)
128
+ for f in fl:
129
+ batchdisp('converting parquet to zoo for {}'.format(f), level=2, verbose=verbose)
130
+ data = csv2zoo_data(f, type='parquet')
131
+ f_zoo = f.replace('.parquet', '.zoo')
132
+ zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
133
+ method_name = inspect.currentframe().f_code.co_name
134
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
104
135
  # Update self.folder after processing
105
136
  self._update_folder(out_folder, inplace, in_folder)
106
137
 
@@ -110,67 +141,70 @@ class BiomechZoo:
110
141
 
111
142
  def phase_angle(self, ch, out_folder=None, inplace=None):
112
143
  """ computes phase angles"""
144
+ start_time = time.time()
113
145
  verbose = self.verbose
114
146
  in_folder = self.in_folder
115
147
  if inplace is None:
116
148
  inplace = self.inplace
117
-
118
- fl = engine(in_folder)
149
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
119
150
  for f in fl:
120
151
  if verbose:
121
152
  batchdisp('computing phase angles for {}'.format(f), level=2, verbose=verbose)
122
153
  data = zload(f)
123
154
  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)
155
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
156
+ method_name = inspect.currentframe().f_code.co_name
157
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
126
158
 
127
159
  # Update self.folder after processing
128
160
  self._update_folder(out_folder, inplace, in_folder)
129
161
 
130
162
  def continuous_relative_phase(self, ch_prox, ch_dist, out_folder=None, inplace=None):
131
163
  """ computes CRP angles"""
164
+ start_time = time.time()
132
165
  verbose = self.verbose
133
166
  in_folder = self.in_folder
134
167
  if inplace is None:
135
168
  inplace = self.inplace
136
-
137
- fl = engine(in_folder)
169
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
138
170
  for f in fl:
139
171
  if verbose:
140
172
  batchdisp('computing CRP angles between channel {} (prox) and {} (dist) for {}'.format(ch_prox, ch_dist, f), level=2, verbose=verbose)
141
173
  data = zload(f)
142
174
  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)
175
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
176
+ method_name = inspect.currentframe().f_code.co_name
177
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
145
178
 
146
179
  # Update self.folder after processing
147
180
  self._update_folder(out_folder, inplace, in_folder)
148
181
 
149
182
  def split_trial_by_gait_cycle(self, first_event_name, out_folder=None, inplace=None):
150
183
  """ split by gait cycle according to event_name"""
184
+ start_time = time.time()
151
185
  verbose = self.verbose
152
186
  in_folder = self.in_folder
153
187
  if inplace is None:
154
188
  inplace = self.inplace
155
-
156
- fl = engine(in_folder)
189
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
157
190
  for f in fl:
158
191
  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
192
  data = zload(f)
161
193
  split_events = get_split_events(data, first_event_name)
162
194
  if split_events is None:
163
195
  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)
196
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
165
197
  else:
166
198
  for i, _ in enumerate(split_events[0:-1]):
167
199
  fl_new = f.replace(f_name, f_name + '_' + str(i + 1))
168
200
  start = split_events[i]
169
201
  end = split_events[i + 1]
202
+ batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
203
+ verbose=verbose)
170
204
  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)
205
+ zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
206
+ method_name = inspect.currentframe().f_code.co_name
207
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
174
208
 
175
209
  # Update self.folder after processing
176
210
  self._update_folder(out_folder, inplace, in_folder)
@@ -195,130 +229,137 @@ class BiomechZoo:
195
229
  # self._update_folder(out_folder, inplace, in_folder)
196
230
  def renameevent(self, evt, nevt, out_folder=None, inplace=None):
197
231
  """ renames event evt to nevt in all zoo files """
232
+ start_time = time.time()
198
233
  verbose = self.verbose
199
234
  in_folder = self.in_folder
200
235
  if inplace is None:
201
236
  inplace = self.inplace
202
-
203
- fl = engine(in_folder)
237
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
204
238
  for f in fl:
205
239
  batchdisp('renaming events from {} to {} for {}'.format(evt, nevt ,f), level=2, verbose=verbose)
206
240
  data = zload(f)
207
241
  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)
242
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
243
+ method_name = inspect.currentframe().f_code.co_name
244
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
210
245
 
211
246
  # Update self.folder after processing
212
247
  self._update_folder(out_folder, inplace, in_folder)
213
248
 
214
249
  def renamechannnel(self, ch, ch_new, out_folder=None, inplace=None):
215
250
  """ renames channels from ch to ch_new in all zoo files """
251
+ start_time = time.time()
216
252
  verbose = self.verbose
217
253
  in_folder = self.in_folder
218
254
  if inplace is None:
219
255
  inplace = self.inplace
220
-
221
- fl = engine(in_folder)
256
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
222
257
  for f in fl:
223
258
  batchdisp('renaming channels from {} to {} for {}'.format(ch, ch_new ,f), level=2, verbose=verbose)
224
259
  data = zload(f)
225
260
  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)
261
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
262
+ method_name = inspect.currentframe().f_code.co_name
263
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
228
264
 
229
265
  # Update self.folder after processing
230
266
  self._update_folder(out_folder, inplace, in_folder)
231
267
 
232
268
  def removechannel(self, ch, mode='remove', out_folder=None, inplace=None):
233
269
  """ removes channels from zoo files """
270
+ start_time = time.time()
234
271
  verbose = self.verbose
235
272
  in_folder = self.in_folder
236
273
  if inplace is None:
237
274
  inplace = self.inplace
238
-
239
- fl = engine(in_folder)
275
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
240
276
  for f in fl:
241
277
  batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
242
278
  data = zload(f)
243
279
  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)
280
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
281
+ method_name = inspect.currentframe().f_code.co_name
282
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
246
283
 
247
284
  # Update self.folder after processing
248
285
  self._update_folder(out_folder, inplace, in_folder)
249
286
 
250
287
  def explodechannel(self, out_folder=None, inplace=None):
251
288
  """ explodes all channels in a zoo file """
289
+ start_time = time.time()
252
290
  verbose = self.verbose
253
291
  in_folder = self.in_folder
254
292
  if inplace is None:
255
293
  inplace = self.inplace
256
-
257
- fl = engine(in_folder)
294
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
258
295
  for f in fl:
259
296
  if verbose:
260
297
  batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
261
298
  data = zload(f)
262
299
  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)
300
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
301
+ method_name = inspect.currentframe().f_code.co_name
302
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
265
303
 
266
304
  # Update self.folder after processing
267
305
  self._update_folder(out_folder, inplace, in_folder)
268
306
 
269
307
  def normalize(self, nlen=101, out_folder=None, inplace=None):
270
308
  """ time normalizes all channels to length nlen """
309
+ start_time = time.time()
271
310
  verbose = self.verbose
272
311
  in_folder = self.in_folder
273
312
  if inplace is None:
274
313
  inplace = self.inplace
275
-
276
- fl = engine(in_folder)
314
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
277
315
  for f in fl:
278
316
  if verbose:
279
317
  batchdisp('normalizing channels to length {} for {}'.format(nlen, f), level=2, verbose=verbose)
280
318
  data = zload(f)
281
319
  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)
320
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
321
+ method_name = inspect.currentframe().f_code.co_name
322
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
284
323
 
285
324
  # Update self.folder after processing
286
325
  self._update_folder(out_folder, inplace, in_folder)
287
326
 
288
327
  def addevent(self, ch, evt_type, evt_name, out_folder=None, inplace=None):
289
328
  """ adds events of type evt_type with name evt_name to channel ch """
329
+ start_time = time.time()
290
330
  verbose = self.verbose
291
331
  in_folder = self.in_folder
292
332
  if inplace is None:
293
333
  inplace = self.inplace
294
-
295
- fl = engine(in_folder)
334
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
296
335
  for f in fl:
297
336
  if verbose:
298
337
  batchdisp('adding event {} to channel {} for {}'.format(evt_type, ch, f), level=2, verbose=verbose)
299
338
  data = zload(f)
300
339
  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)
340
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
341
+ method_name = inspect.currentframe().f_code.co_name
342
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
303
343
 
304
344
  # Update self.folder after processing
305
345
  self._update_folder(out_folder, inplace, in_folder)
306
346
 
307
347
  def partition(self, evt_start, evt_end, out_folder=None, inplace=None):
308
348
  """ partitions data between events evt_start and evt_end """
349
+ start_time = time.time()
309
350
  verbose = self.verbose
310
351
  in_folder = self.in_folder
311
352
  if inplace is None:
312
353
  inplace = self.inplace
313
-
314
- fl = engine(in_folder)
354
+ fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
315
355
  for f in fl:
316
356
  if verbose:
317
357
  batchdisp('partitioning data between events {} and {} for {}'.format(evt_start, evt_end, f), level=2, verbose=verbose)
318
358
  data = zload(f)
319
359
  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)
360
+ zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
361
+ method_name = inspect.currentframe().f_code.co_name
362
+ batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
322
363
  # Update self.folder after processing
323
364
  self._update_folder(out_folder, inplace, in_folder)
324
365
 
@@ -340,8 +381,9 @@ class BiomechZoo:
340
381
  # batchdisp('filtering data in channels {} for {}'.format(ch, f), level=2, verbose=verbose)
341
382
  # data = zload(f)
342
383
  # 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)
384
+ # zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder, verbose=verbose)
385
+ # method_name = inspect.currentframe().f_code.co_name
386
+ # batchdisp('{} computation complete for {} file(s)'.format(method_name, len(fl)), level=1, verbose=verbose)
345
387
  #
346
388
  # # Update self.folder after processing
347
389
  # 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,3 +1,5 @@
1
+ import copy
2
+
1
3
  def explodechannel_data(data, channels=None):
2
4
  """ Explodes 3D channels (n x 3 arrays) into separate X, Y, Z channels.
3
5
 
@@ -10,8 +12,7 @@ def explodechannel_data(data, channels=None):
10
12
  data_new (dict): Modified zoo dictionary with exploded channels.
11
13
  """
12
14
 
13
- data_new = data.copy()
14
-
15
+ data_new = copy.deepcopy(data)
15
16
  # Find default channels if none provided
16
17
  if channels is None:
17
18
  channels = []
@@ -1,19 +1,20 @@
1
1
  from biomechzoo.utils.findfield import findfield
2
2
  import warnings
3
+ import copy
3
4
 
4
5
 
5
6
  def partition_data(data, evt_start, evt_end):
6
7
  """ partition data for all channels between events evt_start and evt_end"""
7
8
 
8
9
  # extract event values
9
- e1, _ = findfield(data,evt_start)
10
- e2, _ = findfield(data,evt_end)
10
+ e1, _ = findfield(data, evt_start)
11
+ e2, _ = findfield(data, evt_end)
11
12
 
12
- data_new = data.copy()
13
+ data_new = copy.deepcopy(data)
13
14
  for ch_name, ch_data in data_new.items():
14
15
  if ch_name != 'zoosystem':
15
16
  try:
16
- data_new[ch_name]['line'] = ch_data[e1[0]:e2[0], :]
17
+ data_new[ch_name]['line'] = ch_data[e1[0]:e2[0],]
17
18
  except (IndexError, ValueError) as e:
18
19
  # IndexError: if e1[0]:e2[0] goes beyond the available indices
19
20
  # ValueError: less likely, but may arise with shape mismatches
@@ -29,6 +30,7 @@ def partition_data(data, evt_start, evt_end):
29
30
  continue # do not change outlier markers
30
31
  else:
31
32
  new_frame = original_frame - e1[0] + 1
33
+ print(new_frame)
32
34
  data_new[ch_name]['event'][event_name][0] = new_frame
33
35
 
34
36
  return data_new
@@ -48,4 +50,4 @@ def _partition_event(event_dict, evt_start, evt_end, arr_len):
48
50
  #
49
51
  #
50
52
  #
51
- # return event_dict_new
53
+ # 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
biomechzoo/utils/zsave.py CHANGED
@@ -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.1
3
+ Version: 0.4.5
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
@@ -30,3 +31,15 @@ This is a development version of the biomechzoo toolbox for python.
30
31
  - We use Numpy 2.2.6 for compatibility with https://pypi.org/project/numba/
31
32
 
32
33
  See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechzoo.com for more information
34
+
35
+ ## Developer notes
36
+
37
+ ### Installing a dev environment
38
+ conda create -n biomechzoo-dev python=3.11
39
+ conda activate biomechzoo-dev
40
+ pip install -e ".[dev]"
41
+
42
+ ### import issues
43
+ if using PyCharm:
44
+ - Right-click on src/.
45
+ - Select Mark Directory as → Sources Root.
@@ -1,6 +1,7 @@
1
+ __init__.py,sha256=Uy3ykqw4l_lZKiUWSUFPRZpkZajYUfZLBaQLVhKzxdI,772
1
2
  biomechzoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
3
  biomechzoo/__main__.py,sha256=hSMHN1Rxn2367fSGTLHoOQ4_pocZw0IWI7HFxl-74oY,88
3
- biomechzoo/biomechzoo.py,sha256=bmP81dLlYPE2NnZ76xK-MVlVyDuKKU08OF58dtzieps,15433
4
+ biomechzoo/biomechzoo.py,sha256=6YpqhUaMH76d9zjNAsnOvyU5zppbCMD1t72FIzA8VyI,20782
4
5
  biomechzoo/biomech_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
6
  biomechzoo/biomech_ops/continuous_relative_phase_data.py,sha256=RePbt6zyOI1iv74CWhxSrunIokTFYVfFmFnoW51781E,1300
6
7
  biomechzoo/biomech_ops/continuous_relative_phase_line.py,sha256=Fa1LFRuPlmGPLQLvln6HVnJy3zMSm9z5YeooHmTu0lc,1365
@@ -12,7 +13,7 @@ biomechzoo/biomech_ops/phase_angle_data.py,sha256=_ekUBW2v3iC4UawcDL38ZZLYJmQsAm
12
13
  biomechzoo/biomech_ops/phase_angle_line.py,sha256=p6osB17_3QQSyKLNojuc6nYhv-k0K6EUUH75EXu8ifc,1391
13
14
  biomechzoo/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  biomechzoo/conversion/c3d2zoo_data.py,sha256=kmiLQHglchSHty6DrMEl5yTOUi3U4Ai5nHoSGvbF6k8,2660
15
- biomechzoo/conversion/csv2zoo_data.py,sha256=-c4CXxcKd70HJWWgHRztOxOckvA1XgIDzo8EhgJhcKE,2185
16
+ biomechzoo/conversion/csv2zoo_data.py,sha256=hNslzga9Iy3pnD_cMiHZuPupjAz-Qb1VwGSJ5rm3uDM,2928
16
17
  biomechzoo/conversion/mvnx2zoo_data.py,sha256=tlqg8iVmTxQPo9u9T8mghS2eAEtqOF3xB7tTNR0H1mQ,3504
17
18
  biomechzoo/conversion/opencap2zoo_data.py,sha256=n4bLsrJI0wTSzG5bgJcmxj1dp2plnUKFNRzcTIbmV1o,608
18
19
  biomechzoo/mvn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,8 +25,8 @@ biomechzoo/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
24
25
  biomechzoo/processing/add_channel_data.py,sha256=U1xLLBSnyJeeDWzgmHSxOz1hyguzuuDXxQCQ8IFoZkw,1955
25
26
  biomechzoo/processing/addchannel_data.py,sha256=rmnnlMRVkoMlQCR-nRg1jjh-hzMDt37Sx9fAmocrpqA,1953
26
27
  biomechzoo/processing/addevent_data.py,sha256=DyTfvjOWZIlsWfnT6XDwvsU1jOzSDf4HhLa1TaKIhwI,1325
27
- biomechzoo/processing/explodechannel_data.py,sha256=t4gC2ZQD4baECexj0hQYDzOnXV5GHb-z068mtZzh0DQ,1470
28
- biomechzoo/processing/partition_data.py,sha256=8pMGRVXzYbPa4tuB7K3OOjUCnROpXb2v1gcRRNAbSig,1704
28
+ biomechzoo/processing/explodechannel_data.py,sha256=AC2BOEw1tXcgJ1WuYWSE-yToS-q9XGdgkOHS4D3iUFo,1490
29
+ biomechzoo/processing/partition_data.py,sha256=8miAL8kzOSJtg6j6WetAxRv8whJD8gtDVR9FEpj6mo8,1760
29
30
  biomechzoo/processing/removechannel_data.py,sha256=ndZcbWJRDvd7drlahGaq8MseT-rsxiu7pgdMtA1cTlo,1218
30
31
  biomechzoo/processing/renamechannel_data.py,sha256=5lEnWJknAwnJYXDlwO6cFe7C8ct5o42tTHW0Y4GeIL4,2210
31
32
  biomechzoo/processing/renameevent_data.py,sha256=9w7C_fQOsQ8XbdTr_hrg_iQe51oDczq2Rj7yJLyYG0M,2215
@@ -33,16 +34,16 @@ biomechzoo/processing/split_trial_by_gait_cycle.py,sha256=maMUwumSlFLFc5LHdNdBO9
33
34
  biomechzoo/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
35
  biomechzoo/utils/batchdisp.py,sha256=Ke7yeZ4cYQvyf8bmgsiLaRElUeQm49TYIYzAcPWan9s,839
35
36
  biomechzoo/utils/compute_sampling_rate_from_time.py,sha256=iyHbN734vYe5bXEoAsHScJN7Qw--2quUgII7judSQDk,579
36
- biomechzoo/utils/engine.py,sha256=WSZmi6DDZHcdOhgsu2LvmLF-Ftxcjteh9TbKDMZIN5k,3240
37
+ biomechzoo/utils/engine.py,sha256=7Ztf1PbjnEn4C6jueJ8AhQG3W6OAWS2Ze5CDiF7Xu_I,3395
37
38
  biomechzoo/utils/findfield.py,sha256=i7ewGQOMIiTC06tYFK-JctehHLCippkY9FoXIygx14w,381
38
39
  biomechzoo/utils/get_split_events.py,sha256=xNhEuG6Yqsr1bjWIBHLbepfX-fcqcCYIXZzS3eaDDHQ,911
39
40
  biomechzoo/utils/split_trial.py,sha256=Fumz_ZukNBNtPauUhCge5EAHkg05dYDhA1_njQw0lHE,886
40
41
  biomechzoo/utils/zload.py,sha256=FPT6_-gwaOOqOckjgPRfnKEVKMsmNVIcenmQZF2KOvo,1535
41
42
  biomechzoo/utils/zplot.py,sha256=WVA8aCy1Pqy_bo_HXab9AmW-cBd8J8MPX2LAOd6dznU,1512
42
- biomechzoo/utils/zsave.py,sha256=2DeF6A1R32SnXJXtb8jtPqZAmlSEB0PVi09UGiskzMo,1625
43
- biomechzoo-0.4.1.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
44
- biomechzoo-0.4.1.dist-info/METADATA,sha256=4wd_Ax4xnp2kUdrPrQEeprveTWZfv7a7lYfhN76n3NA,1259
45
- biomechzoo-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
- biomechzoo-0.4.1.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
47
- biomechzoo-0.4.1.dist-info/top_level.txt,sha256=W148kdiK-6L0Nohbw8UALkAzHJvJLAhFDRykuva2WjY,11
48
- biomechzoo-0.4.1.dist-info/RECORD,,
43
+ biomechzoo/utils/zsave.py,sha256=wnRNuDxQc8bwCji4UrfoGjYrSZmka4eaDxQ5rMa78pE,1759
44
+ biomechzoo-0.4.5.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
45
+ biomechzoo-0.4.5.dist-info/METADATA,sha256=tKqy3v7eordmf6uD0bCDHqXtoLBGHAu8rDrIjqhxJWs,1553
46
+ biomechzoo-0.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ biomechzoo-0.4.5.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
48
+ biomechzoo-0.4.5.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
49
+ biomechzoo-0.4.5.dist-info/RECORD,,
@@ -1 +1,2 @@
1
+ __init__
1
2
  biomechzoo