Skip to content
tee-ar-exPublic

About

Rust library for the TRX format

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

65 Commits

Folders and files

Repository files navigation

trx-rs

A Rust library for reading, writing, and manipulating TRX brain tractography files, plus format-conversion helpers for common streamline formats.

TRX is a binary file format for storing streamline tractography data using memory-mapped arrays. It supports multiple coordinate precisions (f16, f32, f64), per-vertex data (DPV), per-streamline data (DPS), named groups, and data-per-group (DPG) arrays — all backed by zero-copy memory mapping for efficient access to large datasets.

Features

  • Zero-copy memory mapping via memmap2 for efficient access to multi-gigabyte tractography files
  • Multiple precision support — f16, f32, and f64 positions with runtime dtype detection
  • Full read/write support for both directory-based and ZIP archive (.trx) formats
  • Format conversion helpers for .tck, .tck.gz, .vtk, and .tt.gz -> .trx
  • Streaming construction — build TRX files incrementally with TrxStream
  • Ancillary data access — DPS (per-streamline), DPV (per-vertex), group membership arrays, and DPG metadata
  • Set operations — intersection, union, and difference on streamline sets
  • Connectivity matrices — compute group-to-group connectivity (count or weighted)
  • Subset and merge — extract streamlines by index or concatenate multiple files

Quick start

Add to your Cargo.toml:

[dependencies]
trx-rs = { git = "https://github.com/tee-ar-ex/trx-rs" }

Load and iterate streamlines

use trx_rs::TrxFile;

let trx = TrxFile::<f32>::load("tractogram.trx")?;

println!("{} streamlines, {} vertices", trx.nb_streamlines(), trx.nb_vertices());

for (i, streamline) in trx.streamlines().enumerate() {
    println!("Streamline {i}: {} points", streamline.len());
}

Runtime dtype detection

When the position dtype isn't known at compile time:

use trx_rs::AnyTrxFile;

let any = AnyTrxFile::load("tractogram.trx")?;

any.with_typed(
    |trx_f16| { /* work with TrxFile<f16> */ },
    |trx_f32| { /* work with TrxFile<f32> */ },
    |trx_f64| { /* work with TrxFile<f64> */ },
);

Access ancillary data

// Per-streamline scalar (e.g., SIFT2 weights)
let weights = trx.dps::<f32>("weights")?;
for row in weights.rows() {
    println!("weight: {}", row[0]);
}

// Per-vertex scalar (e.g., FA along streamline)
let fa = trx.dpv::<f32>("fa")?;

// Group membership (streamline indices)
let members: &[u32] = trx.group("corticospinal_tract")?;

// Data-per-group metadata (for example, RGB color stored on a group)
let color = trx.dpg::<u8>("corticospinal_tract", "color")?;
assert_eq!(color.shape(), (1, 3));

Build and save

use trx_rs::TrxStream;

let affine = [[1.0, 0.0, 0.0, 0.0],
              [0.0, 1.0, 0.0, 0.0],
              [0.0, 0.0, 1.0, 0.0],
              [0.0, 0.0, 0.0, 1.0]];
let dimensions = [128, 128, 80];

let mut stream = TrxStream::<f32>::new(affine, dimensions);
stream.push_streamline(&points);
let trx = stream.finalize();

trx.save("output.trx")?;           // ZIP archive
trx.save_to_directory("output/")?;  // directory format

Operations

use trx_rs::ops::merge::merge_trx_shards;
use trx_rs::ops::subset::subset_streamlines;
use trx_rs::ops::streamline_ops::{difference, intersection};

// Extract a subset of streamlines by index
let sub = subset_streamlines(&trx, &[0, 5, 10, 42])?;

// Merge multiple files (must share affine and dimensions)
let merged = merge_trx_shards(&[&shard1, &shard2])?;

// Set operations on streamlines
let common = intersection(&trx_a, &trx_b)?;
let only_a = difference(&trx_a, &trx_b)?;

Format conversion

trx-rs currently exposes conversion at the library level:

use trx_rs::{convert, read_tractogram, write_tractogram, ConversionOptions, DType};

// Convert between file formats.
convert("input.tck.gz".as_ref(), "output.trx".as_ref(), &ConversionOptions::default())?;

// Read a non-TRX tractogram into the neutral in-memory representation.
let tractogram = read_tractogram("bundles.tt.gz".as_ref(), &ConversionOptions::default())?;

// Write TRX with explicit positions dtype.
let options = ConversionOptions {
    trx_positions_dtype: DType::Float16,
    ..Default::default()
};
write_tractogram("bundles_f16.trx".as_ref(), &tractogram, &options)?;

Supported conversion formats:

Format Read Write Notes
TRX Yes Yes Directory and .trx zip archives
TCK / TCK.GZ Yes Yes Gzipped TCK is supported
VTK Yes Yes Legacy PolyData POINTS + LINES subset
TT.GZ Yes No Import only; TT clusters become TRX groups and TT colors become dpg/<group>/color.3.uint8

TT import behavior:

  • TT streamline points are decoded in voxel space and mapped to TRX positions with trans_to_mni directly
  • TT cluster values become TRX groups
  • file.tt.gz.txt sidecar labels are used for group names when present
  • TT colors become dpg/<group>/color.3.uint8
  • TT report and parameter_id are stored in header.extra

Building

Build the library and tests:

cargo build
cargo test --workspace

Build an optimized release:

cargo build --release

Installation

As a library dependency:

[dependencies]
trx-rs = { git = "https://github.com/tee-ar-ex/trx-rs" }

For local development:

git clone https://github.com/tee-ar-ex/trx-rs
cd trx-rs
cargo build --release

Command-line interface

The repository now ships a trxrs binary under src/bin/trxrs.rs.

Build it locally:

cargo build --release
./target/release/trxrs --help

Install it into Cargo's bin directory:

cargo install --path .
trxrs --help

Available subcommands:

  • trxrs info <input>
  • trxrs convert <input> <output> [--positions-dtype f16|f32|f64] [--reference <nifti|trx>]
  • trxrs concatenate <input-a.trx> <input-b.trx> ... --output <output.trx> [--positions-dtype f16|f32|f64]
  • trxrs manipulate-dtype <input.trx> <output.trx> [--positions-dtype f16|f32|f64]
  • trxrs transform <input.trx> <output.trx> --transform <h5-or-txt> — apply an ANTs/ITK spatial transform to streamline coordinates (see Applying ANTs transforms)

Examples:

# Inspect a TRX or supported foreign-format tractogram
trxrs info bundles.trx

# Convert gzipped TCK to TRX with float16 positions
trxrs convert bundles.tck.gz bundles.trx --positions-dtype f16

# Export to TrackVis (.trk); a reference supplies the affine when the input has none
trxrs convert bundles.tck bundles.trk --reference dwi.nii.gz

# Rewrite an existing TRX with float32 positions
trxrs manipulate-dtype input.trx output.trx --positions-dtype f32

# Concatenate TRX files and write float16 output
trxrs concatenate shard1.trx shard2.trx --output merged.trx --positions-dtype f16

Notes:

  • trx convert uses the library conversion layer and supports trx, tck, tck.gz, vtk, and tt.gz input
  • output may be trx, tck, vtk, or trk; TrackVis (.trk) export needs a spatial affine, taken from the input (TRX/TRK) or from --reference (TCK/VTK), and does not carry per-vertex/per-streamline scalars
  • .tt.gz is import-only; writing Tiny Track is not implemented
  • trx concatenate currently supports TRX inputs and TRX output only
  • offsets are written as uint32 when every offset fits in u32 (i.e., for any tractogram with ≤ 4 G vertices), otherwise uint64 — the TRX spec accepts both

Applying ANTs transforms to tractograms

trxrs transform is the Rust counterpart to antsApplyTransformsToTRX. It warps streamline vertex coordinates through an ITK Composite .h5 (with embedded warp + affines), an Insight Transform File V1.0 (.txt, affine-only), or an ITK MATLAB v4 binary (.mat, affine-only — what ANTs writes for *0GenericAffine.mat).

The "opposite-named h5" rule (cartoon BIDS)

Tractograms warp in the opposite spatial direction from images. Concretely, with paired BIDS h5 files for subject sub-01:

You have You want Pass to --transform
sub-01_space-ACPC_tracts.trx tracts in MNI152NLin2009cAsym sub-01_from-MNI152NLin2009cAsym_to-ACPC_xfm.h5
sub-01_space-MNI152NLin2009cAsym_tracts.trx tracts in ACPC sub-01_from-ACPC_to-MNI152NLin2009cAsym_xfm.h5
sub-01_space-T1w_tracts.trx tracts in MNI152NLin6Asym sub-01_from-MNI152NLin6Asym_to-T1w_xfm.h5

If you are coming from antsApplyTransforms for images: pass the same h5 you would use to warp an image of the destination space into the source space. (That's the same convention as antsApplyTransformsToPoints.)

Why opposite-named?

Image warping with antsApplyTransforms is pull-based: the chain inside from-X_to-Y_xfm.h5 (the file that warps an X-image onto a Y-grid, per BIDS) internally maps target Y voxels back to source X coordinates. Applied to a point, that same chain sends a Y-point to an X-point. So to warp a streamline FROM space A TO space B, you need a chain that maps A → B — which lives in the opposite-named file from-B_to-A_xfm.h5.

Worked example (warp ACPC tracts into MNI)

trxrs transform \
    sub-01_space-ACPC_tracts.trx \
    sub-01_space-MNI152NLin2009cAsym_tracts.trx \
    --transform sub-01_from-MNI152NLin2009cAsym_to-ACPC_xfm.h5 \
    --reference sub-01_space-MNI152NLin2009cAsym_T1w.nii.gz   # optional, for header

The --reference flag is optional — if given, the output TRX header's voxel_to_rasmm and dimensions are taken from it. The streamline coordinates themselves are warped regardless.

Affine-only chains

For an affine-only .txt or .mat, --invert numerically flips the chain so you can use a single file in either direction. Warps cannot be numerically inverted; for those, use the paired from-Y_to-X_xfm.h5 instead.

Supported data types

Type Positions DPS/DPV
f16 (half) Yes Yes
f32 Yes Yes
f64 Yes Yes
u8, u16, u32, u64 - Yes
i8, i16, i32, i64 - Yes

Testing

# Full test suite
cargo test --workspace

# Formatting check
cargo fmt --check

# Lint with warnings denied
cargo clippy --workspace --all-targets --all-features -- -D warnings

# Coverage report (requires cargo-llvm-cov)
cargo llvm-cov --workspace --html

Real-data integration tests download the fixture archives on first run and cache them in target/test_data/. Set TRX_TEST_DATA_DIR to reuse an existing copy of the fixture data instead of downloading it again.

Benchmarks

Benchmarks use a real 10M-streamline HCP dataset (~7.7 GB, auto-downloaded from figshare):

# Quick smoke test with 100k streamlines
TRX_BENCH_MAX_STREAMLINES=100000 cargo bench

# Full benchmark
cargo bench

Set TRX_BENCH_DATA_DIR to point to a cached copy of the benchmark data.

License

BSD 2-Clause

About

Rust library for the TRX format

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages