8.1. Reading C3D files#
Let’s first download a sample C3D file:
import kineticstoolkit.lab as ktk
filename = ktk.doc.download("c3d_sample.c3d")
We read it using ktk.read_c3d:
c3d_contents = ktk.read_c3d(filename)
c3d_contents
UserWarning [/Users/felix/Documents/git/kineticstoolkit_doc/src/kineticstoolkit/files.py:555] In the specified file, points are expressed in mm. They have been automatically converted to meters (scaled by 0.001). Please note that if this file also contains calculated values such as angles, powers, etc., they have been also (wrongly) scaled by 0.001. Consult https://kineticstoolkit.uqam.ca/doc/api/ktk.read_c3d.html for more information. You can mute this warning by explicitely setting `convert_point_unit` to either True or False.
{
'Points': TimeSeries with attributes: time: <array of shape (675...
'Analogs': TimeSeries with attributes: time: array([0. , 0.001, 0...
'ForcePlatforms': TimeSeries with attributes: time: array([0. , 0.001, 0...
'C3DParameters': <dict with 7 entries>
}
Caution
As indicated in the warning above, the points in this file are expressed in millimetres; however, Kinetics Toolkit uses SI units and therefore metres by default. The point positions have been converted to metres by default, and therefore you can ignore this warning, or suppress it altogether using:
c3d_contents = ktk.read_c3d(filename, convert_point_unit=True)
You can generally ignore this warning if you read unprocessed marker positions and analog signals.
You should not ignore this warning if you are reading a C3D file that has been processed by some software or pipeline to calculate joint kinetics, powers, etc., and if these values are expressed as “points” in the C3D. Consult the help of ktk.read_c3d for more details.
The content of the file is expressed as a dictionary with two keys:
Points: A TimeSeries that contains the point data (markers)Analogs(only when applicable): A TimeSeries that contains the raw analog data from force platforms, EMG, or other analog signals recorded into the C3D file.ForcePlatforms(only when applicable): A TimeSeries that contains the forces, moments, centres of pressure, and force platform positions.
8.1.1. Points#
Each data key of the Points TimeSeries corresponds to one point (marker):
c3d_contents["Points"].data
{
'c7': <array of shape (675, 4)>
'r should': <array of shape (675, 4)>
'l should': <array of shape (675, 4)>
'sacrum': <array of shape (675, 4)>
'r asis': <array of shape (675, 4)>
'r bar 1': <array of shape (675, 4)>
'r knee 1': <array of shape (675, 4)>
'RATT': <array of shape (675, 4)>
'SCR1': <array of shape (675, 4)>
'SCR2': <array of shape (675, 4)>
'r bar 2': <array of shape (675, 4)>
'r heel': <array of shape (675, 4)>
'r met': <array of shape (675, 4)>
'l asis': <array of shape (675, 4)>
'l bar 1': <array of shape (675, 4)>
'l knee 1': <array of shape (675, 4)>
'LATT': <array of shape (675, 4)>
'SCL1': <array of shape (675, 4)>
'SCL2': <array of shape (675, 4)>
'l bar 2': <array of shape (675, 4)>
'l heel': <array of shape (675, 4)>
'l met': <array of shape (675, 4)>
}
We will learn how to visualize these markers using an interactive player in Visualizing 3D points, frames, and forces. For now, let’s plot some of them:
c3d_contents["Points"].plot(["c7", "r should"])
8.1.2. Analogs#
Each data key of the Analogs TimeSeries corresponds to one analog signal:
c3d_contents["Analogs"].data
{
'Fx1': array([0., 0., 0., ..., 0., 0., 0.])
'Fy1': array([0., 0., 0., ..., 0., 0., 0.])
'Fz1': array([0., 0., 0., ..., 0., 0., 0.])
'Mx1': array([0., 0., 0., ..., 0., 0., 0.])
'My1': array([0., 0., 0., ..., 0., 0., 0.])
'Mz1': array([0., 0., 0., ..., 0., 0., 0.])
'Fx2': array([0., 0., 0., ..., 0., 0., 0.])
'Fy2': array([0., 0., 0., ..., 0., 0., 0.])
'Fz2': array([0., 0., 0., ..., 0., 0., 0.])
'Mx2': array([0., 0., 0., ..., 0., 0., 0.])
'My2': array([0., 0., 0., ..., 0., 0., 0.])
'Mz2': array([0., 0., 0., ..., 0., 0., 0.])
'Fx3': array([0., 0., 0., ..., 0., 0., 0.])
'Fy3': array([0., 0., 0., ..., 0., 0., 0.])
'Fz3': array([0., 0., 0., ..., 0., 0., 0.])
'Mx3': array([0., 0., 0., ..., 0., 0., 0.])
'My3': array([0., 0., 0., ..., 0., 0., 0.])
'Mz3': array([0., 0., 0., ..., 0., 0., 0.])
'Fx4': array([0., 0., 0., ..., 0., 0., 0.])
'Fy4': array([0., 0., 0., ..., 0., 0., 0.])
'Fz4': array([0., 0., 0., ..., 0., 0., 0.])
'Mx4': array([0., 0., 0., ..., 0., 0., 0.])
'My4': array([0., 0., 0., ..., 0., 0., 0.])
'Mz4': array([0., 0., 0., ..., 0., 0., 0.])
'Fx5': array([0., 0., 0., ..., 0., 0., 0.])
'Fy5': array([0., 0., 0., ..., 0., 0., 0.])
'Fz5': array([0., 0., 0., ..., 0., 0., 0.])
'Mx5': array([0., 0., 0., ..., 0., 0., 0.])
'My5': array([0., 0., 0., ..., 0., 0., 0.])
'Mz5': array([0., 0., 0., ..., 0., 0., 0.])
'Fx6': array([0., 0., 0., ..., 0., 0., 0.])
'Fy6': array([0., 0., 0., ..., 0., 0., 0.])
'Fz6': array([0., 0., 0., ..., 0., 0., 0.])
'Mx6': array([0., 0., 0., ..., 0., 0., 0.])
'My6': array([0., 0., 0., ..., 0., 0., 0.])
'Mz6': array([0., 0., 0., ..., 0., 0., 0.])
'Left Rectus femoris': <array of shape (6750,)>
'Right Rectus femoris': <array of shape (6750,)>
'Left Semimembranosus': <array of shape (6750,)>
'Right Semimembranosus': <array of shape (6750,)>
'Left Tibialis anterior': <array of shape (6750,)>
'Right Tibialis anterior': <array of shape (6750,)>
'Left Gastrocnemius medialis': <array of shape (6750,)>
'Right Gastrocnemius medialis': <array of shape (6750,)>
}
Let’s plot some of them:
c3d_contents["Analogs"].plot(["Left Rectus femoris", "Left Semimembranosus"])
8.1.3. Force platforms#
The ForcePlatforms TimeSeries contains information regarding all force platforms in the file. In this particular file, there are 6 force platforms, represented as FP0 to FP5:
The position of the force platforms is given by
FPx_Corner1toFPx_Corner4.Their local coordinate systems are given by the
FPx_LCStransform series.Ground reaction forces are given by
Forces.Moments are reported both around the force platform geometrical centre (
FPx_MomentAtCentre) and at the point of pressure (FPx_MomentAtCOP).The centre of pressure is reported in
FPx_COP.
c3d_contents["ForcePlatforms"].data
{
'FP0_Corner1': <array of shape (6750, 4)>
'FP0_Corner2': <array of shape (6750, 4)>
'FP0_Corner3': <array of shape (6750, 4)>
'FP0_Corner4': <array of shape (6750, 4)>
'FP0_LCS': <array of shape (6750, 4, 4)>
'FP0_Force': <array of shape (6750, 4)>
'FP0_MomentAtCenter': <array of shape (6750, 4)>
'FP0_COP': <array of shape (6750, 4)>
'FP0_MomentAtCOP': <array of shape (6750, 4)>
'FP1_Corner1': <array of shape (6750, 4)>
'FP1_Corner2': <array of shape (6750, 4)>
'FP1_Corner3': <array of shape (6750, 4)>
'FP1_Corner4': <array of shape (6750, 4)>
'FP1_LCS': <array of shape (6750, 4, 4)>
'FP1_Force': <array of shape (6750, 4)>
'FP1_MomentAtCenter': <array of shape (6750, 4)>
'FP1_COP': <array of shape (6750, 4)>
'FP1_MomentAtCOP': <array of shape (6750, 4)>
'FP2_Corner1': <array of shape (6750, 4)>
'FP2_Corner2': <array of shape (6750, 4)>
'FP2_Corner3': <array of shape (6750, 4)>
'FP2_Corner4': <array of shape (6750, 4)>
'FP2_LCS': <array of shape (6750, 4, 4)>
'FP2_Force': <array of shape (6750, 4)>
'FP2_MomentAtCenter': <array of shape (6750, 4)>
'FP2_COP': <array of shape (6750, 4)>
'FP2_MomentAtCOP': <array of shape (6750, 4)>
'FP3_Corner1': <array of shape (6750, 4)>
'FP3_Corner2': <array of shape (6750, 4)>
'FP3_Corner3': <array of shape (6750, 4)>
'FP3_Corner4': <array of shape (6750, 4)>
'FP3_LCS': <array of shape (6750, 4, 4)>
'FP3_Force': <array of shape (6750, 4)>
'FP3_MomentAtCenter': <array of shape (6750, 4)>
'FP3_COP': <array of shape (6750, 4)>
'FP3_MomentAtCOP': <array of shape (6750, 4)>
'FP4_Corner1': <array of shape (6750, 4)>
'FP4_Corner2': <array of shape (6750, 4)>
'FP4_Corner3': <array of shape (6750, 4)>
'FP4_Corner4': <array of shape (6750, 4)>
'FP4_LCS': <array of shape (6750, 4, 4)>
'FP4_Force': <array of shape (6750, 4)>
'FP4_MomentAtCenter': <array of shape (6750, 4)>
'FP4_COP': <array of shape (6750, 4)>
'FP4_MomentAtCOP': <array of shape (6750, 4)>
'FP5_Corner1': <array of shape (6750, 4)>
'FP5_Corner2': <array of shape (6750, 4)>
'FP5_Corner3': <array of shape (6750, 4)>
'FP5_Corner4': <array of shape (6750, 4)>
'FP5_LCS': <array of shape (6750, 4, 4)>
'FP5_Force': <array of shape (6750, 4)>
'FP5_MomentAtCenter': <array of shape (6750, 4)>
'FP5_COP': <array of shape (6750, 4)>
'FP5_MomentAtCOP': <array of shape (6750, 4)>
}
Let’s plot the COP on force plates 0:
c3d_contents["ForcePlatforms"].plot('FP0_COP')
8.1.4. Quick visualization#
Although visualizing data using the interactive 3D Player is explained in section Visualizing 3D points, frames, and forces, here is how we can quickly visualize the kinematic and kinetic data in this file. Note that every TimeSeries provided to the Player must have the same sampling rate, which means we need to downsample the force platform data to the points sampling rate:
ktk.Player(
c3d_contents["Points"],
c3d_contents["ForcePlatforms"].resample(c3d_contents["Points"].time)
)