Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crop c3d #321

Closed
mickaelbegon opened this issue May 16, 2024 · 9 comments
Closed

crop c3d #321

mickaelbegon opened this issue May 16, 2024 · 9 comments

Comments

@mickaelbegon
Copy link
Collaborator

Hi @pariterre,

Could you please provide a Python example for cropping a c3d and save it in another file?

Mickael

@mickaelbegon
Copy link
Collaborator Author

I started writing a Python code but at least one dimension is not well informed.
I will send you the c3d file on Element.

Traceback (most recent call last):
File "", line 1, in
File "/Users/mickaelbegon/miniconda3/envs/bioptim_04_24/lib/python3.11/site-packages/ezc3d/init.py", line 573, in write
dim = [len(old_param["value"])]
^^^^^^^^^^^^^^^^^^^^^^^
TypeError: len() of unsized object

import ezc3d
import numpy as np

## Example to remove unlabelled markers in Nexus/Vicon (identified by "*") and crop it
directory = "/Users/mickaelbegon/Downloads/CaMa"
file_name = "L_Squat.c3d"
start_time = 2.955
end_time = 4.977
test = True

# Construct the full file path
file_path = os.path.join(directory, file_name)

# Read the c3d file
c3d = ezc3d.c3d(file_path)
print(f"Read file: {file_name}")

print(f"Remove unlabelled markers")
labels = c3d["parameters"]["POINT"]["LABELS"]["value"]
descriptions = c3d['parameters']['POINT']['DESCRIPTIONS']['value']
points_data = c3d["data"]["points"]

# Find labelled markers (those that do not start with '*')
labelled_markers = [lab for lab in labels if not lab.startswith('*')]

# Find indices of labelled markers
labelled_indices = [labels.index(marker) for marker in labelled_markers]

# Crop descriptions and points's data to only keep those corresponding to labelled markers
cropped_labels = [labels[i] for i in labelled_indices]
cropped_descriptions = [descriptions[i] for i in labelled_indices]
cropped_points_data = points_data[:, labelled_indices, :]


# Update the c3d file's descriptions with the cropped list
c3d['parameters']['POINT']['USED']['value'] = np.array(len(cropped_labels))
c3d['header']['points']['size'] = len(cropped_labels)
c3d['parameters']['POINT']['LABELS']['value'] = cropped_labels
c3d['parameters']['POINT']['DESCRIPTIONS']['value'] = cropped_descriptions
c3d["data"]["points"] = cropped_points_data


# Remove the meta_points if it exists
if 'meta_points' in c3d['data']:
    del c3d['data']['meta_points']


print(f"Crop markers to selected period")
start_point_idx = int(np.floor(start_time * c3d['parameters']['POINT']['RATE']['value']))-1
end_point_idx =int(np.ceil(end_time * c3d['parameters']['POINT']['RATE']['value']))
cropped_points_data = cropped_points_data[:, :, start_point_idx:end_point_idx]

c3d['parameters']['POINT']['FRAMES']['value'] = cropped_points_data.shape[2]
c3d['parameters']['POINT']['DATA_START']['value'] += start_point_idx - 1
c3d['header']['points']['first_frame'] += start_point_idx - 1
c3d['header']['points']['last_frame'] = c3d['header']['points']['first_frame'] + cropped_points_data.shape[2] - 1
c3d["data"]["points"] = cropped_points_data

print(f"Crop analogs to selected period")
ratio = int(c3d['parameters']['ANALOG']['RATE']['value'] / c3d['parameters']['POINT']['RATE']['value'])
start_analog_idx = (start_point_idx) * ratio
end_analog_idx = (end_point_idx) * ratio
analog_data = c3d["data"]["analogs"]
cropped_analog_data = analog_data[:, :, start_analog_idx:end_analog_idx]

c3d['header']['analogs']['first_frame'] = c3d['header']['points']['first_frame'] * ratio
c3d['header']['analogs']['last_frame'] = c3d['header']['points']['last_frame'] * ratio + (ratio - 1)

if test is True:
    test_analog_data = c3d["data"]["analogs"][:, :, ::20]
    test_analog_data_cropped = test_analog_data[:, :, start_point_idx:end_point_idx]
    test_analog_data_cropped[:, 1, 0] == cropped_analog_data[:, 1, 0]
    test_analog_data_cropped[:, 1, -1] == cropped_analog_data[:, 1, -ratio]

c3d["data"]["analogs"] = cropped_analog_data

# Save the modified file with a new name
new_file_name = f"{os.path.splitext(file_name)[0]}_modified.c3d"
new_file_path = os.path.join(directory, new_file_name)
print(f"Write modified file: {new_file_name}")
c3d.write(new_file_path)

@mickaelbegon
Copy link
Collaborator Author

are these two variables supposed to be the same?

c3d['parameters']['POINT']['DATA_START']['value'] 
c3d['header']['points']['first_frame']

@pariterre
Copy link
Member

Hello there!

The script you provided seems slightly complicated... so I feel I may have oversimplified what you are trying to achieve. As far as I am concerned the following snippet does what you need. Can you confirm that I am not oversimplifying?

import ezc3d
import numpy as np

file_path = "PATH_TO_C3D.c3d"
start_time = 2.955
end_time = 4.977

# Read the c3d file
c3d = ezc3d.c3d(file_path)

# Rewrite important parameters and the data only keeping the labelled markers that don't start with "*"
labels = c3d["parameters"]["POINT"]["LABELS"]["value"]
descriptions = c3d["parameters"]["POINT"]["DESCRIPTIONS"]["value"]
indices_to_keep = [i for i, lab in enumerate(labels) if not lab.startswith("*")]
c3d["parameters"]["POINT"]["LABELS"]["value"] = [labels[i] for i in indices_to_keep]
c3d["parameters"]["POINT"]["DESCRIPTIONS"]["value"] = [descriptions[i] for i in indices_to_keep]

# Change the data accondingly
c3d["data"]["points"] = c3d["data"]["points"][:, indices_to_keep, :]
del c3d["data"]["meta_points"]  # Let ezc3d do the job for the meta_points

# Change the time parameters
start_point_idx = int(np.floor(start_time * c3d["parameters"]["POINT"]["RATE"]["value"]))
end_point_idx = int(np.ceil(end_time * c3d["parameters"]["POINT"]["RATE"]["value"]))
c3d["header"]["points"]["first_frame"] = start_point_idx
c3d["header"]["points"]["last_frame"] = end_point_idx


# Save the modified file with a new name
c3d["parameters"]["ANALOG"]["UNITS"]["value"] = []  # This seems like a bug in ezc3d, as it should not be a problem
c3d.write("new_modified.c3d")

# Read the new file
c3d_copied = ezc3d.c3d("new_modified.c3d")

@mickaelbegon
Copy link
Collaborator Author

mickaelbegon commented May 17, 2024

Than you.
If I understand well
['parameters']['POINT']['USED']['value']; ['header']['points']['size']; and probably more
are all updated when writing the c3d file ?

Your approach will not reduce much the size of the c3d (all data are still inside).
I found my bug: c3d['parameters']['POINT']['FRAMES']['value'] needed to be a list of array.

# Read the c3d file
c3d = ezc3d.c3d(file_path)
print(f"Read file: {file_name}")

print(f"Remove unlabelled markers")
labels = c3d["parameters"]["POINT"]["LABELS"]["value"]
descriptions = c3d['parameters']['POINT']['DESCRIPTIONS']['value']
indices_to_keep = [i for i, lab in enumerate(labels) if not lab.startswith("*")]
c3d["parameters"]["POINT"]["LABELS"]["value"] = [labels[i] for i in indices_to_keep]
c3d["parameters"]["POINT"]["DESCRIPTIONS"]["value"] = [descriptions[i] for i in indices_to_keep]

# Change the data accordingly
c3d["data"]["points"] = c3d["data"]["points"][:, indices_to_keep, :]
del c3d["data"]["meta_points"]  # Let ezc3d do the job for the meta_points

print(f"Crop markers to selected period")
start_point_idx = int(np.floor(start_time * c3d['parameters']['POINT']['RATE']['value']))-1
end_point_idx =int(np.ceil(end_time * c3d['parameters']['POINT']['RATE']['value']))
cropped_points_data = c3d["data"]["points"][:, :, start_point_idx:end_point_idx]
c3d['parameters']['POINT']['FRAMES']['value'] = np.array([cropped_points_data.shape[2]])
c3d["header"]["points"]["first_frame"] = 1
c3d["header"]["points"]["last_frame"] = cropped_points_data.shape[2]
c3d["data"]["points"] = cropped_points_data

print(f"Crop analogs to selected period")
ratio = int(c3d['parameters']['ANALOG']['RATE']['value'] / c3d['parameters']['POINT']['RATE']['value'])
start_analog_idx = (start_point_idx) * ratio
end_analog_idx = (end_point_idx) * ratio
analog_data = c3d["data"]["analogs"]
cropped_analog_data = analog_data[:, :, start_analog_idx:end_analog_idx]

c3d['header']['analogs']['first_frame'] = 0
c3d['header']['analogs']['last_frame'] = cropped_analog_data.shape[2]
c3d["data"]["analogs"] = cropped_analog_data
c3d["parameters"]["ANALOG"]["UNITS"]["value"] = []  # This seems like a bug in ezc3d, as it should not be a problem

# Save the modified file with a new name
new_file_name = f"{os.path.splitext(file_name)[0]}_cropped.c3d"
new_file_path = os.path.join(directory, new_file_name)
print(f"Write modified file: {new_file_name}")
c3d.write(new_file_path)

@pariterre
Copy link
Member

['parameters']['POINT']['USED']['value']; ['header']['points']['size']; and probably more
are all updated when writing the c3d file ?

That is correct, most of these NEED to be very precise, so ezc3d won't let the user set them as they please, it will be updated to ensure internal consistency.

The "starting" flag won't reduce the size of the data as 0.00 will be registered, for the non values. If you want to reduce the data, you must also remove the data points before the starting index. If you do so, you must reset the first_frame to 0 though

@mickaelbegon
Copy link
Collaborator Author

@pariterre, I let you decide if you want to put an example to crop (and not change the start - end) a c3d file in the python example and you may close this issue.
I don't know why
c3d['parameters']['POINT']['FRAMES']['value'] needs to be a list of arrays... and not an integer.
Perhaps it may be good to have a specific error.

@mickaelbegon
Copy link
Collaborator Author

by the way, thank you for the help.

@pariterre
Copy link
Member

Done in #324

@pariterre
Copy link
Member

For posterity:

c3d["parameters"]["ANALOG"]["UNITS"]["value"] = [] # This seems like a bug in ezc3d, as it should not be a problem

This was not a bug, it was because the underlying c3d had unsupported value formatting from the 35th element. This is why we had to remove the values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants