biomechzoo 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of biomechzoo might be problematic. Click here for more details.

Files changed (44) hide show
  1. biomechzoo/__init__.py +5 -0
  2. biomechzoo/__main__.py +6 -0
  3. biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
  4. biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
  5. biomechzoo/biomech_ops/filter_data.py +56 -0
  6. biomechzoo/biomech_ops/filter_line.py +88 -0
  7. biomechzoo/biomech_ops/normalize_data.py +35 -0
  8. biomechzoo/biomech_ops/normalize_line.py +27 -0
  9. biomechzoo/biomech_ops/phase_angle_data.py +39 -0
  10. biomechzoo/biomech_ops/phase_angle_line.py +48 -0
  11. biomechzoo/biomechzoo.py +349 -0
  12. biomechzoo/conversion/__init__.py +0 -0
  13. biomechzoo/conversion/c3d2zoo_data.py +65 -0
  14. biomechzoo/conversion/csv2zoo_data.py +78 -0
  15. biomechzoo/conversion/mvnx2zoo_data.py +71 -0
  16. biomechzoo/conversion/opencap2zoo_data.py +23 -0
  17. biomechzoo/mvn/load_mvnx.py +514 -0
  18. biomechzoo/mvn/main_mvnx.py +75 -0
  19. biomechzoo/mvn/mvn.py +232 -0
  20. biomechzoo/mvn/mvnx_file_accessor.py +463 -0
  21. biomechzoo/processing/add_channel_data.py +71 -0
  22. biomechzoo/processing/addchannel_data.py +71 -0
  23. biomechzoo/processing/addevent_data.py +46 -0
  24. biomechzoo/processing/explodechannel_data.py +46 -0
  25. biomechzoo/processing/partition_data.py +51 -0
  26. biomechzoo/processing/removechannel_data.py +36 -0
  27. biomechzoo/processing/renamechannel_data.py +79 -0
  28. biomechzoo/processing/renameevent_data.py +68 -0
  29. biomechzoo/processing/split_trial_by_gait_cycle.py +52 -0
  30. biomechzoo/utils/batchdisp.py +21 -0
  31. biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
  32. biomechzoo/utils/engine.py +68 -0
  33. biomechzoo/utils/findfield.py +11 -0
  34. biomechzoo/utils/get_split_events.py +33 -0
  35. biomechzoo/utils/split_trial.py +23 -0
  36. biomechzoo/utils/zload.py +46 -0
  37. biomechzoo/utils/zplot.py +61 -0
  38. biomechzoo/utils/zsave.py +50 -0
  39. biomechzoo-0.1.1.dist-info/METADATA +48 -0
  40. biomechzoo-0.1.1.dist-info/RECORD +44 -0
  41. biomechzoo-0.1.1.dist-info/WHEEL +5 -0
  42. biomechzoo-0.1.1.dist-info/entry_points.txt +2 -0
  43. biomechzoo-0.1.1.dist-info/licenses/LICENSE +21 -0
  44. biomechzoo-0.1.1.dist-info/top_level.txt +1 -0
biomechzoo/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .biomechzoo import BiomechZoo # or from .core, etc.
2
+
3
+ __all__ = ['BiomechZoo']
4
+
5
+ __version__ = "0.1.0"
biomechzoo/__main__.py ADDED
@@ -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,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
+