biomechzoo 0.5.3__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.

Files changed (61) hide show
  1. biomechzoo-0.5.3/LICENSE +21 -0
  2. biomechzoo-0.5.3/PKG-INFO +46 -0
  3. biomechzoo-0.5.3/README.md +29 -0
  4. biomechzoo-0.5.3/pyproject.toml +32 -0
  5. biomechzoo-0.5.3/setup.cfg +4 -0
  6. biomechzoo-0.5.3/src/__init__.py +33 -0
  7. biomechzoo-0.5.3/src/biomechzoo/__init__.py +0 -0
  8. biomechzoo-0.5.3/src/biomechzoo/__main__.py +6 -0
  9. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/__init__.py +0 -0
  10. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
  11. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
  12. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/filter_data.py +58 -0
  13. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/filter_line.py +85 -0
  14. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/movement_onset.py +51 -0
  15. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/normalize_data.py +36 -0
  16. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/normalize_line.py +27 -0
  17. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/phase_angle_data.py +39 -0
  18. biomechzoo-0.5.3/src/biomechzoo/biomech_ops/phase_angle_line.py +48 -0
  19. biomechzoo-0.5.3/src/biomechzoo/biomechzoo.py +426 -0
  20. biomechzoo-0.5.3/src/biomechzoo/conversion/__init__.py +0 -0
  21. biomechzoo-0.5.3/src/biomechzoo/conversion/c3d2zoo_data.py +95 -0
  22. biomechzoo-0.5.3/src/biomechzoo/conversion/mvnx2zoo_data.py +113 -0
  23. biomechzoo-0.5.3/src/biomechzoo/conversion/opencap2zoo_data.py +23 -0
  24. biomechzoo-0.5.3/src/biomechzoo/conversion/table2zoo_data.py +114 -0
  25. biomechzoo-0.5.3/src/biomechzoo/imu/tilt_algorithm.py +112 -0
  26. biomechzoo-0.5.3/src/biomechzoo/mvn/__init__.py +0 -0
  27. biomechzoo-0.5.3/src/biomechzoo/mvn/load_mvnx.py +514 -0
  28. biomechzoo-0.5.3/src/biomechzoo/mvn/main_mvnx.py +75 -0
  29. biomechzoo-0.5.3/src/biomechzoo/mvn/mvn.py +232 -0
  30. biomechzoo-0.5.3/src/biomechzoo/mvn/mvnx_file_accessor.py +464 -0
  31. biomechzoo-0.5.3/src/biomechzoo/processing/__init__.py +0 -0
  32. biomechzoo-0.5.3/src/biomechzoo/processing/add_channel_data.py +71 -0
  33. biomechzoo-0.5.3/src/biomechzoo/processing/addchannel_data.py +71 -0
  34. biomechzoo-0.5.3/src/biomechzoo/processing/addevent_data.py +116 -0
  35. biomechzoo-0.5.3/src/biomechzoo/processing/explodechannel_data.py +69 -0
  36. biomechzoo-0.5.3/src/biomechzoo/processing/partition_data.py +46 -0
  37. biomechzoo-0.5.3/src/biomechzoo/processing/removechannel_data.py +46 -0
  38. biomechzoo-0.5.3/src/biomechzoo/processing/removeevent_data.py +57 -0
  39. biomechzoo-0.5.3/src/biomechzoo/processing/renamechannel_data.py +79 -0
  40. biomechzoo-0.5.3/src/biomechzoo/processing/renameevent_data.py +62 -0
  41. biomechzoo-0.5.3/src/biomechzoo/processing/split_trial_data.py +40 -0
  42. biomechzoo-0.5.3/src/biomechzoo/statistics/__init__.py +0 -0
  43. biomechzoo-0.5.3/src/biomechzoo/statistics/eventval.py +118 -0
  44. biomechzoo-0.5.3/src/biomechzoo/utils/__init__.py +0 -0
  45. biomechzoo-0.5.3/src/biomechzoo/utils/batchdisp.py +21 -0
  46. biomechzoo-0.5.3/src/biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
  47. biomechzoo-0.5.3/src/biomechzoo/utils/engine.py +88 -0
  48. biomechzoo-0.5.3/src/biomechzoo/utils/findfield.py +11 -0
  49. biomechzoo-0.5.3/src/biomechzoo/utils/get_split_events.py +33 -0
  50. biomechzoo-0.5.3/src/biomechzoo/utils/peak_sign.py +24 -0
  51. biomechzoo-0.5.3/src/biomechzoo/utils/set_zoosystem.py +66 -0
  52. biomechzoo-0.5.3/src/biomechzoo/utils/version.py +5 -0
  53. biomechzoo-0.5.3/src/biomechzoo/utils/zload.py +57 -0
  54. biomechzoo-0.5.3/src/biomechzoo/utils/zplot.py +61 -0
  55. biomechzoo-0.5.3/src/biomechzoo/utils/zsave.py +54 -0
  56. biomechzoo-0.5.3/src/biomechzoo.egg-info/PKG-INFO +46 -0
  57. biomechzoo-0.5.3/src/biomechzoo.egg-info/SOURCES.txt +59 -0
  58. biomechzoo-0.5.3/src/biomechzoo.egg-info/dependency_links.txt +1 -0
  59. biomechzoo-0.5.3/src/biomechzoo.egg-info/entry_points.txt +2 -0
  60. biomechzoo-0.5.3/src/biomechzoo.egg-info/requires.txt +6 -0
  61. biomechzoo-0.5.3/src/biomechzoo.egg-info/top_level.txt +2 -0
@@ -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,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: biomechzoo
3
+ Version: 0.5.3
4
+ Summary: Python implementation of the biomechZoo toolbox
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/mcgillmotionlab/biomechzoo
7
+ Requires-Python: <3.12,>=3.11
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.2.6
13
+ Requires-Dist: pandas>=2.3.2
14
+ Requires-Dist: scipy>=1.16.2
15
+ Requires-Dist: pyarrow>=19.0.0
16
+ Dynamic: license-file
17
+
18
+ # BiomechZoo for Python
19
+ This is a development version of the biomechzoo toolbox for python.
20
+
21
+ ## How to install
22
+ - biomechZoo for python is now an official package, you can simply add biomechZoo to your environment using
23
+ ``pip install biomechzoo``
24
+
25
+ ## Usage notes
26
+ - If you need to install a specific version, run ``pip install biomechzoo==x.x.x`` where x.x.x is the version number.
27
+ - If you need to update biomechzoo to the latest version in your env, run ``pip install biomechzoo --upgrade``
28
+
29
+ ## Dependencies notes
30
+ - We use Python 3.11 for compatibility with https://github.com/stanfordnmbl/opencap-processing
31
+ - We use Numpy 2.2.6 for compatibility with https://pypi.org/project/numba/
32
+
33
+ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechzoo.com for more information
34
+
35
+ ## Developer notes
36
+
37
+ ### Installing a dev environment
38
+ conda create -n biomechzoo-dev python=3.11
39
+ conda activate biomechzoo-dev
40
+ cd biomechzoo root folder
41
+ pip install -e ".[dev]"
42
+
43
+ ### import issues
44
+ if using PyCharm:
45
+ - Right-click on src/.
46
+ - Select Mark Directory as → Sources Root.
@@ -0,0 +1,29 @@
1
+ # BiomechZoo for Python
2
+ This is a development version of the biomechzoo toolbox for python.
3
+
4
+ ## How to install
5
+ - biomechZoo for python is now an official package, you can simply add biomechZoo to your environment using
6
+ ``pip install biomechzoo``
7
+
8
+ ## Usage notes
9
+ - If you need to install a specific version, run ``pip install biomechzoo==x.x.x`` where x.x.x is the version number.
10
+ - If you need to update biomechzoo to the latest version in your env, run ``pip install biomechzoo --upgrade``
11
+
12
+ ## Dependencies notes
13
+ - We use Python 3.11 for compatibility with https://github.com/stanfordnmbl/opencap-processing
14
+ - We use Numpy 2.2.6 for compatibility with https://pypi.org/project/numba/
15
+
16
+ See also http://www.github.com/mcgillmotionlab/biomechzoo or http://www.biomechzoo.com for more information
17
+
18
+ ## Developer notes
19
+
20
+ ### Installing a dev environment
21
+ conda create -n biomechzoo-dev python=3.11
22
+ conda activate biomechzoo-dev
23
+ cd biomechzoo root folder
24
+ pip install -e ".[dev]"
25
+
26
+ ### import issues
27
+ if using PyCharm:
28
+ - Right-click on src/.
29
+ - Select Mark Directory as → Sources Root.
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "biomechzoo"
3
+ version = "0.5.3"
4
+ description = "Python implementation of the biomechZoo toolbox"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11,<3.12" # max version for opencap-process tools is 3.11
7
+ license = "MIT"
8
+ license-files = ["LICEN[CS]E*"]
9
+ dependencies = [
10
+ "ezc3d>=1.5.19", # for reading c3d files
11
+ "matplotlib>=3.10.6",
12
+ "numpy==2.2.6", # max version for opencap-process tools
13
+ "pandas>=2.3.2",
14
+ "scipy>=1.16.2",
15
+ "pyarrow>=19.0.0", # for parquet support
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/mcgillmotionlab/biomechzoo"
20
+
21
+ [project.scripts]
22
+ biomechzoo = "main:main"
23
+
24
+ [build-system]
25
+ requires = ["setuptools>=78.1.0", "wheel>=0.45.1"]
26
+ build-backend = "setuptools.build_meta"
27
+
28
+ [[tool.uv.index]]
29
+ name = "testbiomechzoo"
30
+ url = "https://test.pypi.org/simple/"
31
+ publish-url = "https://test.pypi.org/legacy/"
32
+ explicit = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ """
2
+ BiomechZoo: A Python toolbox for processing and analyzing human movement data.
3
+
4
+ This package provides functions for converting, processing, analyzing,
5
+ and visualizing biomechanical data (e.g., motion capture, EMG, kinetics).
6
+
7
+ Example:
8
+ from biomechzoo import BiomechZoo
9
+ from biomechzoo.conversion import c3d2zoo
10
+
11
+ zoo = BiomechZoo()
12
+ zoo.conversion.c3d2zoo('path/to/data')
13
+ """
14
+
15
+ # Import main class or entry point
16
+ from .biomechzo import BiomechZoo
17
+
18
+ # Import commonly used submodules
19
+ from . import conversion
20
+ from . import processing
21
+ from . import plotting
22
+ from . import utils
23
+
24
+ # Define what gets exposed with "from biomechzoo import *"
25
+ __all__ = [
26
+ "BiomechZoo",
27
+ "conversion",
28
+ "processing",
29
+ "plotting",
30
+ "utils",
31
+ ]
32
+
33
+ __version__ = "0.4.4"
File without changes
@@ -0,0 +1,6 @@
1
+ def main():
2
+ print("Hello from biomechzoo!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -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,58 @@
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
+ - 'ftype': 'butter' (default)
17
+ - 'order': filter order (default: 4)
18
+ - 'cutoff': cutoff frequency or tuple (Hz)
19
+ - 'btype': 'low', 'high', 'bandpass', 'bandstop' (default: 'lowpass')
20
+
21
+ Returns
22
+ -------
23
+ dict
24
+ The updated data dictionary with filtered channels.
25
+ """
26
+
27
+ if filt is None:
28
+ filt = {'ftype': 'butter',
29
+ 'order': 4,
30
+ 'cutoff': 10,
31
+ 'btype': 'lowpass',
32
+ 'filtfilt': True}
33
+
34
+ if isinstance(ch, str):
35
+ ch = [ch]
36
+
37
+ # loop through all channels and filter
38
+ for c in ch:
39
+ if c not in data:
40
+ raise KeyError('Channel {} not found in data'.format(c))
41
+
42
+ if 'fs' not in filt:
43
+
44
+ video_channels = data['zoosystem']['Video']['Channels']
45
+ analog_channels = data['zoosystem']['Analog']['Channels']
46
+
47
+ if c in analog_channels:
48
+ filt['fs'] = data['zoosystem']['Analog']['Freq']
49
+ elif c in video_channels:
50
+ filt['fs'] = data['zoosystem']['Video']['Freq']
51
+ else:
52
+ raise ValueError('Channel not analog or video')
53
+
54
+ signal_raw = data[c]['line']
55
+ signal_filtered = filter_line(signal_raw=signal_raw, filt=filt)
56
+ data[c]['line'] = signal_filtered
57
+
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
@@ -0,0 +1,51 @@
1
+ def movement_onset(yd, constant):
2
+ """
3
+ Extracts movement onset based on the average and standard deviation of a sliding window
4
+ Standard thresholds for running are mean_thresh=1.2, std_thresh=0.2. For walking mean_thresh=0.6, std_thresh=0.2.
5
+
6
+ yd: 1d array of the vector
7
+ constants: [sample_frequency, mean_thresh, std_thresh]
8
+ """
9
+ acc_mag = yd.copy()
10
+
11
+ # ----extract the constants----
12
+ fs = constant[0]
13
+ mean_thresh = constant[1]
14
+ std_thresh = constant[2]
15
+
16
+ # ----sliding window features----
17
+ features = []
18
+ timestamps = []
19
+ window_size = 2 * fs # windows van 2 seconds
20
+ step_size = 1 * fs # with an overlap of 1 seconds
21
+
22
+ for start in range(0, len(acc_mag) - window_size, step_size):
23
+ segment = acc_mag[start:start + window_size]
24
+ mean_val = segment.mean()
25
+ std_val = segment.std()
26
+ # entropy = -np.sum((segment / np.sum(segment)) * np.log2(segment / np.sum(segment) + 1e-12))
27
+ timestamps.append(start)
28
+ features.append((mean_val, std_val))
29
+
30
+ features = np.array(features)
31
+ timestamps = np.array(timestamps)
32
+
33
+ # ----Check already moving else find start----
34
+ initial_window = features[:5] # First few seconds
35
+ if np.all(initial_window[:, 0] > mean_thresh) and np.all(initial_window[:, 1] > std_thresh):
36
+ print("already moving")
37
+ if etype == 'movement_offset':
38
+ index = 0
39
+ else:
40
+ # features, timestamps = self.sliding_window_features(acc_mag)
41
+ movement_flags = (features[:, 0] > mean_thresh) & (features[:, 1] > std_thresh)
42
+ index = None
43
+ for i in range(len(movement_flags) - int(min_duration * self.fs / 50)):
44
+ if np.all(movement_flags[i:i + int(min_duration * self.fs / 50)]):
45
+ index = i
46
+ break
47
+
48
+ if etype == 'movement_offset':
49
+ index = len(yd) - end_time
50
+
51
+ return timestamps[index] if index is not None else timestamps[0]
@@ -0,0 +1,36 @@
1
+ import warnings
2
+ import copy
3
+ from biomechzoo.biomech_ops.normalize_line import normalize_line
4
+
5
+
6
+ def normalize_data(data, nlength=101):
7
+ """normalize all channels in the loaded zoo dict to nlen.
8
+ Arguments
9
+ data: dict, loaded zoo file
10
+ nlength: int: new length of data. Default = 101, usually a movement cycle
11
+ Returns:
12
+ None
13
+ Notes:
14
+ -It is often needed to partition data to a single cycle first (see partition_data)
15
+ """
16
+
17
+ # normalize channel length
18
+ data_new = copy.deepcopy(data)
19
+ for ch_name, ch_data in data_new.items():
20
+ if ch_name != 'zoosystem':
21
+ ch_data_line = ch_data['line']
22
+ ch_data_event = ch_data['event']
23
+ ch_data_normalized = normalize_line(ch_data_line, nlength)
24
+ data_new[ch_name]['line'] = ch_data_normalized
25
+ data_new[ch_name]['event'] = ch_data_event
26
+ warnings.warn('event data have not been normalized')
27
+
28
+ # update zoosystem
29
+ # todo: update all relevant zoosystem meta data related to data lengths
30
+ warnings.warn('zoosystem data have not been fully updated')
31
+ if 'Video' in data['zoosystem']:
32
+ data['zoosystem']['Video']['CURRENT_END_FRAME'] = nlength
33
+ if 'Analog' in data['zoosystem']:
34
+ data['zoosystem']['Analog']['CURRENT_END_FRAME'] = nlength
35
+
36
+ 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
+