Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
halleysfifthinc authored Aug 14, 2024
1 parent 07439a8 commit 46e15b1
Showing 1 changed file with 43 additions and 25 deletions.
68 changes: 43 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
[![codecov](https://codecov.io/gh/halleysfifthinc/C3D.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/halleysfifthinc/C3D.jl)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)

C3D is a common file format for motion capture and other biomechanics related measurement systems (force plate data, EMG, etc). The goal of this package is to completely implement the [C3D file spec](https://www.c3d.org), and be compatible with files from major C3D producing programs (Vicon Nexus, etc.) where they might differ from or extend the C3D file spec.
C3D is a common file format for motion capture and other biomechanics related measurement systems (force plate data, EMG, etc). This package completely implements the [C3D file spec](https://www.c3d.org), and can read files from all major manufacturers where they might differ from or extend the C3D file spec.

Current test data is gathered from sample data found on the [C3D website](https://www.c3d.org/sampledata.html).
Pull requests welcome! Please open an issue if you have a file that is not being read correctly.
C3D.jl is exhaustively tested against sample data found on the [C3D website](https://www.c3d.org/sampledata.html) and can read many technically out-of-spec files.
Please open an issue if you have a file that is not being read correctly. Pull requests welcome!

## Usage

Expand All @@ -22,7 +22,7 @@ julia> # The artifacts with the test data can only be used from the `C3D.jl` dir

julia> pc_real = readc3d(artifact"sample01/Eb015pr.c3d")
C3DFile("~/.julia/artifacts/318c299a26ba07c015fa86768512b677fbb7e64c/Eb015pr.c3d")
0:9+0 frames
Duration: 9 s
26 points @ 50 Hz; 16 analog channels @ 200 Hz

julia> pc_real.point["LTH1"]
Expand All @@ -46,14 +46,31 @@ julia> pc_real.analog["FZ1"]
-22.32
```

### Writing data

Write a C3D file using the `writec3d` function. The groups and parameters of a .c3d file describe the data contained by the file. As of v0.8, there are no C3D.jl functions that coordinate modifying a `C3DFile` object, therefore, it is your responsibility to ensure that any modifications (adding/removing a marker or analog channel, etc) produce a internally-consistent (i.e. groups/parameters have been correctly updated to match the modified data, etc) file before writing.

```julia
julia> writec3d("myfile.c3d", pc_real)
307200 # number of bytes written
```

Writing c3d files is exhaustively tested against the corpus of sample data from the C3D.org website, and `writec3d` is tested to ensure that all files that are written are functionally[^1] and/or bitwise identical to the original at the binary file level in the vast majority[^2] of cases, and in all cases, the groups, parameters, and data for a `C3DFile` that was "copied" with `writec3d` will be exactly identical to the groups, parameters, and data from the original `C3DFile`.

[^1]: Many manufacturers include unnecessary trailing whitespace in string parameters. C3D.jl strips trailing whitespace when reading .c3d files; this results in slightly different (smaller) parameters when written to file, but the parameter data is otherwise the same.

[^2]: There are only two situations in which the binary data in the file will differ from the original file:
1. Some manufacturers write residuals as unsigned integers; this is incorrect according to the file-spec and C3D.jl follows the spec when writing the residuals back to file. However, the actual residual data is unchanged.
2. Limitations of [floating-point arithmetic](https://en.wikipedia.org/wiki/Floating-point_arithmetic) mean that some analog samples may not convert exactly back after un-scaling (i.e. slightly different in the file), but the scaled values are exactly identical.

#### Point residuals, invalid and calculated points

According to the C3D format documentation, invalid data points are signified by setting the residual word to `-1.0`. This convention is respected in C3D.jl by changing the residual and coordinates of invalid points/frames to `missing`. If your C3D files do not respect this convention, or if you wish to ignore this for some other reason, this behavior can be disabled by setting keyword arg `missingpoints=false` in the `readc3d` function. Convention is to signify calculated points (e.g. filtered, interpolated, etc) by setting the residual word to `0.0`.
According to the C3D format documentation, invalid data points are signified by setting the residual word to `-1.0`. This convention is respected in C3D.jl by changing the residual and coordinates of invalid points/frames to `missing`. If your C3D files do not respect this convention, or if you wish to ignore this for some other reason, this behavior can be disabled by setting keyword arg `missingpoints=false` in the `readc3d` function. Convention is to signify calculated points (e.g. filtered, interpolated, etc) by setting the residual value to `0.0`.

```julia
julia> bball = readc3d(artifact"sample16/basketball.c3d")
C3DFile("~/.julia/artifacts/042cc43a45ace35e97473c6cf0d08e25f1c73fcb/basketball.c3d")
0:1+9 frames
Duration: 1+09 s+ff
22 points @ 25 Hz

julia> bball.point["2003"]
Expand Down Expand Up @@ -81,13 +98,13 @@ Point residuals can be accessed using the `residual` field which is indexed by m
```julia
julia> pc_real.residual["RFT2"]
450-element Array{Union{Missing, Float32},1}:
10.333334f0
10.333334f0
9.666667f0
2.0833335f0
2.3333335f0
1.6666667f0
2.0f0
2.0f0
2.0f0
0.6666667f0
1.4166667f0
0.5833334f0
```

### Accessing C3D parameters
Expand All @@ -104,7 +121,17 @@ Dict{Symbol,C3D.Group} with 5 entries:
:FPLOC => Symbol[:INT, :OBJ, :MAX]

julia> pc_real.groups[:POINT]
Symbol[:DESCRIPTIONS, :RATE, :DATA_START, :FRAMES, :USED, :UNITS, :Y_SCREEN, :LABELS, :X_SCREEN, :SCALE]
Group(:POINT), "3-D point parameters"
POINT:DESCRIPTIONS::String @ (20,) ["DIST/LAT FOOT", "INSTEP", "PROX LAT FOOT", "SHANK", "SHANK", "SHANK", "SHANK", "ANKLE", "KNEE", "DISTAL FOOT", "*", "*", "*", "*", "*", "*", "*", "*", "*", "TARGET"]
POINT:X_SCREEN::String ["+Y"]
POINT:Y_SCREEN::String ["+Z"]
POINT:LABELS::String @ (48,) ["RFT1", "RFT2", "RFT3", "LFT1", "LFT2", "LFT3", "RSK1", "RSK2", "RSK3", "RSK4" "", "", "", "", "", "", "", "", "", ""]
POINT:UNITS::String ["mm"]
POINT:USED::UInt16 26
POINT:FRAMES::UInt16 450
POINT:SCALE::Float32 -0.0833333
POINT:DATA_START::UInt16 11
POINT:RATE::Float32 50.0
```

Parameter values can be accessed like this:
Expand All @@ -123,16 +150,15 @@ julia> pc_real.groups[:POINT][:LABELS]
""
""

julia> # Or, if you know the type (and you need the type-stability)

# Or, if you know the type (and you need the type-stability)
julia> pc_real.groups[:POINT][Int, :USED]
26

```

# Advanced: Debugging

There are two main steps to reading a C3D file: reading the parameters, and reading the point and/or analog data. In the event a file read fails, the stacktrace will show whether the error happened in `_readparams` or `readdata`. If the error occurred in `readdata`, try only reading the parameters, optionally setting the keyword argument `validate` to `false`:
Set the `JULIA_DEBUG` environment variable to `"C3D"` (e.g. from within Julia, `ENV["JULIA_DEBUG"] = "C3D"`) to enable debug logging. In addition, there are two keyword arguments to `readc3d` which may be useful if a file is error'ing when being read: `paramsonly=true` will only read the parameter section and skip reading the data, and `validate=false` will disable parameter validation.

```julia
julia> pc_real = readc3d("data/sample01/Eb015pr.c3d"; paramsonly=true)
Expand All @@ -152,12 +178,4 @@ Dict{Symbol,C3D.Group} with 5 entries:
:FPLOC => Symbol[:INT, :OBJ, :MAX]
```

If the error occurred in `readdata`, it is likely that there is an incorrect setting in one of the parameters. (If this is consistent among several files from the same vendor, open an issue and send an example file so I can fix whatever is causing the problem.)

If the error occurred in `_readparams`, try starting julia with `$ JULIA_DEBUG=C3D julia`. This will enable debug messages that may help narrow down the parameter causing the problem.

Please open an issue if you have a file that is being read incorrectly.

## Roadmap

I plan to eventually add support for saving files that have been modified and for creating new files, but this is not a use case that I require currently or in the foreseeable future. If this is important to you, open an issue or submit a PR!
Please open an issue if you have a file that C3D.jl is unable to read.

0 comments on commit 46e15b1

Please sign in to comment.