-
Notifications
You must be signed in to change notification settings - Fork 45
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
Potential bug when writing c3d file with rotations #316
Comments
Thanks for reporting! I am not surprised that there are still bugs in rotations support as not much people use them! I'll have a look soon :) |
Dear @Alek050 I'll slowly answer the comments in different posts as I am going through the problems, sorry if it spams your email box!
This is actually expected behavior. I made the header is read-only because it is enterely determined by the parameters section (it is redundant information). Hence, the user is not expected to fill the header.rotation sections, it will be filled automatically at write time, if the the ROTATION parameter if properly provided. Another way to say it is that any information the user would provide in the header section (if they changed it) is simply ignored when writting a new C3D based on the parameters section, this is true for rotations, but also points and analogs |
Do you mean: if there are data it works properly, but if there are missing data, instead of getting the last row being If my understanding is correct, it is expected behavior. The reason I chose to do that is that filling with nan is a first initialisation and filling the last line with One way to fix that for the tests could be to ignore the last line as it is always a line equals to |
Hi @pariterre Thanks for both explenations! I was not aware that the I understand your second point as well, and from a efficiency and functionality perspective you are totally right. From a users perspective I still think it is a bit unexpected to write an array with partially floats, and a get back fully nans, but I am also not aware of how much extra time/resources it would take to still do that. On top of that, you created a great package and your suggestion solves my current problem. Thanks for the clear explenation and I think the issue can be closed! |
Sorry to bother you again @pariterre, but I still have some questions regarding writing c3d files with rotations: QUESTION 1: >>> import ezc3d
>>> c3d_rotations= ezc3d.c3d("data/C3DRotationExample.c3d")
>>> print(c3d_rotations["header"]["rotations"])
{'size': 21, 'frame_rate': 85.0, 'first_frame': 0.0, 'last_frame': 28899.0}
>>> print(c3d_rotations["data"]["rotations"].shape)
(4, 4, 21, 340) Here, the >>> c3d_points = ezc3d.c3d("data/my_points_data.c3d")
>>> c3d_points["header"]["points"]
{'size': 142, 'frame_rate': 200.0, 'first_frame': 0, 'last_frame': 1232}
>>> c3d_points["data"]["points"].shape
(4, 142, 1233) Here the last_frame corresponds with the number of data_points. This is similar for other c3d files I have with points and rotations. I would at least expect both the refer to the same value, am I overlooking something? QUESTION2 >>> import kineticstoolkit as ktk
>>> import ezc3d
>>> filename = ktk.doc.download("walk.c3d")
>>> c3d = ezc3d.c3d(filename)
>>> c3d["header"]["points"]
{'size': 96, 'frame_rate': 100.0, 'first_frame': 152, 'last_frame': 372}
>>> c3d["header"]["analogs"]
{'size': 248, 'frame_rate': 2000.0, 'first_frame': 3040, 'last_frame': 7459}
>>> c3d["parameters"]["EVENT"]["TIMES"]["value"][1]
array([2.22350001, 2.89199996, 1.66050005, 2.36400008, 3.32999992,
1.80999994, 2.74000001, 3.5 ]) As you can see, both the points and the analogs start after 1.52 seconds, which is important since the events are linked to the data based on the time. If I were to write this to a new c3d file: import kineticstoolkit as ktk
import ezc3d
filename = ktk.doc.download("walk.c3d")
c3d = ezc3d.c3d(filename)
c3d_writer = ezc3d.c3d()
c3d_writer.add_parameter("POINT", "RATE", [100.])
c3d_writer.add_parameter("POINT", "LABELS", c3d["parameters"]["POINT"]["LABELS"]["value"])
c3d_writer.add_parameter("POINT", "UNITS", ["mm"])
c3d_writer.add_parameter("POINT", "USED", [len(c3d["parameters"]["POINT"]["LABELS"]["value"])])
c3d_writer.add_parameter("POINT", "DATA_START", 1)
c3d_writer.add_parameter("TRIAL", "ACTUAL_START_FIELD", [c3d["header"]["points"]["first_frame"] + 1, 0])
c3d_writer.add_parameter("TRIAL", "ACTUAL_END_FIELD", [c3d["header"]["points"]["last_frame"] + 1, 0])
c3d_writer["data"]["points"] = c3d["data"]["points"]
c3d_writer.add_parameter("ANALOG", "RATE", [2000.])
c3d_writer.add_parameter("ANALOG", "LABELS", c3d["parameters"]["ANALOG"]["LABELS"]["value"])
c3d_writer.add_parameter("ANALOG", "USED", (len(c3d["parameters"]["ANALOG"]["LABELS"]["value"])))
c3d_writer["data"]["analogs"] = c3d["data"]["analogs"]
for event_time, event_label, event_context in zip(c3d["parameters"]["EVENT"]["TIMES"]["value"].T, c3d["parameters"]["EVENT"]["LABELS"]["value"], c3d["parameters"]["EVENT"]["CONTEXTS"]["value"]):
c3d_writer.add_event(event_time, event_context, event_label)
c3d_writer.write("test.c3d")
new_c3d = ezc3d.c3d("test.c3d")
print(new_c3d["header"]["points"])
print(new_c3d["header"]["analogs"])
The header is fully filled, except for the PS, its fine if you answer in different comments! I appreciate your expertise. |
Hi again!
Can I see the parameter "ROTATION:RATIO" you set for the rotations? It may seem counter intuitive, but the number of frames for the rotations (as well as the analogs) is based on the number of points (even though there are no points in the C3D). The computation is:
where frameRate is the point frame rate, ratio should probably be 1 in your case and last frame is the last frame of the points. The reason for this confusing way of computing the last frame is that historically C3D was designed for points with other data). |
As of now, it seems that it is indeed not possible to do that from the Python interface! I'll have to add a setter... sorry about that! EDIT: If you want a dirty workaround for the short term, you can tap into the core of the c3d by accessing the |
RE-EDIT: |
Certainly: >>> c3d_rotations["header"]["points"]
{'size': 0, 'frame_rate': 85.0, 'first_frame': 0, 'last_frame': 339}
>>> c3d_rotations["parameters"]["ROTATION"]["RATIO"]
{'type': 2, 'description': '', 'is_locked': False, 'value': array([1])} Assuming that the
|
I understand your line if thought, and the previous solution for us was to just change the # previous code ...
c3d_writer["header"]["points"]["first_frame"] = 152
c3d_writer["header"]["analogs"]["first_frame"] = 3040
print(c3d_writer["header"]["points"])
print(c3d_writer["header"]["analogs"])
c3d_writer.write("test.c3d")
new_c3d = ezc3d.c3d("test.c3d")
print(new_c3d["header"]["points"])
print(new_c3d["header"]["analogs"])
As you can see, the last_frame is automatically updated an now the first frame is kept in place. This may not be the right way to do it officially, but it does work for the |
Nah, that is just me being dumb. Obviously the computation should not be Fixed in #317 ! |
I think it makes sense to have a standard output, regardless of the internal data (that is returning a structure with "rotations" set to 0 instead of skipping it). I've added it in the same PR. I also added easy accessors to the dictionary keys, so now, for instance, header can be accessed using: import ezc3d
c = ezc3d.c3d("path")
print(c.header) # Equivalent to c["header"]
print(c.parameters.POINT.USED) # Equivalent to c["parameters"]["POINT"]["USED"]
print(c.data.points) # Equivalent to c["data"]["points"] |
That's a great update @pariterre ! Thanks for the quick and clear replies and solves! |
Please let me know if it fixes your issues! |
I think all issues are fixed:
Thanks again! |
Hi,
I am using python 3.12 and ezc3d 1.5.9 on a macOS system. I was working on writing c3d files using this great package and was thrilled to see that you added support for c3d files with rotation matrices last year. Reading in the files is really easy and works as expected.
However, when writing c3d files with rotations I noticed some unexpected behaviour (which might be because I am not fully aware of the proper usecase of the package). To exemplify I used this example file from the c-motion site. When reading in the data, everything works as expected:
When writing files, it seems like the "rotations" are not expected:
As you can see, the
"rotations"
is not pre-added to the data and header objects, whereas all other possibilities are. For the data that is not a big problem since you can just add a new key, however, that is not allowed for the headers. Is there a workaround for this?ADDED LATER:
I think I may have stumbled on another unexpected behaviour when writing rotations. When reading rotation matrixes where there are missing values, there are no real problems:
If I now want to write that same rotation matrix, and read it again, I get the following:
As you can see, after writing and reading the full matrix is filled with
nan
s. Although it is not a huge problem in analysis, since you can not read the transformations from the initial transoframtion matrices either. I do think it is unexpected behaviour. In my situation, it creates problems when writing tests for the kineticstoolkit where I want to test if we correctly parse the writing and reading ofezc3d
commands.The text was updated successfully, but these errors were encountered: