5 Simulation Control

5.5 Working with TRC data

The TRC data format was originally developed by Motion Analysis Corporation to describe marker position data acquired during motion capture. An overview of the format is provided as part of the OpenSim documentation.

ArtiSynth supplies a simple reader and writer for TRC data, with the reader able to create probes directly from TRC files and the writer able to write files directly from probes.

It is assumed the TRC data is tab delimited; files that do not use tabs to separate their data will not be readable. There may also be other limitations of which we are unaware.

5.5.1 TRC reader

TRCReader allows one to read TRC data from a file using the simple code fragment

import artisynth.core.probes.TRCReader;
...
  File trcFile;
  ...
  TRCReader reader = new TRCReader (trcFile);
  reader.readData();

A constructor also exists that accepts a string file name argument.

Both the constructors and the readData() method throw an IOException, which must either be handled in a try/catch block or thrown by the calling method.

Once the data has been read, it may be queried with the following methods:

int numFrames()

Return the number of frames.

double getFrameTime (int fidx)

Return time of the fidx-th frame.

int numMarkers()

Return the number of markers.

ArrayList<String> getMarkerLabels()

Return all the marker labels.

int getMarkerIndex (String label)

Return index of the labeled marker.

ArrayList<Point3d> getMarkerPositions (int fidx)

Return all marker positions at frame fidx.

void getMarkerPositions (VectorNd mpos, int fidx)

Return all marker positions at frame fidx into mpos.

Point3d getMarkerPosition (int fidx, int midx)

Return position of midx-th marker at frame fidx.

Point3d getMarkerPosition (int fidx, String label)

Return position of labeled marker at frame fidx.

Both the frame and marker indices in the above methods are 0-based.

TRCReader also supplies the following methods for creating PositionInputProbes directly from the data:

PositionInputProbe createInputProbe (String name, Collection<? extends Point> points, Mboolean useTargetProps, double startTime, double stopTime)
PositionInputProbe createInputProbeUsingLabels (String name, Collection<? extends Point> points, MList<String> labels, boolean useTargetProps, double startTime, double stopTime)

For these, name is the probe name (optional, can be null), points are the points to which the probe should be attached, useTargetProps indicates if the targetPosition or position properties should be bound, startTime and stopTime give the start and stop times, and labels, if present, gives a list of the marker labels that should be used for each point; otherwise, points are assigned to marker data in order.

As a convenience, the following static methods are also supplied:

5.5.2 TRC writer

TRCWriter allows an application to write a TRC file directly from the data in a numeric probe, using a code fragment such as

import artisynth.core.probes.TRCWriter;
...
  File trcFile;
  NumericProbeBase probe;
  ...
  TRCWriter writer = new TRCWriter (trcFile);
  reader.writeData (probe, /*labels*/null);

A constructor also exists that accepts a string file name argument.

The full signature for writeData() is

void writeData (NumericProbeBase probe, List<String> labels)

where probe is any type of probe associated with position or targetPosition properties for a set of Point components. The labels argument is an optional list of marker labels that should be assigned to the data for each point. If this is not supplied, labels are assigned automatically, either from the point names, or when these are null, by prepending "mkr" to the marker index.

The writeData() method throws an IOException, which must either be handled in a try/catch block or thrown by the calling method.

TRCWriter also supplies a few methods that can be used to format the data before it is written:

String getNumberFormat()

Return the format string for float data.

void setNumberFormat (String fmtStr)

Set the format string for float data.

String getUnitsString()

Return the string used to represent units.

void setUnitsString (String str)

Set the string used to represent units.

5.5.3 Example: moving MultiJointedArm with TRC data

Figure 5.12: TRCMultiJointedArm, overlaid with the tracking stiffness control panel. The tracking stiffness itself is set low enough that the springs (red) between the target points (cyan) and markers (white) are visible.

The example model

  artisynth.demos.tutorial.TRCMultiJointedArm

illustrates several features that have been introduced in this chapter. It extends the model MultiJointedArm, previously described in Section 3.5.16. Then it reads TRC data defining target trajectories for the markers attached to the arm, with the intention of moving the arm. While a standard way to make marker data move an articulated structure is to employ an inverse kinematic solver (Section 10.5), this example uses a simpler solution. For each marker, a target point is defined and attached to it by means of a spring. The target points (which are non-dynamic) are then moved by means of a PositionInputProbe created from the TRC data. As they move, they drag the markers (and the attached bodies) around by means of the resulting spring forces. It can be shown that the resulting rest position of the system is identical to that of the inverse kinematic solver of Section 10.5.1 (assuming equal marker weights).

Tracking accuracy increases with spring stiffness (although very high stiffnesses may cause stability issues). To illustrate this relationship, a control panel is created that allows the user to adjust the spring stiffness interactively. To be able to control the stiffness of one spring with a single parameter, a custom model property, called trackingStiffness, is created per the description of Section 5.2, to control the stiffness values for all the springs.

The model’s class definition, excluding include directives, is given in

1 public class TRCMultiJointedArm extends MultiJointedArm {
2
3    public double myTrackingStiffness = 5000; // for tracking springs
4    public final double startTime = 0; // probe start time
5    public final double stopTime = 3;  // probe stop time
6
7    // define a ’trackingStiffness’ property for this model that manages stiffness
8    // for all tracking springs at once
9    public static PropertyList myProps =
10       new PropertyList (TRCMultiJointedArm.class, MultiJointedArm.class);
11
12    static {
13       myProps.add (
14          "trackingStiffness",
15          "stiffness used to track marker targets", 5000);
16    }
17
18    public PropertyList getAllPropertyInfo() {
19       return myProps;
20    }
21
22    // get() accessor for tracking stiffness property
23    public double getTrackingStiffness() {
24       return myTrackingStiffness;
25    }
26
27    // set() accessor for tracking stiffness property
28    public void setTrackingStiffness (double stiffness) {
29       myTrackingStiffness = stiffness;
30       for (AxialSpring spr : myMech.axialSprings()) {
31          AxialMaterial mat = spr.getMaterial();
32          if (mat instanceof LinearAxialMaterial) {
33             ((LinearAxialMaterial)mat).setStiffness (stiffness);
34          }
35       }
36    }
37
38    public void build (String[] args) throws IOException {
39       super.build(args); // create MultiJointArm
40
41       // For each marker, create a target point and attach it to the marker
42       // using a spring
43       for (FrameMarker mkr : myMech.frameMarkers()) {
44          Point point = new Point (/*name*/null, mkr.getPosition());
45          myMech.addPoint (point);
46          // create a spring between the target point and the marker
47          AxialSpring spr = new AxialSpring (
48             myTrackingStiffness, /*damping*/0, /*restLength*/0);
49          myMech.attachAxialSpring (point, mkr, spr);
50       }
51
52       // read a TRC file containing tracking data for all the markers.
53       File trcfile = new File(getSourceRelativePath ("data/multiJointMkrs.trc"));
54       TRCReader reader = new TRCReader (trcfile);
55       reader.readData();
56       // use this to create an input probe to drive the target postions
57       PositionInputProbe trcprobe = reader.createInputProbe (
58          "trc targets", myMech.points(),
59          /*useTargetProps*/true, startTime, stopTime);
60       addInputProbe (trcprobe);
61
62       // create a control panel to adjust the tracking stiffness
63       ControlPanel panel = new ControlPanel();
64       panel.addWidget (this, "trackingStiffness");
65       addControlPanel (panel);
66
67       // render properties: target points cyan, springs as red spindles
68       RenderProps.setPointColor (myMech.points(), Color.CYAN);
69       RenderProps.setSpindleLines (
70          myMech.axialSprings(), 0.01, Color.RED);
71    }
72
73    public void postscanInitialize() {
74       // sets ’myMech’ if model is read from a .art file
75       myMech = (MechModel)findComponent ("models/mech");
76    }
77 }

The first parts of the code define the trackingStiffness property, by first creating a local static property list (myProps) that inherits from the parent class MultiJointedArm.class (lines 9-10). A static block adds the property definition (lines 12-16), and the new list is made visible by a local implementation of getAllPropertyInfo() (lines 18-20). Get/set accessors are then defined for the property (lines 22-36). The property value is kept in the member variable myTrackingStiffness, but when it is set the stiffness is updated for all springs stored in the MechModel’s axial spring list.

The build() method starts by calling super.build() to create the original MultiJointedArm model (line 39), after which a reference to the created MechModel will be stored in the inherited member myMech. Then for each model marker, a target point is created, together with a connecting axial spring (lines 41-50). For simplicity, the targets and springs are stored in the MechModel’s default points and axialSprings lists, but they could be stored in custom containers, as per Section 4.8.

Next, TRC marker data is read from the file "data/multiJointMkrs.trc" located relative to the model’s source folder, using the TRCReader described in Section 5.5.1. This is used to create a PositionInputProbe attached to the target points (which are located under myMech.points()) (lines 57-60). Lastly, a control panel is created to allow adjustment of the tracking stiffness, and some render properties are set.

The last part of the class definition overrides the method postscanInitialize(), which is called in place of the build() method when a model is read from an ArtiSynth .art file. This method should initialize any class members which are unknown to the component hierarchy and would normally be set in the build() method. In this case, it is necessary to initialize myMech, which is needed by setTrackingStiffness() and would otherwise not be set. Models which are not intended to be saved to .art files do not need to override postscanInitialize().

To run this example in ArtiSynth, select All demos > tutorial > TRCMultiJointedArm from the Models menu. The demo will appear, together with the control panel (shown overlaid in Figure 5.12). When the model is run, the target points will be moved under the control of their position probe, dragging the markers along via the attached springs. Increasing/decreasing the spring stiffness will decrease/increase the tracking error.