biomechzoo 0.4.10__py3-none-any.whl → 0.5.0__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/biomech_ops/filter_data.py +17 -15
- biomechzoo/biomech_ops/filter_line.py +70 -73
- biomechzoo/biomech_ops/normalize_data.py +4 -3
- biomechzoo/biomechzoo.py +61 -32
- biomechzoo/conversion/c3d2zoo_data.py +35 -8
- biomechzoo/conversion/mvnx2zoo_data.py +1 -3
- biomechzoo/conversion/table2zoo_data.py +51 -42
- biomechzoo/processing/addevent_data.py +53 -11
- biomechzoo/processing/explodechannel_data.py +25 -3
- biomechzoo/processing/partition_data.py +14 -27
- biomechzoo/processing/removechannel_data.py +16 -6
- biomechzoo/processing/removeevent_data.py +57 -0
- biomechzoo/processing/renameevent_data.py +0 -6
- biomechzoo/utils/engine.py +7 -3
- biomechzoo/utils/peak_sign.py +24 -0
- biomechzoo/utils/split_trial.py +24 -12
- biomechzoo/utils/zload.py +11 -0
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/METADATA +1 -1
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/RECORD +23 -21
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/WHEEL +0 -0
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/entry_points.txt +0 -0
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {biomechzoo-0.4.10.dist-info → biomechzoo-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -13,10 +13,10 @@ def filter_data(data, ch, filt=None):
|
|
|
13
13
|
The name(s) of the channel(s) to filter.
|
|
14
14
|
filt : dict, optional
|
|
15
15
|
Dictionary specifying filter parameters. Keys may include:
|
|
16
|
-
- '
|
|
16
|
+
- 'ftype': 'butter' (default)
|
|
17
17
|
- 'order': filter order (default: 4)
|
|
18
18
|
- 'cutoff': cutoff frequency or tuple (Hz)
|
|
19
|
-
- 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: '
|
|
19
|
+
- 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'lowpass')
|
|
20
20
|
|
|
21
21
|
Returns
|
|
22
22
|
-------
|
|
@@ -25,32 +25,34 @@ def filter_data(data, ch, filt=None):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
if filt is None:
|
|
28
|
-
filt = {
|
|
28
|
+
filt = {'ftype': 'butter',
|
|
29
|
+
'order': 4,
|
|
30
|
+
'cutoff': 10,
|
|
31
|
+
'btype': 'lowpass',
|
|
32
|
+
'filtfilt': True}
|
|
29
33
|
|
|
30
34
|
if isinstance(ch, str):
|
|
31
35
|
ch = [ch]
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
if analog_channels:
|
|
35
|
-
analog_freq = data['zoosystem']['Analog']['Freq']
|
|
36
|
-
video_channels = data['zoosystem']['Video']['Channels']
|
|
37
|
-
if video_channels:
|
|
38
|
-
video_freq = data['zoosystem']['Video']['Freq']
|
|
39
|
-
|
|
37
|
+
# loop through all channels and filter
|
|
40
38
|
for c in ch:
|
|
41
39
|
if c not in data:
|
|
42
40
|
raise KeyError('Channel {} not found in data'.format(c))
|
|
43
41
|
|
|
44
42
|
if 'fs' not in filt:
|
|
43
|
+
|
|
44
|
+
video_channels = data['zoosystem']['Video']['Channels']
|
|
45
|
+
analog_channels = data['zoosystem']['Analog']['Channels']
|
|
46
|
+
|
|
45
47
|
if c in analog_channels:
|
|
46
|
-
filt['fs'] =
|
|
47
|
-
elif c in
|
|
48
|
-
filt['fs'] =
|
|
48
|
+
filt['fs'] = data['zoosystem']['Analog']['Freq']
|
|
49
|
+
elif c in video_channels:
|
|
50
|
+
filt['fs'] = data['zoosystem']['Video']['Freq']
|
|
49
51
|
else:
|
|
50
|
-
raise ValueError('
|
|
52
|
+
raise ValueError('Channel not analog or video')
|
|
51
53
|
|
|
52
54
|
signal_raw = data[c]['line']
|
|
53
|
-
signal_filtered = filter_line(signal_raw, filt)
|
|
55
|
+
signal_filtered = filter_line(signal_raw=signal_raw, filt=filt)
|
|
54
56
|
data[c]['line'] = signal_filtered
|
|
55
57
|
|
|
56
58
|
return data
|
|
@@ -1,88 +1,85 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
|
|
2
|
+
import scipy.signal as sgl
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def filter_line(signal_raw, filt):
|
|
6
|
-
"""
|
|
5
|
+
def filter_line(signal_raw, filt=None, fs=None):
|
|
6
|
+
"""Filter an array using a Butterworth filter."""
|
|
7
|
+
#todo: verify that filter is working correctly
|
|
8
|
+
#todo add more filters
|
|
9
|
+
#todo: consider using kineticstoolkit
|
|
7
10
|
|
|
8
|
-
Arguments
|
|
9
|
-
----------
|
|
10
|
-
signal_raw : n, or n x 3 array signal to be filtered
|
|
11
|
-
filt : dict, optional
|
|
12
|
-
Dictionary specifying filter parameters. Keys may include:
|
|
13
|
-
- 'type': 'butter' (default)
|
|
14
|
-
- 'order': filter order (default: 4)
|
|
15
|
-
- 'cutoff': cutoff frequency or tuple (Hz)
|
|
16
|
-
- 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'low')
|
|
17
|
-
- 'fs' frequency
|
|
18
|
-
|
|
19
|
-
Returns
|
|
20
|
-
-------
|
|
21
|
-
signal_filtered: filtered version of signal_raw"""
|
|
22
|
-
# todo allow for missing frequency to be obtained from zoosystem metadata
|
|
23
11
|
if filt is None:
|
|
24
|
-
filt = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
btype = filt.get('btype', 'low')
|
|
32
|
-
fs = filt.get('fs', None)
|
|
33
|
-
|
|
34
|
-
if ftype != 'butter':
|
|
35
|
-
raise NotImplementedError(f"Filter type '{ftype}' not implemented.")
|
|
36
|
-
|
|
37
|
-
if fs is None:
|
|
38
|
-
raise ValueError("Sampling frequency 'fs' must be specified in filt.")
|
|
39
|
-
|
|
40
|
-
if cutoff is None:
|
|
41
|
-
raise ValueError("Cutoff frequency 'cutoff' must be specified in filt.")
|
|
12
|
+
filt = {'ftype': 'butter',
|
|
13
|
+
'order': 4,
|
|
14
|
+
'cutoff': 10,
|
|
15
|
+
'btype': 'lowpass',
|
|
16
|
+
'filtfilt': True}
|
|
17
|
+
if fs is None:
|
|
18
|
+
raise ValueError('fs is required if no filt is specified')
|
|
42
19
|
|
|
20
|
+
else:
|
|
21
|
+
if 'fs' not in filt:
|
|
22
|
+
raise ValueError('fs is a required key of filt')
|
|
23
|
+
|
|
24
|
+
# Normalize filter type strings
|
|
25
|
+
if filt['ftype'] == 'butterworth':
|
|
26
|
+
filt['ftype'] = 'butter'
|
|
27
|
+
if filt['btype'] is 'low':
|
|
28
|
+
filt['btype'] = 'lowpass'
|
|
29
|
+
if filt['btype'] is 'high':
|
|
30
|
+
filt['btype'] = 'highpass'
|
|
31
|
+
|
|
32
|
+
# Extract parameters
|
|
33
|
+
ftype = filt['ftype']
|
|
34
|
+
order = filt['order']
|
|
35
|
+
cutoff = filt['cutoff']
|
|
36
|
+
btype = filt['btype']
|
|
37
|
+
filtfilt = filt['filtfilt']
|
|
38
|
+
fs = filt['fs']
|
|
39
|
+
|
|
40
|
+
# prepare normalized cutoff(s)
|
|
43
41
|
nyq = 0.5 * fs
|
|
44
|
-
norm_cutoff = np.array(cutoff) / nyq
|
|
45
|
-
|
|
46
|
-
b, a = butter(order, norm_cutoff, btype=btype, analog=False)
|
|
42
|
+
norm_cutoff = np.atleast_1d(np.array(cutoff) / nyq)
|
|
47
43
|
|
|
48
|
-
if
|
|
49
|
-
signal_filtered =
|
|
44
|
+
if ftype is 'butter':
|
|
45
|
+
signal_filtered = kt_butter(ts=signal_raw, fc=norm_cutoff, fs=fs, order=order, btype=btype, filtfilt=filtfilt)
|
|
50
46
|
else:
|
|
51
|
-
|
|
52
|
-
signal_filtered = np.array([filtfilt(b, a, signal_raw[:, i]) for i in range(signal_raw.shape[1])]).T
|
|
47
|
+
raise NotImplementedError(f"Filter type '{ftype}' not implemented.")
|
|
53
48
|
|
|
54
49
|
return signal_filtered
|
|
55
50
|
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
plt.legend()
|
|
84
|
-
plt.grid(True)
|
|
85
|
-
plt.tight_layout()
|
|
86
|
-
plt.show()
|
|
52
|
+
def kt_butter(ts, fc, fs, order=2, btype='lowpass', filtfilt=True):
|
|
53
|
+
"""
|
|
54
|
+
Apply a Butterworth filter to data.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
ts, ndarray, 1d.
|
|
59
|
+
fc, Cut-off frequency in Hz. This is a float for single-frequency filters
|
|
60
|
+
(lowpass, highpass), or a tuple of two floats (e.g., (10., 13.)
|
|
61
|
+
for two-frequency filters (bandpass, bandstop)).
|
|
62
|
+
order, Optional. Order of the filter. Default is 2.
|
|
63
|
+
btype, Optional. Can be either "lowpass", "highpass", "bandpass" or
|
|
64
|
+
"bandstop". Default is "lowpass".
|
|
65
|
+
filtfilt, Optional. If True, the filter is applied two times in reverse direction
|
|
66
|
+
to eliminate time lag. If False, the filter is applied only in forward
|
|
67
|
+
direction. Default is True.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
ts_f, A copy of the input data which each data being filtered.
|
|
72
|
+
|
|
73
|
+
Notes:
|
|
74
|
+
- This code was adapted from kineticstoolkit Thanks @felxi
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
sos = sgl.butter(order, fc, btype, analog=False, output="sos", fs=fs)
|
|
87
78
|
|
|
79
|
+
# Filter
|
|
80
|
+
if filtfilt:
|
|
81
|
+
ts_f = sgl.sosfiltfilt(sos, ts, axis=0)
|
|
82
|
+
else:
|
|
83
|
+
ts_f = sgl.sosfilt(sos,ts, axis=0)
|
|
88
84
|
|
|
85
|
+
return ts_f
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import warnings
|
|
2
|
+
import copy
|
|
2
3
|
from biomechzoo.biomech_ops.normalize_line import normalize_line
|
|
3
4
|
|
|
4
5
|
|
|
@@ -14,14 +15,14 @@ def normalize_data(data, nlength=101):
|
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
# normalize channel length
|
|
17
|
-
data_new =
|
|
18
|
+
data_new = copy.deepcopy(data)
|
|
18
19
|
for ch_name, ch_data in data_new.items():
|
|
19
20
|
if ch_name != 'zoosystem':
|
|
20
21
|
ch_data_line = ch_data['line']
|
|
21
22
|
ch_data_event = ch_data['event']
|
|
22
23
|
ch_data_normalized = normalize_line(ch_data_line, nlength)
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
data_new[ch_name]['line'] = ch_data_normalized
|
|
25
|
+
data_new[ch_name]['event'] = ch_data_event
|
|
25
26
|
warnings.warn('event data have not been normalized')
|
|
26
27
|
|
|
27
28
|
# update zoosystem
|
biomechzoo/biomechzoo.py
CHANGED
|
@@ -12,6 +12,7 @@ from biomechzoo.conversion.table2zoo_data import table2zoo_data
|
|
|
12
12
|
from biomechzoo.conversion.mvnx2zoo_data import mvnx2zoo_data
|
|
13
13
|
from biomechzoo.processing.removechannel_data import removechannel_data
|
|
14
14
|
from biomechzoo.processing.renamechannel_data import renamechannel_data
|
|
15
|
+
from biomechzoo.processing.removeevent_data import removeevent_data
|
|
15
16
|
from biomechzoo.processing.explodechannel_data import explodechannel_data
|
|
16
17
|
from biomechzoo.processing.addevent_data import addevent_data
|
|
17
18
|
from biomechzoo.processing.partition_data import partition_data
|
|
@@ -19,6 +20,7 @@ from biomechzoo.processing.renameevent_data import renameevent_data
|
|
|
19
20
|
from biomechzoo.biomech_ops.normalize_data import normalize_data
|
|
20
21
|
from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
|
|
21
22
|
from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
|
|
23
|
+
from biomechzoo.biomech_ops.filter_data import filter_data
|
|
22
24
|
|
|
23
25
|
class BiomechZoo:
|
|
24
26
|
def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
|
|
@@ -61,6 +63,8 @@ class BiomechZoo:
|
|
|
61
63
|
in_folder_path = os.path.dirname(in_folder)
|
|
62
64
|
self.in_folder = os.path.join(in_folder_path, out_folder)
|
|
63
65
|
|
|
66
|
+
batchdisp('all files saved to: {}'.format(self.in_folder ), level=1, verbose=self.verbose)
|
|
67
|
+
|
|
64
68
|
def mvnx2zoo(self, out_folder=None, inplace=False):
|
|
65
69
|
""" Converts all .mvnx files in the folder to .zoo format """
|
|
66
70
|
start_time = time.time()
|
|
@@ -75,7 +79,7 @@ class BiomechZoo:
|
|
|
75
79
|
f_zoo = f.replace('.mvnx', '.zoo')
|
|
76
80
|
zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
77
81
|
method_name = inspect.currentframe().f_code.co_name
|
|
78
|
-
batchdisp('{}
|
|
82
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
79
83
|
# Update self.folder after processing
|
|
80
84
|
self._update_folder(out_folder, inplace, in_folder)
|
|
81
85
|
|
|
@@ -99,17 +103,21 @@ class BiomechZoo:
|
|
|
99
103
|
# Update self.folder after processing
|
|
100
104
|
self._update_folder(out_folder, inplace, in_folder)
|
|
101
105
|
|
|
102
|
-
def table2zoo(self, out_folder=None, inplace=None, skip_rows=0,
|
|
106
|
+
def table2zoo(self, extension, out_folder=None, inplace=None, skip_rows=0, freq=None):
|
|
103
107
|
""" Converts generic .csv file in the folder to .zoo format """
|
|
104
108
|
start_time = time.time()
|
|
105
109
|
verbose = self.verbose
|
|
106
110
|
in_folder = self.in_folder
|
|
111
|
+
|
|
112
|
+
if extension.startswith('.'):
|
|
113
|
+
extension = extension[1:]
|
|
114
|
+
|
|
107
115
|
if inplace is None:
|
|
108
116
|
inplace = self.inplace
|
|
109
117
|
fl = engine(in_folder, extension=extension, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
110
118
|
for f in fl:
|
|
111
119
|
batchdisp('converting {} to zoo for {}'.format(extension, f), level=2, verbose=verbose)
|
|
112
|
-
data = table2zoo_data(f,
|
|
120
|
+
data = table2zoo_data(f, extension=extension, skip_rows=skip_rows, freq=freq)
|
|
113
121
|
f_zoo = f.replace(extension, '.zoo')
|
|
114
122
|
zsave(f_zoo, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
115
123
|
method_name = inspect.currentframe().f_code.co_name
|
|
@@ -118,8 +126,13 @@ class BiomechZoo:
|
|
|
118
126
|
self._update_folder(out_folder, inplace, in_folder)
|
|
119
127
|
|
|
120
128
|
def xls2zoo(self, out_folder=None, inplace=None):
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
130
|
+
|
|
131
|
+
def csv2zoo(self, out_folder=None, inplace=None):
|
|
132
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
133
|
+
|
|
134
|
+
def parquet2zoo(self, out_folder=None, inplace=None):
|
|
135
|
+
raise NotImplementedError('Use table2zoo instead')
|
|
123
136
|
|
|
124
137
|
def phase_angle(self, ch, out_folder=None, inplace=None):
|
|
125
138
|
""" computes phase angles"""
|
|
@@ -184,7 +197,8 @@ class BiomechZoo:
|
|
|
184
197
|
batchdisp('splitting by gait cycle from {} to {} for {}'.format(start, end, f), level=2,
|
|
185
198
|
verbose=verbose)
|
|
186
199
|
data_new = split_trial(data, start, end)
|
|
187
|
-
|
|
200
|
+
if data_new is not None:
|
|
201
|
+
zsave(fl_new, data_new, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
188
202
|
method_name = inspect.currentframe().f_code.co_name
|
|
189
203
|
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
190
204
|
|
|
@@ -266,6 +280,27 @@ class BiomechZoo:
|
|
|
266
280
|
# Update self.folder after processing
|
|
267
281
|
self._update_folder(out_folder, inplace, in_folder)
|
|
268
282
|
|
|
283
|
+
|
|
284
|
+
def removeevent(self, events, mode='remove', out_folder=None, inplace=None):
|
|
285
|
+
""" removes channels from zoo files """
|
|
286
|
+
start_time = time.time()
|
|
287
|
+
verbose = self.verbose
|
|
288
|
+
in_folder = self.in_folder
|
|
289
|
+
if inplace is None:
|
|
290
|
+
inplace = self.inplace
|
|
291
|
+
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
292
|
+
for f in fl:
|
|
293
|
+
batchdisp('removing events {} for {}'.format(events, f), level=2, verbose=verbose)
|
|
294
|
+
data = zload(f)
|
|
295
|
+
data = removeevent_data(data, events, mode)
|
|
296
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
297
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
298
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
299
|
+
|
|
300
|
+
# Update self.folder after processing
|
|
301
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
302
|
+
|
|
303
|
+
|
|
269
304
|
def explodechannel(self, out_folder=None, inplace=None):
|
|
270
305
|
""" explodes all channels in a zoo file """
|
|
271
306
|
start_time = time.time()
|
|
@@ -306,7 +341,7 @@ class BiomechZoo:
|
|
|
306
341
|
# Update self.folder after processing
|
|
307
342
|
self._update_folder(out_folder, inplace, in_folder)
|
|
308
343
|
|
|
309
|
-
def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
|
|
344
|
+
def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None, constant=None):
|
|
310
345
|
""" adds events of type evt_type with name evt_name to channel ch """
|
|
311
346
|
start_time = time.time()
|
|
312
347
|
verbose = self.verbose
|
|
@@ -318,7 +353,7 @@ class BiomechZoo:
|
|
|
318
353
|
if verbose:
|
|
319
354
|
batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
|
|
320
355
|
data = zload(f)
|
|
321
|
-
data = addevent_data(data, ch, event_type, event_name)
|
|
356
|
+
data = addevent_data(data, ch, event_type, event_name, constant)
|
|
322
357
|
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
323
358
|
method_name = inspect.currentframe().f_code.co_name
|
|
324
359
|
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
@@ -346,28 +381,22 @@ class BiomechZoo:
|
|
|
346
381
|
self._update_folder(out_folder, inplace, in_folder)
|
|
347
382
|
|
|
348
383
|
def filter(self, ch, filt=None, out_folder=None, inplace=None):
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
#
|
|
366
|
-
|
|
367
|
-
# method_name = inspect.currentframe().f_code.co_name
|
|
368
|
-
# batchdisp('{} computation complete for {} file(s)'.format(method_name, len(fl)), level=1, verbose=verbose)
|
|
369
|
-
#
|
|
370
|
-
# # Update self.folder after processing
|
|
371
|
-
# self._update_folder(out_folder, inplace, in_folder)
|
|
372
|
-
|
|
384
|
+
""" filter data"""
|
|
385
|
+
start_time = time.time()
|
|
386
|
+
verbose = self.verbose
|
|
387
|
+
in_folder = self.in_folder
|
|
388
|
+
if inplace is None:
|
|
389
|
+
inplace = self.inplace
|
|
390
|
+
fl = engine(in_folder, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
391
|
+
for f in fl:
|
|
392
|
+
if verbose:
|
|
393
|
+
batchdisp('filtering data for channel {} in {}'.format(ch, f), level=2, verbose=verbose)
|
|
394
|
+
data = zload(f)
|
|
395
|
+
data = filter_data(data, ch, filt)
|
|
396
|
+
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
397
|
+
method_name = inspect.currentframe().f_code.co_name
|
|
398
|
+
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time),
|
|
399
|
+
level=1, verbose=verbose)
|
|
400
|
+
# Update self.folder after processing
|
|
401
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
373
402
|
|
|
@@ -8,10 +8,14 @@ def c3d2zoo_data(c3d_obj):
|
|
|
8
8
|
- data (dict): Zoo dictionary with 'line' and 'event' fields per channel.
|
|
9
9
|
"""
|
|
10
10
|
data = {}
|
|
11
|
-
|
|
11
|
+
data['zoosystem'] = set_zoosystem()
|
|
12
|
+
video_freq = None
|
|
13
|
+
analog_freq = None
|
|
14
|
+
# extract "video" data
|
|
12
15
|
if 'points' in c3d_obj['data']:
|
|
13
16
|
points = c3d_obj['data']['points'] # shape: (4, n_markers, n_frames)
|
|
14
|
-
labels = c3d_obj['parameters']['POINT']['LABELS']['value']
|
|
17
|
+
labels = list(c3d_obj['parameters']['POINT']['LABELS']['value'])
|
|
18
|
+
video_freq = int(c3d_obj['parameters']['POINT']['RATE']['value'][0])
|
|
15
19
|
for i, label in enumerate(labels):
|
|
16
20
|
line_data = points[:3, i, :].T # shape: (frames, 3)
|
|
17
21
|
data[label] = {
|
|
@@ -19,9 +23,32 @@ def c3d2zoo_data(c3d_obj):
|
|
|
19
23
|
'event': {} # empty for now
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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']:
|
|
25
52
|
times_array = params['EVENT']['TIMES']['value']
|
|
26
53
|
frames = times_array[1] # should be time depending on C3D file
|
|
27
54
|
|
|
@@ -61,8 +88,8 @@ def c3d2zoo_data(c3d_obj):
|
|
|
61
88
|
else:
|
|
62
89
|
data[labels[0]]['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
|
|
63
90
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
67
94
|
|
|
68
95
|
return data
|
|
@@ -9,8 +9,7 @@ def mvnx2zoo_data(fl):
|
|
|
9
9
|
mvnx_file = load_mvnx(fl)
|
|
10
10
|
|
|
11
11
|
# create zoo data dict
|
|
12
|
-
data = {}
|
|
13
|
-
|
|
12
|
+
data = {'zoosystem': set_zoosystem()}
|
|
14
13
|
# extract joint angle data (All JOINTS may not exist in a given dataset)
|
|
15
14
|
for key, val in JOINTS.items():
|
|
16
15
|
try:
|
|
@@ -58,7 +57,6 @@ def is_valid_for_zoo(val):
|
|
|
58
57
|
|
|
59
58
|
def _get_meta_info(fl, mvnx_file, data):
|
|
60
59
|
# todo: add more, see mvnx_file object
|
|
61
|
-
data['zoosystem'] = set_zoosystem(fl)
|
|
62
60
|
data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
|
|
63
61
|
data['zoosystem']['mvnx_version'] = mvnx_file.version
|
|
64
62
|
data['zoosystem']['mvnx_configuration'] = mvnx_file.configuration
|
|
@@ -6,42 +6,59 @@ from biomechzoo.utils.set_zoosystem import set_zoosystem
|
|
|
6
6
|
from biomechzoo.utils.compute_sampling_rate_from_time import compute_sampling_rate_from_time
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def table2zoo_data(
|
|
10
|
-
# todo: check calculation for sampling rate
|
|
9
|
+
def table2zoo_data(fl, extension, skip_rows=0, freq=None):
|
|
11
10
|
|
|
12
|
-
if
|
|
13
|
-
|
|
11
|
+
if extension == 'csv':
|
|
12
|
+
df, metadata, freq = _csv2zoo(fl, skip_rows=skip_rows, freq=freq)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if type == 'csv' and skip_rows > 0:
|
|
18
|
-
header_lines = []
|
|
19
|
-
with open(csv_path, 'r') as f:
|
|
20
|
-
for line in f:
|
|
21
|
-
header_lines.append(line.strip())
|
|
22
|
-
if line.strip().lower() == 'endheader':
|
|
23
|
-
break
|
|
24
|
-
# Parse metadata
|
|
25
|
-
metadata = _parse_metadata(header_lines)
|
|
26
|
-
|
|
27
|
-
if type == 'csv':
|
|
28
|
-
df = pd.read_csv(csv_path, skiprows=skip_rows)
|
|
29
|
-
elif type =='parquet':
|
|
30
|
-
df = pd.read_parquet(csv_path)
|
|
14
|
+
elif extension == 'parquet':
|
|
15
|
+
df, metadata, freq = _parquet2zoo(fl, skip_rows=skip_rows, freq=freq)
|
|
31
16
|
else:
|
|
32
|
-
raise ValueError('
|
|
33
|
-
|
|
34
|
-
# Use all columns
|
|
35
|
-
data = df.iloc[:, 0:]
|
|
17
|
+
raise ValueError('extension {} not implemented'.format(extension))
|
|
36
18
|
|
|
37
19
|
# assemble zoo data
|
|
38
|
-
|
|
39
|
-
for ch in
|
|
40
|
-
|
|
41
|
-
'line':
|
|
20
|
+
data = {'zoosystem': set_zoosystem()}
|
|
21
|
+
for ch in df.columns:
|
|
22
|
+
data[ch] = {
|
|
23
|
+
'line': df[ch].values,
|
|
42
24
|
'event': []
|
|
43
25
|
}
|
|
44
26
|
|
|
27
|
+
|
|
28
|
+
# now try to calculate freq from a time column
|
|
29
|
+
if freq is None:
|
|
30
|
+
time_col = [col for col in df.columns if 'time' in col.lower()]
|
|
31
|
+
if time_col is not None and len(time_col) > 0:
|
|
32
|
+
time_data = df[time_col].to_numpy()[:, 0]
|
|
33
|
+
freq = compute_sampling_rate_from_time(time_data)
|
|
34
|
+
else:
|
|
35
|
+
raise ValueError('Unable to compute sampling rate for time column, please specify a sampling frequency'
|
|
36
|
+
)
|
|
37
|
+
# add metadata
|
|
38
|
+
data['zoosystem']['Video']['Freq'] = freq
|
|
39
|
+
data['zoosystem']['Analog']['Freq'] = 'None'
|
|
40
|
+
|
|
41
|
+
if metadata is not None:
|
|
42
|
+
data['zoosystem']['Other'] = metadata
|
|
43
|
+
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parquet2zoo(fl, skip_rows=0, freq=None):
|
|
48
|
+
df = pd.read_parquet(fl)
|
|
49
|
+
metadata = None
|
|
50
|
+
return df, metadata, freq
|
|
51
|
+
|
|
52
|
+
def _csv2zoo(fl, skip_rows=0, freq=None):
|
|
53
|
+
header_lines = []
|
|
54
|
+
with open(fl, 'r') as f:
|
|
55
|
+
for line in f:
|
|
56
|
+
header_lines.append(line.strip())
|
|
57
|
+
if line.strip().lower() == 'endheader':
|
|
58
|
+
break
|
|
59
|
+
# Parse metadata
|
|
60
|
+
metadata = _parse_metadata(header_lines)
|
|
61
|
+
|
|
45
62
|
# try to find frequency in metadata
|
|
46
63
|
if freq is None:
|
|
47
64
|
if 'freq' in metadata:
|
|
@@ -51,22 +68,12 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
|
|
|
51
68
|
else:
|
|
52
69
|
freq = None # or raise an error
|
|
53
70
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
time_data = df[time_col].to_numpy()[:, 0]
|
|
59
|
-
freq = compute_sampling_rate_from_time(time_data)
|
|
71
|
+
# read csv
|
|
72
|
+
df = pd.read_csv(fl, skiprows=skip_rows)
|
|
73
|
+
|
|
74
|
+
return df, metadata, freq
|
|
60
75
|
|
|
61
|
-
# add metadata
|
|
62
|
-
# todo update zoosystem to match biomechzoo requirements
|
|
63
|
-
zoo_data['zoosystem'] = set_zoosystem(csv_path)
|
|
64
|
-
zoo_data['zoosystem']['Video']['Freq'] = freq
|
|
65
|
-
zoo_data['zoosystem']['Analog']['Freq'] = 'None'
|
|
66
|
-
if 'version' in metadata:
|
|
67
|
-
zoo_data['zoosystem']['collection_system_version'] = metadata['version']
|
|
68
76
|
|
|
69
|
-
return zoo_data
|
|
70
77
|
|
|
71
78
|
|
|
72
79
|
def _parse_metadata(header_lines):
|
|
@@ -96,6 +103,8 @@ def _parse_metadata(header_lines):
|
|
|
96
103
|
return metadata
|
|
97
104
|
|
|
98
105
|
|
|
106
|
+
|
|
107
|
+
|
|
99
108
|
if __name__ == '__main__':
|
|
100
109
|
""" for unit testing"""
|
|
101
110
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import copy
|
|
3
|
+
import warnings
|
|
4
|
+
from biomechzoo.utils.peak_sign import peak_sign
|
|
2
5
|
|
|
6
|
+
def addevent_data(data, channels, ename, etype, constant=None):
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
if isinstance(ch, str):
|
|
6
|
-
ch = [ch]
|
|
8
|
+
data_new = copy.deepcopy(data)
|
|
7
9
|
|
|
8
|
-
if
|
|
9
|
-
|
|
10
|
+
if isinstance(channels, str):
|
|
11
|
+
channels = [channels]
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
if len(channels) == 1 and channels[0].lower() == 'all':
|
|
14
|
+
channels = [key for key in data if key != 'zoosystem']
|
|
15
|
+
|
|
16
|
+
for channel in channels:
|
|
12
17
|
if ename == '':
|
|
13
18
|
data[channel]['event'] = {}
|
|
14
19
|
continue
|
|
15
20
|
|
|
16
21
|
if channel not in data:
|
|
17
|
-
|
|
18
|
-
continue
|
|
22
|
+
raise KeyError('Channel {} does not exist'.format(channel))
|
|
19
23
|
|
|
20
|
-
yd =
|
|
24
|
+
yd = data_new[channel]['line'] # 1D array
|
|
21
25
|
etype = etype.lower()
|
|
22
26
|
if etype == 'absmax':
|
|
23
27
|
exd = int(np.argmax(np.abs(yd)))
|
|
@@ -42,13 +46,51 @@ def addevent_data(data, ch, ename, etype):
|
|
|
42
46
|
exd = max_stance(yd)
|
|
43
47
|
eyd = float(yd[exd])
|
|
44
48
|
eyd = float(yd[exd])
|
|
49
|
+
elif etype in ['fs_fp', 'fo_fp']:
|
|
50
|
+
# --- Handle constant ---
|
|
51
|
+
if constant is None:
|
|
52
|
+
print('Warning: Force plate threshold not set, defaulting to 0.')
|
|
53
|
+
constant = 0.0
|
|
54
|
+
|
|
55
|
+
# --- Check sampling rates ---
|
|
56
|
+
AVR = data['zoosystem']['AVR']
|
|
57
|
+
if AVR != 1:
|
|
58
|
+
warnings.warn('Video and Analog channels must be at the same sampling rate or events will be incorrect.')
|
|
59
|
+
|
|
60
|
+
# --- Handle units ---
|
|
61
|
+
units = data['zoosystem']['Units']['Forces']
|
|
62
|
+
if units == 'N/kg':
|
|
63
|
+
m = data['zoosystem']['Anthro']['Bodymass']
|
|
64
|
+
else:
|
|
65
|
+
m = 1.0
|
|
66
|
+
|
|
67
|
+
# --- Extract force signal ---
|
|
68
|
+
if '_' not in channel:
|
|
69
|
+
yd = data_new[channel]['line'][:, 2] # looking for GRF Z
|
|
70
|
+
else:
|
|
71
|
+
yd = data_new[channel]['line']
|
|
72
|
+
|
|
73
|
+
# --- Determine peak sign ---
|
|
74
|
+
peak = peak_sign(yd) # user-defined function
|
|
75
|
+
|
|
76
|
+
# --- Find threshold crossing ---
|
|
77
|
+
threshold_signal = peak * yd * m
|
|
78
|
+
if 'fs' in etype:
|
|
79
|
+
exd_array = np.where(threshold_signal > constant)[0]
|
|
80
|
+
exd = exd_array[0] - 1 # MATLAB indexing correction
|
|
81
|
+
eyd = yd[exd]
|
|
82
|
+
else: # 'FO' type
|
|
83
|
+
exd_array = np.where(threshold_signal > constant)[0]
|
|
84
|
+
exd = exd_array[-1] + 1
|
|
85
|
+
eyd = yd[exd]
|
|
86
|
+
|
|
45
87
|
else:
|
|
46
88
|
raise ValueError(f'Unknown event type: {etype}')
|
|
47
89
|
|
|
48
90
|
# Add event to the channel's event dict
|
|
49
|
-
|
|
91
|
+
data_new[channel]['event'][ename] = [exd, eyd, 0]
|
|
50
92
|
|
|
51
|
-
return
|
|
93
|
+
return data_new
|
|
52
94
|
|
|
53
95
|
def max_stance(yd):
|
|
54
96
|
""" extracts max from first 40% of the gait cycle"""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import numpy as np
|
|
2
3
|
|
|
3
4
|
def explodechannel_data(data, channels=None):
|
|
4
5
|
""" Explodes 3D channels (n x 3 arrays) into separate X, Y, Z channels.
|
|
@@ -6,13 +7,23 @@ def explodechannel_data(data, channels=None):
|
|
|
6
7
|
Arguments:
|
|
7
8
|
data (dict): Zoo data loaded from a file
|
|
8
9
|
channels (list of str or None): Channels to explode.
|
|
9
|
-
|
|
10
|
+
If None, explode all channels with 'line' shaped (n x 3).
|
|
10
11
|
|
|
11
12
|
Returns:
|
|
12
13
|
data_new (dict): Modified zoo dictionary with exploded channels.
|
|
13
14
|
"""
|
|
14
|
-
|
|
15
15
|
data_new = copy.deepcopy(data)
|
|
16
|
+
|
|
17
|
+
# Ensure zoosystem channel lists are Python lists
|
|
18
|
+
for sys in ['Video', 'Analog']:
|
|
19
|
+
if sys in data_new.get('zoosystem', {}):
|
|
20
|
+
ch_list = data_new['zoosystem'][sys].get('Channels', [])
|
|
21
|
+
if isinstance(ch_list, np.ndarray):
|
|
22
|
+
ch_list = ch_list.tolist()
|
|
23
|
+
# strip whitespace
|
|
24
|
+
ch_list = [str(ch).strip() for ch in ch_list]
|
|
25
|
+
data_new['zoosystem'][sys]['Channels'] = ch_list
|
|
26
|
+
|
|
16
27
|
# Find default channels if none provided
|
|
17
28
|
if channels is None:
|
|
18
29
|
channels = []
|
|
@@ -39,9 +50,20 @@ def explodechannel_data(data, channels=None):
|
|
|
39
50
|
key = ch + axis
|
|
40
51
|
data_new[key] = {
|
|
41
52
|
'line': line,
|
|
42
|
-
'event':
|
|
53
|
+
'event': data_new[ch]['event']}
|
|
43
54
|
|
|
44
55
|
# Remove original channel
|
|
45
56
|
del data_new[ch]
|
|
46
57
|
|
|
58
|
+
# --- Update zoosystem lists ---
|
|
59
|
+
for sys in ['Video', 'Analog']:
|
|
60
|
+
if sys in data_new['zoosystem']:
|
|
61
|
+
ch_list = data_new['zoosystem'][sys]['Channels']
|
|
62
|
+
if ch in ch_list:
|
|
63
|
+
# Remove original channel
|
|
64
|
+
ch_list = [c for c in ch_list if c != ch]
|
|
65
|
+
# Add exploded channels
|
|
66
|
+
ch_list.extend([ch + '_x', ch + '_y', ch + '_z'])
|
|
67
|
+
data_new['zoosystem'][sys]['Channels'] = ch_list
|
|
68
|
+
|
|
47
69
|
return data_new
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from biomechzoo.utils.findfield import findfield
|
|
2
2
|
import warnings
|
|
3
3
|
import copy
|
|
4
|
+
import numpy as np
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def partition_data(data, evt_start, evt_end):
|
|
@@ -13,16 +14,19 @@ def partition_data(data, evt_start, evt_end):
|
|
|
13
14
|
if e1 is None or e2 is None or len(e1) == 0 or len(e2) == 0:
|
|
14
15
|
raise ValueError(f"Event not found: evt_start='{evt_start}' returned {e1}, evt_end='{evt_end}' returned {e2}")
|
|
15
16
|
|
|
17
|
+
# convert to int and get first value
|
|
18
|
+
e1 = int(e1[0])
|
|
19
|
+
e2 = int(e2[0])
|
|
20
|
+
|
|
16
21
|
data_new = copy.deepcopy(data)
|
|
17
|
-
for ch_name, ch_data in data_new.items():
|
|
22
|
+
for ch_name, ch_data in sorted(data_new.items()):
|
|
18
23
|
if ch_name != 'zoosystem':
|
|
19
|
-
|
|
20
|
-
line = ch_data['line']
|
|
24
|
+
r = ch_data['line']
|
|
21
25
|
try:
|
|
22
|
-
if
|
|
23
|
-
data_new[ch_name]['line'] =
|
|
26
|
+
if r.ndim == 1:
|
|
27
|
+
data_new[ch_name]['line'] = r[e1:e2]
|
|
24
28
|
else:
|
|
25
|
-
data_new[ch_name]['line'] =
|
|
29
|
+
data_new[ch_name]['line'] = r[e1:e2, :]
|
|
26
30
|
except (IndexError, ValueError) as e:
|
|
27
31
|
# IndexError: if e1[0]:e2[0] goes beyond the available indices
|
|
28
32
|
# ValueError: less likely, but may arise with shape mismatches
|
|
@@ -31,29 +35,12 @@ def partition_data(data, evt_start, evt_end):
|
|
|
31
35
|
# partition events
|
|
32
36
|
events = ch_data['event']
|
|
33
37
|
for event_name, value in events.items():
|
|
34
|
-
original_frame = value[0]
|
|
38
|
+
original_frame = int(value[0])
|
|
35
39
|
if original_frame == 999:
|
|
36
40
|
continue # do not change outlier markers
|
|
37
41
|
else:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
data_new[ch_name]['event'][event_name]
|
|
42
|
+
arr = np.array(data_new[ch_name]['event'][event_name], dtype=np.int32)
|
|
43
|
+
arr[0] = original_frame - e1
|
|
44
|
+
data_new[ch_name]['event'][event_name] = arr
|
|
41
45
|
|
|
42
46
|
return data_new
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _partition_line(arr, evt_start, evt_end):
|
|
46
|
-
arr_new = arr[evt_start:evt_end, :]
|
|
47
|
-
return arr_new
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _partition_event(event_dict, evt_start, evt_end, arr_len):
|
|
51
|
-
raise NotImplementedError
|
|
52
|
-
# event_dict_new = {}
|
|
53
|
-
# for event, event_val in event_dict:
|
|
54
|
-
# event_val_new =
|
|
55
|
-
# event_dict_new[event] =
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# return event_dict_new
|
|
@@ -13,7 +13,6 @@ def removechannel_data(data, channels, mode='remove'):
|
|
|
13
13
|
if mode not in ['remove', 'keep']:
|
|
14
14
|
raise ValueError("mode must be 'remove' or 'keep'.")
|
|
15
15
|
|
|
16
|
-
zoosystem = data.get('zoosystem', {})
|
|
17
16
|
all_channels = [ch for ch in data if ch != 'zoosystem']
|
|
18
17
|
|
|
19
18
|
# Check for missing channels
|
|
@@ -28,9 +27,20 @@ def removechannel_data(data, channels, mode='remove'):
|
|
|
28
27
|
else:
|
|
29
28
|
raise ValueError("Mode must be 'remove' or 'keep'.")
|
|
30
29
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
for ch in keep_channels:
|
|
34
|
-
data_new[ch] = data[ch]
|
|
30
|
+
# --- Compute channels to remove ---
|
|
31
|
+
remove_channels = [ch for ch in all_channels if ch not in keep_channels]
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
if remove_channels:
|
|
34
|
+
print('Removing channels: {}'.format(remove_channels))
|
|
35
|
+
else:
|
|
36
|
+
print('No channels to remove')
|
|
37
|
+
|
|
38
|
+
# Remove from main data dict ---
|
|
39
|
+
for ch in remove_channels:
|
|
40
|
+
data.pop(ch, None)
|
|
41
|
+
if ch in data['zoosystem']['Video']['Channels']:
|
|
42
|
+
data['zoosystem']['Video']['Channels'] = [c for c in data['zoosystem']['Video']['Channels'] if c != ch]
|
|
43
|
+
if ch in data['zoosystem']['Analog']['Channels']:
|
|
44
|
+
data['zoosystem']['Analog']['Channels'] = [c for c in data['zoosystem']['Analog']['Channels'] if c != ch]
|
|
45
|
+
|
|
46
|
+
return data
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import warnings
|
|
3
|
+
from biomechzoo.utils.findfield import findfield
|
|
4
|
+
|
|
5
|
+
def removeevent_data(data, events, mode='remove'):
|
|
6
|
+
"""
|
|
7
|
+
Remove or keep specified events in all channels of a zoo dictionary.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
data : dict
|
|
12
|
+
Zoo data loaded from a file
|
|
13
|
+
events : list of str
|
|
14
|
+
Events to remove or keep
|
|
15
|
+
mode : str
|
|
16
|
+
'remove' or 'keep'
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
dict
|
|
21
|
+
Modified zoo dictionary with events removed or kept
|
|
22
|
+
"""
|
|
23
|
+
if mode not in ['remove', 'keep']:
|
|
24
|
+
raise ValueError("mode must be 'remove' or 'keep'.")
|
|
25
|
+
|
|
26
|
+
if isinstance(events, str):
|
|
27
|
+
events = [events]
|
|
28
|
+
|
|
29
|
+
# check if any events are not present
|
|
30
|
+
valid_events = []
|
|
31
|
+
for evt in events:
|
|
32
|
+
e, _ = findfield(data, evt)
|
|
33
|
+
if e is None:
|
|
34
|
+
warnings.warn('Could not find event {} in zoo file, skipping'.format(evt))
|
|
35
|
+
else:
|
|
36
|
+
valid_events.append(evt)
|
|
37
|
+
events = valid_events
|
|
38
|
+
|
|
39
|
+
data_new = copy.deepcopy(data)
|
|
40
|
+
channels = sorted([ch for ch in data_new if ch != 'zoosystem'])
|
|
41
|
+
for ch in channels:
|
|
42
|
+
event_dict = data_new[ch].get('event', {})
|
|
43
|
+
events_to_remove = []
|
|
44
|
+
|
|
45
|
+
for evt in list(event_dict.keys()):
|
|
46
|
+
if mode == 'remove' and evt in events:
|
|
47
|
+
events_to_remove.append(evt)
|
|
48
|
+
elif mode == 'keep' and evt not in events:
|
|
49
|
+
events_to_remove.append(evt)
|
|
50
|
+
|
|
51
|
+
for evt in events_to_remove:
|
|
52
|
+
event_dict.pop(evt, None)
|
|
53
|
+
# print('Removed event "{}" from channel "{}"'.format(evt, ch))
|
|
54
|
+
|
|
55
|
+
data_new[ch]['event'] = event_dict
|
|
56
|
+
|
|
57
|
+
return data_new
|
|
@@ -28,17 +28,11 @@ def renameevent_data(data, evt, nevt):
|
|
|
28
28
|
# Get all data channels except 'zoosystem'
|
|
29
29
|
channels = [ch for ch in data if ch != 'zoosystem']
|
|
30
30
|
for old_name, new_name in zip(evt, nevt):
|
|
31
|
-
eventsRenamed = False
|
|
32
31
|
for ch in channels:
|
|
33
32
|
events = data[ch].get('event', {})
|
|
34
33
|
if old_name in events:
|
|
35
|
-
print('Renaming event {} in channel {} to {}'.format(old_name, ch, new_name))
|
|
36
34
|
data[ch]['event'][new_name] = events[old_name]
|
|
37
35
|
del data[ch]['event'][old_name]
|
|
38
|
-
eventsRenamed = True
|
|
39
|
-
|
|
40
|
-
if not eventsRenamed:
|
|
41
|
-
print('no event {} found in any channel'.format(old_name))
|
|
42
36
|
|
|
43
37
|
return data
|
|
44
38
|
|
biomechzoo/utils/engine.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import numpy as np
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, verbose=False):
|
|
@@ -66,10 +67,13 @@ def engine(root_folder, extension='.zoo', subfolders=None, name_contains=None, v
|
|
|
66
67
|
continue
|
|
67
68
|
matched_files.append(full_path)
|
|
68
69
|
|
|
70
|
+
# sort list
|
|
71
|
+
matched_files = np.sort(matched_files)
|
|
72
|
+
|
|
69
73
|
if verbose:
|
|
70
|
-
print("Found {} {} files in
|
|
74
|
+
print("Found {} {} files in subfolder(s) named {} with substring {}:".format(len(matched_files), extension, subfolders, name_contains))
|
|
71
75
|
for f in matched_files:
|
|
72
|
-
print(
|
|
76
|
+
print('{}'.format(f))
|
|
73
77
|
|
|
74
78
|
return matched_files
|
|
75
79
|
|
|
@@ -79,6 +83,6 @@ if __name__ == '__main__':
|
|
|
79
83
|
with extension .c3d in the sample study folder (data)"""
|
|
80
84
|
# -------TESTING--------
|
|
81
85
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
82
|
-
project_root = os.path.dirname(current_dir)
|
|
86
|
+
project_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
|
|
83
87
|
sample_dir = os.path.join(project_root, 'data', 'sample_study', 'raw c3d files')
|
|
84
88
|
c3d_files = engine(sample_dir, '.c3d', subfolders=['Straight'], name_contains='HC03', verbose=True)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def peak_sign(r):
|
|
4
|
+
"""
|
|
5
|
+
Determine whether the largest absolute peak in the signal is positive or negative.
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
r : array-like
|
|
10
|
+
Signal vector.
|
|
11
|
+
|
|
12
|
+
Returns
|
|
13
|
+
-------
|
|
14
|
+
sign : int
|
|
15
|
+
1 if the maximum peak is positive, -1 if negative.
|
|
16
|
+
"""
|
|
17
|
+
r = np.asarray(r)
|
|
18
|
+
max_val = np.max(r)
|
|
19
|
+
min_val = np.min(r)
|
|
20
|
+
|
|
21
|
+
if abs(max_val) > abs(min_val):
|
|
22
|
+
return 1
|
|
23
|
+
else:
|
|
24
|
+
return -1
|
biomechzoo/utils/split_trial.py
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
import copy
|
|
2
|
+
from biomechzoo.utils.findfield import findfield
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def split_trial(data, start_event, end_event):
|
|
2
6
|
# todo check index problem compared to matlab start at 0 or 1
|
|
3
|
-
data_new =
|
|
7
|
+
data_new = copy.deepcopy(data)
|
|
8
|
+
|
|
9
|
+
start_event_indx, _ = findfield(data_new, start_event)
|
|
10
|
+
end_event_indx, _ = findfield(data_new, end_event)
|
|
4
11
|
|
|
5
12
|
for key, value in data_new.items():
|
|
6
13
|
if key == 'zoosystem':
|
|
7
14
|
continue
|
|
8
15
|
|
|
9
16
|
# Slice the line data
|
|
10
|
-
data_new[key]['line']
|
|
17
|
+
trial_length = len(data_new[key]['line'])
|
|
18
|
+
if trial_length > end_event_indx[0]:
|
|
19
|
+
data_new[key]['line'] = value['line'][start_event_indx[0]:end_event_indx[0]]
|
|
20
|
+
else:
|
|
21
|
+
print('skipping split trial since event is outside range of data')
|
|
22
|
+
return None
|
|
11
23
|
|
|
12
24
|
# Update events if present
|
|
13
|
-
if 'event' in value:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
# if 'event' in value:
|
|
26
|
+
# new_events = {}
|
|
27
|
+
# for evt_name, evt_val in value['event'].items():
|
|
28
|
+
# event_frame = evt_val[0]
|
|
29
|
+
# # Check if event falls within the new window
|
|
30
|
+
# if start_event_indx <= event_frame < end_event_indx:
|
|
31
|
+
# # Adjust index relative to new start
|
|
32
|
+
# new_events[evt_name] = [event_frame - start_event_indx, 0, 0]
|
|
33
|
+
# data_new[key]['event'] = new_events
|
|
22
34
|
|
|
23
35
|
return data_new
|
biomechzoo/utils/zload.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from scipy.io import loadmat
|
|
2
2
|
import os
|
|
3
|
+
import numpy as np
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def zload(filepath):
|
|
@@ -29,6 +30,16 @@ def zload(filepath):
|
|
|
29
30
|
if 'data' in data:
|
|
30
31
|
data = data['data']
|
|
31
32
|
|
|
33
|
+
# Convert Video and Analog channel arrays to Python lists
|
|
34
|
+
for sys in ['Video', 'Analog']:
|
|
35
|
+
if 'zoosystem' in data and sys in data['zoosystem']:
|
|
36
|
+
channels = data['zoosystem'][sys].get('Channels', [])
|
|
37
|
+
# Convert to list and strip spaces
|
|
38
|
+
if isinstance(channels, np.ndarray):
|
|
39
|
+
channels = channels.tolist()
|
|
40
|
+
channels = [str(ch).strip() for ch in channels]
|
|
41
|
+
data['zoosystem'][sys]['Channels'] = channels
|
|
42
|
+
|
|
32
43
|
return data
|
|
33
44
|
|
|
34
45
|
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
__init__.py,sha256=Uy3ykqw4l_lZKiUWSUFPRZpkZajYUfZLBaQLVhKzxdI,772
|
|
2
2
|
biomechzoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
biomechzoo/__main__.py,sha256=hSMHN1Rxn2367fSGTLHoOQ4_pocZw0IWI7HFxl-74oY,88
|
|
4
|
-
biomechzoo/biomechzoo.py,sha256=
|
|
4
|
+
biomechzoo/biomechzoo.py,sha256=EBh92xNnkYtQan1Q2vS2lnleoMUVEwoDJxw0YPD4FYA,21353
|
|
5
5
|
biomechzoo/biomech_ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
biomechzoo/biomech_ops/continuous_relative_phase_data.py,sha256=RePbt6zyOI1iv74CWhxSrunIokTFYVfFmFnoW51781E,1300
|
|
7
7
|
biomechzoo/biomech_ops/continuous_relative_phase_line.py,sha256=Fa1LFRuPlmGPLQLvln6HVnJy3zMSm9z5YeooHmTu0lc,1365
|
|
8
|
-
biomechzoo/biomech_ops/filter_data.py,sha256=
|
|
9
|
-
biomechzoo/biomech_ops/filter_line.py,sha256=
|
|
10
|
-
biomechzoo/biomech_ops/normalize_data.py,sha256=
|
|
8
|
+
biomechzoo/biomech_ops/filter_data.py,sha256=Q9knW23Ft_WeWVCBjaIQ5GkKU0NYV4SdGiDZV8Fm-hM,1805
|
|
9
|
+
biomechzoo/biomech_ops/filter_line.py,sha256=XKUdRsxU5AO1gSldnwp3qNzsUUL8qpOpAMyQbEMo5uI,2600
|
|
10
|
+
biomechzoo/biomech_ops/normalize_data.py,sha256=ESdXeUkKdd0WpjP6FW2EYrOAd7NKWkX1JWUNn1SzDB4,1335
|
|
11
11
|
biomechzoo/biomech_ops/normalize_line.py,sha256=KUE8gEkIolA-VDFCdUuaskk-waO8jjJ20ZMZaS8Qha8,854
|
|
12
12
|
biomechzoo/biomech_ops/phase_angle_data.py,sha256=_ekUBW2v3iC4UawcDL38ZZLYJmQsAmyqA61Q6_AMtmQ,1435
|
|
13
13
|
biomechzoo/biomech_ops/phase_angle_line.py,sha256=p6osB17_3QQSyKLNojuc6nYhv-k0K6EUUH75EXu8ifc,1391
|
|
14
14
|
biomechzoo/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
biomechzoo/conversion/c3d2zoo_data.py,sha256=
|
|
16
|
-
biomechzoo/conversion/mvnx2zoo_data.py,sha256=
|
|
15
|
+
biomechzoo/conversion/c3d2zoo_data.py,sha256=28JCj1Jpn7zsv2HjQdzH2F30jnGMwzmbBwYuRoWPJHo,4052
|
|
16
|
+
biomechzoo/conversion/mvnx2zoo_data.py,sha256=uMAZ4pNSSZ7NToW1WnawrXeVP8D-xE3dNDnooPvALE4,3545
|
|
17
17
|
biomechzoo/conversion/opencap2zoo_data.py,sha256=n4bLsrJI0wTSzG5bgJcmxj1dp2plnUKFNRzcTIbmV1o,608
|
|
18
|
-
biomechzoo/conversion/table2zoo_data.py,sha256=
|
|
18
|
+
biomechzoo/conversion/table2zoo_data.py,sha256=rAP7OrKbVrb50NHJ6h8fgoMntV87-t9wT2xQ30AD96I,3330
|
|
19
19
|
biomechzoo/mvn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
biomechzoo/mvn/load_mvnx.py,sha256=B4VGuidQ-5G_5E1t5vpU51Nb_Lu85_EOS_FmGZYjfX8,22499
|
|
21
21
|
biomechzoo/mvn/main_mvnx.py,sha256=e1LasJQ9icyzjWnRCEcAWQq_-L13-mIzf7PzYA3QYDg,2394
|
|
@@ -24,28 +24,30 @@ biomechzoo/mvn/mvnx_file_accessor.py,sha256=Gk2vKq9v_gPbnOS_12zgeJehjFz7v3ClTnN2
|
|
|
24
24
|
biomechzoo/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
biomechzoo/processing/add_channel_data.py,sha256=U1xLLBSnyJeeDWzgmHSxOz1hyguzuuDXxQCQ8IFoZkw,1955
|
|
26
26
|
biomechzoo/processing/addchannel_data.py,sha256=rmnnlMRVkoMlQCR-nRg1jjh-hzMDt37Sx9fAmocrpqA,1953
|
|
27
|
-
biomechzoo/processing/addevent_data.py,sha256=
|
|
28
|
-
biomechzoo/processing/explodechannel_data.py,sha256=
|
|
29
|
-
biomechzoo/processing/partition_data.py,sha256=
|
|
30
|
-
biomechzoo/processing/removechannel_data.py,sha256=
|
|
27
|
+
biomechzoo/processing/addevent_data.py,sha256=T1l7u7cOuILO0mAk3Oac5FjOxH6VBKHHgXUgr8cnCTk,3282
|
|
28
|
+
biomechzoo/processing/explodechannel_data.py,sha256=ceyXfcCmeNqj4p0zCksVdrnmsYR4t-JHLIyv3JlfNpU,2484
|
|
29
|
+
biomechzoo/processing/partition_data.py,sha256=4wuKrnMmRMNobymYwZ0WuRvNGsvkhThZ2ZhoSkkhntg,1741
|
|
30
|
+
biomechzoo/processing/removechannel_data.py,sha256=uO7jjuHapRr2CGLsrvYQ1eJLvb1y_8KR6Ct4M6TPALA,1740
|
|
31
|
+
biomechzoo/processing/removeevent_data.py,sha256=DC1vBIwuo_mk5Oz0UGLK7Nx3BVqAeVDfT29XgUM_1Rc,1631
|
|
31
32
|
biomechzoo/processing/renamechannel_data.py,sha256=5lEnWJknAwnJYXDlwO6cFe7C8ct5o42tTHW0Y4GeIL4,2210
|
|
32
|
-
biomechzoo/processing/renameevent_data.py,sha256=
|
|
33
|
+
biomechzoo/processing/renameevent_data.py,sha256=PRGHEs-t9qJMzDY1DCNgQz_h-aAOIII4ryQeCYBXwGo,1952
|
|
33
34
|
biomechzoo/processing/split_trial_by_gait_cycle.py,sha256=maMUwumSlFLFc5LHdNdBO9JCoT06aaLbpdp43dgowDA,1684
|
|
34
35
|
biomechzoo/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
36
|
biomechzoo/utils/batchdisp.py,sha256=Ke7yeZ4cYQvyf8bmgsiLaRElUeQm49TYIYzAcPWan9s,839
|
|
36
37
|
biomechzoo/utils/compute_sampling_rate_from_time.py,sha256=iyHbN734vYe5bXEoAsHScJN7Qw--2quUgII7judSQDk,579
|
|
37
|
-
biomechzoo/utils/engine.py,sha256=
|
|
38
|
+
biomechzoo/utils/engine.py,sha256=tatGMBbgIe8DIHx2_z_JQwA9TJYCeVYqyWiKiMoRHw4,3971
|
|
38
39
|
biomechzoo/utils/findfield.py,sha256=i7ewGQOMIiTC06tYFK-JctehHLCippkY9FoXIygx14w,381
|
|
39
40
|
biomechzoo/utils/get_split_events.py,sha256=xNhEuG6Yqsr1bjWIBHLbepfX-fcqcCYIXZzS3eaDDHQ,911
|
|
41
|
+
biomechzoo/utils/peak_sign.py,sha256=4XNqVIrAmpLw9C9RmWwIKkI1rluBTzeCXJE4-AHCzC4,465
|
|
40
42
|
biomechzoo/utils/set_zoosystem.py,sha256=oubEc3fy0x6y-AlqQWL3v7QYJA951jU9CRnlJ9ikwQo,1750
|
|
41
|
-
biomechzoo/utils/split_trial.py,sha256=
|
|
43
|
+
biomechzoo/utils/split_trial.py,sha256=0SNpKKkRoizRe9MCuMrQ6Ev6O5V51rN0D-e8FGlLlo4,1302
|
|
42
44
|
biomechzoo/utils/version.py,sha256=JIXDUuOcaJiZv9ruMP6PtWvJBh4sP0D5kAvlqPiZK_I,130
|
|
43
|
-
biomechzoo/utils/zload.py,sha256=
|
|
45
|
+
biomechzoo/utils/zload.py,sha256=_qmbQpiEwUNRcB86aS6dHiytOrz1ZXJVjYkk8h5fg8s,2039
|
|
44
46
|
biomechzoo/utils/zplot.py,sha256=WVA8aCy1Pqy_bo_HXab9AmW-cBd8J8MPX2LAOd6dznU,1512
|
|
45
47
|
biomechzoo/utils/zsave.py,sha256=wnRNuDxQc8bwCji4UrfoGjYrSZmka4eaDxQ5rMa78pE,1759
|
|
46
|
-
biomechzoo-0.
|
|
47
|
-
biomechzoo-0.
|
|
48
|
-
biomechzoo-0.
|
|
49
|
-
biomechzoo-0.
|
|
50
|
-
biomechzoo-0.
|
|
51
|
-
biomechzoo-0.
|
|
48
|
+
biomechzoo-0.5.0.dist-info/licenses/LICENSE,sha256=Fsz62nrgRORre3A1wNXUDISaHoostodMvocRPDdXc9w,1076
|
|
49
|
+
biomechzoo-0.5.0.dist-info/METADATA,sha256=GuXAmEs9sQ2uVq0SzUirvtKWzmWTGC-t6DMNKxWgkiI,1579
|
|
50
|
+
biomechzoo-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
51
|
+
biomechzoo-0.5.0.dist-info/entry_points.txt,sha256=VdryUUiwwvx0WZxrgmMrsyfe5Z1jtyaxdXOi0zWHOqk,41
|
|
52
|
+
biomechzoo-0.5.0.dist-info/top_level.txt,sha256=nJEtuEZ9UPoN3EOR-BJ6myevEu7B5quWsWhaM_YeQpw,20
|
|
53
|
+
biomechzoo-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|