biomechzoo 0.4.9__tar.gz → 0.4.11__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.9/src/biomechzoo.egg-info → biomechzoo-0.4.11}/PKG-INFO +2 -1
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/README.md +1 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/pyproject.toml +1 -1
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/filter_data.py +17 -15
- biomechzoo-0.4.11/src/biomechzoo/biomech_ops/filter_line.py +85 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomechzoo.py +23 -28
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/conversion/c3d2zoo_data.py +35 -8
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/conversion/mvnx2zoo_data.py +1 -1
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/conversion/table2zoo_data.py +11 -11
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/addevent_data.py +10 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/explodechannel_data.py +25 -3
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/removechannel_data.py +16 -6
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/zload.py +11 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11/src/biomechzoo.egg-info}/PKG-INFO +2 -1
- biomechzoo-0.4.9/src/biomechzoo/biomech_ops/filter_line.py +0 -88
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/LICENSE +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/setup.cfg +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/__main__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/normalize_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/normalize_line.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/phase_angle_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/phase_angle_line.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/conversion/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/conversion/opencap2zoo_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/mvn/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/mvn/load_mvnx.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/mvn/main_mvnx.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/mvn/mvn.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/mvn/mvnx_file_accessor.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/add_channel_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/addchannel_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/partition_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/renamechannel_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/renameevent_data.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/split_trial_by_gait_cycle.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/__init__.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/batchdisp.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/compute_sampling_rate_from_time.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/engine.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/findfield.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/get_split_events.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/set_zoosystem.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/split_trial.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/version.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/zplot.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/zsave.py +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo.egg-info/SOURCES.txt +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo.egg-info/dependency_links.txt +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo.egg-info/entry_points.txt +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo.egg-info/requires.txt +0 -0
- {biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: biomechzoo
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.11
|
|
4
4
|
Summary: Python implementation of the biomechZoo toolbox
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
|
|
@@ -37,6 +37,7 @@ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechz
|
|
|
37
37
|
### Installing a dev environment
|
|
38
38
|
conda create -n biomechzoo-dev python=3.11
|
|
39
39
|
conda activate biomechzoo-dev
|
|
40
|
+
cd biomechzoo root folder
|
|
40
41
|
pip install -e ".[dev]"
|
|
41
42
|
|
|
42
43
|
### import issues
|
|
@@ -20,6 +20,7 @@ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechz
|
|
|
20
20
|
### Installing a dev environment
|
|
21
21
|
conda create -n biomechzoo-dev python=3.11
|
|
22
22
|
conda activate biomechzoo-dev
|
|
23
|
+
cd biomechzoo root folder
|
|
23
24
|
pip install -e ".[dev]"
|
|
24
25
|
|
|
25
26
|
### import issues
|
|
@@ -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
|
|
@@ -8,7 +8,7 @@ from biomechzoo.utils.batchdisp import batchdisp
|
|
|
8
8
|
from biomechzoo.utils.get_split_events import get_split_events
|
|
9
9
|
from biomechzoo.utils.split_trial import split_trial
|
|
10
10
|
from biomechzoo.conversion.c3d2zoo_data import c3d2zoo_data
|
|
11
|
-
from biomechzoo.conversion.
|
|
11
|
+
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
|
|
@@ -19,6 +19,7 @@ from biomechzoo.processing.renameevent_data import renameevent_data
|
|
|
19
19
|
from biomechzoo.biomech_ops.normalize_data import normalize_data
|
|
20
20
|
from biomechzoo.biomech_ops.phase_angle_data import phase_angle_data
|
|
21
21
|
from biomechzoo.biomech_ops.continuous_relative_phase_data import continuous_relative_phase_data
|
|
22
|
+
from biomechzoo.biomech_ops.filter_data import filter_data
|
|
22
23
|
|
|
23
24
|
class BiomechZoo:
|
|
24
25
|
def __init__(self, in_folder, inplace=False, subfolders=None, name_contains=None, verbose=0):
|
|
@@ -306,7 +307,7 @@ class BiomechZoo:
|
|
|
306
307
|
# Update self.folder after processing
|
|
307
308
|
self._update_folder(out_folder, inplace, in_folder)
|
|
308
309
|
|
|
309
|
-
def addevent(self, ch,
|
|
310
|
+
def addevent(self, ch, event_type, event_name, out_folder=None, inplace=None):
|
|
310
311
|
""" adds events of type evt_type with name evt_name to channel ch """
|
|
311
312
|
start_time = time.time()
|
|
312
313
|
verbose = self.verbose
|
|
@@ -316,9 +317,9 @@ class BiomechZoo:
|
|
|
316
317
|
fl = engine(in_folder, extension='.zoo', name_contains=self.name_contains, subfolders=self.subfolders)
|
|
317
318
|
for f in fl:
|
|
318
319
|
if verbose:
|
|
319
|
-
batchdisp('adding event {} to channel {} for {}'.format(
|
|
320
|
+
batchdisp('adding event {} to channel {} for {}'.format(event_type, ch, f), level=2, verbose=verbose)
|
|
320
321
|
data = zload(f)
|
|
321
|
-
data = addevent_data(data, ch,
|
|
322
|
+
data = addevent_data(data, ch, event_type, event_name)
|
|
322
323
|
zsave(f, data, inplace=inplace, out_folder=out_folder, root_folder=in_folder)
|
|
323
324
|
method_name = inspect.currentframe().f_code.co_name
|
|
324
325
|
batchdisp('{} process complete for {} file(s) in {:.2f} secs'.format(method_name, len(fl), time.time() - start_time), level=1, verbose=verbose)
|
|
@@ -346,28 +347,22 @@ class BiomechZoo:
|
|
|
346
347
|
self._update_folder(out_folder, inplace, in_folder)
|
|
347
348
|
|
|
348
349
|
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
|
-
|
|
350
|
+
""" filter data"""
|
|
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, name_contains=self.name_contains, subfolders=self.subfolders)
|
|
357
|
+
for f in fl:
|
|
358
|
+
if verbose:
|
|
359
|
+
batchdisp('filtering data for channel {} in {}'.format(ch, f), level=2, verbose=verbose)
|
|
360
|
+
data = zload(f)
|
|
361
|
+
data = filter_data(data, ch, filt)
|
|
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),
|
|
365
|
+
level=1, verbose=verbose)
|
|
366
|
+
# Update self.folder after processing
|
|
367
|
+
self._update_folder(out_folder, inplace, in_folder)
|
|
373
368
|
|
|
@@ -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
|
|
@@ -10,6 +10,7 @@ def mvnx2zoo_data(fl):
|
|
|
10
10
|
|
|
11
11
|
# create zoo data dict
|
|
12
12
|
data = {}
|
|
13
|
+
data['zoosystem'] = set_zoosystem()
|
|
13
14
|
|
|
14
15
|
# extract joint angle data (All JOINTS may not exist in a given dataset)
|
|
15
16
|
for key, val in JOINTS.items():
|
|
@@ -58,7 +59,6 @@ def is_valid_for_zoo(val):
|
|
|
58
59
|
|
|
59
60
|
def _get_meta_info(fl, mvnx_file, data):
|
|
60
61
|
# todo: add more, see mvnx_file object
|
|
61
|
-
data['zoosystem'] = set_zoosystem(fl)
|
|
62
62
|
data['zoosystem']['Video']['Freq'] = int(mvnx_file.frame_rate)
|
|
63
63
|
data['zoosystem']['mvnx_version'] = mvnx_file.version
|
|
64
64
|
data['zoosystem']['mvnx_configuration'] = mvnx_file.configuration
|
|
@@ -32,13 +32,15 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
|
|
|
32
32
|
raise ValueError('type must be csv or parquet')
|
|
33
33
|
|
|
34
34
|
# Use all columns
|
|
35
|
-
|
|
35
|
+
df_data = df.iloc[:, 0:]
|
|
36
36
|
|
|
37
37
|
# assemble zoo data
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
data = {}
|
|
39
|
+
data['zoosystem'] = set_zoosystem()
|
|
40
|
+
|
|
41
|
+
for ch in df_data.columns:
|
|
42
|
+
data[ch] = {
|
|
43
|
+
'line': df_data[ch].values,
|
|
42
44
|
'event': []
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -59,14 +61,12 @@ def table2zoo_data(csv_path, type='csv', skip_rows=0, freq=None):
|
|
|
59
61
|
freq = compute_sampling_rate_from_time(time_data)
|
|
60
62
|
|
|
61
63
|
# add metadata
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
zoo_data['zoosystem']['Video']['Freq'] = freq
|
|
65
|
-
zoo_data['zoosystem']['Analog']['Freq'] = 'None'
|
|
64
|
+
data['zoosystem']['Video']['Freq'] = freq
|
|
65
|
+
data['zoosystem']['Analog']['Freq'] = 'None'
|
|
66
66
|
if 'version' in metadata:
|
|
67
|
-
|
|
67
|
+
data['zoosystem']['collection_system_version'] = metadata['version']
|
|
68
68
|
|
|
69
|
-
return
|
|
69
|
+
return data
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _parse_metadata(header_lines):
|
|
@@ -37,6 +37,11 @@ def addevent_data(data, ch, ename, etype):
|
|
|
37
37
|
elif etype == 'rom':
|
|
38
38
|
eyd = float(np.max(yd) - np.min(yd))
|
|
39
39
|
exd = 0 # dummy index (like MATLAB version)
|
|
40
|
+
elif etype == 'max_stance':
|
|
41
|
+
# special event for gait and running
|
|
42
|
+
exd = max_stance(yd)
|
|
43
|
+
eyd = float(yd[exd])
|
|
44
|
+
eyd = float(yd[exd])
|
|
40
45
|
else:
|
|
41
46
|
raise ValueError(f'Unknown event type: {etype}')
|
|
42
47
|
|
|
@@ -44,3 +49,8 @@ def addevent_data(data, ch, ename, etype):
|
|
|
44
49
|
data[channel]['event'][ename] = [exd, eyd, 0]
|
|
45
50
|
|
|
46
51
|
return data
|
|
52
|
+
|
|
53
|
+
def max_stance(yd):
|
|
54
|
+
""" extracts max from first 40% of the gait cycle"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
return exd
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: biomechzoo
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.11
|
|
4
4
|
Summary: Python implementation of the biomechZoo toolbox
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
|
|
@@ -37,6 +37,7 @@ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechz
|
|
|
37
37
|
### Installing a dev environment
|
|
38
38
|
conda create -n biomechzoo-dev python=3.11
|
|
39
39
|
conda activate biomechzoo-dev
|
|
40
|
+
cd biomechzoo root folder
|
|
40
41
|
pip install -e ".[dev]"
|
|
41
42
|
|
|
42
43
|
### import issues
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from scipy.signal import butter, filtfilt
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def filter_line(signal_raw, filt):
|
|
6
|
-
""" filter an array
|
|
7
|
-
|
|
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
|
-
if filt is None:
|
|
24
|
-
filt = {}
|
|
25
|
-
if filt['type'] is 'butterworth':
|
|
26
|
-
filt['type'] = 'butter'
|
|
27
|
-
# Set default filter parameters
|
|
28
|
-
ftype = filt.get('type', 'butter')
|
|
29
|
-
order = filt.get('order', 4)
|
|
30
|
-
cutoff = filt.get('cutoff', None)
|
|
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.")
|
|
42
|
-
|
|
43
|
-
nyq = 0.5 * fs
|
|
44
|
-
norm_cutoff = np.array(cutoff) / nyq
|
|
45
|
-
|
|
46
|
-
b, a = butter(order, norm_cutoff, btype=btype, analog=False)
|
|
47
|
-
|
|
48
|
-
if signal_raw.ndim == 1:
|
|
49
|
-
signal_filtered = filtfilt(b, a, signal_raw)
|
|
50
|
-
else:
|
|
51
|
-
# Apply filter to each column if multivariate
|
|
52
|
-
signal_filtered = np.array([filtfilt(b, a, signal_raw[:, i]) for i in range(signal_raw.shape[1])]).T
|
|
53
|
-
|
|
54
|
-
return signal_filtered
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if __name__ == '__main__':
|
|
58
|
-
""" -------TESTING--------"""
|
|
59
|
-
import os
|
|
60
|
-
import matplotlib.pyplot as plt
|
|
61
|
-
from src.biomechzoo.utils.zload import zload
|
|
62
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
63
|
-
project_root = os.path.dirname(current_dir)
|
|
64
|
-
fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
|
|
65
|
-
data = zload(fl)
|
|
66
|
-
data = data['data']
|
|
67
|
-
signal_raw = data['ForceFz1']['line']
|
|
68
|
-
filt = {'type': 'butterworth',
|
|
69
|
-
'order': 3,
|
|
70
|
-
'cutoff': 20,
|
|
71
|
-
'btype': 'low',
|
|
72
|
-
'fs': data['zoosystem']['Analog']['Freq']
|
|
73
|
-
}
|
|
74
|
-
signal_filtered = filter_line(signal_raw, filt)
|
|
75
|
-
|
|
76
|
-
# now plot
|
|
77
|
-
plt.figure(figsize=(10, 4))
|
|
78
|
-
plt.plot(signal_raw, label='Raw', alpha=0.6)
|
|
79
|
-
plt.plot(signal_filtered, label='Filtered', linewidth=2)
|
|
80
|
-
plt.xlabel('Frame')
|
|
81
|
-
plt.ylabel('Amplitude')
|
|
82
|
-
plt.title('Testing filter_line')
|
|
83
|
-
plt.legend()
|
|
84
|
-
plt.grid(True)
|
|
85
|
-
plt.tight_layout()
|
|
86
|
-
plt.show()
|
|
87
|
-
|
|
88
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py
RENAMED
|
File without changes
|
{biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/processing/split_trial_by_gait_cycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{biomechzoo-0.4.9 → biomechzoo-0.4.11}/src/biomechzoo/utils/compute_sampling_rate_from_time.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|