biomechzoo 0.1.1__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.1.1/LICENSE +21 -0
- biomechzoo-0.1.1/PKG-INFO +48 -0
- biomechzoo-0.1.1/README.md +32 -0
- biomechzoo-0.1.1/pyproject.toml +31 -0
- biomechzoo-0.1.1/setup.cfg +4 -0
- biomechzoo-0.1.1/src/biomechzoo/__init__.py +5 -0
- biomechzoo-0.1.1/src/biomechzoo/__main__.py +6 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/filter_data.py +56 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/filter_line.py +88 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/normalize_data.py +35 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/normalize_line.py +27 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/phase_angle_data.py +39 -0
- biomechzoo-0.1.1/src/biomechzoo/biomech_ops/phase_angle_line.py +48 -0
- biomechzoo-0.1.1/src/biomechzoo/biomechzoo.py +349 -0
- biomechzoo-0.1.1/src/biomechzoo/conversion/__init__.py +0 -0
- biomechzoo-0.1.1/src/biomechzoo/conversion/c3d2zoo_data.py +65 -0
- biomechzoo-0.1.1/src/biomechzoo/conversion/csv2zoo_data.py +78 -0
- biomechzoo-0.1.1/src/biomechzoo/conversion/mvnx2zoo_data.py +71 -0
- biomechzoo-0.1.1/src/biomechzoo/conversion/opencap2zoo_data.py +23 -0
- biomechzoo-0.1.1/src/biomechzoo/mvn/load_mvnx.py +514 -0
- biomechzoo-0.1.1/src/biomechzoo/mvn/main_mvnx.py +75 -0
- biomechzoo-0.1.1/src/biomechzoo/mvn/mvn.py +232 -0
- biomechzoo-0.1.1/src/biomechzoo/mvn/mvnx_file_accessor.py +463 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/add_channel_data.py +71 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/addchannel_data.py +71 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/addevent_data.py +46 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/explodechannel_data.py +46 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/partition_data.py +51 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/removechannel_data.py +36 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/renamechannel_data.py +79 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/renameevent_data.py +68 -0
- biomechzoo-0.1.1/src/biomechzoo/processing/split_trial_by_gait_cycle.py +52 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/batchdisp.py +21 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/engine.py +68 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/findfield.py +11 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/get_split_events.py +33 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/split_trial.py +23 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/zload.py +46 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/zplot.py +61 -0
- biomechzoo-0.1.1/src/biomechzoo/utils/zsave.py +50 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/PKG-INFO +48 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/SOURCES.txt +47 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/dependency_links.txt +1 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/entry_points.txt +2 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/requires.txt +5 -0
- biomechzoo-0.1.1/src/biomechzoo.egg-info/top_level.txt +1 -0
biomechzoo-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] McGill MOTION Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: biomechzoo
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python implementation of the biomechZoo Matlab toolbox
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: ezc3d>=1.5.19
|
|
11
|
+
Requires-Dist: matplotlib>=3.10.6
|
|
12
|
+
Requires-Dist: numpy>=2.3.3
|
|
13
|
+
Requires-Dist: pandas>=2.3.2
|
|
14
|
+
Requires-Dist: scipy>=1.16.2
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Repository for the Python version of biomechZoo
|
|
18
|
+
This is a development version of the biomechzoo toolbox for python. To use biomechzoo
|
|
19
|
+
as a packagage alongside your code, follow the "How to install" instructions below
|
|
20
|
+
|
|
21
|
+
## How to install
|
|
22
|
+
- make sure the local version of your code sits at the same level as biomechzoo.
|
|
23
|
+
e.g.
|
|
24
|
+
code_root/
|
|
25
|
+
code_root/biomechzoo/ ← the repo with the actual package
|
|
26
|
+
code_root/student_code/ ← where your scripts or notebooks live
|
|
27
|
+
|
|
28
|
+
- open terminal or command window
|
|
29
|
+
- create an environment for your research project: ``conda create -n name python=3.13 -c conda-forge``, where name is of your choice
|
|
30
|
+
- activate your new environment: ``conda activate name``
|
|
31
|
+
- navigate to parent directory where biomechzoo was cloned. e.g. ``code_root``
|
|
32
|
+
- install biomechzoo: ``pip install -e biomechzoo``
|
|
33
|
+
- install additional requirements: ``pip install -r biomechzoo/pip_requirements.txt``
|
|
34
|
+
|
|
35
|
+
## Updates (not tested)
|
|
36
|
+
- If updates are made to biomechzoo, simply pull the latest version
|
|
37
|
+
from github, you won't need to reinstall (unless there are new dependencies)
|
|
38
|
+
|
|
39
|
+
## Usage notes
|
|
40
|
+
- To use biomechzoo in your project, you will need to import biomechzoo as:
|
|
41
|
+
``from biomechzoo.biomechzoo import BiomechZoo``
|
|
42
|
+
- Then, you can create an object that is an instance of the BimechZoo class as:
|
|
43
|
+
``bmech = BiomechZoo(fld)`` where ``fld`` is the path to your data
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# opencap users
|
|
47
|
+
- opencap users should pre-process their data using ``https://github.com/stanfordnmbl/opencap-processing``
|
|
48
|
+
- processed data could be saved to csv using pandas and then imported to biomechzoo using csv2zoo (not yet functional)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Repository for the Python version of biomechZoo
|
|
2
|
+
This is a development version of the biomechzoo toolbox for python. To use biomechzoo
|
|
3
|
+
as a packagage alongside your code, follow the "How to install" instructions below
|
|
4
|
+
|
|
5
|
+
## How to install
|
|
6
|
+
- make sure the local version of your code sits at the same level as biomechzoo.
|
|
7
|
+
e.g.
|
|
8
|
+
code_root/
|
|
9
|
+
code_root/biomechzoo/ ← the repo with the actual package
|
|
10
|
+
code_root/student_code/ ← where your scripts or notebooks live
|
|
11
|
+
|
|
12
|
+
- open terminal or command window
|
|
13
|
+
- create an environment for your research project: ``conda create -n name python=3.13 -c conda-forge``, where name is of your choice
|
|
14
|
+
- activate your new environment: ``conda activate name``
|
|
15
|
+
- navigate to parent directory where biomechzoo was cloned. e.g. ``code_root``
|
|
16
|
+
- install biomechzoo: ``pip install -e biomechzoo``
|
|
17
|
+
- install additional requirements: ``pip install -r biomechzoo/pip_requirements.txt``
|
|
18
|
+
|
|
19
|
+
## Updates (not tested)
|
|
20
|
+
- If updates are made to biomechzoo, simply pull the latest version
|
|
21
|
+
from github, you won't need to reinstall (unless there are new dependencies)
|
|
22
|
+
|
|
23
|
+
## Usage notes
|
|
24
|
+
- To use biomechzoo in your project, you will need to import biomechzoo as:
|
|
25
|
+
``from biomechzoo.biomechzoo import BiomechZoo``
|
|
26
|
+
- Then, you can create an object that is an instance of the BimechZoo class as:
|
|
27
|
+
``bmech = BiomechZoo(fld)`` where ``fld`` is the path to your data
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# opencap users
|
|
31
|
+
- opencap users should pre-process their data using ``https://github.com/stanfordnmbl/opencap-processing``
|
|
32
|
+
- processed data could be saved to csv using pandas and then imported to biomechzoo using csv2zoo (not yet functional)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "biomechzoo"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Python implementation of the biomechZoo Matlab toolbox"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICEN[CS]E*"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"ezc3d>=1.5.19",
|
|
11
|
+
"matplotlib>=3.10.6",
|
|
12
|
+
"numpy>=2.3.3",
|
|
13
|
+
"pandas>=2.3.2",
|
|
14
|
+
"scipy>=1.16.2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/mcgillmotionlab/biomechzoo"
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
biomechzoo = "main:main"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["setuptools>=78.1.0", "wheel>=0.45.1"]
|
|
25
|
+
build-backend = "setuptools.build_meta"
|
|
26
|
+
|
|
27
|
+
[[tool.uv.index]]
|
|
28
|
+
name = "testbiomechzoo"
|
|
29
|
+
url = "https://test.pypi.org/simple/"
|
|
30
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
31
|
+
explicit = true
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from biomechzoo.biomech_ops.continuous_relative_phase_line import continuous_relative_phase_line
|
|
2
|
+
from biomechzoo.processing.addchannel_data import addchannel_data
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def continuous_relative_phase_data(data, ch_dist, ch_prox):
|
|
6
|
+
""" This function determines the CRP on a 0-180 scale, correcting for
|
|
7
|
+
discontinuity in the signals >180.
|
|
8
|
+
See Also phase_angle_data.py and phase_angle_line.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
data_new = data.copy()
|
|
12
|
+
prox = data[ch_prox]['line']
|
|
13
|
+
dist = data[ch_dist]['line']
|
|
14
|
+
crp = continuous_relative_phase_line(dist, prox)
|
|
15
|
+
data_new = addchannel_data(data_new, ch_new_name=ch_dist + '_' + ch_prox + '_' + 'crp', ch_new_data=crp)
|
|
16
|
+
return data_new
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
# -------TESTING--------
|
|
21
|
+
import os
|
|
22
|
+
from biomechzoo.utils.zload import zload
|
|
23
|
+
from biomechzoo.utils.zplot import zplot
|
|
24
|
+
# note: crp should be computed on phase angle data. Here we just demonstrate that it works.
|
|
25
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
26
|
+
project_root = os.path.dirname(current_dir)
|
|
27
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC032A18_exploded.zoo')
|
|
28
|
+
data = zload(fl)
|
|
29
|
+
data = continuous_relative_phase_data(data, ch_dist='RKneeAngles_x', ch_prox='RHipAngles_x')
|
|
30
|
+
zplot(data, 'RKneeAngles_x_RHipAngles_x_crp')
|
|
31
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
def continuous_relative_phase_line(dist, prox):
|
|
2
|
+
""" This function determines the CRP on a 0-180 scale, correcting for
|
|
3
|
+
discontinuity in the signals >180.
|
|
4
|
+
|
|
5
|
+
Arguments
|
|
6
|
+
dist, ndarray: data of distal segment or joint
|
|
7
|
+
prox, ndarray: data of proximal segment or joibt
|
|
8
|
+
|
|
9
|
+
Returns
|
|
10
|
+
crp, ndarray: continous relative phase betweeen dist and prox data
|
|
11
|
+
"""
|
|
12
|
+
temp_CRP = abs(dist - prox)
|
|
13
|
+
idx = temp_CRP > 180 # This corrects discontinuity in the data and puts everything on a 0-180 scale.
|
|
14
|
+
temp_CRP[idx] = 360 - temp_CRP[idx]
|
|
15
|
+
crp = temp_CRP
|
|
16
|
+
return crp
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
# -------TESTING--------
|
|
21
|
+
import os
|
|
22
|
+
from biomechzoo.utils.zload import zload
|
|
23
|
+
from biomechzoo.biomech_ops.phase_angle_line import phase_angle_line
|
|
24
|
+
from matplotlib import pyplot as plt
|
|
25
|
+
# note: crp should be computed on phase angle data. Here we just demonstrate that it works.
|
|
26
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
27
|
+
project_root = os.path.dirname(current_dir)
|
|
28
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC032A18_exploded.zoo')
|
|
29
|
+
data = zload(fl)
|
|
30
|
+
knee = data['RKneeAngles_x']['line']
|
|
31
|
+
hip = data['RHipAngles_x']['line']
|
|
32
|
+
knee_pa = phase_angle_line(knee)
|
|
33
|
+
hip_pa = phase_angle_line(hip)
|
|
34
|
+
crp = continuous_relative_phase_line(knee_pa, hip_pa)
|
|
35
|
+
plt.plot(crp)
|
|
36
|
+
plt.show()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from biomechzoo.biomech_ops.filter_line import filter_line
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def filter_data(data, ch, filt=None):
|
|
5
|
+
"""
|
|
6
|
+
Filter one or more channels from a zoo data dictionary using specified filter parameters.
|
|
7
|
+
|
|
8
|
+
Arguments
|
|
9
|
+
----------
|
|
10
|
+
data : dict
|
|
11
|
+
The zoo data dictionary containing signal channels.
|
|
12
|
+
ch : str or list of str
|
|
13
|
+
The name(s) of the channel(s) to filter.
|
|
14
|
+
filt : dict, optional
|
|
15
|
+
Dictionary specifying filter parameters. Keys may include:
|
|
16
|
+
- 'type': 'butter' (default)
|
|
17
|
+
- 'order': filter order (default: 4)
|
|
18
|
+
- 'cutoff': cutoff frequency or tuple (Hz)
|
|
19
|
+
- 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'low')
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
dict
|
|
24
|
+
The updated data dictionary with filtered channels.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
if filt is None:
|
|
28
|
+
filt = {}
|
|
29
|
+
|
|
30
|
+
if isinstance(ch, str):
|
|
31
|
+
ch = [ch]
|
|
32
|
+
|
|
33
|
+
analog_channels = data['zoosystem']['Analog']['Channels']
|
|
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
|
+
|
|
40
|
+
for c in ch:
|
|
41
|
+
if c not in data:
|
|
42
|
+
raise KeyError('Channel {} not found in data'.format(c))
|
|
43
|
+
|
|
44
|
+
if 'fs' not in filt:
|
|
45
|
+
if c in analog_channels:
|
|
46
|
+
filt['fs'] = analog_freq
|
|
47
|
+
elif c in video_freq:
|
|
48
|
+
filt['fs'] = video_freq
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError('frequency not provided and cannot be inferred from zoosystem for channel'.format(c))
|
|
51
|
+
|
|
52
|
+
signal_raw = data[c]['line']
|
|
53
|
+
signal_filtered = filter_line(signal_raw, filt)
|
|
54
|
+
data[c]['line'] = signal_filtered
|
|
55
|
+
|
|
56
|
+
return data
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from biomechzoo.biomech_ops.normalize_line import normalize_line
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def normalize_data(data, nlength=101):
|
|
6
|
+
"""normalize all channels in the loaded zoo dict to nlen.
|
|
7
|
+
Arguments
|
|
8
|
+
data: dict, loaded zoo file
|
|
9
|
+
nlength: int: new length of data. Default = 101, usually a movement cycle
|
|
10
|
+
Returns:
|
|
11
|
+
None
|
|
12
|
+
Notes:
|
|
13
|
+
-It is often needed to partition data to a single cycle first (see partition_data)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# normalize channel length
|
|
17
|
+
data_new = data.copy()
|
|
18
|
+
for ch_name, ch_data in data_new.items():
|
|
19
|
+
if ch_name != 'zoosystem':
|
|
20
|
+
ch_data_line = ch_data['line']
|
|
21
|
+
ch_data_event = ch_data['event']
|
|
22
|
+
ch_data_normalized = normalize_line(ch_data_line, nlength)
|
|
23
|
+
data_new[ch_name]['line'] = ch_data_normalized
|
|
24
|
+
data_new[ch_name]['event'] = ch_data_event
|
|
25
|
+
warnings.warn('event data have not been normalized')
|
|
26
|
+
|
|
27
|
+
# update zoosystem
|
|
28
|
+
# todo: update all relevant zoosystem meta data related to data lengths
|
|
29
|
+
warnings.warn('zoosystem data have not been fully updated')
|
|
30
|
+
if 'Video' in data['zoosystem']:
|
|
31
|
+
data['zoosystem']['Video']['CURRENT_END_FRAME'] = nlength
|
|
32
|
+
if 'Analog' in data['zoosystem']:
|
|
33
|
+
data['zoosystem']['Analog']['CURRENT_END_FRAME'] = nlength
|
|
34
|
+
|
|
35
|
+
return data_new
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.interpolate import interp1d
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def normalize_line(channel_data, nlength=101):
|
|
6
|
+
"""
|
|
7
|
+
Channel-level: interpolate channel data to target length.
|
|
8
|
+
Assumes channel_data is a 1D or 2D numpy array.
|
|
9
|
+
"""
|
|
10
|
+
original_length = channel_data.shape[0]
|
|
11
|
+
|
|
12
|
+
if original_length == nlength:
|
|
13
|
+
return channel_data
|
|
14
|
+
|
|
15
|
+
x_original = np.linspace(0, 1, original_length)
|
|
16
|
+
x_target = np.linspace(0, 1, nlength)
|
|
17
|
+
|
|
18
|
+
if channel_data.ndim == 1:
|
|
19
|
+
f = interp1d(x_original, channel_data, kind='linear')
|
|
20
|
+
channel_data_norm = f(x_target)
|
|
21
|
+
else:
|
|
22
|
+
channel_data_norm = np.zeros((nlength, channel_data.shape[1]))
|
|
23
|
+
for i in range(channel_data.shape[1]):
|
|
24
|
+
f = interp1d(x_original, channel_data[:, i], kind='linear')
|
|
25
|
+
channel_data_norm[:, i] = f(x_target)
|
|
26
|
+
|
|
27
|
+
return channel_data_norm
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from biomechzoo.biomech_ops.phase_angle_line import phase_angle_line
|
|
2
|
+
from biomechzoo.processing.addchannel_data import addchannel_data
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def phase_angle_data(data, channels):
|
|
6
|
+
"""Compute phase angle using Hilbert Transform.
|
|
7
|
+
Arguments
|
|
8
|
+
data: dict, zoo data to operate on
|
|
9
|
+
channels, list. Channel names on which to apply calculations
|
|
10
|
+
Returns:
|
|
11
|
+
data: dict, zoo data with calculations appended to new channel(s)
|
|
12
|
+
"""
|
|
13
|
+
data_new = data.copy()
|
|
14
|
+
for ch in channels:
|
|
15
|
+
if ch not in data_new:
|
|
16
|
+
raise ValueError('Channel {} not in data. Available keys: {}'.format(ch, list(data_new.keys())))
|
|
17
|
+
r = data_new[ch]['line']
|
|
18
|
+
phase_angle = phase_angle_line(r)
|
|
19
|
+
ch_new = ch + '_phase_angle'
|
|
20
|
+
data_new = addchannel_data(data_new, ch_new_name=ch_new, ch_new_data=phase_angle)
|
|
21
|
+
return data_new
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == '__main__':
|
|
25
|
+
# -------TESTING--------
|
|
26
|
+
import os
|
|
27
|
+
from biomechzoo.utils.zload import zload
|
|
28
|
+
from biomechzoo.utils.zplot import zplot
|
|
29
|
+
# get path to sample zoo file
|
|
30
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
31
|
+
project_root = os.path.dirname(current_dir)
|
|
32
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC032A18_exploded.zoo')
|
|
33
|
+
|
|
34
|
+
# load zoo file
|
|
35
|
+
data = zload(fl)
|
|
36
|
+
data = data['data']
|
|
37
|
+
data = phase_angle_data(data, channels=['RKneeAngles_x', 'RHipAngles_x'])
|
|
38
|
+
zplot(data, 'RKneeAngles_x_phase_angle')
|
|
39
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.signal import hilbert
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def phase_angle_line(r):
|
|
6
|
+
"""
|
|
7
|
+
Computes the phase angle for a single kinematic waveform using the Hilbert transform method.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
r : array_like
|
|
11
|
+
(n, 1) array of kinematic data (e.g., joint or segment angle)
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
PA_data : ndarray
|
|
15
|
+
1D array of phase angle (in degrees) computed from input using the Hilbert transform.
|
|
16
|
+
|
|
17
|
+
Reference:
|
|
18
|
+
Lamb and Stöckl (2014). "On the use of continuous relative phase..."
|
|
19
|
+
Clinical Biomechanics. https://doi.org/10.1016/j.clinbiomech.2014.03.008
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Step 1: Center the data around zero as per Lamb and Stöckl eq. 11
|
|
23
|
+
cdata = r - np.min(r) - (np.max(r) - np.min(r)) / 2
|
|
24
|
+
|
|
25
|
+
# Step 2: Hilbert transform
|
|
26
|
+
X = hilbert(cdata)
|
|
27
|
+
|
|
28
|
+
# Step 3: Phase angle calculation
|
|
29
|
+
PA = np.rad2deg(np.arctan2(np.imag(X), np.real(X)))
|
|
30
|
+
|
|
31
|
+
return PA
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == '__main__':
|
|
35
|
+
# -------TESTING--------
|
|
36
|
+
import os
|
|
37
|
+
from biomechzoo.utils.zload import zload
|
|
38
|
+
from matplotlib import pyplot as plt
|
|
39
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
40
|
+
project_root = os.path.dirname(current_dir)
|
|
41
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC032A18_exploded.zoo')
|
|
42
|
+
data = zload(fl)
|
|
43
|
+
print(data)
|
|
44
|
+
r = data['RKneeAngles_x']['line']
|
|
45
|
+
phase_angle = phase_angle_line(r)
|
|
46
|
+
plt.plot(phase_angle)
|
|
47
|
+
plt.show()
|
|
48
|
+
|