Source code for biomechzoo.conversion.c3d2zoo_data
from biomechzoo.utils.set_zoosystem import set_zoosystem
[docs]
def c3d2zoo_data(c3d_obj):
"""
Convert an ezc3d C3D object to zoo format.
Parameters
----------
c3d_obj : dict
C3D object loaded via ezc3d, containing 'data' and 'parameters' keys.
Returns
-------
dict
Zoo dictionary with 'line' and 'event' fields per channel, plus
a 'zoosystem' metadata entry.
"""
data = {}
data['zoosystem'] = set_zoosystem()
video_freq = None
analog_freq = None
# extract "video" data
if 'points' in c3d_obj['data']:
points = c3d_obj['data']['points'] # shape: (4, n_markers, n_frames)
labels = list(c3d_obj['parameters']['POINT']['LABELS']['value'])
video_freq = int(c3d_obj['parameters']['POINT']['RATE']['value'][0])
for i, label in enumerate(labels):
line_data = points[:3, i, :].T # shape: (frames, 3)
data[label] = {
'line': line_data,
'event': {} # empty for now
}
data['zoosystem']['Video']['Freq'] = video_freq
data['zoosystem']['Video']['Channels'] = labels
if 'analogs' in c3d_obj['data']:
analog_data = c3d_obj['data']['analogs'] # shape: (subframes, n_analog_channels, n_frames)
analog_labels = list(c3d_obj['parameters']['ANALOG']['LABELS']['value'])
analog_freq = int(c3d_obj['parameters']['ANALOG']['RATE']['value'][0])
# Flatten to 2D: (n_samples, n_channels)
# ezc3d stores analogs as subframes per frame, so we flatten across all
n_subframes, n_channels, n_frames = analog_data.shape
analog_data = analog_data.reshape(n_subframes * n_frames, n_channels)
for i, label in enumerate(analog_labels):
line_data = analog_data[:, i].reshape(-1, 1) # shape: (samples, 1)
data[label] = {
'line': line_data,
'event': {},
}
data['zoosystem']['Analog']['Freq'] = analog_freq
data['zoosystem']['Analog']['Channels'] = analog_labels
# extract event information
params = c3d_obj['parameters']
if 'EVENT' in params and 'TIMES' in params['EVENT']:
if 'points' in c3d_obj['data']:
times_array = params['EVENT']['TIMES']['value']
frames = times_array[1] # should be time depending on C3D file
# Extract sides, types, subjects
contexts = params['EVENT']['CONTEXTS']['value'] if 'CONTEXTS' in params['EVENT'] else ['']
labels = params['EVENT']['LABELS']['value']
subjects = params['EVENT']['SUBJECTS']['value'] if 'SUBJECTS' in params['EVENT'] else ['']
events = {}
for i in range(len(labels)):
side = contexts[i].strip()
label = labels[i].strip()
subject = subjects[i].strip()
# Event channel name: e.g. 'Right_FootStrike' -> 'RightFootStrike'
event_name = f"{side}_{label}".replace(' ', '')
event_name = ''.join(c for c in event_name if c.isalnum() or c == '_') # make it a valid field name
if event_name not in events:
events[event_name] = []
events[event_name].append(frames[i]) # This is in seconds or frame number?
original_start = 1
for event_name, time_list in events.items():
# Clean and sort times
valid_times = sorted([t for t in time_list if t != 0])
for j, time_val in enumerate(valid_times):
frame = round(time_val * video_freq) - original_start + 1 # MATLAB logic
key_name = f"{event_name}{j + 1}"
# Place in correct channel
if 'SACR' in data:
data['SACR']['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
else:
data[labels[0]]['event'][key_name] = [frame-1, 0, 0] # remove 1 to follow python
# add more zoosystem
if analog_freq is not None and video_freq is not None:
data['zoosystem']['AVR'] = analog_freq/video_freq
return data