biomechzoo 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (44) hide show
  1. biomechzoo/__init__.py +5 -0
  2. biomechzoo/__main__.py +6 -0
  3. biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
  4. biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
  5. biomechzoo/biomech_ops/filter_data.py +56 -0
  6. biomechzoo/biomech_ops/filter_line.py +88 -0
  7. biomechzoo/biomech_ops/normalize_data.py +35 -0
  8. biomechzoo/biomech_ops/normalize_line.py +27 -0
  9. biomechzoo/biomech_ops/phase_angle_data.py +39 -0
  10. biomechzoo/biomech_ops/phase_angle_line.py +48 -0
  11. biomechzoo/biomechzoo.py +349 -0
  12. biomechzoo/conversion/__init__.py +0 -0
  13. biomechzoo/conversion/c3d2zoo_data.py +65 -0
  14. biomechzoo/conversion/csv2zoo_data.py +78 -0
  15. biomechzoo/conversion/mvnx2zoo_data.py +71 -0
  16. biomechzoo/conversion/opencap2zoo_data.py +23 -0
  17. biomechzoo/mvn/load_mvnx.py +514 -0
  18. biomechzoo/mvn/main_mvnx.py +75 -0
  19. biomechzoo/mvn/mvn.py +232 -0
  20. biomechzoo/mvn/mvnx_file_accessor.py +463 -0
  21. biomechzoo/processing/add_channel_data.py +71 -0
  22. biomechzoo/processing/addchannel_data.py +71 -0
  23. biomechzoo/processing/addevent_data.py +46 -0
  24. biomechzoo/processing/explodechannel_data.py +46 -0
  25. biomechzoo/processing/partition_data.py +51 -0
  26. biomechzoo/processing/removechannel_data.py +36 -0
  27. biomechzoo/processing/renamechannel_data.py +79 -0
  28. biomechzoo/processing/renameevent_data.py +68 -0
  29. biomechzoo/processing/split_trial_by_gait_cycle.py +52 -0
  30. biomechzoo/utils/batchdisp.py +21 -0
  31. biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
  32. biomechzoo/utils/engine.py +68 -0
  33. biomechzoo/utils/findfield.py +11 -0
  34. biomechzoo/utils/get_split_events.py +33 -0
  35. biomechzoo/utils/split_trial.py +23 -0
  36. biomechzoo/utils/zload.py +46 -0
  37. biomechzoo/utils/zplot.py +61 -0
  38. biomechzoo/utils/zsave.py +50 -0
  39. biomechzoo-0.1.1.dist-info/METADATA +48 -0
  40. biomechzoo-0.1.1.dist-info/RECORD +44 -0
  41. biomechzoo-0.1.1.dist-info/WHEEL +5 -0
  42. biomechzoo-0.1.1.dist-info/entry_points.txt +2 -0
  43. biomechzoo-0.1.1.dist-info/licenses/LICENSE +21 -0
  44. biomechzoo-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,349 @@
1
+ import os
2
+ from biomechzoo.utils.engine import engine # assumes this returns .zoo files in folder
3
+ from biomechzoo.utils.zload import zload
4
+ from biomechzoo.utils.zsave import zsave
5
+ from biomechzoo.utils.batchdisp import batchdisp
6
+ from biomechzoo.utils.get_split_events import get_split_events
7
+ from biomechzoo.utils.split_trial import split_trial
8
+ from biomechzoo.conversion.c3d2zoo_data import c3d2zoo_data
9
+ from biomechzoo.conversion.csv2zoo_data import csv2zoo_data
10
+ from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
11
+ from biomechzoo.processing.removechannel_data import removechannel_data
12
+ from biomechzoo.processing.renamechannel_data import renamechannel_data
13
+ from biomechzoo.processing.explodechannel_data import explodechannel_data
14
+ from biomechzoo.processing.addevent_data import addevent_data
15
+ from biomechzoo.processing.partition_data import partition_data
16
+ from biomechzoo.processing.renameevent_data import renameevent_data
17
+ from biomechzoo.biomech_ops.normalize_data import normalize_data
18
+ from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
19
+ from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
20
+
21
+
22
+ class BiomechZoo:
23
+ def __init__(self, in_folder, inplace=False, verbose=0):
24
+ self.verbose = verbose
25
+ self.in_folder = in_folder
26
+ self.verbose = verbose
27
+ self.inplace = inplace # choice to save processed files to new folder
28
+
29
+ batchdisp('BiomechZoo initialized', level=1, verbose=verbose)
30
+ batchdisp('verbosity set to: {}'.format(verbose), level=1, verbose=verbose)
31
+ batchdisp('processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
32
+ if inplace:
33
+ batchdisp('Processing mode: overwrite (inplace=True) (each step will be applied to same folder)', level=1, verbose=verbose)
34
+ else:
35
+ batchdisp('Processing mode: backup (inplace=False)(each step will be applied to a new folder)', level=1, verbose=verbose)
36
+
37
+ def _update_folder(self, out_folder, inplace, in_folder):
38
+ """
39
+ Utility to update self.in_folder if not inplace.
40
+
41
+ Parameters:
42
+ - out_folder (str or None): The output folder provided by user
43
+ - inplace (bool): Whether processing is inplace
44
+ - in_folder (str): The current input folder
45
+ """
46
+ if not inplace:
47
+ # get full path for out_folder
48
+ in_folder_path = os.path.dirname(in_folder)
49
+ self.in_folder = os.path.join(in_folder_path, out_folder)
50
+
51
+ def mvnx2zoo(self, out_folder=None, inplace=False):
52
+ """ Converts all .mvnx files in the folder to .zoo format """
53
+ verbose = self.verbose
54
+ in_folder = self.in_folder
55
+ if inplace is None:
56
+ inplace = self.inplace
57
+
58
+ fl = engine(in_folder, extension='.mvnx')
59
+ for f in fl:
60
+ batchdisp('converting mvnx to zoo for {}'.format(f), level=2, verbose=verbose)
61
+ data = mvnx2zoo_data(f)
62
+ f_zoo = f.replace('.mvnx', '.zoo')
63
+ 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
+
66
+ # Update self.folder after processing
67
+ self._update_folder(out_folder, inplace, in_folder)
68
+
69
+ def c3d2zoo(self, out_folder=None, inplace=None):
70
+ """ Converts all .c3d files in the folder to .zoo format """
71
+ from ezc3d import c3d
72
+ verbose = self.verbose
73
+ in_folder = self.in_folder
74
+ if inplace is None:
75
+ inplace = self.inplace
76
+
77
+ fl = engine(in_folder, extension='.c3d')
78
+ for f in fl:
79
+ batchdisp('converting c3d to zoo for {}'.format(f), level=2, verbose=verbose)
80
+ c3d_obj = c3d(f)
81
+ data = c3d2zoo_data(c3d_obj)
82
+ f_zoo = f.replace('.c3d', '.zoo')
83
+ 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
+
86
+ # Update self.folder after processing
87
+ self._update_folder(out_folder, inplace, in_folder)
88
+
89
+ def csv2zoo(self, out_folder=None, inplace=None):
90
+ """ Converts generic .csv file in the folder to .zoo format """
91
+ verbose = self.verbose
92
+ in_folder = self.in_folder
93
+ if inplace is None:
94
+ inplace = self.inplace
95
+
96
+ fl = engine(in_folder, extension='.csv')
97
+ for f in fl:
98
+ batchdisp('converting csv to zoo for {}'.format(f), level=2, verbose=verbose)
99
+ data = csv2zoo_data(f)
100
+ f_zoo = f.replace('.csv', '.zoo')
101
+ 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)
103
+
104
+ # Update self.folder after processing
105
+ self._update_folder(out_folder, inplace, in_folder)
106
+
107
+ def xls2zoo(self, out_folder=None, inplace=None):
108
+ """ Converts generic .xls file in the folder to .zoo format """
109
+ raise NotImplementedError
110
+
111
+ def phase_angle(self, ch, out_folder=None, inplace=None):
112
+ """ computes phase angles"""
113
+ verbose = self.verbose
114
+ in_folder = self.in_folder
115
+ if inplace is None:
116
+ inplace = self.inplace
117
+
118
+ fl = engine(in_folder)
119
+ for f in fl:
120
+ if verbose:
121
+ batchdisp('computing phase angles for {}'.format(f), level=2, verbose=verbose)
122
+ data = zload(f)
123
+ 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)
126
+
127
+ # Update self.folder after processing
128
+ self._update_folder(out_folder, inplace, in_folder)
129
+
130
+ def continuous_relative_phase(self, ch_prox, ch_dist, out_folder=None, inplace=None):
131
+ """ computes CRP angles"""
132
+ verbose = self.verbose
133
+ in_folder = self.in_folder
134
+ if inplace is None:
135
+ inplace = self.inplace
136
+
137
+ fl = engine(in_folder)
138
+ for f in fl:
139
+ if verbose:
140
+ batchdisp('computing CRP angles between channel {} (prox) and {} (dist) for {}'.format(ch_prox, ch_dist, f), level=2, verbose=verbose)
141
+ data = zload(f)
142
+ 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)
145
+
146
+ # Update self.folder after processing
147
+ self._update_folder(out_folder, inplace, in_folder)
148
+
149
+ def split_trial_by_gait_cycle(self, first_event_name, out_folder=None, inplace=None):
150
+ """ split by gait cycle according to event_name"""
151
+ verbose = self.verbose
152
+ in_folder = self.in_folder
153
+ if inplace is None:
154
+ inplace = self.inplace
155
+
156
+ fl = engine(in_folder)
157
+ for f in fl:
158
+ 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
+ data = zload(f)
161
+ split_events = get_split_events(data, first_event_name)
162
+ if split_events is None:
163
+ 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)
165
+ else:
166
+ for i, _ in enumerate(split_events[0:-1]):
167
+ fl_new = f.replace(f_name, f_name + '_' + str(i + 1))
168
+ start = split_events[i]
169
+ end = split_events[i + 1]
170
+ 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)
174
+
175
+ # Update self.folder after processing
176
+ self._update_folder(out_folder, inplace, in_folder)
177
+
178
+
179
+ # def mean_absolute_relative_phase_deviation_phase(self, channels, out_folder=None, inplace=None):
180
+ # verbose = self.verbose
181
+ # in_folder = self.in_folder
182
+ # if inplace is None:
183
+ # inplace = self.inplace
184
+ #
185
+ # fl = engine(in_folder)
186
+ # for f in fl:
187
+ # for channel in channels:
188
+ # batchdisp('collecting trials for marp and dp for {}'.format(f), level=2, verbose=verbose)
189
+ # data = zload(f)
190
+ # data = removechannel_data(data, ch, mode)
191
+ # zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
192
+ # batchdisp('remove channel complete', level=1, verbose=verbose)
193
+ #
194
+ # # Update self.folder after processing
195
+ # self._update_folder(out_folder, inplace, in_folder)
196
+ def renameevent(self, evt, nevt, out_folder=None, inplace=None):
197
+ """ renames event evt to nevt in all zoo files """
198
+ verbose = self.verbose
199
+ in_folder = self.in_folder
200
+ if inplace is None:
201
+ inplace = self.inplace
202
+
203
+ fl = engine(in_folder)
204
+ for f in fl:
205
+ batchdisp('renaming events from {} to {} for {}'.format(evt, nevt ,f), level=2, verbose=verbose)
206
+ data = zload(f)
207
+ 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)
210
+
211
+ # Update self.folder after processing
212
+ self._update_folder(out_folder, inplace, in_folder)
213
+
214
+ def renamechannnel(self, ch, ch_new, out_folder=None, inplace=None):
215
+ """ renames channels from ch to ch_new in all zoo files """
216
+ verbose = self.verbose
217
+ in_folder = self.in_folder
218
+ if inplace is None:
219
+ inplace = self.inplace
220
+
221
+ fl = engine(in_folder)
222
+ for f in fl:
223
+ batchdisp('renaming channels from {} to {} for {}'.format(ch, ch_new ,f), level=2, verbose=verbose)
224
+ data = zload(f)
225
+ 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)
228
+
229
+ # Update self.folder after processing
230
+ self._update_folder(out_folder, inplace, in_folder)
231
+
232
+ def removechannel(self, ch, mode='remove', out_folder=None, inplace=None):
233
+ """ removes channels from zoo files """
234
+ verbose = self.verbose
235
+ in_folder = self.in_folder
236
+ if inplace is None:
237
+ inplace = self.inplace
238
+
239
+ fl = engine(in_folder)
240
+ for f in fl:
241
+ batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
242
+ data = zload(f)
243
+ 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)
246
+
247
+ # Update self.folder after processing
248
+ self._update_folder(out_folder, inplace, in_folder)
249
+
250
+ def explodechannel(self, out_folder=None, inplace=None):
251
+ """ explodes all channels in a zoo file """
252
+ verbose = self.verbose
253
+ in_folder = self.in_folder
254
+ if inplace is None:
255
+ inplace = self.inplace
256
+
257
+ fl = engine(in_folder)
258
+ for f in fl:
259
+ if verbose:
260
+ batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
261
+ data = zload(f)
262
+ 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)
265
+
266
+ # Update self.folder after processing
267
+ self._update_folder(out_folder, inplace, in_folder)
268
+
269
+ def normalize(self, nlen=101, out_folder=None, inplace=None):
270
+ """ time normalizes all channels to length nlen """
271
+ verbose = self.verbose
272
+ in_folder = self.in_folder
273
+ if inplace is None:
274
+ inplace = self.inplace
275
+
276
+ fl = engine(in_folder)
277
+ for f in fl:
278
+ if verbose:
279
+ batchdisp('normalizing channels to length {} for {}'.format(nlen, f), level=2, verbose=verbose)
280
+ data = zload(f)
281
+ 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)
284
+
285
+ # Update self.folder after processing
286
+ self._update_folder(out_folder, inplace, in_folder)
287
+
288
+ def addevent(self, ch, evt_type, evt_name, out_folder=None, inplace=None):
289
+ """ adds events of type evt_type with name evt_name to channel ch """
290
+ verbose = self.verbose
291
+ in_folder = self.in_folder
292
+ if inplace is None:
293
+ inplace = self.inplace
294
+
295
+ fl = engine(in_folder)
296
+ for f in fl:
297
+ if verbose:
298
+ batchdisp('adding event {} to channel {} for {}'.format(evt_type, ch, f), level=2, verbose=verbose)
299
+ data = zload(f)
300
+ 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)
303
+
304
+ # Update self.folder after processing
305
+ self._update_folder(out_folder, inplace, in_folder)
306
+
307
+ def partition(self, evt_start, evt_end, out_folder=None, inplace=None):
308
+ """ partitions data between events evt_start and evt_end """
309
+ verbose = self.verbose
310
+ in_folder = self.in_folder
311
+ if inplace is None:
312
+ inplace = self.inplace
313
+
314
+ fl = engine(in_folder)
315
+ for f in fl:
316
+ if verbose:
317
+ batchdisp('partitioning data between events {} and {} for {}'.format(evt_start, evt_end, f), level=2, verbose=verbose)
318
+ data = zload(f)
319
+ 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)
322
+ # Update self.folder after processing
323
+ self._update_folder(out_folder, inplace, in_folder)
324
+
325
+ def filter(self, ch, filt=None, out_folder=None, inplace=None):
326
+ raise NotImplementedError
327
+ # verbose = self.verbose
328
+ # in_folder = self.in_folder
329
+ # if inplace is None:
330
+ # inplace = self.inplace
331
+ #
332
+ # # set filter type
333
+ # if filt is None:
334
+ # filt = {'type': 'butterworth',
335
+ # 'order': 3,
336
+ # 'pass': 'lowpass'}
337
+ #
338
+ # fl = engine(in_folder)
339
+ # for f in fl:
340
+ # batchdisp('filtering data in channels {} for {}'.format(ch, f), level=2, verbose=verbose)
341
+ # data = zload(f)
342
+ # 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)
345
+ #
346
+ # # Update self.folder after processing
347
+ # self._update_folder(out_folder, inplace, in_folder)
348
+
349
+
File without changes
@@ -0,0 +1,65 @@
1
+ def c3d2zoo_data(c3d_obj):
2
+ """
3
+ Converts an ezc3d C3D object to zoo format.
4
+
5
+ Returns:
6
+ - data (dict): Zoo dictionary with 'line' and 'event' fields per channel.
7
+ """
8
+ data = {}
9
+
10
+ if 'points' in c3d_obj['data']:
11
+ points = c3d_obj['data']['points'] # shape: (4, n_markers, n_frames)
12
+ labels = c3d_obj['parameters']['POINT']['LABELS']['value']
13
+ for i, label in enumerate(labels):
14
+ line_data = points[:3, i, :].T # shape: (frames, 3)
15
+ data[label] = {
16
+ 'line': line_data,
17
+ 'event': {} # empty for now
18
+ }
19
+
20
+ params = c3d_obj['parameters']
21
+ video_freq = c3d_obj['parameters']['POINT']['RATE']['value'][0]
22
+ if 'EVENT' in params and 'TIMES' in params['EVENT']:
23
+ times_array = params['EVENT']['TIMES']['value']
24
+ frames = times_array[1] # second row = frames (or time, depending on C3D file)
25
+
26
+ # Extract sides, types, subjects
27
+ contexts = params['EVENT']['CONTEXTS']['value'] if 'CONTEXTS' in params['EVENT'] else ['']
28
+ labels = params['EVENT']['LABELS']['value']
29
+ subjects = params['EVENT']['SUBJECTS']['value'] if 'SUBJECTS' in params['EVENT'] else ['']
30
+
31
+ events = {}
32
+
33
+ for i in range(len(labels)):
34
+ side = contexts[i].strip()
35
+ label = labels[i].strip()
36
+ subject = subjects[i].strip()
37
+
38
+ # Event channel name: e.g. 'Right_FootStrike' -> 'RightFootStrike'
39
+ event_name = f"{side}_{label}".replace(' ', '')
40
+ event_name = ''.join(c for c in event_name if c.isalnum() or c == '_') # make it a valid field name
41
+
42
+ if event_name not in events:
43
+ events[event_name] = []
44
+
45
+ events[event_name].append(frames[i]) # This is in seconds or frame number?
46
+
47
+ original_start = 1
48
+
49
+ for event_name, time_list in events.items():
50
+ # Clean and sort times
51
+ valid_times = sorted([t for t in time_list if t != 0])
52
+ for j, time_val in enumerate(valid_times):
53
+ frame = round(time_val * video_freq) - original_start + 1 # MATLAB logic
54
+ key_name = f"{event_name}{j + 1}"
55
+
56
+ # Place in correct channel
57
+ if 'SACR' in data:
58
+ data['SACR']['event'][key_name] = [frame, 0, 0]
59
+ else:
60
+ data[labels[0]]['event'][key_name] = [frame, 0, 0]
61
+
62
+ # todo add relevant meta data to zoosystem
63
+ data['zoosystem'] = params['EVENT']
64
+
65
+ return data
@@ -0,0 +1,78 @@
1
+ import pandas as pd
2
+ import os
3
+ import re
4
+
5
+ from biomechzoo.utils.compute_sampling_rate_from_time import compute_sampling_rate_from_time
6
+
7
+
8
+ def csv2zoo_data(csv_path, header_len=10):
9
+
10
+ # 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
27
+ zoo_data = {}
28
+ for ch in data.columns:
29
+ zoo_data[ch] = {
30
+ 'line': data[ch].values,
31
+ 'event': []
32
+ }
33
+
34
+ # compute sampling rate
35
+ fsamp = compute_sampling_rate_from_time(time)
36
+
37
+ # add metadata
38
+ # todo update zoosystem to match biomechzoo requirements
39
+ zoo_data['zoosystem'] = metadata
40
+ zoo_data['zoosystem']['Freq'] = fsamp
41
+
42
+ return zoo_data
43
+
44
+
45
+ def _parse_metadata(header_lines):
46
+ metadata = {}
47
+ for line in header_lines:
48
+ if '=' in line:
49
+ key, val = line.split('=', 1)
50
+ key = key.strip()
51
+ val = val.strip()
52
+
53
+ # Strip trailing commas and whitespace explicitly
54
+ val = val.rstrip(',').strip()
55
+
56
+ # Extract first numeric token if any
57
+ match = re.search(r'[-+]?\d*\.?\d+', val)
58
+ if match:
59
+ num_str = match.group(0)
60
+ try:
61
+ val_num = int(num_str)
62
+ except ValueError:
63
+ val_num = float(num_str)
64
+ else:
65
+ # Now val should be clean of trailing commas, so just lower case it
66
+ val_num = val.lower()
67
+
68
+ metadata[key] = val_num
69
+ return metadata
70
+
71
+
72
+ if __name__ == '__main__':
73
+ """ for unit testing"""
74
+ current_dir = os.path.dirname(os.path.abspath(__file__))
75
+ project_root = os.path.dirname(current_dir)
76
+ csv_file = os.path.join(project_root, 'data', 'other', 'opencap_walking1.csv')
77
+
78
+ data = csv2zoo_data(csv_file)
@@ -0,0 +1,71 @@
1
+ import numpy as np
2
+ from biomechzoo.mvn.load_mvnx import load_mvnx
3
+
4
+
5
+ def mvnx2zoo_data(fl):
6
+ """ loads mvnx file from xsens"""
7
+ #todo: needs to be updated for the new version of mvnx direcly loaded in biomechzoo
8
+ mvnx_file = load_mvnx(fl)
9
+
10
+ # create zoo data dict
11
+ data = {}
12
+
13
+ # Accessing joint data :
14
+ joint_angle_data = mvnx_file.get_joint_angle()
15
+ joint_names = mvnx_file.joints
16
+ for i, joint in enumerate(joint_names):
17
+ start = i * 3
18
+ stop = start + 3
19
+ angles = joint_angle_data[:, start:stop] # shape: (n_frames, 3)
20
+
21
+ data[joint] = {
22
+ 'line': angles,
23
+ 'event': {}
24
+ }
25
+ # get foot strike events
26
+ # Index 0: Left Heel contact (1 for contact, 0 for no contact)
27
+ # Index 1: Left Toe contact (1 for contact, 0 for no contact)
28
+ # Index 2: Right Heel contact (1 for contact, 0 for no contact)
29
+ # Index 3: Right Toe contact (1 for contact, 0 for no contact)
30
+ left_heel_contacts = np.array(mvnx_file.footContacts[:, 0])
31
+ right_heel_contacts = np.array(mvnx_file.footContacts[:, 2])
32
+
33
+ # Detect transitions from no contact (0) to contact (1)
34
+ left_contact_start = (left_heel_contacts[:-1] == 0) & (left_heel_contacts[1:] == 1)
35
+ right_contact_start = (right_heel_contacts[:-1] == 0) & (right_heel_contacts[1:] == 1)
36
+
37
+ # Get indices where these transitions occur (add 1 because we're checking between frames)
38
+ left_contact_frames = np.where(left_contact_start)[0] + 1
39
+ right_contact_frames = np.where(right_contact_start)[0] + 1
40
+
41
+ # add to zoo
42
+ data['jL5S1']['event'] = {}
43
+ for i, right_contact_frame in enumerate(right_contact_frames):
44
+ data['jL5S1']['event']['R_FS'+str(i+1)] = [right_contact_frame, 0, 0]
45
+ for i, left_contact_frame in enumerate(left_contact_frames):
46
+ data['jL5S1']['event']['L_FS' + str(i + 1)] = [left_contact_frame, 0, 0]
47
+
48
+ # add meta information
49
+ # todo: add more, see mvnx_file object
50
+ data['zoosystem'] = {}
51
+ data['zoosystem']['Video'] = {}
52
+ data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
53
+ data['zoosystem']['Version'] = mvnx_file.version
54
+ data['zoosystem']['configuration'] = mvnx_file.configuration
55
+ data['zoosystem']['recording_date'] = mvnx_file.recording_date
56
+ data['zoosystem']['original_file_name'] = mvnx_file.original_file_name
57
+ data['zoosystem']['frame_count'] = mvnx_file.frame_count
58
+ data['zoosystem']['comments'] = mvnx_file.comments
59
+
60
+ return data
61
+
62
+
63
+ if __name__ == '__main__':
64
+ """ testing """
65
+ import os
66
+ from src.biomechzoo.utils.zplot import zplot
67
+ # -------TESTING--------
68
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
69
+ fl = os.path.join(project_root, 'data', 'other', 'Flat-001.mvnx')
70
+ data = mvnx2zoo_data(fl)
71
+ zplot(data, 'jRightKnee')
@@ -0,0 +1,23 @@
1
+ import os
2
+ import pandas as pd
3
+
4
+ #todo: review. do not use in current form
5
+
6
+ def opencap2zoo_data(ik_file):
7
+ raise NotImplementedError
8
+ ik_data = pd.read_csv(ik_file, sep='\t', comment='#') # OpenSim .mot format
9
+ a = 1
10
+
11
+ def _compute_opencap_quantities():
12
+ b = 2
13
+
14
+
15
+ if __name__ == '__main__':
16
+ """ for unit testing"""
17
+ # -------TESTING--------
18
+ current_dir = os.path.dirname(os.path.abspath(__file__))
19
+ project_root = os.path.dirname(current_dir)
20
+
21
+ fl = os.path.join(project_root, 'data', 'OpenCap', 'gait_3', 'OpenSimData', 'Kinematics', 'gait_3.mot')
22
+
23
+ data = opencap2zoo_data(fl)