Skip to content

Add fmri_surface_data object for CIFTI/GIFTI surface & grayordinate data#86

Open
torwager wants to merge 6 commits into
masterfrom
canlab_fmri_surface_object
Open

Add fmri_surface_data object for CIFTI/GIFTI surface & grayordinate data#86
torwager wants to merge 6 commits into
masterfrom
canlab_fmri_surface_object

Conversation

@torwager

@torwager torwager commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

New CANlab object fmri_surface_data (subclass of image_vector) — the surface/grayordinate analogue of fmri_data, wrapping the HCP CIFTI grayordinate standard as a true CANlab object. Runs natively in MATLAB with no external toolbox (sole exception: ica, which needs GIFT/icatb like the base class).

What's included

  • Native I/O: CIFTI-2 + GIFTI readers/writers (CanlabCore/Surface_tools/canlab_*), bit-exact vs cifti_read/gifti; verified to work with those tools removed from the path.
  • Object model: brain_model replaces volInfo (populated for the subcortical sub-block); .dat always full — no empty-squeezing (remove_empty/replace_empty no-ops).
  • Interop: reconstruct_image, to_fmri_data, compare_space, write.
  • Volume↔surface: vol2surf/surf2vol via the vendored CBIG RF warps (vol→surf→vol r≈1.0), fully native.
  • Analysis: cat/horzcat, mean, apply_mask, threshold (+cluster-extent k), ttest, regress, predict, ica (predict/ttest/ica delegate to fmri_data + remap).
  • Rendering: surface (native + any addbrain/MNI surface via resampling), render_on_surface, plot; reparse_contiguous, apply_parcellation, surface_region.
  • Docs: docs/fmri_surface_data_methods.md (integrated into Object_methods.md/Workflows.md), docs/workflows/fmri_surface_data_howto.md (with figures), plus developer docs + runnable walkthrough under CanlabCore/docs/.
  • Sample data: one small HCP Open Access group myelin map in Sample_datasets/CIFTI_surface_examples/; other atlases resolve from Neuroimaging_Pattern_Masks.
  • Tests: CanlabCore/Unit_tests/surface_data/ — 7 files, 41 tests passing (ica skipped when its toolbox is absent).

Incidental fixes

  • @image_vector/ica.m: removed a stray non-functional debug line that made ica error for all image_vector subclasses.
  • addbrain.m help: documented the fs_LR/fsaverage standard-mesh correspondence.
  • Deprecation pointers in the external-dependent Cifti_plotting readers.

Notes / deferred (see design plan)

  • vol2surf/surf2vol use a fixed group MNI152↔fsaverage correspondence (not a per-subject ribbon mapper); fsaverage↔fs_LR deformation is a planned enhancement.
  • Optional future polish: dedicated fmri_surface_statistic_image/fmri_surface_atlas subclasses; CC0 TemplateFlow meshes.

🤖 Generated with Claude Code

torwager and others added 6 commits June 19, 2026 19:57
Convert two quarantined standalone scripts into real matlab.unittest tests
and move them into the discovered suite, and make the runner robust against
non-test files that match the canlab_test_*.m glob.

- image_vector/canlab_test_check_roi_extraction.m: converts
  old_to_integrate/check_roi_extraction.m. The original printed PASS/FAIL but
  never asserted. Now asserts multi-region 'unique_mask_values' extraction and
  invariance of the region averages across a write->reload round-trip
  (RelTol 1e-3 / AbsTol 1e-2 absorbs single-precision NIfTI rounding).
  Complements canlab_test_extract_roi (single-mask only).
- image_vector/canlab_test_resampling_pattern_expression.m: converts
  old_to_integrate/resampling_pattern_expression_unit_test1.m, which only
  plotted. Now asserts SIIPS pattern expression is stable (corr > 0.99) across
  default/nearest/spline resampling; skips if SIIPS is unavailable.
- helpers/canlab_safe_suite_from_file.m + canlab_run_all_tests.m: wrap
  TestSuite.fromFile so a stray non-test canlab_test_*.m is warn-skipped
  instead of throwing NonTestFile and aborting discovery of the whole suite.
- canlab_test_runner_robustness.m: pins that behavior (non-test file ->
  warn + empty + ok=false; valid file -> loads).

Old originals removed from old_to_integrate (jackknife_similarity_unit_test.m
stays - it is a characterization/figure script, not a unit test).

Full default suite after: 147 passed, 0 failed, 2 incomplete (pre-existing
environment skips).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The five xval_*_unit_test.m files at the Unit_tests root were genuine tests
(real assertions, no graphics, deterministic, bundled DPSP / synthetic data)
but were plain-assert functions named outside the canlab_test_* convention,
so the runner never discovered them.

Convert each into a functiontests file under a new predictive_model/ subdir:
the model is fit once in setupOnce (skipped via assumeTrue if the DPSP sample
data is absent) and cached, and the assertions are split into focused test
functions using tc.verify* for granular reporting. Behavior preserved.

- predictive_model/canlab_test_xval_SVM.m
- predictive_model/canlab_test_xval_SVR.m
- predictive_model/canlab_test_xval_regression_multisubject.m
- predictive_model/canlab_test_xval_regression_multisubject_featureselect.m
- predictive_model/canlab_test_xval_discriminant_classifier.m
- helpers/canlab_get_dpsp_hot_warm.m: shared DPSP Hot/Warm loader.

Old xval_*_unit_test.m removed. New suite: 25 test points, all pass, ~7.5s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nperm)

predictive_model_unit_test.m exercised the @predictive_model object's own
public API (fit/predict/score/crossval/bootstrap/weight_map_object/
permutation_test/calibrate/select_features/grid_search/stability_selection/
pipeline/newapi routing/regression+report/summary). This is non-redundant
with the xval_* tests, which cover the legacy wrapper functions rather than
the object methods - so it is worth keeping.

Convert to predictive_model/canlab_test_predictive_model_api.m: the shared
fit -> crossval -> bootstrap chain is computed once in setupOnce and cached;
the rest are focused test functions using tc.verify*; visualisation methods
(plot/confusionchart/montage) are smoke-tested and skipped on a headless
runner via canlab_classify_environment_error.

Drop the bootstrap/permutation/stability counts to minimum-viable values
(nboot 25->5, nperm 10->3, stability nboot 20->5) since this is a smoke test,
not real inference; assertions are structural (shapes/ranges/p-floor), so the
counts only need to exercise the code paths. Runtime 131s -> 73s locally; the
residual cost is the high-dimensional (194,676-feature) cross-validation, not
the resampling counts. Skipped if DPSP_hotwarm is not on the path.

Old predictive_model_unit_test.m removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dominant cost was cross-validating on the full ~195k-voxel volume. In
setupOnce, restrict hw_obj to gray matter (gray_matter_mask.img) and then
deterministically thin to every 8th voxel (~20k features). hw_obj and X stay
in lockstep, so weight_map_object / montage back-project consistently, and
all assertions are structural so masking does not change them. Falls back to
the full volume if the mask is not on the path.

Runtime: 131s (original) -> 73s (minimal nboot/nperm) -> ~20s now. All 17
test points still pass; model behaviour unchanged (AUC ~0.83).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New CANlab object `fmri_surface_data` (subclass of image_vector), the surface/
grayordinate analogue of fmri_data. Wraps the HCP CIFTI grayordinate standard as
a true CANlab object with full fmri_data-style method parity, running natively in
MATLAB with no external toolbox (sole exception: ica, which needs GIFT/icatb like
the base class).

Highlights:
- Native CIFTI-2 + GIFTI readers/writers (Surface_tools/canlab_read|write_cifti|
  gifti), bit-exact vs cifti_read/gifti oracles; proven to work with those tools
  removed from the path.
- Object: construction from CIFTI/GIFTI/struct/key-value; brain_model replaces
  volInfo (volInfo populated for the subcortical sub-block); no empty-squeezing
  (.dat always full; remove_empty/replace_empty are no-ops).
- Interop: reconstruct_image, to_fmri_data, compare_space (0/1/2/3), write.
- Volume<->surface: vol2surf / surf2vol via vendored CBIG RF warps (vol->surf->
  vol r~1.0), fully native.
- Data/analysis: cat/horzcat, mean, apply_mask, threshold (+cluster-extent 'k'),
  ttest, regress, predict, ica (predict/ttest/ica delegate to fmri_data via a
  proxy and remap results).
- Rendering: surface (native 4-panel + on any addbrain/MNI surface via volume
  resampling), render_on_surface, plot; reparse_contiguous (mesh graph),
  apply_parcellation, surface_region.
- Docs: docs/fmri_surface_data_{design_plan,methods}.md + runnable walkthrough.m.
- Tests: Unit_tests/surface_data/ (7 files, 41 tests passing).

Also: fix a stray non-functional debug line in @image_vector/ica.m that errored
ica() for all image_vector subclasses; document the fs_LR/fsaverage standard-mesh
correspondence in addbrain.m; add deprecation pointers in the external-dependent
Cifti_plotting readers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/fmri_surface_data_methods.md: per-class methods page (properties +
  methods grouped by area), integrated into docs/Object_methods.md (class
  hierarchy + object-classes table) and docs/Workflows.md.
- docs/workflows/fmri_surface_data_howto.md: intro workflow — load surface data
  and render it natively; map volumetric data to the surface (vol2surf) and back
  (surf2vol / to_fmri_data); parcellation, thresholding, and group analysis.
  Includes three rendered example figures.
- Methods page includes a summarized inventory of surface atlases / maps available
  in the companion Neuroimaging_Pattern_Masks repository.
- Sample data: bundle one small HCP Open Access group myelin map
  (Sample_datasets/CIFTI_surface_examples/) with a provenance README; additional
  surface atlases are resolved from Neuroimaging_Pattern_Masks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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