biomechzoo 0.5.9__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.
- __init__.py +33 -0
- biomechzoo/__init__.py +0 -0
- biomechzoo/__main__.py +6 -0
- biomechzoo/biomech_ops/__init__.py +0 -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 +58 -0
- biomechzoo/biomech_ops/filter_line.py +85 -0
- biomechzoo/biomech_ops/movement_onset.py +53 -0
- biomechzoo/biomech_ops/normalize_data.py +36 -0
- biomechzoo/biomech_ops/normalize_line.py +51 -0
- biomechzoo/biomech_ops/phase_angle_data.py +39 -0
- biomechzoo/biomech_ops/phase_angle_line.py +48 -0
- biomechzoo/biomechzoo.py +447 -0
- biomechzoo/conversion/__init__.py +0 -0
- biomechzoo/conversion/c3d2zoo_data.py +95 -0
- biomechzoo/conversion/mvnx2zoo_data.py +113 -0
- biomechzoo/conversion/opencap2zoo_data.py +23 -0
- biomechzoo/conversion/table2zoo_data.py +114 -0
- biomechzoo/imu/__init__.py +0 -0
- biomechzoo/imu/kinematics.py +0 -0
- biomechzoo/imu/tilt_algorithm.py +112 -0
- biomechzoo/linear_algebra_ops/__init__.py +0 -0
- biomechzoo/linear_algebra_ops/compute_magnitude_data.py +43 -0
- biomechzoo/mvn/__init__.py +0 -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 +464 -0
- biomechzoo/processing/__init__.py +0 -0
- biomechzoo/processing/addchannel_data.py +71 -0
- biomechzoo/processing/addevent_data.py +116 -0
- biomechzoo/processing/explodechannel_data.py +69 -0
- biomechzoo/processing/partition_data.py +46 -0
- biomechzoo/processing/removechannel_data.py +46 -0
- biomechzoo/processing/removeevent_data.py +57 -0
- biomechzoo/processing/renamechannel_data.py +79 -0
- biomechzoo/processing/renameevent_data.py +62 -0
- biomechzoo/processing/split_trial_data.py +40 -0
- biomechzoo/statistics/eventval.py +118 -0
- biomechzoo/utils/__init__.py +0 -0
- biomechzoo/utils/batchdisp.py +21 -0
- biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
- biomechzoo/utils/engine.py +88 -0
- biomechzoo/utils/findfield.py +11 -0
- biomechzoo/utils/get_split_events.py +33 -0
- biomechzoo/utils/peak_sign.py +24 -0
- biomechzoo/utils/set_zoosystem.py +66 -0
- biomechzoo/utils/version.py +5 -0
- biomechzoo/utils/zload.py +57 -0
- biomechzoo/utils/zplot.py +61 -0
- biomechzoo/utils/zsave.py +54 -0
- biomechzoo-0.5.9.dist-info/METADATA +46 -0
- biomechzoo-0.5.9.dist-info/RECORD +58 -0
- biomechzoo-0.5.9.dist-info/WHEEL +5 -0
- biomechzoo-0.5.9.dist-info/entry_points.txt +2 -0
- biomechzoo-0.5.9.dist-info/licenses/LICENSE +21 -0
- biomechzoo-0.5.9.dist-info/top_level.txt +2 -0
biomechzoo/biomechzoo.py
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import inspect
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from biomechzoo.imu.tilt_algorithm import tilt_algorithm_data
|
|
6
|
+
from biomechzoo.utils.engine import engine # assumes this returns .zoo files in folder
|
|
7
|
+
from biomechzoo.utils.zload import zload
|
|
8
|
+
from biomechzoo.utils.zsave import zsave
|
|
9
|
+
from biomechzoo.utils.batchdisp import batchdisp
|
|
10
|
+
from biomechzoo.utils.get_split_events import get_split_events
|
|
11
|
+
from biomechzoo.processing.split_trial_data import split_trial_data
|
|
12
|
+
from biomechzoo.conversion.c3d2zoo_data import c3d2zoo_data
|
|
13
|
+
from biomechzoo.conversion.table2zoo_data import table2zoo_data
|
|
14
|
+
from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
|
|
15
|
+
from biomechzoo.processing.removechannel_data import removechannel_data
|
|
16
|
+
from biomechzoo.processing.renamechannel_data import renamechannel_data
|
|
17
|
+
from biomechzoo.processing.removeevent_data import removeevent_data
|
|
18
|
+
from biomechzoo.processing.explodechannel_data import explodechannel_data
|
|
19
|
+
from biomechzoo.processing.addevent_data import addevent_data
|
|
20
|
+
from biomechzoo.processing.partition_data import partition_data
|
|
21
|
+
from biomechzoo.processing.renameevent_data import renameevent_data
|
|
22
|
+
from biomechzoo.biomech_ops.normalize_data import normalize_data
|
|
23
|
+
from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
|
|
24
|
+
from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
|
|
25
|
+
from biomechzoo.biomech_ops.filter_data import filter_data
|
|
26
|
+
from biomechzoo.linear_algebra_ops.compute_magnitude_data import compute_magnitude_data
|
|
27
|
+
|
|
28
|
+
class BiomechZoo:
|
|
29
|
+
def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
|
|
30
|
+
self.verbose = verbose
|
|
31
|
+
self.in_folder = in_folder
|
|
32
|
+
self.verbose = verbose
|
|
33
|
+
self.inplace = inplace # choice to save processed files to new folder
|
|
34
|
+
self.subfolders = subfolders # only run processes on list in subfolder
|
|
35
|
+
self.name_contains = name_contains # only run processes on files with name_contains in file name
|
|
36
|
+
|
|
37
|
+
batchdisp('BiomechZoo initialized', level=1, verbose=verbose)
|
|
38
|
+
batchdisp('verbosity set to: {}'.format(verbose), level=1, verbose=verbose)
|
|
39
|
+
batchdisp('root processing folder set to: {}'.format(self.in_folder), level=1, verbose=verbose)
|
|
40
|
+
if name_contains is not None:
|
|
41
|
+
batchdisp('only include files containing name_contains string: {}'.format(self.name_contains), level=1, verbose=verbose)
|
|
42
|
+
if subfolders is not None:
|
|
43
|
+
if type(subfolders) is list:
|
|
44
|
+
batchdisp('only process files in subfolder(s):', level=1, verbose=verbose)
|
|
45
|
+
for subfolder in self.subfolders:
|
|
46
|
+
batchdisp('{}'.format(os.path.join(self.in_folder, subfolder)), level=1, verbose=verbose)
|
|
47
|
+
else:
|
|
48
|
+
batchdisp('only process files in subfolder(s): {}'.format(os.path.join(self.in_folder, self.subfolders)), level=1, verbose=verbose)
|
|
49
|
+
|
|
50
|
+
if inplace:
|
|
51
|
+
batchdisp('Processing mode: overwrite (inplace=True) (each step will be applied to same folder)', level=1, verbose=verbose)
|
|
52
|
+
else:
|
|
53
|
+
batchdisp('Processing mode: backup (inplace=False)(each step will be applied to a new folder)', level=1, verbose=verbose)
|
|
54
|
+
|
|
55
|
+
def _update_folder(self, out_folder, inplace, in_folder):
|
|
56
|
+
"""
|
|
57
|
+
Utility to update self.in_folder if not inplace.
|
|
58
|
+
|
|
59
|
+
Parameters:
|
|
60
|
+
- out_folder (str or None): The output folder provided by user
|
|
61
|
+
- inplace (bool): Whether processing is inplace
|
|
62
|
+
- in_folder (str): The current input folder
|
|
63
|
+
"""
|
|
64
|
+
if not inplace:
|
|
65
|
+
# get full path for out_folder
|
|
66
|
+
in_folder_path = os.path.dirname(in_folder)
|
|
67
|
+
self.in_folder = os.path.join(in_folder_path, out_folder)
|
|
68
|
+
|
|
69
|
+
batchdisp('all files saved to: {}'.format(self.in_folder ), level=1, verbose=self.verbose)
|
|
70
|
+
|
|
71
|
+
def mvnx2zoo(self, out_folder=None, inplace=False):
|
|
72
|
+
""" Converts all .mvnx files in the folder to .zoo format """
|
|
73
|
+
start_time = time.time()
|
|
74
|
+
verbose = self.verbose
|
|
75
|
+
in_folder = self.in_folder
|
|
76
|
+
if inplace is None:
|
|
77
|
+
inplace = self.inplace
|
|
78
|
+
fl = engine(in_folder, extension='.mvnx', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
79
|
+
for f in fl:
|
|
80
|
+
batchdisp('converting mvnx to zoo for {}'.format(f), level=2, verbose=verbose)
|
|
81
|
+
data = mvnx2zoo_data(f)
|
|
82
|
+
f_zoo = f.replace('.mvnx', '.zoo')
|
|
83
|
+
zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
84
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
85
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
86
|
+
# Update self.folder after processing
|
|
87
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
88
|
+
|
|
89
|
+
def c3d2zoo(self, out_folder=None, inplace=None):
|
|
90
|
+
""" Converts all .c3d files in the folder to .zoo format """
|
|
91
|
+
start_time = time.time()
|
|
92
|
+
from ezc3d import c3d
|
|
93
|
+
verbose = self.verbose
|
|
94
|
+
in_folder = self.in_folder
|
|
95
|
+
if inplace is None:
|
|
96
|
+
inplace = self.inplace
|
|
97
|
+
fl = engine(in_folder, extension='.c3d', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
98
|
+
for f in fl:
|
|
99
|
+
batchdisp('converting c3d to zoo for {}'.format(f), level=2, verbose=verbose)
|
|
100
|
+
c3d_obj = c3d(f)
|
|
101
|
+
data = c3d2zoo_data(c3d_obj)
|
|
102
|
+
f_zoo = f.replace('.c3d', '.zoo')
|
|
103
|
+
zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
104
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
105
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
106
|
+
# Update self.folder after processing
|
|
107
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
108
|
+
|
|
109
|
+
def table2zoo(self, extension, out_folder=None, inplace=None, skip_rows=0, freq=None):
|
|
110
|
+
""" Converts generic .csv file in the folder to .zoo format """
|
|
111
|
+
start_time = time.time()
|
|
112
|
+
verbose = self.verbose
|
|
113
|
+
in_folder = self.in_folder
|
|
114
|
+
|
|
115
|
+
if extension.startswith('.'):
|
|
116
|
+
extension = extension[1:]
|
|
117
|
+
|
|
118
|
+
if inplace is None:
|
|
119
|
+
inplace = self.inplace
|
|
120
|
+
fl = engine(in_folder, extension=extension, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
121
|
+
for f in fl:
|
|
122
|
+
batchdisp('converting {} to zoo for {}'.format(extension, f), level=2, verbose=verbose)
|
|
123
|
+
data = table2zoo_data(f, extension=extension, skip_rows=skip_rows, freq=freq)
|
|
124
|
+
f_zoo = f.replace(extension, '.zoo')
|
|
125
|
+
zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
126
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
127
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
128
|
+
# Update self.folder after processing
|
|
129
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
130
|
+
|
|
131
|
+
def xls2zoo(self, out_folder=None, inplace=None):
|
|
132
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
133
|
+
|
|
134
|
+
def csv2zoo(self, out_folder=None, inplace=None):
|
|
135
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
136
|
+
|
|
137
|
+
def parquet2zoo(self, out_folder=None, inplace=None):
|
|
138
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
139
|
+
|
|
140
|
+
def rectify(self):
|
|
141
|
+
raise NotImplementedError
|
|
142
|
+
def tilt_algorithm(self, chname_avert, chname_medlat, chname_antpost, out_folder=None, inplace=False):
|
|
143
|
+
""" tilt correction for acceleration data """
|
|
144
|
+
start_time = time.time()
|
|
145
|
+
verbose = self.verbose
|
|
146
|
+
in_folder = self.in_folder
|
|
147
|
+
if inplace is None:
|
|
148
|
+
inplace = self.inplace
|
|
149
|
+
fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
150
|
+
for f in fl:
|
|
151
|
+
batchdisp('tilt correction of acceleration channels for {}'.format(f), level=2, verbose=verbose)
|
|
152
|
+
data = zload(f)
|
|
153
|
+
data = tilt_algorithm_data(data, chname_avert, chname_medlat, chname_antpost)
|
|
154
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
155
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
156
|
+
batchdisp(
|
|
157
|
+
'{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
|
|
158
|
+
level=1, verbose=verbose)
|
|
159
|
+
# Update self.folder after processing
|
|
160
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
161
|
+
|
|
162
|
+
def compute_magnitude(self, chname1, chname2, chname3, out_folder=None, inplace=False):
|
|
163
|
+
""" tilt correction for acceleration data """
|
|
164
|
+
start_time = time.time()
|
|
165
|
+
verbose = self.verbose
|
|
166
|
+
in_folder = self.in_folder
|
|
167
|
+
if inplace is None:
|
|
168
|
+
inplace = self.inplace
|
|
169
|
+
fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
170
|
+
for f in fl:
|
|
171
|
+
batchdisp('compute magnitude from channels {}, {}, {} for {}'.format(chname1, chname2, chname3, f), level=2, verbose=verbose)
|
|
172
|
+
data = zload(f)
|
|
173
|
+
data = compute_magnitude_data(data, chname1, chname2, chname3)
|
|
174
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
175
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
176
|
+
batchdisp(
|
|
177
|
+
'{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
|
|
178
|
+
level=1, verbose=verbose)
|
|
179
|
+
# Update self.folder after processing
|
|
180
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
181
|
+
|
|
182
|
+
def phase_angle(self, ch, out_folder=None, inplace=None):
|
|
183
|
+
""" computes phase angles"""
|
|
184
|
+
start_time = time.time()
|
|
185
|
+
verbose = self.verbose
|
|
186
|
+
in_folder = self.in_folder
|
|
187
|
+
if inplace is None:
|
|
188
|
+
inplace = self.inplace
|
|
189
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
190
|
+
for f in fl:
|
|
191
|
+
if verbose:
|
|
192
|
+
batchdisp('computing phase angles for {}'.format(f), level=2, verbose=verbose)
|
|
193
|
+
data = zload(f)
|
|
194
|
+
data = phase_angle_data(data, ch)
|
|
195
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
196
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
197
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
198
|
+
|
|
199
|
+
# Update self.folder after processing
|
|
200
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
201
|
+
|
|
202
|
+
def continuous_relative_phase(self, ch_prox, ch_dist, out_folder=None, inplace=None):
|
|
203
|
+
""" computes CRP angles"""
|
|
204
|
+
start_time = time.time()
|
|
205
|
+
verbose = self.verbose
|
|
206
|
+
in_folder = self.in_folder
|
|
207
|
+
if inplace is None:
|
|
208
|
+
inplace = self.inplace
|
|
209
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
210
|
+
for f in fl:
|
|
211
|
+
if verbose:
|
|
212
|
+
batchdisp('computing CRP angles between channel {} (prox) and {} (dist) for {}'.format(ch_prox, ch_dist, f), level=2, verbose=verbose)
|
|
213
|
+
data = zload(f)
|
|
214
|
+
data = continuous_relative_phase_data(data, ch_dist, ch_prox)
|
|
215
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
216
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
217
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
218
|
+
|
|
219
|
+
# Update self.folder after processing
|
|
220
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
221
|
+
|
|
222
|
+
def split_trial_by_gait_cycle(self, first_event_name, out_folder=None, inplace=None):
|
|
223
|
+
""" split by gait cycle according to event_name"""
|
|
224
|
+
start_time = time.time()
|
|
225
|
+
verbose = self.verbose
|
|
226
|
+
in_folder = self.in_folder
|
|
227
|
+
if inplace is None:
|
|
228
|
+
inplace = self.inplace
|
|
229
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
230
|
+
for f in fl:
|
|
231
|
+
f_name = os.path.splitext(os.path.basename(f))[0]
|
|
232
|
+
data = zload(f)
|
|
233
|
+
split_events = get_split_events(data, first_event_name)
|
|
234
|
+
if split_events is None:
|
|
235
|
+
print('no event {} found, saving original file'.format(first_event_name))
|
|
236
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
237
|
+
else:
|
|
238
|
+
for i, _ in enumerate(split_events[0:-1]):
|
|
239
|
+
fl_new = f.replace(f_name, f_name + '_' + str(i + 1))
|
|
240
|
+
start = split_events[i]
|
|
241
|
+
end = split_events[i + 1]
|
|
242
|
+
batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
|
|
243
|
+
verbose=verbose)
|
|
244
|
+
data_new = split_trial_data(data, start, end)
|
|
245
|
+
if data_new is not None:
|
|
246
|
+
zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
247
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
248
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
249
|
+
|
|
250
|
+
# Update self.folder after processing
|
|
251
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# def mean_absolute_relative_phase_deviation_phase(self, channels, out_folder=None, inplace=None):
|
|
255
|
+
# verbose = self.verbose
|
|
256
|
+
# in_folder = self.in_folder
|
|
257
|
+
# if inplace is None:
|
|
258
|
+
# inplace = self.inplace
|
|
259
|
+
#
|
|
260
|
+
# fl = engine(in_folder)
|
|
261
|
+
# for f in fl:
|
|
262
|
+
# for channel in channels:
|
|
263
|
+
# batchdisp('collecting trials for marp and dp for {}'.format(f), level=2, verbose=verbose)
|
|
264
|
+
# data = zload(f)
|
|
265
|
+
# data = removechannel_data(data, ch, mode)
|
|
266
|
+
# zsave(f, data, inplace=inplace, root_folder=in_folder, out_folder=out_folder)
|
|
267
|
+
# batchdisp('remove channel complete', level=1, verbose=verbose)
|
|
268
|
+
#
|
|
269
|
+
# # Update self.folder after processing
|
|
270
|
+
# self._update_folder(out_folder, inplace, in_folder)
|
|
271
|
+
def renameevent(self, evt, nevt, out_folder=None, inplace=None):
|
|
272
|
+
""" renames event evt to nevt in all zoo files """
|
|
273
|
+
start_time = time.time()
|
|
274
|
+
verbose = self.verbose
|
|
275
|
+
in_folder = self.in_folder
|
|
276
|
+
if inplace is None:
|
|
277
|
+
inplace = self.inplace
|
|
278
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
279
|
+
for f in fl:
|
|
280
|
+
batchdisp('renaming events from {} to {} for {}'.format(evt, nevt ,f), level=2, verbose=verbose)
|
|
281
|
+
data = zload(f)
|
|
282
|
+
data = renameevent_data(data, evt, nevt)
|
|
283
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
284
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
285
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
286
|
+
|
|
287
|
+
# Update self.folder after processing
|
|
288
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
289
|
+
|
|
290
|
+
def renamechannnel(self, ch, ch_new, out_folder=None, inplace=None):
|
|
291
|
+
""" renames channels from ch to ch_new in all zoo files """
|
|
292
|
+
start_time = time.time()
|
|
293
|
+
verbose = self.verbose
|
|
294
|
+
in_folder = self.in_folder
|
|
295
|
+
if inplace is None:
|
|
296
|
+
inplace = self.inplace
|
|
297
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
298
|
+
for f in fl:
|
|
299
|
+
batchdisp('renaming channels from {} to {} for {}'.format(ch, ch_new ,f), level=2, verbose=verbose)
|
|
300
|
+
data = zload(f)
|
|
301
|
+
data = renamechannel_data(data, ch, ch_new)
|
|
302
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
303
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
304
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
305
|
+
|
|
306
|
+
# Update self.folder after processing
|
|
307
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
308
|
+
|
|
309
|
+
def removechannel(self, ch, mode='remove', out_folder=None, inplace=None):
|
|
310
|
+
""" removes channels from zoo files """
|
|
311
|
+
start_time = time.time()
|
|
312
|
+
verbose = self.verbose
|
|
313
|
+
in_folder = self.in_folder
|
|
314
|
+
if inplace is None:
|
|
315
|
+
inplace = self.inplace
|
|
316
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
317
|
+
for f in fl:
|
|
318
|
+
batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
|
|
319
|
+
data = zload(f)
|
|
320
|
+
data = removechannel_data(data, ch, mode)
|
|
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)
|
|
324
|
+
|
|
325
|
+
# Update self.folder after processing
|
|
326
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def removeevent(self, events, mode='remove', out_folder=None, inplace=None):
|
|
330
|
+
""" removes channels from zoo files """
|
|
331
|
+
start_time = time.time()
|
|
332
|
+
verbose = self.verbose
|
|
333
|
+
in_folder = self.in_folder
|
|
334
|
+
if inplace is None:
|
|
335
|
+
inplace = self.inplace
|
|
336
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
337
|
+
for f in fl:
|
|
338
|
+
batchdisp('removing events {} for {}'.format(events, f), level=2, verbose=verbose)
|
|
339
|
+
data = zload(f)
|
|
340
|
+
data = removeevent_data(data, events, mode)
|
|
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)
|
|
344
|
+
|
|
345
|
+
# Update self.folder after processing
|
|
346
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def explodechannel(self, out_folder=None, inplace=None):
|
|
350
|
+
""" explodes all channels in a zoo file """
|
|
351
|
+
start_time = time.time()
|
|
352
|
+
verbose = self.verbose
|
|
353
|
+
in_folder = self.in_folder
|
|
354
|
+
if inplace is None:
|
|
355
|
+
inplace = self.inplace
|
|
356
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
357
|
+
for f in fl:
|
|
358
|
+
if verbose:
|
|
359
|
+
batchdisp('removing channels for {}'.format(f), level=2, verbose=verbose)
|
|
360
|
+
data = zload(f)
|
|
361
|
+
data = explodechannel_data(data)
|
|
362
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
363
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
364
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
365
|
+
|
|
366
|
+
# Update self.folder after processing
|
|
367
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
368
|
+
|
|
369
|
+
def normalize(self, nlen=101, out_folder=None, inplace=None):
|
|
370
|
+
""" time normalizes all channels to length nlen """
|
|
371
|
+
start_time = time.time()
|
|
372
|
+
verbose = self.verbose
|
|
373
|
+
in_folder = self.in_folder
|
|
374
|
+
if inplace is None:
|
|
375
|
+
inplace = self.inplace
|
|
376
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
377
|
+
for f in fl:
|
|
378
|
+
if verbose:
|
|
379
|
+
batchdisp('normalizing channels to length {} for {}'.format(nlen, f), level=2, verbose=verbose)
|
|
380
|
+
data = zload(f)
|
|
381
|
+
data = normalize_data(data, nlen)
|
|
382
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
383
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
384
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
385
|
+
|
|
386
|
+
# Update self.folder after processing
|
|
387
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
388
|
+
|
|
389
|
+
def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None, constant=None):
|
|
390
|
+
""" adds events of type evt_type with name evt_name to channel ch """
|
|
391
|
+
start_time = time.time()
|
|
392
|
+
verbose = self.verbose
|
|
393
|
+
in_folder = self.in_folder
|
|
394
|
+
if inplace is None:
|
|
395
|
+
inplace = self.inplace
|
|
396
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
397
|
+
for f in fl:
|
|
398
|
+
if verbose:
|
|
399
|
+
batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
|
|
400
|
+
data = zload(f)
|
|
401
|
+
data = addevent_data(data, ch, event_type, event_name, constant)
|
|
402
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
403
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
404
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
405
|
+
|
|
406
|
+
# Update self.folder after processing
|
|
407
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
408
|
+
|
|
409
|
+
def partition(self, evt_start, evt_end, out_folder=None, inplace=None):
|
|
410
|
+
""" partitions data between events evt_start and evt_end """
|
|
411
|
+
start_time = time.time()
|
|
412
|
+
verbose = self.verbose
|
|
413
|
+
in_folder = self.in_folder
|
|
414
|
+
if inplace is None:
|
|
415
|
+
inplace = self.inplace
|
|
416
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
417
|
+
for f in fl:
|
|
418
|
+
if verbose:
|
|
419
|
+
batchdisp('partitioning data between events {} and {} for {}'.format(evt_start, evt_end, f), level=2, verbose=verbose)
|
|
420
|
+
data = zload(f)
|
|
421
|
+
data = partition_data(data, evt_start, evt_end)
|
|
422
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
423
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
424
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
425
|
+
# Update self.folder after processing
|
|
426
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
427
|
+
|
|
428
|
+
def filter(self, ch, filt=None, out_folder=None, inplace=None):
|
|
429
|
+
""" filter data"""
|
|
430
|
+
start_time = time.time()
|
|
431
|
+
verbose = self.verbose
|
|
432
|
+
in_folder = self.in_folder
|
|
433
|
+
if inplace is None:
|
|
434
|
+
inplace = self.inplace
|
|
435
|
+
fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
436
|
+
for f in fl:
|
|
437
|
+
if verbose:
|
|
438
|
+
batchdisp('filtering data for channel {} in {}'.format(ch, f), level=2, verbose=verbose)
|
|
439
|
+
data = zload(f)
|
|
440
|
+
data = filter_data(data, ch, filt)
|
|
441
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
442
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
443
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
|
|
444
|
+
level=1, verbose=verbose)
|
|
445
|
+
# Update self.folder after processing
|
|
446
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
447
|
+
|
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from biomechzoo.utils.set_zoosystem import set_zoosystem
|
|
2
|
+
|
|
3
|
+
def c3d2zoo_data(c3d_obj):
|
|
4
|
+
"""
|
|
5
|
+
Converts an ezc3d C3D object to zoo format.
|
|
6
|
+
|
|
7
|
+
Returns:
|
|
8
|
+
- data (dict): Zoo dictionary with 'line' and 'event' fields per channel.
|
|
9
|
+
"""
|
|
10
|
+
data = {}
|
|
11
|
+
data['zoosystem'] = set_zoosystem()
|
|
12
|
+
video_freq = None
|
|
13
|
+
analog_freq = None
|
|
14
|
+
# extract "video" data
|
|
15
|
+
if 'points' in c3d_obj['data']:
|
|
16
|
+
points = c3d_obj['data']['points'] # shape: (4, n_markers, n_frames)
|
|
17
|
+
labels = list(c3d_obj['parameters']['POINT']['LABELS']['value'])
|
|
18
|
+
video_freq = int(c3d_obj['parameters']['POINT']['RATE']['value'][0])
|
|
19
|
+
for i, label in enumerate(labels):
|
|
20
|
+
line_data = points[:3, i, :].T # shape: (frames, 3)
|
|
21
|
+
data[label] = {
|
|
22
|
+
'line': line_data,
|
|
23
|
+
'event': {} # empty for now
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
data['zoosystem']['Video']['Freq'] = video_freq
|
|
27
|
+
data['zoosystem']['Video']['Channels'] = labels
|
|
28
|
+
|
|
29
|
+
if 'analogs' in c3d_obj['data']:
|
|
30
|
+
analog_data = c3d_obj['data']['analogs'] # shape: (subframes, n_analog_channels, n_frames)
|
|
31
|
+
analog_labels = list(c3d_obj['parameters']['ANALOG']['LABELS']['value'])
|
|
32
|
+
analog_freq = int(c3d_obj['parameters']['ANALOG']['RATE']['value'][0])
|
|
33
|
+
# Flatten to 2D: (n_samples, n_channels)
|
|
34
|
+
# ezc3d stores analogs as subframes per frame, so we flatten across all
|
|
35
|
+
n_subframes, n_channels, n_frames = analog_data.shape
|
|
36
|
+
analog_data = analog_data.reshape(n_subframes * n_frames, n_channels)
|
|
37
|
+
|
|
38
|
+
for i, label in enumerate(analog_labels):
|
|
39
|
+
line_data = analog_data[:, i].reshape(-1, 1) # shape: (samples, 1)
|
|
40
|
+
data[label] = {
|
|
41
|
+
'line': line_data,
|
|
42
|
+
'event': {},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
data['zoosystem']['Analog']['Freq'] = analog_freq
|
|
46
|
+
data['zoosystem']['Analog']['Channels'] = analog_labels
|
|
47
|
+
|
|
48
|
+
# extract event information
|
|
49
|
+
params = c3d_obj['parameters']
|
|
50
|
+
if 'EVENT' in params and 'TIMES' in params['EVENT']:
|
|
51
|
+
if 'points' in c3d_obj['data']:
|
|
52
|
+
times_array = params['EVENT']['TIMES']['value']
|
|
53
|
+
frames = times_array[1] # should be time depending on C3D file
|
|
54
|
+
|
|
55
|
+
# Extract sides, types, subjects
|
|
56
|
+
contexts = params['EVENT']['CONTEXTS']['value'] if 'CONTEXTS' in params['EVENT'] else ['']
|
|
57
|
+
labels = params['EVENT']['LABELS']['value']
|
|
58
|
+
subjects = params['EVENT']['SUBJECTS']['value'] if 'SUBJECTS' in params['EVENT'] else ['']
|
|
59
|
+
|
|
60
|
+
events = {}
|
|
61
|
+
|
|
62
|
+
for i in range(len(labels)):
|
|
63
|
+
side = contexts[i].strip()
|
|
64
|
+
label = labels[i].strip()
|
|
65
|
+
subject = subjects[i].strip()
|
|
66
|
+
|
|
67
|
+
# Event channel name: e.g. 'Right_FootStrike' -> 'RightFootStrike'
|
|
68
|
+
event_name = f"{side}_{label}".replace(' ', '')
|
|
69
|
+
event_name = ''.join(c for c in event_name if c.isalnum() or c == '_') # make it a valid field name
|
|
70
|
+
|
|
71
|
+
if event_name not in events:
|
|
72
|
+
events[event_name] = []
|
|
73
|
+
|
|
74
|
+
events[event_name].append(frames[i]) # This is in seconds or frame number?
|
|
75
|
+
|
|
76
|
+
original_start = 1
|
|
77
|
+
|
|
78
|
+
for event_name, time_list in events.items():
|
|
79
|
+
# Clean and sort times
|
|
80
|
+
valid_times = sorted([t for t in time_list if t != 0])
|
|
81
|
+
for j, time_val in enumerate(valid_times):
|
|
82
|
+
frame = round(time_val * video_freq) - original_start + 1 # MATLAB logic
|
|
83
|
+
key_name = f"{event_name}{j + 1}"
|
|
84
|
+
|
|
85
|
+
# Place in correct channel
|
|
86
|
+
if 'SACR' in data:
|
|
87
|
+
data['SACR']['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
|
|
88
|
+
else:
|
|
89
|
+
data[labels[0]]['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
|
|
90
|
+
|
|
91
|
+
# add more zoosystem
|
|
92
|
+
if analog_freq is not None and video_freq is not None:
|
|
93
|
+
data['zoosystem']['AVR'] = analog_freq/video_freq
|
|
94
|
+
|
|
95
|
+
return data
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from biomechzoo.mvn.load_mvnx import load_mvnx
|
|
3
|
+
from biomechzoo.mvn.mvn import JOINTS, SEGMENTS
|
|
4
|
+
from biomechzoo.utils.set_zoosystem import set_zoosystem
|
|
5
|
+
|
|
6
|
+
def mvnx2zoo_data(fl):
|
|
7
|
+
""" loads mvnx file from xsens"""
|
|
8
|
+
|
|
9
|
+
mvnx_file = load_mvnx(fl)
|
|
10
|
+
|
|
11
|
+
# create zoo data dict
|
|
12
|
+
data = {'zoosystem': set_zoosystem()}
|
|
13
|
+
# extract joint angle data (All JOINTS may not exist in a given dataset)
|
|
14
|
+
for key, val in JOINTS.items():
|
|
15
|
+
try:
|
|
16
|
+
r = mvnx_file.get_joint_angle(joint=key)
|
|
17
|
+
data[val] = {
|
|
18
|
+
'line': np.array(r),
|
|
19
|
+
'event': {}
|
|
20
|
+
}
|
|
21
|
+
except KeyError:
|
|
22
|
+
print('joint {} does not exist, skipping'.format(val))
|
|
23
|
+
|
|
24
|
+
# extract segment orientations (All SEGMENTS may not exist in a given dataset)
|
|
25
|
+
for key, val in SEGMENTS.items():
|
|
26
|
+
try:
|
|
27
|
+
r = mvnx_file.get_sensor_ori(segment=key)
|
|
28
|
+
|
|
29
|
+
data[val] = {
|
|
30
|
+
'line': np.array(r),
|
|
31
|
+
'event': {}
|
|
32
|
+
}
|
|
33
|
+
except KeyError:
|
|
34
|
+
print('segment {} does not exist, skipping'.format(val))
|
|
35
|
+
|
|
36
|
+
# get foot strike events
|
|
37
|
+
data = _get_foot_strike_events(mvnx_file, data)
|
|
38
|
+
|
|
39
|
+
# add meta information
|
|
40
|
+
data = _get_meta_info(fl, mvnx_file, data)
|
|
41
|
+
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_valid_for_zoo(val):
|
|
46
|
+
"""
|
|
47
|
+
Returns True if the value is valid for a MATLAB-compatible zoo structure.
|
|
48
|
+
"""
|
|
49
|
+
if val is None:
|
|
50
|
+
return False
|
|
51
|
+
if isinstance(val, list) and len(val) == 0:
|
|
52
|
+
return False
|
|
53
|
+
if isinstance(val, np.ndarray) and val.size == 0:
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _get_meta_info(fl, mvnx_file, data):
|
|
59
|
+
# todo: add more, see mvnx_file object
|
|
60
|
+
data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
|
|
61
|
+
data['zoosystem']['mvnx_version'] = mvnx_file.version
|
|
62
|
+
data['zoosystem']['mvnx_configuration'] = mvnx_file.configuration
|
|
63
|
+
data['zoosystem']['recording_date'] = mvnx_file.recording_date
|
|
64
|
+
data['zoosystem']['original_file_name'] = mvnx_file.original_file_name
|
|
65
|
+
data['zoosystem']['frame_count'] = mvnx_file.frame_count
|
|
66
|
+
return data
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _get_foot_strike_events(mvnx_file, data):
|
|
70
|
+
RHeel = np.zeros(mvnx_file.frame_count)
|
|
71
|
+
LHeel = np.zeros(mvnx_file.frame_count)
|
|
72
|
+
|
|
73
|
+
for n in range(mvnx_file.frame_count):
|
|
74
|
+
list_contact = mvnx_file.get_foot_contacts(n)
|
|
75
|
+
for contact in list_contact:
|
|
76
|
+
if contact['segment_index'] == 17:
|
|
77
|
+
RHeel[n] = True
|
|
78
|
+
elif contact['segment_index'] == 21:
|
|
79
|
+
LHeel[n] = True
|
|
80
|
+
|
|
81
|
+
hs_r = []
|
|
82
|
+
hs_l = []
|
|
83
|
+
for i in range(1, len(LHeel)): # Start from 1 to avoid i-1 out-of-range
|
|
84
|
+
if RHeel[i - 1] == 0 and RHeel[i] == 1:
|
|
85
|
+
hs_r.append(i)
|
|
86
|
+
if LHeel[i - 1] == 0 and LHeel[i] == 1:
|
|
87
|
+
hs_l.append(i)
|
|
88
|
+
|
|
89
|
+
# add to event branch of any channel
|
|
90
|
+
if 'jL5S1' in data:
|
|
91
|
+
ch = 'jL5S1'
|
|
92
|
+
else:
|
|
93
|
+
ch = next(iter(data))
|
|
94
|
+
|
|
95
|
+
if hs_r:
|
|
96
|
+
for i, rHS in enumerate(hs_r):
|
|
97
|
+
data[ch]['event']['R_FS' + str(i + 1)] = [rHS, 0, 0]
|
|
98
|
+
if hs_l:
|
|
99
|
+
for i, lHS in enumerate(hs_l):
|
|
100
|
+
data[ch]['event']['L_FS' + str(i + 1)] = [lHS, 0, 0]
|
|
101
|
+
|
|
102
|
+
return data
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == '__main__':
|
|
106
|
+
""" testing """
|
|
107
|
+
import os
|
|
108
|
+
from biomechzoo.processing.split_trial_data import split_trial_data
|
|
109
|
+
# -------TESTING--------
|
|
110
|
+
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
111
|
+
fl = os.path.join(project_root, 'data', 'other', 'Flat001.mvnx')
|
|
112
|
+
fl_zoo = fl.replace('.mvnx', '.zoo')
|
|
113
|
+
data = mvnx2zoo_data(fl)
|
|
@@ -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)
|