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.
- biomechzoo/__init__.py +5 -0
- biomechzoo/__main__.py +6 -0
- biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
- biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
- biomechzoo/biomech_ops/filter_data.py +56 -0
- biomechzoo/biomech_ops/filter_line.py +88 -0
- biomechzoo/biomech_ops/normalize_data.py +35 -0
- biomechzoo/biomech_ops/normalize_line.py +27 -0
- biomechzoo/biomech_ops/phase_angle_data.py +39 -0
- biomechzoo/biomech_ops/phase_angle_line.py +48 -0
- biomechzoo/biomechzoo.py +349 -0
- biomechzoo/conversion/__init__.py +0 -0
- biomechzoo/conversion/c3d2zoo_data.py +65 -0
- biomechzoo/conversion/csv2zoo_data.py +78 -0
- biomechzoo/conversion/mvnx2zoo_data.py +71 -0
- biomechzoo/conversion/opencap2zoo_data.py +23 -0
- biomechzoo/mvn/load_mvnx.py +514 -0
- biomechzoo/mvn/main_mvnx.py +75 -0
- biomechzoo/mvn/mvn.py +232 -0
- biomechzoo/mvn/mvnx_file_accessor.py +463 -0
- biomechzoo/processing/add_channel_data.py +71 -0
- biomechzoo/processing/addchannel_data.py +71 -0
- biomechzoo/processing/addevent_data.py +46 -0
- biomechzoo/processing/explodechannel_data.py +46 -0
- biomechzoo/processing/partition_data.py +51 -0
- biomechzoo/processing/removechannel_data.py +36 -0
- biomechzoo/processing/renamechannel_data.py +79 -0
- biomechzoo/processing/renameevent_data.py +68 -0
- biomechzoo/processing/split_trial_by_gait_cycle.py +52 -0
- biomechzoo/utils/batchdisp.py +21 -0
- biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
- biomechzoo/utils/engine.py +68 -0
- biomechzoo/utils/findfield.py +11 -0
- biomechzoo/utils/get_split_events.py +33 -0
- biomechzoo/utils/split_trial.py +23 -0
- biomechzoo/utils/zload.py +46 -0
- biomechzoo/utils/zplot.py +61 -0
- biomechzoo/utils/zsave.py +50 -0
- biomechzoo-0.1.1.dist-info/METADATA +48 -0
- biomechzoo-0.1.1.dist-info/RECORD +44 -0
- biomechzoo-0.1.1.dist-info/WHEEL +5 -0
- biomechzoo-0.1.1.dist-info/entry_points.txt +2 -0
- biomechzoo-0.1.1.dist-info/licenses/LICENSE +21 -0
- biomechzoo-0.1.1.dist-info/top_level.txt +1 -0
biomechzoo/biomechzoo.py
ADDED
|
@@ -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)
|