Skip to content

Add a Tractogram dataview for streamlines - #741

Open
mvdoc wants to merge 3 commits into
tractography/1-surface-opacityfrom
tractography/2-dataview
Open

mvdoc wants to merge 3 commits into
tractography/1-surface-opacityfrom
tractography/2-dataview

Conversation

@mvdoc

@mvdoc mvdoc commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Stack

Merge bottom-up; each PR is based on the one above it.

  1. Add a surface_opacity control to the WebGL viewer #740 — Add a surface_opacity control to the WebGL viewer
  2. Add a Tractogram dataview for streamlines #741 — Add a Tractogram dataview for streamlines
  3. Render Tractogram streamlines in the WebGL viewer #742 — Render Tractogram streamlines in the WebGL viewer
  4. Document tractography, with an example and visual references #743 — Document tractography, with an example and visual references

cortex.Tractogram holds a bundle of streamlines — diffusion tractography
output such as pyAFQ or DIPY bundles — as a Dataview, so it travels through
Dataset and the viewer machinery like any other data. It is the odd one out
among the dataviews in that it is not braindata: the geometry is a flat
(N, 3) array of points plus an (M+1,) offset table delimiting the M
streamlines, which is the TRX convention.

tract = cortex.Tractogram.from_trx("bundles.trx", "S1")
tract = cortex.Tractogram.from_streamlines(list_of_arrays, "S1")

Per-point and per-streamline scalars and named groups of streamlines (pyAFQ
writes one per bundle) come across from the file, and select, get_group and
subsample return new tractograms with the group memberships remapped.
Colouring is resolved in Python by vertex_colors: by local tangent direction,
by a constant colour, or through a colormap from a named scalar.

This is a container only — nothing renders it yet. HDF5 persistence is
deliberately NotImplementedError: a Dataset carrying a tractogram cannot be
saved, and the TRX file stays the source of truth.

Dependency and CI

Reading TRX needs trx-python, a new optional tractography extra. The import
is lazy, so pycortex works normally without it, and the tests importorskip.

trx-python requires Python 3.11 and the test dependency group pulls the extra
in, so the CI matrix drops its 3.10 row. On 3.10 the install step fails
outright rather than the TRX tests quietly skipping. Note that the project
declares no requires-python, so this changes what is tested without changing
what the package claims to support; making the two agree is a separate
decision.

Testing

cortex/tests/test_tractogram.py, 30 tests: construction and validation,
round-tripping a hand-built TRX file, the xfm, every colour mode, select /
get_group / subsample invariants including overlapping groups, and the
wire-format to_json keys.


🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved identity, metadata, dataset integration, validation, and empty-selection issues can break normal tractogram workflows.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 4 Medium severity

Open (7)
What changed in this PR

Adds a Tractogram dataview for storing, selecting, coloring, and transporting streamline data.

Changes:

  • Adds TRX and raw-streamline constructors with grouping and coloring support.
  • Exposes Tractogram publicly and documents its API.
  • Adds tractogram tests and an optional dependency while updating CI.
File Description
pyproject.toml Adds the tractography dependency extra.
docs/​dataset.rst Documents the new dataview and constructors.
cortex/​tests/​test_tractogram.py Tests construction, selection, coloring, and TRX loading.
cortex/​dataset/​tractogram.py Implements the tractogram container.
cortex/​dataset/​__init__.py Exports Tractogram from the dataset package.
cortex/​__init__.py Exposes Tractogram through the public API.
.github/​workflows/​run_tests.yml Removes Python 3.10 from the test matrix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cortex/dataset/tractogram.py
Comment thread cortex/dataset/tractogram.py Outdated
Comment thread cortex/dataset/tractogram.py
Comment thread .github/workflows/run_tests.yml Outdated
Comment thread cortex/dataset/tractogram.py Outdated
Comment thread cortex/dataset/tractogram.py
Comment thread cortex/dataset/tractogram.py
@mvdoc
mvdoc force-pushed the tractography/2-dataview branch from 5d5a4ed to 4b3e733 Compare September 20, 2026 21:54
mvdoc and others added 3 commits September 20, 2026 18:35
cortex.Tractogram holds a bundle of streamlines -- diffusion tractography
output such as pyAFQ or DIPY bundles -- as a Dataview, so it travels through
Dataset and the viewer machinery like any other data. It is the odd one out
among the dataviews in that it is not braindata: the geometry is a flat (N, 3)
array of points plus an (M+1,) offset table delimiting the M streamlines,
which is the TRX convention.

  tract = cortex.Tractogram.from_trx("bundles.trx", "S1")
  tract = cortex.Tractogram.from_streamlines(list_of_arrays, "S1")

Per-point and per-streamline scalars and named groups of streamlines (pyAFQ
writes one per bundle) come across from the file, and select/get_group/
subsample return new tractograms with the group memberships remapped.
Coloring is resolved in Python by vertex_colors: by local tangent direction,
by a constant color, or through a colormap from a named scalar.

Reading TRX needs trx-python, a new optional `tractography` extra. The import
is lazy, so pycortex works normally without it, and the tests importorskip.
trx-python requires Python 3.11, and the test dependency group pulls the extra
in, so the CI matrix drops its 3.10 row -- on 3.10 the install fails outright
rather than the TRX tests quietly skipping.

This is a container only. Rendering comes next; HDF5 persistence is
deliberately NotImplementedError, since a Dataset carrying one cannot be
saved, and the TRX file stays the source of truth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Its two groups now go in in reverse alphabetical order. The viewer lists
bundles alphabetically rather than in wire order, and with the two agreeing no
test could tell the difference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven fixes from the review of the Tractogram container, each with a
regression test:

- `uniques()` was missing. `Dataset.uniques()` calls it on every view it
  holds, so `Dataset(tract=tract).uniques()` raised `AttributeError` and a
  dataset carrying a tractogram could not be packaged or saved at all. A
  tractogram wraps no BrainData, so it yields nothing.
- `name` hashed `points` alone, giving the same identity to two
  tractograms whose point buffers match but whose streamline boundaries
  differ. `offsets` now goes into the digest too.
- `to_json()` dropped the base Dataview keys `state`, `attrs` and `desc`,
  losing custom state on the way to the viewer and breaking any consumer
  that reads them off a view's metadata.
- Group index arrays were only range-checked, so a 2-D array was accepted
  and reached `select()` and `groups_wire()`, whose `(K,)` buffer contract
  it breaks. Groups must now be 1-D, and a boolean mask is converted
  rather than silently reinterpreted as the indices 0/1.
- `select()` accepted a boolean mask of any length: `select([True])` on a
  larger tractogram quietly kept only streamline 0. Masks must now have
  one entry per streamline, indices must be 1-D, and an out-of-range
  index raises instead of wrapping (a negative index sliced `points` from
  the end yet never matched when remapping `groups`).
- `vertex_colors()` raised on an empty tractogram in the `dpv:`/`dps:`
  colour modes, where `reshape(0, -1)` cannot be inferred. Empty
  tractograms are legitimate -- an empty named group, or a `select` that
  matched nothing -- and now return the documented `(0, 3)` array.
- `from_trx(xfm=...)` did not check the affine's shape, failing later
  inside the matmul.

Also restore the Python 3.10 row in the test matrix. The incompatibility
is confined to trx-python, an optional test dependency, so gating it with
`python_version >= '3.11'` in the test group keeps 3.10 installable (the
TRX tests skip themselves there) rather than dropping core 3.10 coverage
for a version the wheel-install workflow still exercises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWULoz8DLUFHHio66eTCgh
@mvdoc
mvdoc force-pushed the tractography/2-dataview branch from 4b3e733 to 8f793d0 Compare September 21, 2026 01:42

This branch has not been deployed

No deployments
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.

3 participants