The typical file format for experimental motion capture data in the biomechanics community is C3D. C3D is a flexible format that can store marker, force plate, EMG, and event data. Despite being very flexible, data in C3D files are stored in binary format and require specialized readers to access. To read more information on the C3D file format, we encourage you to visit C3D.org to learn more. 

Starting with OpenSim 4.0, C3D reading and conversion into OpenSim formats is available. Currently, use of C3D reading is limited to C++ and scripting and we will be working to support direct C3D reading in the GUI in future releases. The easiest way to use OpenSim to read your C3D data is through the Matlab interface. Once you have setup OpenSim use in Matlab, you can read C3D files and write marker and force data to .trc and .mot file formats, easily. 


C3DFileAdapter only supports reading Type-2 Force plates (AMTI & Bertec). If you have a Type-3 (Kistler), or any other type, please consider donating your C3D files of forceplate data to the OpenSim project. To contribute C3D files, please email opensim@stanford.edu.

Reading C3D files through Matlab

osimC3D

We have included a utility in Matlab that can be used to perform some common operations, such as rotating data, converting data into Matlab data types, and writing marker and force data to OpenSim file formats. The Matlab file can be found in your resources directory /Code/Matlab/Utilities/osimC3D.m. An example of using the osimC3D function is in the expandable section below. In this example, we

  • read a C3D file containing markers and forces, 
  • get some information about the data (rate, number of markers, and number of forces), 
  • rotate the data, and 
  • then write the markers to a .trc file, and the forces to a .mot file. 

 Click here to expand...

% ----------------------------------------------------------------------- %
% The OpenSim API is a toolkit for musculoskeletal modeling and           %
% simulation. See http://opensim.stanford.edu and the NOTICE file         %
% for more information. OpenSim is developed at Stanford University       %
% and supported by the US National Institutes of Health (U54 GM072970,    %
% R24 HD065690) and by DARPA through the Warrior Web program.             %
%                                                                         %
% Copyright (c) 2005-2018 Stanford University and the Authors             %
% Author(s): James Dunne                                                  %
%                                                                         %
% Licensed under the Apache License, Version 2.0 (the "License");         %
% you may not use this file except in compliance with the License.        %
% You may obtain a copy of the License at                                 %
% http://www.apache.org/licenses/LICENSE-2.0.                             %
%                                                                         %
% Unless required by applicable law or agreed to in writing, software     %
% distributed under the License is distributed on an "AS IS" BASIS,       %
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or         %
% implied. See the License for the specific language governing            %
% permissions and limitations under the License.                          %
% ----------------------------------------------------------------------- %

%% Example of using the Matlab-OpenSim class 

%% Load OpenSim libs
import org.opensim.modeling.*

%% Get the path to a C3D file
[filename, path] = uigetfile('*.c3d');
c3dpath = fullfile(path,filename);

%% Construct an opensimC3D object with input c3d path
% Constructor takes full path to c3d file and an integer for forceplate
% representation (1 = COP). 
c3d = osimC3D(c3dpath,1);

%% Get some stats...
% Get the number of marker trajectories
nTrajectories = c3d.getNumTrajectories();
% Get the marker data rate
rMakers = c3d.getRate_marker();
% Get the number of forces 
nForces = c3d.getNumForces();
% Get the force data rate
rForces = c3d.getRate_force();

% Get Start and end time
t0 = c3d.getStartTime();
tn = c3d.getEndTime();

%% Rotate the data 
c3d.rotateData('x',-90)

%% Get the c3d in different forms
% Get OpenSim tables
markerTable = c3d.getTable_markers();
forceTable = c3d.getTable_forces();
% Get as Matlab Structures
[markerStruct forceStruct] = c3d.getAsStructs();

%% Convert COP (mm to m) and Moments (Nmm to Nm)
c3d.convertMillimeters2Meters();

%% Write the marker and force data to file

% Write marker data to trc file.
% c3d.writeTRC()                       Write to dir of input c3d.
% c3d.writeTRC('Walking.trc')          Write to dir of input c3d with defined file name.
% c3d.writeTRC('C:/data/Walking.trc')  Write to defined path input path.
c3d.writeTRC('test_data_markers.trc');

% Write force data to mot file.
% c3d.writeMOT()                       Write to dir of input c3d.
% c3d.writeMOT('Walking.mot')          Write to dir of input c3d with defined file name.
% c3d.writeMOT('C:/data/Walking.mot')  Write to defined path input path.
% 
% This function assumes point and torque data are in mm and Nmm and
% converts them to m and Nm. If your C3D is already in M and Nm,
% comment out the internal function convertMillimeters2Meters()
c3d.writeMOT('test_data_forces.mot');

Reading C3D files through Python and C++

Some example code for using C3DFileAdapter through Python can be found below. This code is part of a test script that runs the C3D reader and writes data marker and force data to a storage (.sto) file. 

 Click here to expand...
tables = C3DFileAdapter.readFile(os.path.join(test_dir, 'walking2.c3d'), 1)
markers = tables['markers']
forces = tables['forces']
 
# Marker data read from C3D.
markers = tables['markers']
      
# Flatten marker data.
markersFlat = markers.flatten()
      
# Make sure flattenned marker data is writable/readable to/from file.
markersFilename = 'markers.sto'
stoAdapter = osim.STOFileAdapter()
stoAdapter.write(markersFlat, markersFilename)
markersDouble = stoAdapter.read(markersFilename)
      
# Forces data read from C3d.
forces = tables['forces']
fpCalMats = forces.getTableMetaDataVectorMatrix("CalibrationMatrices")
fpCorners = forces.getTableMetaDataVectorMatrix("Corners")
fpOrigins = forces.getTableMetaDataVectorMatrix("Origins")
      
# Flatten forces data.
forcesFlat = forces.flatten()
      
# Make sure flattenned forces data is writable/readable to/from file.
forcesFilename = 'forces.sto'
stoAdapter.write(forcesFlat, forcesFilename)
forcesDouble = stoAdapter.read(forcesFilename)
      
# Clean up.
os.remove(markersFilename)
os.remove(forcesFilename)


Example code for using the C3DFileAdapter in C++ is found below. This code is part of a test script that runs the C3D reader and checks for correct values. 

 Click here to expand...

/* -------------------------------------------------------------------------- *
 *                            OpenSim:  exampleC3DFileAdapter.cpp             *
 * -------------------------------------------------------------------------- *
 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
 * See http://opensim.stanford.edu and the NOTICE file for more information.  *
 * OpenSim is developed at Stanford University and supported by the US        *
 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
 * through the Warrior Web program.                                           *
 *                                                                            *
 * Copyright (c) 2005-2017 Stanford University and the Authors                *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
 * not use this file except in compliance with the License. You may obtain a  *
 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 * -------------------------------------------------------------------------- */

#include "OpenSim/Common/C3DFileAdapter.h"

#include "OpenSim/Common/TRCFileAdapter.h"

#include <vector>
#include <unordered_map>
#include <cstdlib>
#include <chrono>

int main() {
    using namespace OpenSim;

    std::string filename{"OpenSim/Examples/DataAdapter/singleLeglanding_2.c3d"};

    C3DFileAdapter c3d_adapter{};
    auto tables = c3d_adapter.read(filename);

    auto& marker_table = tables.at("markers");
    auto&  force_table = tables.at("forces");

    if(marker_table->getNumRows() != 0) {
        std::cout << "--------------Markers-----------------" << std::endl;

    std::cout << "Dim: " 
              << marker_table->getNumRows() << " "
              << marker_table->getNumColumns() 
              << std::endl;
    std::cout << "DataRate: " 
              << marker_table->getTableMetaData().
                               getValueForKey("DataRate").
                               getValue<std::string>()
              << std::endl;
    std::cout << "Units: " 
              << marker_table->getTableMetaData().
                               getValueForKey("Units").
                               getValue<std::string>()
              << std::endl << std::endl;
    std::cout << marker_table->getRow(0) << std::endl;

    auto& events_table = marker_table->getTableMetaData().
                                      getValueForKey("events").
                                 getValue<std::vector<OpenSim::Event>>();
    for(const auto& elem : events_table)
        std::cout << "label: " << elem.label << " | "
                  << "time: " << elem.time << " | "
                  << "frame: " << elem.frame << " | "
                  << "description: " << elem.description << "\n";

    auto& labels = marker_table->getDependentsMetaData().
                                 getValueArrayForKey("labels");
    for(size_t i = 0; i < labels.size(); ++i)
        std::cout << labels[i].getValue<std::string>() << " ";
    std::cout << "\n";
    }

    if(force_table->getNumRows() != 0) {
        std::cout << "--------------Forces-----------------" << std::endl;

    std::cout << "Dim: "
              << force_table->getNumRows() << " "
              << force_table->getNumColumns()
              << std::endl;
    std::cout << "DataRate: "
              << force_table->getTableMetaData().
                              getValueForKey("DataRate").
                              getValue<std::string>()
              << std::endl;
    std::cout << "CalibrationMatrices: \n";
    for(const auto& elem : force_table->getTableMetaData().
            getValueForKey("CalibrationMatrices").
            getValue<std::vector<SimTK::Matrix_<double>>>())
        std::cout << elem << std::endl;
    std::cout << "Corners: \n";
    for(const auto& elem : force_table->getTableMetaData().
            getValueForKey("Corners").
            getValue<std::vector<SimTK::Matrix_<double>>>())
        std::cout << elem << std::endl;
    std::cout << "Origins: \n";
    for(const auto& elem : force_table->getTableMetaData().
            getValueForKey("Origins").
            getValue<std::vector<SimTK::Matrix_<double>>>())
        std::cout << elem << std::endl;
    std::cout << "Types: \n";
    for(const auto& elem : force_table->getTableMetaData().
            getValueForKey("Types").
            getValue<std::vector<unsigned>>())
        std::cout << elem << std::endl;

    const auto& labels = force_table->getDependentsMetaData().
        getValueArrayForKey("labels");
    const auto& units = force_table->getDependentsMetaData().
        getValueArrayForKey("units");
    for(size_t i = 0; i < labels.size(); ++i)
        std::cout << "[ " << labels[i].getValue<std::string>() << " " 
                  << units[i].getValue<std::string>() << " ] ";
    std::cout << std::endl;
    std::cout << force_table->getRow(0) << std::endl;
    }


    return 0;
}


Importing Force Data:  COP and NaNs

When you import your force data, you can use the origin of the force plate or center of pressure (COP) to express the forces. The C3DFileAdapter includes an input parameter (ForceLocation) that allows you to choose OriginOfForcePlate or CenterOfPressure. Using the COP is helpful for visualization, but can lead to NaN values. In particular, when the force values go to zero, the COP value is undefined (NaN). This is problematic during Inverse Dynamics analysis as you will get all NaN values for the output moments. This is because OpenSim splines the force and COP data before computing joint moments and the splines are undefined in the presence of NaNs for your input data. If you find that your mot files are containing NaNs for COPs after conversion, you can use the OriginOfForcePlate flag when you import your C3D data