diff --git a/README.md b/README.md index 4cc0b6e..c788e82 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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"] @@ -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"] @@ -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 @@ -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: @@ -123,8 +150,7 @@ 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 @@ -132,7 +158,7 @@ julia> pc_real.groups[:POINT][Int, :USED] # 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) @@ -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.