C3D aspects

Software for reading C3D files

We wrote C++ software for reading and, to some extent, writing C3D files. This software is available for use in your projects. The software is documented and some example files are available to get you started. Also, we provide the code that was used for processing the data. The code was compiled both under g++ on linux and under Visual Studio 2008. The code uses shared_ptr and scoped_ptr from the boost library.

The library and some example programs can be found in a zip file. The documentation is available on this site. In order to use the software, some tweaking of the Makefiles or Visual Studio projects will be necessary. Those Makefiles and projects are simple, because there are few dependencies, so we expect that it is not too hard to adapt this.

Below is an example of a file that uses the library to classify the points in a C3D file.

#include "uuc3d.hpp"
#include <vector>

void do_work(std::string filename)
{
    using namespace UuIcsC3d;
    using std::vector;
    // Open the file and read the header information
    // Will throw OpenError if reading is not possible.
    C3dFileInfo fi(filename);
    // Every frame contains the same number of points.
    int ppf = fi.points_per_frame();
    if (ppf <= 0)
        return;
    int fc = fi.frame_count();
    // Get the labels. Not every point needs to have a label name.
    // We add dummy labels if necessary to supply a name for every point.
    vector<SpacePaddedString> labels(fi.point_labels());
    for (int i=labels.size(); i<ppf; ++i) {
        labels.push_back(SpacePaddedString("<no label>"));
    }
    // Declare the vectors that maintain the count for each point (label).
    vector<int> invalid(ppf, 0), generated(ppf, 0), measured(ppf, 0);
    // Open the C3D file
    std::auto_ptr<C3dFile> filep(fi.open());
    // Read every frame in variable frame_data and process it
    FrameData frame_data;
    for (int i=0; i<fc; ++i) {
        filep->get_frame_data(frame_data, i);
        // For every point in the frame record its type
        for (int j=0; j<ppf; ++j) {
            DataPoint3d const &dp=frame_data.points[j];
            switch (dp.status()) {
            case DataPoint3d::Invalid:
                ++invalid[j];
                break;
            case DataPoint3d::Generated:
                ++generated[j];
                break;
            case DataPoint3d::Measured:
                ++measured[j];
                break;
            }
        }
    }
    // report the counts for every label
    for (int i=0; i<ppf; ++i) {
        std::cout<<measured[i]<<"\t"<<generated[i]<<"\t"<<invalid[i]
        <<"\t"<<labels[i].stripped()<<"\n";
    }
}
	    

Interpretation and extension of the specification

The Vicon Nexus software uses the floating point format, where the coordinates of a point are written as three floating point values. Those values are followed by four octets (two words). On page 58 of he specification we those two words are described:

After converting to a signed integer: Byte 1: cameras that measured the marker; Byte 2: average residual divided by the POINT:SCALE factor.

The Nexus software interprets this such that octets 0 and 1 are not used, octet 2 contains the residual and octet 3 contains the camera mask. At least, that is what we understood from looking at the Vicon files; Vicon did not comment on this. Our software follows this interpretation. We use the two unused octets for our own purposes. We fill in the value 0 here if the value was found by the Vicon system. A different value (we use 1 or 5) indicates that the marker was not spotted by the system but added, e.g., by interpolating marker positions. The following C++ code fragment illustrates how we read and process this part. 'flags' is the part we use for our own purposes.

                unsigned char buf[4];
                fread(buf, 1, 4, filep)
                dp.flags = (static_cast<unsigned>(buf[0])<<8) + buf[1];
                dp.camera_mask = buf[3] & 0x7f;
                if (buf[3]&0x80) {
                    /* invalid point */
                    dp.type = DataPoint3d::Invalid;
                    dp.residual = -1.0;
                } else {
                    dp.residual = buf[2]*sc;
                    dp.type = (dp.flags==0)? DataPoint3d::Measured : DataPoint3d::Generated;
                }