biomechzoo 0.4.10__tar.gz → 0.5.0__tar.gz
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-0.4.10/src/biomechzoo.egg-info → biomechzoo-0.5.0}/PKG-INFO +1 -1
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/pyproject.toml +1 -1
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/filter_data.py +17 -15
- biomechzoo-0.5.0/src/biomechzoo/biomech_ops/filter_line.py +85 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/normalize_data.py +4 -3
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomechzoo.py +61 -32
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/conversion/c3d2zoo_data.py +35 -8
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/conversion/mvnx2zoo_data.py +1 -3
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/conversion/table2zoo_data.py +51 -42
- biomechzoo-0.5.0/src/biomechzoo/processing/addevent_data.py +98 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/explodechannel_data.py +25 -3
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/partition_data.py +14 -27
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/removechannel_data.py +16 -6
- biomechzoo-0.5.0/src/biomechzoo/processing/removeevent_data.py +57 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/renameevent_data.py +0 -6
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/engine.py +7 -3
- biomechzoo-0.5.0/src/biomechzoo/utils/peak_sign.py +24 -0
- biomechzoo-0.5.0/src/biomechzoo/utils/split_trial.py +35 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/zload.py +11 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0/src/biomechzoo.egg-info}/PKG-INFO +1 -1
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/SOURCES.txt +2 -0
- biomechzoo-0.4.10/src/biomechzoo/biomech_ops/filter_line.py +0 -88
- biomechzoo-0.4.10/src/biomechzoo/processing/addevent_data.py +0 -56
- biomechzoo-0.4.10/src/biomechzoo/utils/split_trial.py +0 -23
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/LICENSE +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/README.md +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/setup.cfg +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/__main__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/normalize_line.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/phase_angle_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/biomech_ops/phase_angle_line.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/conversion/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/conversion/opencap2zoo_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/mvn/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/mvn/load_mvnx.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/mvn/main_mvnx.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/mvn/mvn.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/mvn/mvnx_file_accessor.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/add_channel_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/addchannel_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/renamechannel_data.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/processing/split_trial_by_gait_cycle.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/__init__.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/batchdisp.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/compute_sampling_rate_from_time.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/findfield.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/get_split_events.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/set_zoosystem.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/version.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/zplot.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo/utils/zsave.py +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/dependency_links.txt +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/entry_points.txt +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo.egg-info/requires.txt +0 -0
- {biomechzoo-0.4.10 → biomechzoo-0.5.0}/src/biomechzoo.egg-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
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy.signal as sgl
|
|
3
|
+
|
|
4
|
+
|
|
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
|
|
10
|
+
|
|
11
|
+
if filt is None:
|
|
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')
|
|
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)
|
|
41
|
+
nyq = 0.5 * fs
|
|
42
|
+
norm_cutoff = np.atleast_1d(np.array(cutoff) / nyq)
|
|
43
|
+
|
|
44
|
+
if ftype is 'butter':
|
|
45
|
+
signal_filtered = kt_butter(ts=signal_raw, fc=norm_cutoff, fs=fs, order=order, btype=btype, filtfilt=filtfilt)
|
|
46
|
+
else:
|
|
47
|
+
raise NotImplementedError(f"Filter type '{ftype}' not implemented.")
|
|
48
|
+
|
|
49
|
+
return signal_filtered
|
|
50
|
+
|
|
51
|
+
|
|
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)
|
|
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)
|
|
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
|
|
@@ -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__))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import copy
|
|
3
|
+
import warnings
|
|
4
|
+
from biomechzoo.utils.peak_sign import peak_sign
|
|
5
|
+
|
|
6
|
+
def addevent_data(data, channels, ename, etype, constant=None):
|
|
7
|
+
|
|
8
|
+
data_new = copy.deepcopy(data)
|
|
9
|
+
|
|
10
|
+
if isinstance(channels, str):
|
|
11
|
+
channels = [channels]
|
|
12
|
+
|
|
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:
|
|
17
|
+
if ename == '':
|
|
18
|
+
data[channel]['event'] = {}
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
if channel not in data:
|
|
22
|
+
raise KeyError('Channel {} does not exist'.format(channel))
|
|
23
|
+
|
|
24
|
+
yd = data_new[channel]['line'] # 1D array
|
|
25
|
+
etype = etype.lower()
|
|
26
|
+
if etype == 'absmax':
|
|
27
|
+
exd = int(np.argmax(np.abs(yd)))
|
|
28
|
+
eyd = float(yd[exd])
|
|
29
|
+
elif etype == 'first':
|
|
30
|
+
exd = 0
|
|
31
|
+
eyd = float(yd[exd])
|
|
32
|
+
elif etype == 'last':
|
|
33
|
+
exd = len(yd) - 1
|
|
34
|
+
eyd = float(yd[exd])
|
|
35
|
+
elif etype == 'max':
|
|
36
|
+
exd = int(np.argmax(yd))
|
|
37
|
+
eyd = float(yd[exd])
|
|
38
|
+
elif etype == 'min':
|
|
39
|
+
exd = int(np.argmin(yd))
|
|
40
|
+
eyd = float(yd[exd])
|
|
41
|
+
elif etype == 'rom':
|
|
42
|
+
eyd = float(np.max(yd) - np.min(yd))
|
|
43
|
+
exd = 0 # dummy index (like MATLAB version)
|
|
44
|
+
elif etype == 'max_stance':
|
|
45
|
+
# special event for gait and running
|
|
46
|
+
exd = max_stance(yd)
|
|
47
|
+
eyd = float(yd[exd])
|
|
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
|
+
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError(f'Unknown event type: {etype}')
|
|
89
|
+
|
|
90
|
+
# Add event to the channel's event dict
|
|
91
|
+
data_new[channel]['event'][ename] = [exd, eyd, 0]
|
|
92
|
+
|
|
93
|
+
return data_new
|
|
94
|
+
|
|
95
|
+
def max_stance(yd):
|
|
96
|
+
""" extracts max from first 40% of the gait cycle"""
|
|
97
|
+
raise NotImplementedError
|
|
98
|
+
return exd
|