Skip to content

Proof-of-concept for total TRX integration#3281

Open
mattcieslak wants to merge 9 commits intoMRtrix3:devfrom
PennLINC:mrtrix-whole-trx
Open

Proof-of-concept for total TRX integration#3281
mattcieslak wants to merge 9 commits intoMRtrix3:devfrom
PennLINC:mrtrix-whole-trx

Conversation

@mattcieslak
Copy link

I tried adding TRX support in #3265 and realized that there are a lot of TRX-related functions that should probably be reusable outside of tckconvert.cpp. So I tried out adding TRX support across all of mrtrix and was able to get it working and passing old and new tests. In this PR, every command that accepted TCK now accepts TRX, with (mostly) no new required CLI options. Many commands gain the ability to embed computed sidecar data (weights, per-vertex scalars, atlas labels) directly into the TRX file rather than scattering it across separate .txt/.tsf/.csv files.

I did some benchmarking of TRX-backed vs classic MRtrix3 workflows here.

Big picture

A single TRX file accumulates all the data classically split across multiple sidecar files:

# Classic: five files needed to reproduce the connectome
tracks.tck  weights.csv  icvf.tsf  isovf.tsf  assignments.txt

# TRX: one self-contained file
tracks.trx   # contains weights (dps), icvf/isovf (dpv), and group assignments

The strategy I took is that a sidecar output argument (weights path, TSF path, scalar dump path) doubles as a TRX field name when given without a file extension. For example:

tcksift2 tracks.trx fods.mif weights        # embeds "weights" dps into tracks.trx
tcksift2 tracks.trx fods.mif weights.csv    # writes external CSV as before
tcksample tracks.trx fa.mif fa              # embeds "fa" dpv into tracks.trx
tcksample tracks.trx fa.mif fa.tsf          # writes external TSF as before
tckmap tracks.trx out.mif -tck_weights_in weights  # reads from embedded dps field
tckmap tracks.tck out.mif -tck_weights_in weights.csv  # unchanged existing behaviour

All existing behavior is preserved when an extension is present or the input is TCK.

Specifics

All of the trx-specific handling is implemented in trx_utils.h, not in command code. Commands do not contain if (is_trx_input) branches. Three helper functions do almost all the integration logic:

  • open_tractogram(path, properties) to open TCK or TRX transparently, returning unique_ptr<ReaderInterface<float>>, populating properties["count"].
  • resolve_dps_weights(path, field_name_or_path) — returns per-streamline weights from an embedded dps field or external text file.
  • resolve_dpv_scalars(path, field_name_or_path) — same for per-vertex scalars.

Most CLI programs only need two line substitutions:

// before
Reader<float> file(argument[0], properties);
TrackLoader loader(file, num_tracks);

// after
auto reader = TRX::open_tractogram(argument[0], properties);
TrackLoader loader(*reader, num_tracks);

This only works because I changed TrackLoader's constructor to accept ReaderInterface<float>& instead of Reader<float>&. Not sure if this is cool.

Gotchas

  • TRX coordinates are RAS+, identical to TCK. VOXEL_TO_RASMM in the trx header is informational only. It's really only useful to convert to trk, which I hope goes away someday. I kind of want to not even print it in tckinfo because it's never applied during reading or writing.
  • Commands that only filter streamlines (namely tckedit, tcksift) use trx::TrxFile::subset_streamlines() to remap all dps/dpv/groups to surviving streamlines automatically.
  • Commands that transform coordinates without changing vertex count (tcktransform TRX→TRX path): modify positions in-place on the loaded TrxFile; all metadata is preserved with no extra infrastructure.
  • For commands that change vertex count (tckresample), dps and groups are copied to the new file. dpv is discarded with a warning (vertex-count change invalidates per-vertex data). If users want to get dpv back they could rerun tckmap with the resampled trx.

Critical Note

I want to be clear this is just a proof of concept showing what TRX integration could look like, not a proposal to merge exactly this code. I'm pretty satisfied with the MRtrix3-TRX workflow in this PR but obviously am a newbie to MRtrix3 development and c++, and want to be a good citizen. I'm happy to implement any suggestions here or in separate PRs!

Also, it's worth mentioning that it will be extremely nice to be able to mix and match software tools. We will soon be able to antsApplyTransformsToTRX, read/write streamlines with DSI Studio and are already able to use trx throughout trekker, ITK and in Python/DIPY.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant