biomechzoo 0.5.9__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.
- __init__.py +33 -0
- biomechzoo/__init__.py +0 -0
- biomechzoo/__main__.py +6 -0
- biomechzoo/biomech_ops/__init__.py +0 -0
- biomechzoo/biomech_ops/continuous_relative_phase_data.py +31 -0
- biomechzoo/biomech_ops/continuous_relative_phase_line.py +36 -0
- biomechzoo/biomech_ops/filter_data.py +58 -0
- biomechzoo/biomech_ops/filter_line.py +85 -0
- biomechzoo/biomech_ops/movement_onset.py +53 -0
- biomechzoo/biomech_ops/normalize_data.py +36 -0
- biomechzoo/biomech_ops/normalize_line.py +51 -0
- biomechzoo/biomech_ops/phase_angle_data.py +39 -0
- biomechzoo/biomech_ops/phase_angle_line.py +48 -0
- biomechzoo/biomechzoo.py +447 -0
- biomechzoo/conversion/__init__.py +0 -0
- biomechzoo/conversion/c3d2zoo_data.py +95 -0
- biomechzoo/conversion/mvnx2zoo_data.py +113 -0
- biomechzoo/conversion/opencap2zoo_data.py +23 -0
- biomechzoo/conversion/table2zoo_data.py +114 -0
- biomechzoo/imu/__init__.py +0 -0
- biomechzoo/imu/kinematics.py +0 -0
- biomechzoo/imu/tilt_algorithm.py +112 -0
- biomechzoo/linear_algebra_ops/__init__.py +0 -0
- biomechzoo/linear_algebra_ops/compute_magnitude_data.py +43 -0
- biomechzoo/mvn/__init__.py +0 -0
- biomechzoo/mvn/load_mvnx.py +514 -0
- biomechzoo/mvn/main_mvnx.py +75 -0
- biomechzoo/mvn/mvn.py +232 -0
- biomechzoo/mvn/mvnx_file_accessor.py +464 -0
- biomechzoo/processing/__init__.py +0 -0
- biomechzoo/processing/addchannel_data.py +71 -0
- biomechzoo/processing/addevent_data.py +116 -0
- biomechzoo/processing/explodechannel_data.py +69 -0
- biomechzoo/processing/partition_data.py +46 -0
- biomechzoo/processing/removechannel_data.py +46 -0
- biomechzoo/processing/removeevent_data.py +57 -0
- biomechzoo/processing/renamechannel_data.py +79 -0
- biomechzoo/processing/renameevent_data.py +62 -0
- biomechzoo/processing/split_trial_data.py +40 -0
- biomechzoo/statistics/eventval.py +118 -0
- biomechzoo/utils/__init__.py +0 -0
- biomechzoo/utils/batchdisp.py +21 -0
- biomechzoo/utils/compute_sampling_rate_from_time.py +25 -0
- biomechzoo/utils/engine.py +88 -0
- biomechzoo/utils/findfield.py +11 -0
- biomechzoo/utils/get_split_events.py +33 -0
- biomechzoo/utils/peak_sign.py +24 -0
- biomechzoo/utils/set_zoosystem.py +66 -0
- biomechzoo/utils/version.py +5 -0
- biomechzoo/utils/zload.py +57 -0
- biomechzoo/utils/zplot.py +61 -0
- biomechzoo/utils/zsave.py +54 -0
- biomechzoo-0.5.9.dist-info/METADATA +46 -0
- biomechzoo-0.5.9.dist-info/RECORD +58 -0
- biomechzoo-0.5.9.dist-info/WHEEL +5 -0
- biomechzoo-0.5.9.dist-info/entry_points.txt +2 -0
- biomechzoo-0.5.9.dist-info/licenses/LICENSE +21 -0
- biomechzoo-0.5.9.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def addchannel_data(data, ch_new_name, ch_new_data, section='Video'):
|
|
5
|
+
"""
|
|
6
|
+
Add a new channel to zoo data.
|
|
7
|
+
|
|
8
|
+
Parameters
|
|
9
|
+
----------
|
|
10
|
+
data : dict
|
|
11
|
+
Zoo file data.
|
|
12
|
+
ch_new_name : str
|
|
13
|
+
Name of the new channel.
|
|
14
|
+
ch_new_data : array-like
|
|
15
|
+
New data to be added to the channel (should be n x 1 or n x 3).
|
|
16
|
+
section : str
|
|
17
|
+
Section of zoo data ('Video' or 'Analog').
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
dict
|
|
22
|
+
Updated zoo data with new channel added.
|
|
23
|
+
|
|
24
|
+
Notes
|
|
25
|
+
-----
|
|
26
|
+
- If the channel already exists, it will be overwritten.
|
|
27
|
+
- Adds channel name to the list in data['zoosystem'][section]['Channels'].
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Warn if overwriting
|
|
31
|
+
if ch_new_name in data:
|
|
32
|
+
print('Warning: channel {} already exists, overwriting...'.format(ch_new_name))
|
|
33
|
+
|
|
34
|
+
# Assign channel data
|
|
35
|
+
data[ch_new_name] = {
|
|
36
|
+
'line': ch_new_data,
|
|
37
|
+
'event': {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Update channel list
|
|
41
|
+
ch_list = data['zoosystem'][section].get('Channels', [])
|
|
42
|
+
|
|
43
|
+
# If the channel list is a NumPy array, convert it to a list
|
|
44
|
+
if isinstance(ch_list, np.ndarray):
|
|
45
|
+
ch_list = ch_list.tolist()
|
|
46
|
+
|
|
47
|
+
# Ensure it's a flat list of strings
|
|
48
|
+
if isinstance(ch_list, list) and ch_new_name not in ch_list:
|
|
49
|
+
ch_list.append(ch_new_name)
|
|
50
|
+
data['zoosystem'][section]['Channels'] = ch_list
|
|
51
|
+
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
# -------TESTING--------
|
|
57
|
+
import os
|
|
58
|
+
from src.biomechzoo.utils.zload import zload
|
|
59
|
+
from src.biomechzoo.utils.zplot import zplot
|
|
60
|
+
# get path to sample zoo file
|
|
61
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
62
|
+
project_root = os.path.dirname(current_dir)
|
|
63
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
|
|
64
|
+
|
|
65
|
+
# load zoo file
|
|
66
|
+
data = zload(fl)
|
|
67
|
+
data = data['data']
|
|
68
|
+
r = data['RKneeAngles']['line']*3
|
|
69
|
+
data= addchannel_data(data, ch_new_name='blah', ch_new_data=r)
|
|
70
|
+
zplot(data, 'blah')
|
|
71
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import copy
|
|
3
|
+
import warnings
|
|
4
|
+
from scipy.signal import find_peaks
|
|
5
|
+
from biomechzoo.utils.peak_sign import peak_sign
|
|
6
|
+
from biomechzoo.biomech_ops.movement_onset import movement_onset
|
|
7
|
+
|
|
8
|
+
def addevent_data(data, channels, ename, etype, constant=None):
|
|
9
|
+
|
|
10
|
+
data_new = copy.deepcopy(data)
|
|
11
|
+
|
|
12
|
+
if isinstance(channels, str):
|
|
13
|
+
channels = [channels]
|
|
14
|
+
|
|
15
|
+
if len(channels) == 1 and channels[0].lower() == 'all':
|
|
16
|
+
channels = [key for key in data if key != 'zoosystem']
|
|
17
|
+
|
|
18
|
+
for channel in channels:
|
|
19
|
+
if ename == '':
|
|
20
|
+
data[channel]['event'] = {}
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
if channel not in data:
|
|
24
|
+
raise KeyError('Channel {} does not exist'.format(channel))
|
|
25
|
+
|
|
26
|
+
yd = data_new[channel]['line'] # 1D array
|
|
27
|
+
etype = etype.lower()
|
|
28
|
+
if etype == 'absmax':
|
|
29
|
+
exd = int(np.argmax(np.abs(yd)))
|
|
30
|
+
eyd = float(yd[exd])
|
|
31
|
+
elif etype == 'first':
|
|
32
|
+
exd = 0
|
|
33
|
+
eyd = float(yd[exd])
|
|
34
|
+
elif etype == 'last':
|
|
35
|
+
exd = len(yd) - 1
|
|
36
|
+
eyd = float(yd[exd])
|
|
37
|
+
elif etype == 'max':
|
|
38
|
+
exd = int(np.argmax(yd))
|
|
39
|
+
eyd = float(yd[exd])
|
|
40
|
+
elif etype == 'min':
|
|
41
|
+
exd = int(np.argmin(yd))
|
|
42
|
+
eyd = float(yd[exd])
|
|
43
|
+
elif etype == 'rom':
|
|
44
|
+
eyd = float(np.max(yd) - np.min(yd))
|
|
45
|
+
exd = 0 # dummy index (like MATLAB version)
|
|
46
|
+
elif etype == 'first peak':
|
|
47
|
+
# special event for gait and running
|
|
48
|
+
exd = find_first_peak(yd, constant)
|
|
49
|
+
eyd = float(yd[exd])
|
|
50
|
+
elif etype == 'movement_onset':
|
|
51
|
+
exd = movement_onset(yd, constant, etype=etype)
|
|
52
|
+
eyd = yd[exd]
|
|
53
|
+
elif etype == 'movement_offset':
|
|
54
|
+
yd2 = yd[::-1].copy() # Reverse the time series.
|
|
55
|
+
exd = movement_onset(yd2, constant, etype=etype)
|
|
56
|
+
eyd = yd[exd]
|
|
57
|
+
elif etype in ['fs_fp', 'fo_fp']:
|
|
58
|
+
# --- Handle constant ---
|
|
59
|
+
if constant is None:
|
|
60
|
+
print('Warning: Force plate threshold not set, defaulting to 0.')
|
|
61
|
+
constant = 0.0
|
|
62
|
+
|
|
63
|
+
# --- Check sampling rates ---
|
|
64
|
+
AVR = data['zoosystem']['AVR']
|
|
65
|
+
if AVR != 1:
|
|
66
|
+
warnings.warn('Video and Analog channels must be at the same sampling rate or events will be incorrect.')
|
|
67
|
+
|
|
68
|
+
# --- Handle units ---
|
|
69
|
+
units = data['zoosystem']['Units']['Forces']
|
|
70
|
+
if units == 'N/kg':
|
|
71
|
+
m = data['zoosystem']['Anthro']['Bodymass']
|
|
72
|
+
else:
|
|
73
|
+
m = 1.0
|
|
74
|
+
|
|
75
|
+
# --- Extract force signal ---
|
|
76
|
+
if '_' not in channel:
|
|
77
|
+
yd = data_new[channel]['line'][:, 2] # looking for GRF Z
|
|
78
|
+
else:
|
|
79
|
+
yd = data_new[channel]['line']
|
|
80
|
+
|
|
81
|
+
# --- Determine peak sign ---
|
|
82
|
+
peak = peak_sign(yd) # user-defined function
|
|
83
|
+
|
|
84
|
+
# --- Find threshold crossing ---
|
|
85
|
+
threshold_signal = peak * yd * m
|
|
86
|
+
if 'fs' in etype:
|
|
87
|
+
exd_array = np.where(threshold_signal > constant)[0]
|
|
88
|
+
exd = exd_array[0] - 1 # MATLAB indexing correction
|
|
89
|
+
eyd = yd[exd]
|
|
90
|
+
else: # 'FO' type
|
|
91
|
+
exd_array = np.where(threshold_signal > constant)[0]
|
|
92
|
+
exd = exd_array[-1] + 1
|
|
93
|
+
eyd = yd[exd]
|
|
94
|
+
|
|
95
|
+
else:
|
|
96
|
+
raise ValueError(f'Unknown event type: {etype}')
|
|
97
|
+
|
|
98
|
+
# Add event to the channel's event dict
|
|
99
|
+
data_new[channel]['event'][ename] = [exd, eyd, 0]
|
|
100
|
+
|
|
101
|
+
return data_new
|
|
102
|
+
|
|
103
|
+
def find_first_peak(yd, constant):
|
|
104
|
+
""" extracts first peak from a series of 2 peaks """
|
|
105
|
+
# Find peaks above threshold
|
|
106
|
+
peaks, _ = find_peaks(yd, height=constant)
|
|
107
|
+
|
|
108
|
+
if len(peaks) == 0:
|
|
109
|
+
raise ValueError('No peaks found')
|
|
110
|
+
elif len(peaks) == 1:
|
|
111
|
+
raise ValueError('Only 1 peak found')
|
|
112
|
+
else:
|
|
113
|
+
# Take the first valid peak
|
|
114
|
+
exd = peaks[0]
|
|
115
|
+
|
|
116
|
+
return exd
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def explodechannel_data(data, channels=None):
|
|
5
|
+
""" Explodes 3D channels (n x 3 arrays) into separate X, Y, Z channels.
|
|
6
|
+
|
|
7
|
+
Arguments:
|
|
8
|
+
data (dict): Zoo data loaded from a file
|
|
9
|
+
channels (list of str or None): Channels to explode.
|
|
10
|
+
If None, explode all channels with 'line' shaped (n x 3).
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
data_new (dict): Modified zoo dictionary with exploded channels.
|
|
14
|
+
"""
|
|
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
|
+
|
|
27
|
+
# Find default channels if none provided
|
|
28
|
+
if channels is None:
|
|
29
|
+
channels = []
|
|
30
|
+
for ch in data_new:
|
|
31
|
+
if ch == 'zoosystem':
|
|
32
|
+
continue
|
|
33
|
+
ch_data = data_new[ch]['line']
|
|
34
|
+
if ch_data.ndim == 2 and ch_data.shape[1] == 3:
|
|
35
|
+
channels.append(ch)
|
|
36
|
+
|
|
37
|
+
# Explode each channel
|
|
38
|
+
for ch in channels:
|
|
39
|
+
if ch not in data_new:
|
|
40
|
+
print('Warning: channel {} not found, skipping.'.format(ch))
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
ch_data = data_new[ch]['line']
|
|
44
|
+
if ch_data.ndim != 2 or ch_data.shape[1] != 3:
|
|
45
|
+
print(f"Warning: channel '{ch}' 'line' is not n x 3 shape, skipping.")
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
x, y, z = ch_data[:, 0], ch_data[:, 1], ch_data[:, 2]
|
|
49
|
+
for axis, line in zip(['_x', '_y', '_z'], [x, y, z]):
|
|
50
|
+
key = ch + axis
|
|
51
|
+
data_new[key] = {
|
|
52
|
+
'line': line,
|
|
53
|
+
'event': data_new[ch]['event']}
|
|
54
|
+
|
|
55
|
+
# Remove original channel
|
|
56
|
+
del data_new[ch]
|
|
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
|
+
|
|
69
|
+
return data_new
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from biomechzoo.utils.findfield import findfield
|
|
2
|
+
import warnings
|
|
3
|
+
import copy
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def partition_data(data, evt_start, evt_end):
|
|
8
|
+
""" partition data for all channels between events evt_start and evt_end"""
|
|
9
|
+
|
|
10
|
+
# extract event values
|
|
11
|
+
e1, _ = findfield(data, evt_start)
|
|
12
|
+
e2, _ = findfield(data, evt_end)
|
|
13
|
+
|
|
14
|
+
if e1 is None or e2 is None or len(e1) == 0 or len(e2) == 0:
|
|
15
|
+
raise ValueError(f"Event not found: evt_start='{evt_start}' returned {e1}, evt_end='{evt_end}' returned {e2}")
|
|
16
|
+
|
|
17
|
+
# convert to int and get first value
|
|
18
|
+
e1 = int(e1[0])
|
|
19
|
+
e2 = int(e2[0])
|
|
20
|
+
|
|
21
|
+
data_new = copy.deepcopy(data)
|
|
22
|
+
for ch_name, ch_data in sorted(data_new.items()):
|
|
23
|
+
if ch_name != 'zoosystem':
|
|
24
|
+
r = ch_data['line']
|
|
25
|
+
try:
|
|
26
|
+
if r.ndim == 1:
|
|
27
|
+
data_new[ch_name]['line'] = r[e1:e2]
|
|
28
|
+
else:
|
|
29
|
+
data_new[ch_name]['line'] = r[e1:e2, :]
|
|
30
|
+
except (IndexError, ValueError) as e:
|
|
31
|
+
# IndexError: if e1[0]:e2[0] goes beyond the available indices
|
|
32
|
+
# ValueError: less likely, but may arise with shape mismatches
|
|
33
|
+
warnings.warn(f"Skipping {ch_name} due to error: {e}")
|
|
34
|
+
|
|
35
|
+
# partition events
|
|
36
|
+
events = ch_data['event']
|
|
37
|
+
for event_name, value in events.items():
|
|
38
|
+
original_frame = int(value[0])
|
|
39
|
+
if original_frame == 999:
|
|
40
|
+
continue # do not change outlier markers
|
|
41
|
+
else:
|
|
42
|
+
arr = np.array(data_new[ch_name]['event'][event_name], dtype=np.int32)
|
|
43
|
+
arr[0] = original_frame - e1
|
|
44
|
+
data_new[ch_name]['event'][event_name] = arr
|
|
45
|
+
|
|
46
|
+
return data_new
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
def removechannel_data(data, channels, mode='remove'):
|
|
2
|
+
"""
|
|
3
|
+
File-level processing: Remove or keep specified channels in a single zoo dictionary.
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- data (dict): Zoo data loaded from a file
|
|
7
|
+
- channels (list of str): List of channels to remove or keep
|
|
8
|
+
- mode (str): 'remove' or 'keep'
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
- dict: Modified zoo dictionary with updated channels
|
|
12
|
+
"""
|
|
13
|
+
if mode not in ['remove', 'keep']:
|
|
14
|
+
raise ValueError("mode must be 'remove' or 'keep'.")
|
|
15
|
+
|
|
16
|
+
all_channels = [ch for ch in data if ch != 'zoosystem']
|
|
17
|
+
|
|
18
|
+
# Check for missing channels
|
|
19
|
+
missing = [ch for ch in channels if ch not in all_channels]
|
|
20
|
+
if missing:
|
|
21
|
+
print('Warning: the following channels were not found {}'.format(missing))
|
|
22
|
+
|
|
23
|
+
if mode == 'remove':
|
|
24
|
+
keep_channels = [ch for ch in all_channels if ch not in channels]
|
|
25
|
+
elif mode == 'keep':
|
|
26
|
+
keep_channels = [ch for ch in all_channels if ch in channels]
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError("Mode must be 'remove' or 'keep'.")
|
|
29
|
+
|
|
30
|
+
# --- Compute channels to remove ---
|
|
31
|
+
remove_channels = [ch for ch in all_channels if ch not in keep_channels]
|
|
32
|
+
|
|
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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import warnings
|
|
3
|
+
from biomechzoo.utils.findfield import findfield
|
|
4
|
+
|
|
5
|
+
def removeevent_data(data, events, mode='remove'):
|
|
6
|
+
"""
|
|
7
|
+
Remove or keep specified events in all channels of a zoo dictionary.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
data : dict
|
|
12
|
+
Zoo data loaded from a file
|
|
13
|
+
events : list of str
|
|
14
|
+
Events to remove or keep
|
|
15
|
+
mode : str
|
|
16
|
+
'remove' or 'keep'
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
dict
|
|
21
|
+
Modified zoo dictionary with events removed or kept
|
|
22
|
+
"""
|
|
23
|
+
if mode not in ['remove', 'keep']:
|
|
24
|
+
raise ValueError("mode must be 'remove' or 'keep'.")
|
|
25
|
+
|
|
26
|
+
if isinstance(events, str):
|
|
27
|
+
events = [events]
|
|
28
|
+
|
|
29
|
+
# check if any events are not present
|
|
30
|
+
valid_events = []
|
|
31
|
+
for evt in events:
|
|
32
|
+
e, _ = findfield(data, evt)
|
|
33
|
+
if e is None:
|
|
34
|
+
warnings.warn('Could not find event {} in zoo file, skipping'.format(evt))
|
|
35
|
+
else:
|
|
36
|
+
valid_events.append(evt)
|
|
37
|
+
events = valid_events
|
|
38
|
+
|
|
39
|
+
data_new = copy.deepcopy(data)
|
|
40
|
+
channels = sorted([ch for ch in data_new if ch != 'zoosystem'])
|
|
41
|
+
for ch in channels:
|
|
42
|
+
event_dict = data_new[ch].get('event', {})
|
|
43
|
+
events_to_remove = []
|
|
44
|
+
|
|
45
|
+
for evt in list(event_dict.keys()):
|
|
46
|
+
if mode == 'remove' and evt in events:
|
|
47
|
+
events_to_remove.append(evt)
|
|
48
|
+
elif mode == 'keep' and evt not in events:
|
|
49
|
+
events_to_remove.append(evt)
|
|
50
|
+
|
|
51
|
+
for evt in events_to_remove:
|
|
52
|
+
event_dict.pop(evt, None)
|
|
53
|
+
# print('Removed event "{}" from channel "{}"'.format(evt, ch))
|
|
54
|
+
|
|
55
|
+
data_new[ch]['event'] = event_dict
|
|
56
|
+
|
|
57
|
+
return data_new
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def renamechannel_data(data, ch_old_names, ch_new_names, section='Video'):
|
|
5
|
+
"""
|
|
6
|
+
Rename channels in a zoo data.
|
|
7
|
+
|
|
8
|
+
Parameters
|
|
9
|
+
----------
|
|
10
|
+
data : dict
|
|
11
|
+
Zoo file data.
|
|
12
|
+
ch_old_names : str or list
|
|
13
|
+
Name of the old channels.
|
|
14
|
+
ch_new_names : str or list
|
|
15
|
+
Name of the new channels.
|
|
16
|
+
|
|
17
|
+
section : str
|
|
18
|
+
Section of zoo data ('Video' or 'Analog').
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
dict
|
|
23
|
+
Updated zoo data with new channel added.
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
- If the channel already exists, it will be overwritten.
|
|
28
|
+
- Adds channel name to the list in data['zoosystem'][section]['Channels'].
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
for i, ch_old_name in enumerate(ch_old_names):
|
|
32
|
+
ch_new_name = ch_new_names[i]
|
|
33
|
+
# Warn if overwriting
|
|
34
|
+
if ch_new_name in data:
|
|
35
|
+
print('Warning: channel {} already exists, overwriting...'.format(ch_new_name))
|
|
36
|
+
|
|
37
|
+
# Assign new channel
|
|
38
|
+
data[ch_new_name] = {
|
|
39
|
+
'line': data[ch_old_name]['line'],
|
|
40
|
+
'event': data[ch_old_name]['event']
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# remove old channel
|
|
44
|
+
data.pop(ch_old_name)
|
|
45
|
+
|
|
46
|
+
# Update channel list
|
|
47
|
+
ch_list = data['zoosystem'][section].get('Channels', [])
|
|
48
|
+
|
|
49
|
+
# If the channel list is a NumPy array, convert it to a list
|
|
50
|
+
if isinstance(ch_list, np.ndarray):
|
|
51
|
+
ch_list = ch_list.tolist()
|
|
52
|
+
|
|
53
|
+
# Ensure it's a flat list of strings
|
|
54
|
+
if isinstance(ch_list, list) and ch_new_name not in ch_list:
|
|
55
|
+
ch_list.append(ch_new_name)
|
|
56
|
+
data['zoosystem'][section]['Channels'] = ch_list
|
|
57
|
+
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
# -------TESTING--------
|
|
63
|
+
import os
|
|
64
|
+
from src.biomechzoo.utils.zload import zload
|
|
65
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
66
|
+
project_root = os.path.dirname(current_dir)
|
|
67
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
|
|
68
|
+
|
|
69
|
+
# load zoo file
|
|
70
|
+
data = zload(fl)
|
|
71
|
+
ch_old_name = 'RKneeAngles'
|
|
72
|
+
ch_new_name = 'RightKneeAngles'
|
|
73
|
+
data = renamechannel_data(data, ch_old_name=ch_old_name, ch_new_name=ch_new_name)
|
|
74
|
+
|
|
75
|
+
if ch_old_name not in data:
|
|
76
|
+
print('RkneeAngles removed')
|
|
77
|
+
if ch_new_name in data:
|
|
78
|
+
print('RightKneeAngles added')
|
|
79
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
def renameevent_data(data, evt, nevt):
|
|
2
|
+
"""
|
|
3
|
+
Rename events in the Zoo data structure.
|
|
4
|
+
|
|
5
|
+
Parameters
|
|
6
|
+
----------
|
|
7
|
+
data : dict
|
|
8
|
+
The Zoo-formatted dictionary.
|
|
9
|
+
evt : str or list of str
|
|
10
|
+
Names of existing events to rename.
|
|
11
|
+
nevt : str or list of str
|
|
12
|
+
Names of new events to apply.
|
|
13
|
+
|
|
14
|
+
Returns
|
|
15
|
+
-------
|
|
16
|
+
data : dict
|
|
17
|
+
Updated Zoo data with renamed events.
|
|
18
|
+
"""
|
|
19
|
+
# Convert to list if passed as single string
|
|
20
|
+
if isinstance(evt, str):
|
|
21
|
+
evt = [evt]
|
|
22
|
+
if isinstance(nevt, str):
|
|
23
|
+
nevt = [nevt]
|
|
24
|
+
|
|
25
|
+
if len(evt) != len(nevt):
|
|
26
|
+
raise ValueError("`evt` and `nevt` must have the same length.")
|
|
27
|
+
|
|
28
|
+
# Get all data channels except 'zoosystem'
|
|
29
|
+
channels = [ch for ch in data if ch != 'zoosystem']
|
|
30
|
+
for old_name, new_name in zip(evt, nevt):
|
|
31
|
+
for ch in channels:
|
|
32
|
+
events = data[ch].get('event', {})
|
|
33
|
+
if old_name in events:
|
|
34
|
+
data[ch]['event'][new_name] = events[old_name]
|
|
35
|
+
del data[ch]['event'][old_name]
|
|
36
|
+
|
|
37
|
+
return data
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
# -------TESTING--------
|
|
42
|
+
import os
|
|
43
|
+
from src.biomechzoo.utils.zload import zload
|
|
44
|
+
# get path to sample zoo file
|
|
45
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
project_root = os.path.dirname(current_dir)
|
|
47
|
+
fl = os.path.join(project_root, 'data', 'other', 'HC030A05.zoo')
|
|
48
|
+
|
|
49
|
+
# load zoo file
|
|
50
|
+
data = zload(fl)
|
|
51
|
+
evt = ['Left_FootStrike1', 'Left_FootStrike2', 'NonExistingEvent']
|
|
52
|
+
|
|
53
|
+
# see existing keys
|
|
54
|
+
missing = [k for k in evt if k not in data['SACR']['event']]
|
|
55
|
+
print("Missing keys:", missing) # expected behavior is missing 'NonExistingEvent'
|
|
56
|
+
|
|
57
|
+
nevt = ['LFS_1', 'LFS2', 'NE_1']
|
|
58
|
+
data = renameevent_data(data, evt=evt, nevt=nevt)
|
|
59
|
+
|
|
60
|
+
# after applying renameevent_data
|
|
61
|
+
missing = [k for k in nevt if k not in data['SACR']['event']]
|
|
62
|
+
print("Missing keys:", missing) # expected behavior is missing 'NE_1'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from biomechzoo.utils.findfield import findfield
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def split_trial_data(data, start_event, end_event):
|
|
6
|
+
# todo check index problem compared to matlab start at 0 or 1
|
|
7
|
+
data_new = copy.deepcopy(data)
|
|
8
|
+
|
|
9
|
+
start_event_indx, _ = findfield(data_new, start_event)
|
|
10
|
+
end_event_indx, _ = findfield(data_new, end_event)
|
|
11
|
+
|
|
12
|
+
if start_event_indx is None:
|
|
13
|
+
raise ValueError('start_event {} not found'.format(start_event))
|
|
14
|
+
|
|
15
|
+
if end_event_indx is None:
|
|
16
|
+
raise ValueError('event_event {} not found'.format(end_event))
|
|
17
|
+
|
|
18
|
+
for key, value in data_new.items():
|
|
19
|
+
if key == 'zoosystem':
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
# Slice the line data
|
|
23
|
+
trial_length = len(data_new[key]['line'])
|
|
24
|
+
if trial_length > end_event_indx[0]:
|
|
25
|
+
data_new[key]['line'] = value['line'][start_event_indx[0]:end_event_indx[0]+1]
|
|
26
|
+
else:
|
|
27
|
+
print('skipping split trial since event is outside range of data')
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
# Update events if present
|
|
31
|
+
if 'event' in value:
|
|
32
|
+
new_events = {}
|
|
33
|
+
for evt_name, evt_val in value['event'].items():
|
|
34
|
+
event_frame = evt_val[0]
|
|
35
|
+
# Adjust index relative to new start
|
|
36
|
+
n = event_frame - start_event_indx[0]
|
|
37
|
+
new_events[evt_name] = [n, 0, 0]
|
|
38
|
+
data_new[key]['event'] = new_events
|
|
39
|
+
|
|
40
|
+
return data_new
|