Reading TRX Files
This page covers the common patterns for loading and inspecting TRX data.
See API Layers for guidance on choosing between AnyTrxFile and
TrxFile<DT>.
Load and inspect
The simplest entry point is trx::load_any(), which detects the dtype
from the file and returns an trx::AnyTrxFile:
#include <trx/trx.h>
auto trx = trx::load_any("/path/to/tracks.trx");
std::cout << "dtype : " << trx.positions.dtype << "\n";
std::cout << "streamlines : " << trx.num_streamlines() << "\n";
std::cout << "vertices : " << trx.num_vertices() << "\n";
trx.close();
Access positions and offsets
Positions and offsets are exposed as trx::TypedArray objects. Call
as_matrix<T>() to obtain an Eigen::Map view without copying data:
auto pos = trx.positions.as_matrix<float>(); // (NB_VERTICES, 3)
auto offs = trx.offsets.as_matrix<uint64_t>(); // (NB_STREAMLINES + 1, 1)
for (size_t i = 0; i < trx.num_streamlines(); ++i) {
const size_t start = static_cast<size_t>(offs(i, 0));
const size_t end = static_cast<size_t>(offs(i + 1, 0));
// vertices for streamline i: pos.block(start, 0, end - start, 3)
}
Access DPV and DPS
Per-vertex (DPV) and per-streamline (DPS) metadata are stored in
std::map<std::string, TypedArray> containers:
// List all DPS fields
for (const auto& [name, arr] : trx.data_per_streamline) {
std::cout << "dps/" << name
<< " (" << arr.rows() << " x " << arr.cols() << ")\n";
}
// Access a specific DPV field
auto fa = trx.data_per_vertex.at("fa").as_matrix<float>(); // (NB_VERTICES, 1)
Access groups
// AnyTrxFile: groups are TypedArray values.
for (const auto& [name, arr] : trx.groups) {
std::cout << "group " << name << ": "
<< arr.rows << " streamlines\n";
}
auto ids = trx.groups.at("CST_L").as_matrix<uint32_t>(); // (N, 1)
Typed access via TrxFile<DT>
When the dtype is known ahead of time, use trx::load() for a typed
view. Positions and DPV arrays are exposed as Eigen::Matrix<DT, ...>
directly, avoiding element-wise conversion:
auto reader = trx::load<float>("tracks.trx");
auto& trx = *reader;
// trx.streamlines->_data is Eigen::Matrix<float, Dynamic, 3>
// trx.streamlines->_offsets is Eigen::Matrix<uint64_t, Dynamic, 1>
// Groups are discovered at load time but loaded lazily on first access.
for (const auto& kv : trx.groups) {
const auto& name = kv.first;
const auto* group = trx.get_group_members(name); // lazy load + cache
if (group == nullptr) {
continue;
}
std::cout << name << ": " << group->_matrix.rows() << " members\n";
}
reader->close();
Iterating streamlines without copying
Because TRX positions are memory-mapped, the full positions array is never read into RAM at once — the OS pages in only the regions you touch. You do not need a separate “streaming reader” to process a 10 M-streamline file without exhausting memory.
To iterate over each streamline with zero per-streamline allocation, use
trx::TrxFile::for_each_streamline(). The callback receives the
streamline index, the start row in _data, and the number of vertices:
auto reader = trx::load<float>("tracks.trx");
auto& trx = *reader;
trx.for_each_streamline([&](size_t idx, uint64_t start, uint64_t length) {
// Zero-copy block view of this streamline's vertices.
auto pts = trx.streamlines->_data.block(
static_cast<Eigen::Index>(start), 0,
static_cast<Eigen::Index>(length), 3);
// pts is an Eigen expression — no heap allocation.
// Example: compute the centroid.
Eigen::Vector3f centroid = pts.colwise().mean();
});
reader->close();
For random access to a single streamline, use
trx::TrxFile::get_streamline(). This copies the vertices into a
std::vector and is convenient for one-off lookups, but avoid it in
tight loops over large tractograms:
auto pts = trx.get_streamline(42); // std::vector<std::array<float,3>>
Chunk-based iteration (AnyTrxFile)
trx::AnyTrxFile provides for_each_positions_chunk, which
iterates the positions buffer in fixed-size byte chunks. This is useful
for transcoding or checksum passes that process all vertices but do not
need the per-streamline boundary structure:
auto trx = trx::load_any("tracks.trx");
trx.for_each_positions_chunk(
4 * 1024 * 1024, // 4 MB chunks
[](trx::TrxScalarType dtype, const void* data,
size_t point_offset, size_t point_count) {
// data points to point_count * 3 values of the given dtype,
// starting at global vertex index point_offset.
});
trx.close();;