Merge with current CANLab Core#8
Conversation
… from repo
Make the BMRK3 6-levels pain dataset self-contained in the repo so
load_image_set('bmrk3') no longer depends on the separately-distributed
bmrk3_6levels_pain_dataset.mat (figshare).
- Gzip the 33 per-subject niftis (bmrk3_6levels_subject0XX.nii -> .nii.gz)
and update single_trial_image_names in bmrk3_6levels_metadata.mat to .gz.
- Rebuild load_bmrk3 to construct the 198-image object directly from the
per-subject images + metadata: fmri_data(names, brainmask) -> remove_empty,
with metadata concatenated subject-major. Verified bit-identical (.dat, .Y,
.metadata_table, volInfo, removed_voxels) to the old .mat. Drop the temporary
'bmrk3_lightweight' loader.
- SPM's spm_vol cannot read .nii.gz, so make the mediation image readers
transparently gunzip on input and clean up temp .nii on exit (onCleanup):
image_eval_function_multisubj (mediation_brain_multilevel) and
tor_extract_rois (mediation_brain_results extraction). Verified the
multilevel mediation walkthrough runs end-to-end on the gzipped data.
- Point Part 2 of the SVM decoding tutorial and sample_datasets.md at the
repo-built load_image_set('bmrk3').
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nit test - docs/ROI_extraction_methods_roadmap.md: internal catalog of all ROI/atlas/ parcel extraction methods (object methods + standalone + legacy), a "which method should I use" decision guide, call-graph, and the resampling-direction notes. Documents that apply_atlas is referenced in class help headers but has no implementation (use apply_parcellation / @atlas/extract_data). - docs/extract_roi_data_howto.md: runnable help doc with sample code using load_image_set/load_atlas, extract_roi_averages, apply_parcellation, and barplot_columns / lineplot_columns / line_plot_multisubject (bmrk3). All code blocks verified to execute under MATLAB R2026a. - Unit_tests/data_extraction/canlab_test_extract_roi_methods.m: function-based test (3/3 pass) verifying cross-method ROI-mean identity (<1e-6 same space), synthetic known-value recovery, and resampling tolerance (corr>0.99, rel err<0.1). Auto-discovered by canlab_run_all_tests. - docs/ROI_extraction_streamlining_plan.md: proposal to consolidate extraction onto an apply_parcellation + canlab_pattern_similarity core and deprecate-then-remove orphaned/legacy functions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…roadmap and figures - Add docs/Workflows.md hub; link it from Object_methods.md "Cross-cutting topics". - Move the ROI extraction roadmap and how-to into docs/workflows/. - Rewrite the roadmap as a public-facing, didactic overview: ROI means, pattern / signature responses (mask/network averages and weight-map dot-products via apply_mask), gray/white/CSF tissue compartments, components, spheres around coordinates, and hand-drawn ROIs. Catalog table gains an "operates on" column (object classes / .nii-.img) and drops file paths; new section explains automatic resampling onto a common grid (assumes shared MNI space) and manual resample_space. - Add a graphical roadmap (workflows/ROI_data_extraction_roadmap.pptx/.png) built from the codemap template via docs/_codemap_tools/build_roi_roadmap.py, and embed the PNG. - Embed generated example figures (ROI montage, barplot_columns, lineplot_columns, line_plot_multisubject) in the how-to and move the quick-reference table to the top. - Fix relative links after the move; add __pycache__/ to .gitignore. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ision table Add two rows to "Which method should I use?" in the ROI extraction roadmap covering how to obtain the region definition an extraction method consumes: load a labeled atlas by keyword (load_atlas) and select/flatten named regions (select_atlas_subset), with a pointer to the how-to walkthrough. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Initial plan * Fix reshape error in render_blobs when cmaprange has equal min/max values Co-authored-by: torwager <6262700+torwager@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: torwager <6262700+torwager@users.noreply.github.com>
* Initial plan
* Fix write function performance for filenames with underscores
- Replace str2num with str2double in make_img_filename to avoid eval-based
path searches that are extremely slow on clusters with large MATLAB paths.
str2double handles non-numeric strings by returning NaN (fast) instead of
triggering a full MATLAB function path search via eval.
- Fix warning('off') to use warning('off', 'all') with proper save/restore
state pattern, fixing deprecated usage in MATLAB R2020a+.
- Fix O(N^2) char concatenation loop in slice write path to use cell array
pre-allocation (O(N)) instead of repeated char() matrix building.
Fixes slow write for filenames like TYPEB_FITHRF.nii where str2num called
with the identifier would trigger expensive MATLAB path lookups.
Co-authored-by: torwager <6262700+torwager@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: torwager <6262700+torwager@users.noreply.github.com>
…mator (#77) * Add glm_map object class scaffold New scikit-learn-style estimator class that bundles GLM design specification, fitted result maps (statistic_image), and design diagnostics in one container. Composition over fmri_glm_design_matrix (wrapped in .design) and fmri_data.regress (the compute engine). - classdef with stored properties (design/level/is_timeseries, betas/t/contrast maps, vif/leverage/collinearity diagnostics, provenance) and true Dependent accessors (X, TR, onsets, durations, regressor_names, num_*, is_fitted) that read through to .design. - Implemented + MATLAB-verified: diagnostics (VIF/cVIF/leverage/ condition number/rank/redundant-column report), add_contrasts, threshold, montage, table, plot_design, summary, check_properties, private select_map. - Documented stubs with planned field mappings: fit (wraps regress), build_design (wraps fmri_glm_design_matrix.build), import_SPM (SPM12/SPM25 -> .design). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * glm_map: implement fit() over fmri_data.regress fit() is now a working orchestration layer: validates the design, attaches obj.X to the fmri_data, assembles regress() arguments (robust/AR/threshold/contrasts/names), runs regress, and unpacks the outputs into the object's statistic_image maps (betas, t, contrast_estimates, contrast_t) plus sigma, dfe, residuals. Records fit_parameters and runs diagnostics() for the full VIF/cVIF/leverage/ collinearity set. AR models are gated on is_timeseries. Also resolve multi-image result maps for display: select_map now parses an optional image index (bare scalar or 'wh_image'), montage selects it when given, and table auto-selects a single image (required because statistic_image.table handles one image at a time). Verified end-to-end on load_image_set('emotionreg'): fit -> dfe=28, betas [35676x2], diagnostics populated, threshold and atlas-labeled table() both working. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * glm_map: implement build_design() for event/1st-level designs build_design() delegates to fmri_glm_design_matrix.build, which convolves onsets/durations with the basis set and assembles design.xX.X. After the call the Dependent obj.X and obj.regressor_names read through to the built design, so event-mode fit() works the same as direct mode. Also harden the Dependent X / regressor_names accessors: the wrapped design seeds xX as a 0x0 struct array (constructor uses 'name',{}), so guard with isscalar() before indexing .X / .name to avoid an "isempty: not enough input arguments" error pre-build. Verified: glm_map(fmri_glm_design_matrix) -> build_design -> X [200x19] from onsets, regressor names read through, event-mode fit() completes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * glm_map: implement import_SPM() for SPM12/SPM25 first-level models import_SPM() populates a glm_map from an SPM structure, an SPM.mat path, or a directory containing one. Because fmri_glm_design_matrix mirrors SPM's schema, the import is a guarded copy of substructs into the wrapped design: xY.RT -> TR, nscan, xBF, Sess (onsets/durations/names/pmods/ covariates), and xX (matrix + names). Sets level=1 and is_timeseries=true. Optional 'load_betas' reads beta_*.nii/.img from SPM.swd into obj.betas, labeled by xX.name. Whole-substruct copy is resilient to SPM12 vs SPM25 auxiliary-field differences. Verified on a synthetic SPM struct: TR/onsets/condition_names/regressor names read through correctly, and fit() against a matching timeseries yields dfe = nscan - nregressors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document glm_map in CLAUDE.md and image_vector see-also Add glm_map to the object-architecture class list in CLAUDE.md and to the image_vector :See also: block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add unit tests for glm_map (canlab_test_glm_map) 12 functiontests covering construction and Dependent property read-through, design diagnostics (incl. rank-deficiency detection), direct-mode (2nd-level) fit over fmri_data.regress with contrasts, threshold delegation, event-mode build_design via the wrapped fmri_glm_design_matrix, import_SPM + event-mode fit on a synthetic SPM struct, and the main input-validation error paths (no design, AR without timeseries, contrast size mismatch). Auto-discovered by canlab_run_all_tests. All 12 pass (~5s). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * glm_map: mirror regress out-struct, add diagnostics, narrative summary Restructure glm_map around fmri_data.regress's results structure and make regress return a glm_map object instead of a plain struct. - glm_map properties now mirror the regress `out` fields, with nested .input_parameters / .input_image_metadata / .diagnostics structs, .df and .sigma kept as fmri_data, and out-struct-name aliases (.b, .con_t, .contrast_images, .resid, .variable_names, .C). Constructor re-casts a regress-style struct; disp lists all properties. - fmri_data.regress returns glm_map(out) (struct-compatible via aliases), replaces deprecated getvif with VIF.m, and adds contrast VIFs (cVIF) to out.diagnostics.Contrast_variance_inflation_factors. - diagnostics: consolidate on Variance_inflation_factors / Leverages names and add per-observation Cook's distance (Cooks_distance); fit auto-computes residuals for it and drops them unless requested. - summary: print a narrative report (analysis, model, diagnostics, and per-predictor/contrast significant-voxel counts at threshold). - Add docs/workflows/regression_with_glm_map.m walkthrough; update unit tests and CLAUDE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs/workflows: expand glm_map regression walkthrough to mirror regress examples Use a real predictor (Reappraisal_Success) from the emotionreg metadata_table and demonstrate the full set of fmri_data.regress example options against the returned glm_map object: regressor/analysis naming, displaying result maps (montage/surface/orthviews/get_wh_image), nested .diagnostics with a VIF/ leverage plot, a 2nd-level CSF nuisance covariate, gray-matter masking + FDR, outlier detection and exclusion, robust regression, residual output, brain-predicts-behavior and map-writing (commented), the estimator path with fit/summary, re-thresholding, and a first-level event design. Fix find/replace artifacts (.obj -> .dat) from the prior revision. Verified all computational sections run end-to-end on the sample dataset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map: add validate_object to guarantee full nested-struct schema Add @glm_map/validate_object, which fills any missing fields in the nested option/diagnostic structs (.input_parameters, .input_image_metadata, .diagnostics incl. .collinearity_report, and .fit_parameters) from canonical field templates without overwriting existing values. The templates are the single source of truth for the valid field names produced by fmri_data.regress and glm_map.diagnostics. - glm_map constructor now calls validate_object before returning, so even a freshly constructed (or struct-recast) object always exposes the complete schema for every nested struct. - fmri_data.regress calls validate_object on the glm_map output before returning it. Also picks up a docs/workflows tweak (run diagnostics(g) before reading .diagnostics fields). glm_map unit tests pass (14/14). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fmri_glm_design_matrix: hi-res microtime pipeline + method audit; glm_map.replace_basis_set Rebuild the design-matrix convolution on a high-resolution (16 samples/sec, TR-independent) pipeline and audit/fix the class methods. get_session_X: build a 16 Hz onset/epoch indicator per condition, convolve with the per-condition basis (resampled to 16 Hz via xBF.dt), and downsample to TR with getPredictors('dsrate', res*TR) -- the same engine onsets2fmridesign uses. Matches onsets2fmridesign to corr >= 0.999 across TR = 0.8/1/1.3/2/2.5 (incl. fractional TRs), handles event durations (epochs), parametric modulators, multiple sessions, and per-condition basis sets. Default basis is now the canonical SPM HRF sampled at 16 Hz. Audit fixes: - build_single_trial: same hi-res timing fix; index xX(1) (dot-assign to an empty struct failed); correct off-by-one in the intercept (iB) indices. - build/build_single_trial check_model: make the " for Condition N" basis-name suffix idempotent so repeated build() calls don't keep appending. - add: condition_names is now correctly shared across sessions (or accepts session-major names), per the documented behavior. - replace_basis_set: harmonize struct fields between basis builders (spm_get_bf vs fmri_spline_basis) so assignment no longer errors, and expand xBF to one-per-condition so replacing one condition keeps the others. - import_onsets: implement (was a non-functional stub) reading onset/duration/ name columns from a CSV/Excel file or table. - plot: drop a dead `bf = obj.xBF.bf` line that errored for multi-condition xBF. glm_map: add replace_basis_set, which delegates to the design's replace_basis_set, rebuilds X, clears fit results/contrasts/diagnostics that no longer apply (restoring the empty schema via validate_object), and either re-fits (if data supplied) or refreshes design diagnostics. Tests: add event-design timing checks (canonical HRF -> 3 cols; match onsets2fmridesign across TRs) and a replace_basis_set test. 16/16 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map: interest/nuisance roles, interest-only diagnostics report, plotDesign hook; import_onsets FSL/SPM inputs Regressor roles: add wh_interest / wh_nuisance / wh_intercept Dependent indicators to glm_map. Event designs read the partition from design.xX (iH = of interest, iC/iG = nuisance, iB = intercept); direct/group designs detect the intercept (constant columns) and use a new nuisance_columns property to mark covariates of no interest. diagnostics(): now computes VIFs/cVIFs and the condition number for the full design AND for the regressors of interest only (nuisance covariates removed), storing both (full values stay in the main fields). By default it prints a narrative report that explains each metric's range/interpretation, warns on problematic cases (VIF/cVIF > threshold, condition number > 30/100, high Cook's distance, rank deficiency), and flags when nuisance covariates substantially inflate the of-interest VIFs (indicating correlation between events of interest and nuisance variables). plot_design(): hooks into plotDesign -- when an event design has <= 12 event types of interest, draws color-coded regressor line plots with event/duration boxes for the events of interest, alongside a heat map of the full design matrix (incl. nuisance covariates). All design heat maps use 'YDir','reverse' (observation 0 at top) and axis tight. import_onsets(): handles FSL/tabular files (CSV/Excel/table) with onset, duration, and a condition column that may be a string name OR an integer event-type code, and SPM-style cell arrays of onsets, durations, and parametric modulators (one cell per condition / event type). validate_object: diagnostics template extended with the new fields. Tests: add role-indicator, interest-only-diagnostics, and import_onsets variant tests. 19/19 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map.plot_design: render actual basis-convolved regressors per event type Replace the plotDesign call (which re-convolved onsets with its own canonical HRF and ignored the model's basis set) with direct rendering of the object's built design matrix. Each event type of interest gets its own panel showing its actual regressor(s) -- one line per basis function, so multi-basis designs (e.g. spline or HRF+derivatives) display correctly -- with filled boxes marking each event and its duration (box-drawing borrowed from drawbox). The full design-matrix heat map (incl. nuisance covariates, YDir reverse + axis tight) is shown alongside. Per-condition columns are located by basis-function counts at the front of the design (verified A->cols 1:4, B->col 5 for a 4-spline + canonical mix). 19/19 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map/design: orthogonal contrast sets, design efficiency, import interest/nuisance flagging create_orthogonal_contrast_set: new method on @glm_map and @fmri_glm_design_matrix. Uses create_orthogonal_contrast_set.m to assign a set of mutually orthogonal, zero-sum contrasts spanning the regressors of interest (glm_map.wh_interest / design xX.iH), with placeholder names; nuisance and intercept columns get zero weight. If no regressors of interest are defined, it explains how to set them and errors gracefully. diagnostics(): now reports design efficiency for contrasts via calcEfficiency.m -- using entered contrasts, or an auto orthogonal set spanning the regressors of interest when none are entered. When no regressors of interest are defined and no contrasts exist, efficiency is skipped with an informative note (other diagnostics still run). Efficiency fields added to the diagnostics struct / validate_object schema. Import interest/nuisance flagging: events entered via import_onsets become the of-interest regressors (the design's H partition); covariates of no interest become nuisance. import_SPM now derives the interest/nuisance/intercept partition from SPM event semantics (task regressors named '...*bf(...)', session 'constant', everything else nuisance) -- SPM's own xX.iH is typically empty for event-related first-level models -- preserving SPM's original indices under xX.spm_*. glm_map.import_onsets: new pass-through method that imports into the wrapped fmri_glm_design_matrix (FSL/tabular or SPM-style cells), bootstrapping the design from 'TR'/'nscan' if needed, then builds when enough info is available or prints what is missing and how to attach it. Tests: add orthogonal-contrast-set, efficiency, and import/SPM-flagging tests. 22/22 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map: scaled condition number, unit-norm heat map, obs/regressor summary, time axis Fix two reported issues and improve summaries: - diagnostics: the condition number is now computed after scaling each column to unit L2 norm (the Belsley scaled condition index). It is scale-invariant like VIF, so a well-conditioned design no longer reports a huge condition number that was purely an artifact of the tiny event regressors vs the unit intercept (e.g. 329 -> 1.74, consistent with VIFs ~1). - plot_design heat map: scale each regressor column to unit norm for display, so small-amplitude event regressors are visible instead of being washed out to black by the large-norm intercept. Event-panel x-axis labelled 'Time (seconds)' (time base is always seconds). - disp() and summary() now report the number of observations and regressors with the of-interest / nuisance / intercept breakdown, and list each regressor's role. (g.X already exposes the design matrix, reading through to design.xX.X; verified by test.) Tests: add a scaled-condition-number regression test (raw cond large, scaled small, VIFs low, g.X == design.xX.X). 23/23 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map: rename diagnostics method -> run_diagnostics (resolve property/method name clash) glm_map had both a .diagnostics property (the results struct, mirroring fmri_data.regress out.diagnostics) and a diagnostics() method that computes it. MATLAB tolerates this (property via dot-access, method via function syntax, as statistic_image does with threshold), but g.diagnostics(args) silently resolves to property indexing and errors, which is a footgun. Rename the verb to run_diagnostics and keep the noun .diagnostics property (the load-bearing name: it mirrors regress's out.diagnostics and is read in 60+ places). Update the internal callers (fit, replace_basis_set), help examples, the workflow doc, CLAUDE.md, and tests. Call as: g = run_diagnostics(g, ...); results are stored in g.diagnostics. 23/23 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * glm_map: VIF/cVIF plots in plot_design, high-pass-filter assessment in run_diagnostics, workflow sec 13 plot_design: when diagnostics have been computed, draw a VIF figure in the scn_spm_design_check style -- orange markers with severity reference lines at VIF = 1/2/4/8 -- showing VIFs of the regressors of interest in the full design and (when nuisance indicators exist) without nuisance covariates, plus contrast VIFs (cVIF) when present. Falls back to full-design VIFs only. run_diagnostics: for timeseries designs (is_timeseries) only, compute the cumulative power by frequency for each regressor of interest and each contrast (borrowing cumulative_power / fft_calc from scn_spm_choose_hpfilter), and recommend a high-pass filter cutoff that keeps the maximum low-frequency variance loss below 5% -- separately for regressors and for contrasts. Stored in .diagnostics.hpfilter and printed in the report (with the more conservative of the two as the suggested cutoff). The scaled condition number is unchanged. docs/workflows section 13: add a simple [1 -1] A-vs-B contrast and show plot_design before vs after run_diagnostics (the VIF figure appears after), and display the recommended HP filter. All method calls use run_diagnostics(). Tests: add an HP-filter / cumulative-power test (timeseries vs group). 24/24 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * onsets2fmridesign: fix R2024b crash from stale pad() call (builtin shadows canlab_pad) CanlabCore's pad.m was renamed to canlab_pad.m, but onsets2fmridesign still called pad() in the TR-resolution delta fallback. In R2024b+, 'pad' resolves to MATLAB's builtin string pad, which errors ("First argument must be text") on the numeric onset vector. On CI (no Image Processing Toolbox), padarray is unavailable so the fallback was taken and crashed -- failing canlab_test_glm_map/test_event_design_matches_onsets2fmridesign, which calls onsets2fmridesign as a reference (the whole function runs even though the test uses only the X output). Replace the padarray/pad try-catch with an explicit one-sided zero-pad to the run length (matching canlab_pad), removing the toolbox/version-fragile dependency. X is unaffected (computed before this block). 24/24 glm_map tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix remaining stale pad() callers -> canlab_pad (builtin pad shadows renamed pad.m on R2024b+) CanlabCore's pad.m was renamed to canlab_pad.m; three callers still used pad(), which now resolves to MATLAB's builtin string pad and errors on numeric input in R2024b+ (same root cause as the onsets2fmridesign fix). - smooth_timeseries.m: pad -> canlab_pad (count semantics unchanged; correct). - nonlin_parammod_predfun.m (hrf): pad -> canlab_pad; the 2nd arg is a vector, so canlab_pad pads hrf to its length (unchanged behavior). - nonlin_parammod_predfun.m (yhi): the call passed a *target* length to pad, but canlab_pad takes a *count* of zeros; under the surrounding `if length(yhi) < runDuration` the intent is to pad yhi to runDuration, so use canlab_pad(yhi, runDuration - length(yhi)). Smoke-tested: smooth_timeseries (50->50) and nonlin_parammod_predfun (yhi length == runDuration) both run without the pad crash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flows Add full documentation for the glm_map object and two end-to-end workflows in the same style as the existing object pages and the ROI-extraction workflow. - docs/glm_map_methods.md: object reference (intro, first-level time-series vs second-level static-design modes, ways to create, design notes, methods by area, properties, workflow links). Linked from Object_methods.md (hierarchy + class table). - docs/workflows/glm_map_first_level_* : roadmap + how-to for single-subject event-related time-series modeling — importing onsets (FSL tables, SPM-style cells, SPM.mat), basis sets, simulating data, contrasts, diagnostics (VIF/cVIF with/without nuisance, efficiency, high-pass filter), AR fitting, thresholding/visualization. Synthetic 3-run, 4-event design with nuisance covariates; embedded design/VIF/montage figures + a graphical roadmap (.pptx/.png via codemap_lib). - docs/workflows/glm_map_second_level_* : roadmap + how-to for group analysis on emotionreg contrast images — object creation via fmri_data.regress or the glm_map API, outlier handling, WM/CSF covariates and normalize_gm_by_wm_csf, OLS vs robust, diagnostics, thresholding/visualization. Embedded figures + graphical roadmap. - Linked both workflows in Workflows.md; added the two codemap builders under docs/_codemap_tools; removed the superseded docs/workflows/regression_with_glm_map.m and updated the CLAUDE.md pointer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…signs)
fmri_data.regress auto-adds an intercept when none is detected, which (a) broke
contrasts on designs whose columns already include intercepts (size mismatch),
and (b) added a spurious all-ones column to multi-run first-level designs that
already carry per-run baseline columns.
- glm_map.fit now prepares the design before fitting: it enforces a single
overall intercept as the LAST column via intercept.m semantics ('which'/'end')
-- an existing intercept is moved (never duplicated), one is added if absent,
and multi-run designs with per-run baseline columns are left as-is -- then
passes 'nointercept' to regress. Regressor names stay aligned, and the
prepared design is recorded on direct-mode objects so g.X / g.regressor_names
match the fit. The model is fit first (all betas), then contrasts applied.
- Contrast vectors that are one or more elements short are padded with 0 weights
for the intercept, in both add_contrasts (entry time) and fit (final width),
so contrasts can omit the intercept entry. add_contrasts errors only when a
contrast is WIDER than the design.
Supporting fixes (exposed by multi-run designs):
- VIF.m: when it adds a temporary intercept (no all-ones column present) and a
contrast is supplied, pad the contrast with a 0 weight so cVIF's width matches
the augmented design (was a dimension-mismatch error).
- fmri_glm_design_matrix.build: give multi-run designs complete, UNIQUE column
names -- run-prefixed interest/covariate names ('Sn2 Cue-BF1') plus per-run
baseline names ('Sn2 constant') -- so repeated condition names no longer
collide (regress's contrast table requires unique names) and every column is
named.
Tests: add a multi-run contrast-fit regression test and an add_contrasts
size-handling test (short pads, too-long errors). Docs note the intercept/
contrast handling and that an intercept name should be supplied. 25/25 glm_map
tests pass; regress tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ition) Add per-run nuisance covariates to test_fit_multisession_contrast and assert the interest/nuisance/intercept partition (4/6/2), unique complete names, and a contrast fit with no spurious intercept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First-level roadmap/how-to + glm_map fit: - Recommend AR(4) over AR(1) for time-series error models (better captures BOLD serial autocorrelation; ~ARMA(1,1)). - fit() now checks AR dependencies up front (fgls/Econometrics, aryule/Signal Processing) and errors with a clear, actionable message if either toolbox is missing, instead of failing mid per-voxel fit. - Document the AR toolbox dependencies in fit.m, the roadmap, the how-to, and the object page. - Note that g.is_timeseries = true also switches run_diagnostics into time-series mode (HP-filter cumulative-power assessment). - Reference fmri_data.sim_data for synthetic signal+noise data (with its real single-predictor interface), alongside the X*betas+noise construction used for a custom convolved design. - Regenerate the first-level roadmap PNG/PPTX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fmri_data.sim_data can now generate data from an explicit design matrix
instead of (or in addition to) its built-in region mean-shift / correlation
model:
- 'design' X [n images x k regressors]; sets the number of images.
- 'betas' [k x v] coefficient maps -> data = (X*betas)' + noise, or
[k x 1] one response placed in the true-signal region.
Errors clearly if betas is the wrong size or missing. The default path is
unchanged (refactored the region-mask resolution into a local helper shared
by both paths). Documented with examples in the help block.
Use it in the first-level workflow docs as the one-line synthetic-data step:
sim = sim_data(template, 'design', g.X, 'betas', betas_true);
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a single-line region-name readout to canlab_orthviews that names the atlas region under the crosshair, updating live on click/drag/reposition. On first open the viewer tries to attach the canlab2024 atlas; if it can't be loaded, the strip shows a one-line hint on how to attach one with 'AddAtlasLabel'. Lookup uses the atlas's own affine (dense int16 label volume cached on the figure), so it is O(1) per redraw and independent of underlay space. New commands: 'AddAtlasLabel' / 'RemoveAtlasLabel'. Displaying an atlas object also makes it the readout source. Display tweaks: - Coordinate titles 10->14pt; region label 11->14pt bold. - Move the region label into a clear band above the colorbar so the two no longer collide. - Render coordinate titles as explicit text children instead of the axes Title (the panels use `axis off`, which suppressed the Title in the export/print path). Tests: 6 new cases (strip present, no-atlas hint, remove hides strip, real-atlas lookup correctness, atlas-display attaches); panel-count assertion now ignores the tagged helper axes. 40/40 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g9Q2z55GY7EfMXoCkg9Px
Add an atlas region layer to the canlab_niivue web viewer and a lightweight statistic_image.orthviews_niivue convenience method. Viewer (assets/canlab_niivue_viewer.js, canlab_niivue.m): - Attach an atlas (on by default; 'noatlas' to disable) as a hidden int16 NIFTI_INTENT_LABEL layer. The crosshair readout names the region under the cursor, and only that single region is highlighted via an "Atlas region" dropdown (Outline default / Shaded / Off), placed in the controls bar. - Atlas written with pinfo=[1;0;0] so raw voxel value == label index (SPM's default int rescaling broke NiiVue's label-LUT indexing). - Single-region highlight = mask colormapLabel LUT alpha to the current code; fixed high-contrast color; bolder outline via a small, clearly-marked, graceful-fallback patch to the vendored niivue.js (atlasOutlineWidth). - Folder bundles are auto-served over a local HTTP server on open (ES-module imports are blocked on file://). New method: - @statistic_image/orthviews_niivue.m — thin wrapper over canlab_niivue that writes a standalone page to a temp folder and opens it in the browser (the web counterpart to orthviews). Sample/emotionreg_ttest.html regenerated to include the atlas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface the interactive viewers throughout the documentation: - Object_methods.md: add canlab_orthviews + canlab_niivue to the group t-test one-liner block; expand the "Visualizing images and results" bullets and the helpers table (atlas region readouts; orthviews_niivue shortcut). - Per-class "Display and visualization" sections (image_vector, fmri_data, statistic_image, region, atlas): add an "Interactive viewers" callout; statistic_image also lists the new orthviews_niivue method. - Workflows.md: fuller visualization list (montage / surface / canlab_results_fmridisplay / table, plus the interactive viewers). - glm_map workflow how-tos + roadmaps: viewer callouts in the visualize steps. Second-level how-to embeds a live NiiVue viewer of its own result and shows surface(t, 'foursurfaces_hcp') with a static figure. - canlab_niivue_guide.md: atlas region readout/highlight, auto-served folder mode, 'atlas'/'noatlas' options, orthviews_niivue shortcut; demo regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* bug fixes to canlab scripts, and completely revamped searchlight_disti * finished searchlight_disti_Lukas function --------- Co-authored-by: Vivek Sagar <VivekSagar2016@u.northwestern.edu>
* Integrate two old_to_integrate tests; harden test discovery 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> * Convert 5 xval_* tests to matlab.unittest; move to predictive_model/ 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> * Convert predictive_model_unit_test to matlab.unittest (minimal nboot/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> * predictive_model API test: mask to sparse gray-matter ROI for speed 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> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…our pipeline (#82) * fmridisplay: stateful handle class with live layers + controller widget Converts fmridisplay from a value class storing baked graphics into a handle class whose blob layers retain their source data and render options, so they can be re-rendered in place. Implements Phases 1-2, 5, and 6 of VISUALIZATION_OVERHAUL_NOTES.md (the deep render-pipeline unification and engine consolidation remain deferred, per doc section 11). - classdef fmridisplay < handle. A full call-site audit found no value->handle aliasing hazard; every site uses the safe `o2 = method(o2, ...)` self-reassign idiom, so back-compat is preserved. - addblobs: each activation_maps{} layer now retains source_region, source_object (e.g. a statistic_image, enabling re-threshold up and down), render_args, wh_montage/wh_surface, applied_threshold. - New @fmridisplay methods: refresh, rethreshold, set_colormap, set_opacity (live in-place re-render on montage views), and controller(obj) (uifigure control panel bound to the instance). - Fix: a bare 'colormap' flag (montage(r, o2, 'colormap')) was a pre-existing crash in render_blobs (which requires an n x 3 matrix); addblobs now strips it before forwarding. Un-breaks canlab_help_5_regression_walkthrough. - New tests: Unit_tests/image_vector/canlab_test_fmridisplay_handle.m. Verified: full unit tier 157 pass / 0 fail / 2 skip; walkthroughs 10/10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: graceful, informative errors for addblobs misuse Replaces cryptic MATLAB errors when addblobs is called incorrectly: - addblobs(o2) with no region -> instead of "Not enough input arguments" (MATLAB:minrhs), explain that a region/clusters second argument is required and show the montage(t) + addblobs(o2, region(t)) usage. - addblobs(o2, badtype) -> name the class received and how to convert, with a stable error id (fmridisplay:addblobs:badRegion). - addblobs(img) on image data (forgot to build a display) -> a new @image_vector/addblobs stub replaces "Undefined function 'addblobs' for input arguments of type 'statistic_image'" with a redirect to canlab_results_fmridisplay/montage then addblobs(o2, region(img)). (Note: surface(o2) on an fmridisplay is valid — it adds a surface view — so it is intentionally left working.) Verified: viz unit + help_examples 42/0, walkthroughs 10/10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: unify surfaces as managed views; silence per-slice readout Surfaces are now first-class managed views (design notes 4.2 / 7.4), so blobs act across montages and surfaces together: - Factor surface drawing out of addblobs into a shared render_layer_surfaces method that derives colors from each layer's stored render_args. addblobs, surface (pull-in), and refresh all route through it. - surface(o2) registers on the SAME handle and pulls in any blob layers that already exist, so a surface added AFTER addblobs still shows the blobs. - refresh (hence rethreshold / set_colormap / set_opacity) re-renders surfaces too, not just montages. - Multi-surface keywords 'foursurfaces' / 'foursurfaces_hcp' / 'foursurfaces_freesurfer' add the four canonical views (L/R lateral + medial) as separate registered views on the same object, instead of being ignored. - Silence the per-slice "N voxels displayed, M not displayed on these slices" readout in render_blobs (commented out, with a re-enable note). - Tests: +4 surface-integration cases in canlab_test_fmridisplay_handle (13/13 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: fix surface re-paint/erase and route opacity to surfaces Fixes two issues with surfaces as managed views: - Re-painting an already-colored surface (addblobs twice, addblobs after registering a new surface, or refresh) used to poison the saved restore data: render_on_surface stashes the *current* FaceVertexCData as the "gray" to restore, so a second paint saved the blob colors. removeblobs then could not clear the surface. render_layer_surfaces now restores the saved gray before re-painting a previously-painted surface, so removeblobs reliably returns every registered surface to anatomy. - set_opacity (and the controller opacity slider) now reaches surfaces: render_layer_surfaces applies a constant 'transvalue' as the surface patch FaceAlpha. set_colormap already propagated via render_args. Both are consequences of surfaces being unified views: add/remove/recolor/ opacity now act on montages and every registered surface together. Tests: +2 regression cases (repeat-paint clears, opacity dims surface); canlab_test_fmridisplay_handle 15/15. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: robust to closed windows, layer-aware rethreshold, live controller Addresses several interactive-use issues: - Closed figure windows no longer crash addblobs/removeblobs/refresh. New prune_dead_views drops montage/surface views whose figures were closed (with a short note); surface erase/render also filter to live handles. - rethreshold is now layer-aware and the montage path keeps the real source: image_vector.montage passes the original object via a new addblobs 'source_object' override, so a montage(t) layer retains its statistic_image and re-thresholds by p-value. rethreshold dispatches by source class (statistic_image -> p-value, fmri_data/mask -> raw value, region -> magnitude cutoff) and honors 'layers'. - Controller is now live: rebuilt in place on repeat calls, auto-refreshed by addblobs/removeblobs (update_controller via a Transient controller_handle property), each control initialized from the layer's current state, and the threshold field is type-aware (p-value vs raw). HandleVisibility='on' so `close all` closes it. Tests: +4 cases (closed-figure prune, montage source class, raw rethreshold, controller auto-update + state); canlab_test_fmridisplay_handle 19/19. checkcode clean; canlab_test_display 4/4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: plan deferred bypass-method -> managed object conversion (surface/orthviews) Record in VISUALIZATION_OVERHAUL_NOTES.md section 11 that image_vector.surface and image_vector.orthviews still return raw handles (unlike montage), and the plan to make them return a managed fmridisplay: opt-in flag first (~24 call sites use the handle return), mirroring image_vector.montage with 'source_object' retention. Captures T. Wager's keyword-parity idea: pass the direction keyword + remaining varargin through to addbrain (as the hcp surfaces already do) to expose addbrain's full surface set with minimal new code, with a caveat that the parametric surface_cutaway types (coronal_slabs, ycut_mm) live in region.surface and would need a second step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: refine deferred surface-keyword plan (addbrain already covers cutaways) Correct an earlier caveat: addbrain.m:844 already dispatches the cutaway/slab keywords (coronal_slabs_4, *_cutaway, slabs) to canlab_canonical_brain_surface_cutaways, so the planned addbrain pass-through covers cutaways in one mechanism (no separate step). Record that supporting all addbrain keywords in @fmridisplay/surface needs bare-keyword parsing + control- opt stripping + post-render view/lighting handling (moderate, not piecemeal), and that making the default surface 'left_cutaway' (in @fmridisplay/surface and image_vector.surface) is a back-compat-affecting decision for the §7.4 phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: surface legend fix + remove_legend + controller upgrades Legend / colorbar: - render_on_surface now parents the colorbar legend to the SURFACE's figure instead of gcf, so it no longer lands on the montage window. - New remove_legend(obj) method deletes surface colorbar legends (keeps blobs). Controller: - Command-line rethreshold / set_colormap / set_opacity now sync an open controller: controls update IN PLACE when the layer count is unchanged (rebuild only on addblobs/removeblobs), so the threshold/colormap/opacity fields reflect command-line changes without destroying controls mid-callback. - Each GUI action echoes the equivalent code line to the command window (e.g. `han = set_colormap(han, 'color', [1 0 0]);`), using the bound variable name (inputname, preserved across rebuilds). - More colormaps (split / warm / cool / winter) plus a 'solid colour…' entry that opens the built-in uisetcolor palette. - Cosmetic: light-green figure, bound-variable name in the title bar, opacity slider tick marks removed. Deferred (documented in section 11): per-layer surface visibility (Visible toggle hides montage blobs only) — needs a visible flag respected across all surface-draw paths; do it with the multi-layer compositing work. True perceptual colormaps (inferno/viridis) also deferred. Tests: +3 (legend on surface figure, remove_legend, rethreshold->controller sync); canlab_test_fmridisplay_handle 22/22; checkcode clean; display 4/4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: hide internal helper methods from the public surface Declare the internal helpers activate_figures, prune_dead_views, refresh, render_layer_surfaces, and update_controller as Hidden (signature-only block in fmridisplay.m; bodies stay in their @fmridisplay/*.m files), so they no longer appear in methods(obj) / tab-completion. Hidden rather than Access=private so existing external callers keep working: activate_figures is used by help-example scripts (c_univariate_contrast_maps*.m) and refresh by the unit tests. All user-facing methods remain public. Test: +1 (public vs internal method surface, and internal methods still callable); canlab_test_fmridisplay_handle 23/23. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: surface colormaps match montage; foursurfaces gets its own figure Colormap fix (render_layer_surfaces): - render_on_surface does not understand maxcolor/mincolor, so warm/cool/winter (max/min colour ramps) and solid colours were silently dropped on surfaces and fell back to the default split hot/cool (blue negatives under "warm"; cool/winter not updating). Now the layer's colour spec is translated into pos_colormap/neg_colormap ramps (surface_colormaps_from_args) and the tokens render_on_surface can't read are stripped. Solid colours map the same colour on both signs (no stray default-cool negatives). Verified: warm surface map B=0.0, cool/winter produce distinct surface colormaps. (Residual: the split hot/cool map still differs slightly montage-vs-surface due to the two engines' colormap construction; full match is the deferred render-pipeline unification.) Figure fix (surface / add_multiple_surfaces): - surface(o2, 'foursurfaces*') opened the 2x2 layout in the current figure, so without a preceding `figure;` it drew over the montage and showed nothing. It now reuses an empty current figure or opens a new one. Tests: +2 (surface colormap follows max/min colour; foursurfaces uses its own figure); canlab_test_fmridisplay_handle 25/25; checkcode clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: foursurfaces layout — white figure, axes off In add_multiple_surfaces, set the layout figure Color to white and turn the four surface axes Visible/Color off, so no axes box or grey background shows behind the surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: foursurfaces — turn off all axes in the layout figure The 2x2 layout figure also contained a stray default-position axes (created during rendering) that showed a visible frame behind the surfaces. Turn off Visible on every axes in the layout figure (surfaces, colorbar axes, and the stray one); surface patches and colorbars are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: fix legend after set_colormap; add 'mango' colormap; plan update - legend() no longer errors after set_colormap switches a layer to a single colormap. Root cause: refresh re-rendered the montage but did not update the layer's legend-defining .mincolor/.maxcolor, so a stale 2-row split mincolor made legend take the split path and index past the 2-element cmaprange. refresh now keeps .mincolor/.maxcolor in sync (as addblobs does), and legend has a defensive guard: a split-shaped mincolor without 4 cmaprange anchors falls back to a single map instead of erroring. - Controller: new 'mango' colormap option -> set_colormap(obj, 'splitcolor', {[.5 0 1] [0 .8 .3] [1 .2 1] [1 1 .3]}); the dropdown reflects it and the action echoes the code line. - Plan: record the agreed long-term colour architecture (one central value->colour mapping module feeding slice and surface renderers; surfaces in true-colour RGB; subsumes the unify-pipeline / compositing / RGB-surface items). Tests: +1 (legend after set_colormap); canlab_test_fmridisplay_handle 26/26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay controller: redesign + mango/seafire; legend single-range to plan Controller: - ~2x wider, two compact rows per layer: Opacity slider + Visible on row 1; a type-aware threshold slider + Colors dropdown on row 2. Bigger text, shorter re-render button, fixed-width opacity/colors controls. - Threshold is now a slider: p-value slider with ticks at .001/.005/.01/.05/.1 for statistic_image layers; raw |x| slider anchored at 0 and the 99.9th percentile of |data| for fmri_data/region layers. - Colormap menu: rename 'mango' -> 'split (mango)' and add 'seafire' (set_colormap splitcolor {[0 0 1] [.2 .8 .6] [1 .4 .1] [1 1 0]}); dropdown reflects both and echoes the code line. Plan: recorded that the single-range colorbar legend (warm/cool should be ONE colorbar spanning zero, not split) is part of the central value->colour mapping work — render_blobs' positive-percentile cmaprange and render_on_surface's pos+neg colorbars both stem from the duplicated range/colorbar logic. Tests: +1 (threshold slider + mango/seafire); canlab_test_fmridisplay_handle 27/27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay controller: narrower single-column redesign, log p-slider, swatches - Revert to a narrower (460px) single-column layout (the wide 2-col version cut off the colormap dropdown). Each layer panel: colormap title stripe, opacity slider, threshold slider, Colors dropdown + live preview swatch, Visible. - Threshold p-value slider is now LOG scale (value = log10 p), ticks at .001/.005/.01/.05/.1 so .05-.1 sit close together and .001-.005 spread out. Raw |x| slider unchanged (linear, 0..99.9th pct). - Colormap preview swatch next to the dropdown and a full-width title stripe, both updating live with the layer's colormap. - Footer groups Re-render / Remove legend / Close buttons. - Fonts +4pt. Window colour set by FIG_COLOR at the top of controller.m (now [1 .5 0]). - seafire colours changed to {[.4 0 1] [.2 .9 .5] [1 .3 .2] [1 .8 .4]}. Tests updated for the log slider; canlab_test_fmridisplay_handle 27/27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: add squeeze_figure to remove montage figure white space A montage figure is typically much taller than the slices it contains (they occupy a ~40% horizontal band, leaving big top/bottom white margins). squeeze_figure(obj) shrinks each montage figure's height and remaps the slice axes to fill it, scaled so each slice keeps its pixel size, aspect ratio, and horizontal placement — only the empty vertical margins are removed. Opt-in (doesn't change montage defaults); skips figures that also contain surfaces. Verified: 670 -> 258 px, slice pixel size preserved to 99.9%. Test: +1 (squeeze_figure preserves slice size); canlab_test_fmridisplay_handle 28/28. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: mango default, toggle-legend, remove-layer, swatch picker, p->0 - mango is now the DEFAULT blob colormap (addblobs default split colours). - Controller footer: 'Remove legend' -> 'Toggle legend' (on/off); dropped the Close button. - Per-layer 'Remove layer' button + new remove_layer(obj, k) method (removes a single layer and re-renders remaining layers on surfaces; removeblobs still removes all). - Clicking the colormap swatch opens the colour picker (set/re-pick a solid colour; works around uidropdown not re-firing on the already-selected item). - p-value threshold slider extends below .001 down to ~0 (1e-6 floor, '~0' tick). - Confirmed rethreshold already supports cluster-extent 'k' (e.g. 'k', 50), passed through to threshold(); verified it shrinks blobs as k grows. Tests: +2 (default mango, remove_layer); slider-range test updated; canlab_test_fmridisplay_handle 30/30. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * viz: add central canlab_colormap value->RGB component (step 1 of colour pipeline) First step of the central value->colour mapping (VISUALIZATION_OVERHAUL_NOTES.md): a single source of truth that turns data values into RGB for one colormap type, independent of the renderer, so montage/surface/legend can agree exactly. canlab_colormap (fmridisplay_helper_functions/canlab_colormap.m): - static factories single / split / solid / indexed, plus from_render_args(args, cmaprange) bridging the existing layer render_args. - map(values) -> Nx3 RGB, matching render_blobs (split: minpos->maxpos for positives, maxneg->minneg for negatives; single: mincolor->maxcolor; solid: constant; indexed: round+index; uncoloured -> NaN). - legend_samples(n) -> one colorbar's samples (split spans zero in a single bar, the basis for fixing the single-range legend); lut(n) -> nx3 table. Tests: Unit_tests/visualization/canlab_test_canlab_colormap.m (14 cases) all pass; no regression in the fmridisplay handle suite (44/44 total). Not yet wired into the renderers — surfaces -> true-colour RGB is the next step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * viz: render surfaces in true-colour RGB via central canlab_colormap (step 2) render_on_surface gains an opt-in 'truecolor', <canlab_colormap> option: after it samples per-vertex values, it colours each vertex via map() as N x 3 RGB, composites over the anatomy gray (anatomy_to_rgb preserves curvature shading) for uncoloured vertices, and sets FaceVertexCData=RGB with CDataMapping direct. Default (option absent) is unchanged, so the @image_vector/surface bypass path still uses indexed colour. render_layer_surfaces builds canlab_colormap.from_render_args(args, cmaprange) and passes it, so the managed display's surfaces now match the montage colours exactly (warm = red->yellow, no blue negatives; verified) and removeblobs/erase still restore gray. Surfaces are now N x 3 true colour, which is the basis for multi-layer compositing and per-layer surface visibility. Tests: +1 (surface true-colour via central map); canlab_test_fmridisplay_handle 31/31; canlab_colormap 14/14; display 4/4 — no regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * viz: fix black uncoloured surface areas on first true-colour render A fresh surface is displayed via a solid FaceColor ([.5 .5 .5]) but its FaceVertexCData can hold a stale dark value (~0.13). The true-colour path used that stale CData as the anatomy gray, so uncoloured areas looked near-black on the first render (re-render worked because eraseblobs had rewritten the CData to a proper gray). Fix: anatomy_to_rgb now prefers the surface's solid FaceColor (the gray actually on screen) when FaceColor is a 1x3 RGB triplet, and render_on_surface saves that proper gray to UserData so eraseblobs restores it too. Test: +1 (first-render uncoloured areas are gray, not black); canlab_test_fmridisplay_handle 32/32; display 4/4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * viz: multi-layer surface compositing in true-colour RGB (step 3) Now that surfaces are true-colour RGB, layers composite instead of overwriting. - render_layer_surfaces paints a layer onto the CURRENT surface colours (the erase-before-repaint is gone), so a new layer's coloured vertices win and its uncoloured vertices keep whatever is underneath (lower layers / gray). The anatomy gray is saved once (save-once UserData) so erase/removeblobs still restore gray, not a composite. - New composite_surfaces(obj[, wh_surface]) does a full recompute: reset each surface to gray, then repaint every layer bottom->top. refresh (hence rethreshold/set_colormap/set_opacity) and remove_layer use it; addblobs composites a single new layer on top incrementally. Verified: a broad green mask under narrower split stats shows green where the stats don't cover and split on top; remove_layer recomposites the remainder; removeblobs restores gray. Tests: +1 (multi-layer compositing + remove_layer recomposite); canlab_test_fmridisplay_handle 33/33; display 4/4 — no regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * viz: per-layer surface visibility + opacity blending + sign-aware default colormap - Surfaces now BLEND layers by opacity (render_on_surface 'truecolor_alpha'): a layer's coloured vertices = a*colour + (1-a)*below, so a semi-transparent layer lets the one below show through, matching montages. render_layer_surfaces passes the layer's transvalue as the alpha (no more whole-patch FaceAlpha). - Per-layer surface visibility: a layer with visible==false is skipped in render_layer_surfaces/composite_surfaces; the controller's Visible toggle sets the flag, hides the montage blobs, and recomposites the surfaces. - Sign-aware default colormap (addblobs, only when no explicit colour given): binary mask -> solid; positive-only -> warm; negative-only -> cool; mixed +/- -> mango split. Propagates to montage and surface via the central map. Tests: +2 (surface visibility, sign-aware default), opacity test updated for blending; canlab_test_fmridisplay_handle 35/35; display 4/4; colormap 14/14. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay/surface: addbrain keyword pass-through + centralized composites @fmridisplay/surface no longer hard-codes a per-direction if/elseif switch. It parses the reserved pairs (direction/orientation/axes), accepts a bare direction token (surface(o2,'thalamus')), and passes remaining args through to addbrain(dir, extra{:}) — so any eligible addbrain surface/region keyword now works in the managed display automatically. addbrain owns the default (lateral) view + lighting per keyword; surface only special-cases bigbrain left/right (different addbrain surface) and mirrors the azimuth 180 deg for a medial view. Managed multi-surface layouts keep identical view angles; a couple of standalone lateral azimuths move to addbrain's surface-specific convention. Per T. Wager's suggestion, the brainstem left/right and caudate left/right composite handle-sets move from surface.m into addbrain (alongside limbic/bg/ *_group), with their oblique 3D views and help/region entries. help fmridisplay/surface now lists the common directions, the bare-token form, and points to help addbrain for the full keyword list. Tests: +test_surface_addbrain_passthrough, +test_surface_medial_flips_azimuth (handle suite 37/37; display 4/4 unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay/surface: informative error for unknown addbrain keyword When a bare direction token is not a recognized addbrain keyword, addbrain errors with a terse 'Unknown method.'. surface() now catches that and raises fmridisplay:surface:unknownDirection naming the offending token and pointing to `help addbrain` (e.g. clarifies cutaways are 'left_cutaway'/'right_cutaway', not 'cutaway_left'). All other addbrain errors pass through unchanged. Test: +test_surface_unknown_keyword_informative_error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay: host the legend in the controller; figure legends off by default The colour stripe in each controller layer panel is now the legend: taller, with numeric end labels underneath (from the layer's cmaprange, 1 sig fig). Split (+/-) maps label both extremes with 0 in the centre; single ramps label the two ends. Labels refresh on rethreshold/recolor. Opacity/threshold/colors/ visible rows shifted down to make room. Figure colorbars are now OFF by default: @image_vector/montage no longer draws the montage-figure legend unless 'legend' is passed; render_layer_surfaces / composite_surfaces take a show_legend flag (default false -> 'nolegend' to render_on_surface) so surfaces have no colorbar by default. The fmridisplay constructor and montage now accept 'legend'/'nolegend' without warning. Toggle legend now toggles colorbars on the montage/surface FIGURES (for export) and works both ways: ON targets the montage figure explicitly (fixing the bug where legend() drew into the controller uifigure via gcf and never reappeared) and re-renders surfaces with colorbars; OFF removes them. State in controller appdata; montage legend axes tagged for clean removal. render_layer_surfaces gains a 4th arg (show_legend); its methods(Hidden) declaration in fmridisplay.m is updated to match (requires a fresh class load). Tests: +test_controller_shows_legend_labels, +test_montage_figure_legend_off_by_default; surface-legend tests force legends on; pull-in test checks vertex colouring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay/surface: fix mixed isosurface+isocaps (coronal_slabs_4) error surface() did `if strcmp(h(1).FaceColor,'interp')`, which errors ("Dot indexing is not supported for variables of this type") when addbrain returns OLD-STYLE numeric handles (doubles) instead of graphics objects -- e.g. coronal_slabs_4 returns 8 patch handles. It would also have blanket-grayed the whole set, destroying the isocaps that use 'interp' to show the anatomy cross-section. Replace with a per-object loop using get/set (works on numeric handles too): keep any 'interp' colouring, set the rest to flat gray, make all opaque. Test: +test_surface_mixed_patch_handles. QA: handle 41/41, display 4/4, colormap 14/14 (interactive). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * render_blobs: route montage colours through central canlab_colormap render_blobs no longer re-derives split/single colours inline. It builds one canlab_colormap (split or single) from the parsed colours + cmaprange before the slice loop and colours each slice via a shared central_map_slice helper -- the same mapping render_on_surface already uses -- so montage and surface colours come from a single implementation. Verified pixel-identical to the old renderer on split/single/solid sample montages (displayed-colour maxdiff: single 0, split 4e-12 float-eps, solid 0). The only raw-CData differences were out-of-range values the old code stored at invisible alpha=0 voxels, which the central map correctly clamps. Retired the now-dead duplicated colour logic: splitcolor_Z_to_slicecdat and the cdat2/cdatminneg/cdatmaxneg colour matrices. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * canlab_colormap: add colorbar_ramp; route controller legend stripe through it Adds canlab_colormap.colorbar_ramp(n) -- the continuous legend-bar ramp with no threshold gap (unlike map/legend_samples): for split it is the negative ramp (minneg->maxneg) then the positive ramp (minpos->maxpos), reading extreme-neg ... 0 ... extreme-pos. The controller's per-layer colour stripe + preview swatch (the in-app legend) now come from from_render_args(args).colorbar_ramp(64) instead of bespoke colormap_tor calls, so the stripe, the rendered blobs, and surfaces share one colour source. +2 colorbar_ramp tests (colormap suite 14->16). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmridisplay colour: single-range legend fix + perceptual colormaps Single-range colorbar legend: - get_default_cmaprange: a single-ramp (warm/cool) map on SIGNED data now spans the full robust range THROUGH ZERO ([prctile2 prctile98]) instead of a positive-only [p10 p90] that hid negatives. Positive/negative-only data and split maps are unchanged. - render_on_surface gains a 'single_colorbar' flag (set by render_layer_surfaces from the central-map type) so single-ramp/solid/continuous maps draw ONE colorbar over the full range, not a misleading pos+neg pair. Split maps still get two. Verified: warm range [2.82 4.86] -> [-2.98 6.07]; surface colorbars 2 -> 1; split/solid montage colours unchanged. Perceptual colormaps: - canlab_colormap gains a 'continuous' type (value range mapped continuously through an n x 3 LUT, distinct from atlas-style 'indexed'). - new canlab_perceptual_colormap(name, n): viridis/inferno/magma/plasma (embedded matplotlib anchors, interpolated) + turbo/parula (built-ins). - controller dropdown offers all six; selecting one routes through set_colormap -> render_args 'colormap' -> central continuous map, so montage, surface, and the controller stripe all render it (one colorbar). Tests: +single-range legend (2), +continuous/perceptual (3, colormap suite ->19). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) * Add CI workflow to codespell master on push and PRs GitHub Actions workflow runs codespell on every push to master and on pull requests targeting master. The workflow is pinned to a commit SHA for reproducibility and uses 'permissions: contents: read' for safety. Co-Authored-By: Claude Code 2.1.138 / Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add codespell configuration Configure codespell to skip vendored / data / build artifacts and to ignore short variable names, MATLAB built-in function names, proper names, and domain abbreviations that would otherwise be flagged. The ignore-regex skips URLs (so we don't "fix" typos in third-party links) and the base64-encoded image data embedded by MATLAB live-script export in docs/markdown_tutorials/*.m. Co-Authored-By: Claude Code 2.1.138 / Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix ambiguous typos requiring context review These typos have multiple plausible corrections (e.g. trough -> through or trough). Fixed manually after reading each occurrence's context: - regoin -> region (@atlas/threshold.m: brain region splitting) - interals -> intervals (@canlab_dataset/plot_var.m: 95% confidence intervals) - converstion -> conversion (×2: @fmri_data/fmri_data.m, Model_building_tools/design_matrix.m) - extacted -> extracted (×2: @fmri_data/fmri_data.m, @fmri_timeseries/fmri_timeseries.m) - interation -> iteration (@fmri_glm_design_matrix/robustfit.m: algorithm iteration) - exlude -> exclude (@image_vector/image_similarity_plot.m: exclude empty data) - followd -> followed (@image_vector/orthviews.m: followed by integer) - pring -> print (Cifti_plotting/plot_surface_map.m: figure to print) - numbe -> number (GLM_Batch_tools/spm_splines.README) - nexted -> nested (HRF_Est_Toolbox4/.../testFMinSearchNew.m: nested function) - fiels -> fields (Image_computation_tools/image_eval_function_multisubj.m: .n fields) - shoul -> should (Image_computation_tools/image_histogram1d.m: trailing comment fragment) - inbetween -> in between (×2: Model_building_tools/design_matrix.m) - specificy -> specify (OptimizeDesign11/.../README_gst_notes) - propotions -> proportions (OptimizeDesign11/core_functions/calcFreqDev.m) - defauls -> defaults (OptimizeDesign11/other_functions/designsim_gui_script.m) - cicles -> circles (Statistics_tools/.../xval_SVM_BKedit25Dec.m: plot legend) - foor -> for (Statistics_tools/cluster_confusion_matrix.m) - decription -> description (×5 in Visualization_functions/riverplot/*.m) - psace -> space (canlab_canonical_brains/.../spherical_icosahedral_interpolation.m) - covert -> convert (diagnostics/effect_size_map.m: convert to power estimate) - coverge -> converge (diagnostics/fmri_mask_thresh_canlab.m: mixture models converge) - sensivity -> sensitivity (web_repository_tools/...: sensitivity, specificity, PPV) - warpper -> wrapper (×2 CBIG_RF_*.sh: wrapper script) Co-Authored-By: Claude Code 2.1.138 / Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix non-ambiguous typos with codespell -w Single-suggestion typos applied automatically by `codespell -w`, plus a few targeted reverts for false positives the auto-fix had introduced: * `desc.prctiles` (struct field, documented public API) reverted from the `percentiles` "fix" in @image_vector/descriptives.m, Image_computation_tools/mean_image.m, and the docs table. * `efficency.m` function name kept (must match file name); sibling `efficiency.m` correctly renamed to its filename. * `groupt` variable in diagnostics/effect_size_map.m kept (script-level example variable); inline `codespell:ignore` annotation added. * `continguous` and `classfy` in docs intentionally retained as they reference real source identifiers; inline pragmas added. * Pipe-column alignment in canlab_glm_dsgninfo.txt restored after word-length changes (because, performed, with, details). The dominant fixes by frequency: saggital -> sagittal (40 occurrences), expermental -> experimental (28), dispaly -> display (22), atleast -> at least (17, all in comments), aproach -> approach (15), concensus -> consensus (12), initalize -> initialize (12), fucntion -> function (9), signficant -> significant (8), homogenous -> homogeneous (7), accomodate -> accommodate (7), re-used -> reused (7), efficency -> efficiency (in efficiency.m only), analagous -> analogous (6), and ~230 unique single-suggestion fixes across docs, comments, strings, and code. Also fixes a long-standing latent bug in Visualization_functions/tor_wedge_plot.m where `handels(i).texth(1) = ...` was a typo of the return variable `handles` -- the text-handle assignment was being discarded into a phantom variable. Co-Authored-By: Claude Code 2.1.138 / Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Code 2.1.138 / Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Tor Wager <torwager@gmail.com>
* Harden walkthrough CI tests: snapshots + fault-tolerant harness The nightly tests-walkthroughs job had never passed (42/42 red). It ran the CANlab_help_examples tutorials end-to-end via evalc() with no hardening, so the first orthviews/surface/prompt/missing-data error on the headless runner failed the whole test. Decouple the tests from the live tutorials and harden them: - Add verbatim snapshots of the 10 walkthroughs under walkthroughs/private/ (genpath-excluded, so they never shadow the real tutorials on CI). Refresh by overwriting from example_help_files/. - Add helpers/canlab_run_walkthrough_snapshot.m: runs a snapshot %%-cell by cell, headless, each cell in its own try/catch in a shared workspace, so a graphics-only section that fails on a headless runner does not abort the compute sections. - Add helpers/canlab_classify_environment_error.m: buckets caught errors into graphics / input / data / cascade / genuine (centralizes the heuristics previously inlined in canlab_test_help_examples). Environment buckets are skipped (Incomplete); only genuine errors fail, with an informative report naming the section, offending line, and error id. - Rewrite the 10 canlab_test_walkthrough_*.m wrappers to run their snapshot through the harness instead of evalc'ing the external script. - Add walkthroughs/README.md documenting the design, refresh process, and why these stay a separate nightly tier (~5 min local / ~15-20 min CI) rather than folding into the fast per-push suite. Full suite now: 10 passed, 0 failed, 0 incomplete. Two genuine tutorial bugs this surfaced (write-without-overwrite in walkthrough 3; dat_descrip -> metadata_table in 4b) are fixed in CANlab_help_examples and the snapshots refreshed accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Walkthrough CI: handle missing Image Processing Toolbox First CI run of the hardened nightly surfaced 3 failures, all the same environment gap: read_nifti_volume falls back to niftiinfo (Image Processing Toolbox), which the runner did not provision (only Statistics + Signal were installed), so plot(obj) failed with Undefined function 'niftiinfo'. - Provision Image_Processing_Toolbox in tests-walkthroughs.yml so plot() and NIfTI I/O actually run on CI. - Safety net: canlab_classify_environment_error now buckets an UndefinedFunction error for a known optional-toolbox function (niftiinfo, niftiread, niftiwrite, cfg_getfile) as 'capability' -> skipped, not failed, so a missing toolbox can never spuriously redden the nightly. Genuine missing-function bugs still classify as 'genuine'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rning all zeros (#70) * Initial plan * Fix deprecated avifile/addframe and all-zeros timeseries_extract_slice Co-authored-by: torwager <6262700+torwager@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: torwager <6262700+torwager@users.noreply.github.com> Co-authored-by: Tor Wager <torwager@gmail.com>
…rrcoef dep) (#85) Adds a RobustToolbox checkout + addpath to tests-walkthroughs.yml so the regression walkthrough can resolve weighted_corrcoef. Verified via a headless workflow_dispatch run: 10 tests, 0 failures, 0 errors, 1 expected skip.
* CLAUDE.md: document the matlab.unittest harness and CI The Tests section still claimed "there is no test harness." Master now has canlab_run_all_tests (matlab.unittest), a canlab_test_*.m naming/discovery convention, a fault-tolerant walkthrough integration tier, and GitHub Actions CI (test.yml, tests-walkthroughs.yml, codespell.yml). Document the runner options, layout, Passed/Failed/Incomplete semantics, and prerequisites (CanlabCore + Neuroimaging_Pattern_Masks + SPM on path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * CLAUDE.md: note walkthrough tier's RobustToolbox/CANlab_help_examples deps Reflects the dependency surfaced by the nightly walkthrough fix (#85): the walkthrough tier needs RobustToolbox on the path (weighted_corrcoef) in addition to the unit-tier prerequisites, and the nightly CI checks out CANlab_help_examples and RobustToolbox. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The codespell gate (merged via #53) was red on master because newer code (merged via #53/#70/#79) contained typos the original sweep never saw. Fix real comment typos: exlude->exclude, meaninful->meaningful (canlab_pattern_similarity.m); INTIALIZE->INITIALIZE (hansen_neurotransmitter_maps.m); and saggital/immmediately/iamges/waht/ sigificant/Treshold in the vendored walkthrough snapshot scripts (Unit_tests/walkthroughs/private/canlab_help_*.m). All are in comments. Whitelist 'hask' -- an intentional "has key" anonymous-function helper in canlab_colormap.m and @fmridisplay/render_layer_surfaces.m. codespell now exits clean. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…isualization walkthrough (#87) * fmridisplay: stateful controller, multiview, uniform colour scaling, fixes Interactive controller - Auto-launch on construction (gated off in -batch / 'nocontroller'); restores the current figure so it never hijacks montage/surface rendering; opens top-right to avoid overlap. - Threshold slider fixes: normalised [0 1] scale for raw |x| (uislider is unreliable on tiny ranges), double-cast (data-derived thresholds are single), numeric entry field, out-of-range shows an empty map. - Split-colormap legend: 4 numeric labels + gap, live-updating with cmaprange. - Colour bar renders via uiimage (fills the cell); larger legend font. - Echoed code + title use the caller's real workspace variable name. Layouts / entry points - New fmridisplay.multiview method exposing all canlab_results_fmridisplay compositions (compact/compact2/full/multirow/hcp/freesurfer/subcortex/...). - canlab_results_fmridisplay reuses a passed-in object: composes onto it if it has no montages, else just adds blobs (long-standing reuse preserved). - fmridisplay.montage default routes through multiview('compact') (sag+axial combo, matching fmri_data.montage) and echoes the equivalent call. - montage pulls existing blob layers onto a newly added montage; surface multi-keyword path (foursurfaces_hcp, ...) routes through addbrain. Uniform colour scaling - Shared canlab_default_cmaprange: robust per-arm percentile default used by montages, render_on_surface, and the surface path (no more min/max vs percentile divergence). Consistency / robustness fixes - Shared canlab_is_uifigure: never draw slices/surfaces into the controller uifigure (open a fresh figure instead) across all entry points. - removeblobs clears surface-only layers (no montage blobhandles). - Multi-image objects handled without errors: montage() lays out a per-image (multirow) display (fixed existing_axes expansion for a single placeholder axis with many slices); addblobs() uses the first image only. - Hide the per-axes "..." interaction toolbar on montage slices and rendered surfaces (canlab_hide_axes_toolbar). Tests - Add regression tests: existing_axes expansion, multi-image montage/addblobs, multiview compose+pull-in; update the split-legend label test. 47/47 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ZJacdhhByLGXqGHH9YQeo * fmridisplay: route all montage colour modes through central canlab_colormap Unify montage value->colour with the surface path by building the central canlab_colormap for EVERY mode, not just split/single. Previously solid, continuous ('colormap' LUT) and indexed ('indexmap'/atlas) blobs used legacy inline render_blobs paths that bypassed canlab_colormap; the indexmap path was also buggy (broken sub2ind assignment). Now: - render_blobs builds central_cm for split, single, solid, continuous and indexed, and colours every slice via central_map_slice (one code path). - canlab_colormap.map is NaN/Inf-robust for indexed and continuous, matching the legacy guards, since blob slices contain out-of-blob NaN voxels. Montage and surface of the same data now produce identical value->colour mappings across all colour modes (verified max|dRGB|=0), and the buggy indexmap rendering is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fmridisplay: unify indexed/atlas colours in controller + surface Route indexed (indexmap/atlas) layers through the central canlab_colormap everywhere, so montage, surface, and the controller legend all agree: - canlab_colormap.from_render_args now builds an 'indexed' colormap for indexmap layers (was falling through to the default mango split). - controller: indexed layers show an 'indexed (atlas)' label, the region's solid colour as the swatch/stripe (not the whole palette), and blank the meaningless numeric range labels. - render_layer_surfaces: index/atlas surfaces render through the same true-colour central-colormap path as every other mode (nearest-neighbour sampling to preserve integer indices), replacing the render_on_surface legacy indexmap branch. Verified: surface vertex colours equal cmap(region_index) exactly (d=0) and match the montage; split/single/solid/continuous unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add visualization walkthrough docs (multi-page guide + figures) Multi-page markdown walkthrough of CanlabCore visualization methods: getting started, montages, surfaces, colormaps, the fmridisplay controller GUI with command-line equivalents, and atlases/regions. Includes per-section figure generators (_gen/) and embedded figures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Walkthrough revisions: canlab_orthviews/niivue, custom montage, catalogs, master script Applies the 8 approved revisions to the visualization walkthrough (docs only): - §1: OO-API intro (methods return manipulable fmridisplay objects); add canlab_orthviews (SPM-free) + pointer to the canlab_niivue web viewer. - §2.6: custom montage via fmridisplay.montage (orientation/slice_range/spacing). - §3.4/§3.6: addbrain help pointers + full surface & composite catalog. - §5.4: montage-set and surface-layout catalog. - §6: atlas object help, atlas-type table, Neuroimaging_Pattern_Masks dependency. - Clean surface figures without colorbar legends; new 01_canlab_orthviews.png and 02_custom_montage.png. - Add master script _gen/visualization_walkthrough.m (one section per page, regenerates all figures; ran clean in R2026a); referenced from §1 and index. - Remove temporary _gen/gen_revise.m; fix R2026a func(){:} syntax in docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: link visualization walkthrough from Object_methods; organize README doc sections - Object_methods.md "Visualizing images and results": add a callout linking the new visualization_walkthrough/index.md as the hands-on starting point. - README.md: reorganize the documentation list into "In this repository" vs "Online", adding the visualization walkthrough, workflows, and markdown tutorials sections alongside the existing Object methods reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 495f687ab3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -1,718 +0,0 @@ | |||
| function [results_obj, stats, indx] = searchlightLukas(dat, varargin) | |||
There was a problem hiding this comment.
Restore the documented searchlightLukas method
Deleting this method removes the @image_vector/fmri_data dispatch target for users who call the documented searchlightLukas(dat, ...) API. I checked repo-wide with rg after the deletion and there is no remaining searchlightLukas.m definition (only the unrelated searchlight_disti_Lukas helper), while docs/image_vector_methods.md and docs/fmri_data_methods.md still list searchlightLukas, so existing scripts following the docs will now fail with an undefined method/function error. Keep a compatibility wrapper or remove/update the documented API with a migration path.
Useful? React with 👍 / 👎.
…ata (#86) * Add fmri_surface_data object for CIFTI/GIFTI surface & grayordinate data 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> * Add fmri_surface_data docs, workflow how-to, and a CIFTI sample map - 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> * Review pass: make fmri_surface_data examples runnable and docs accurate Verified the whole object end-to-end (41 tests pass; walkthrough runs both with and without Neuroimaging_Pattern_Masks). Fixes from the review: - apply_mask: now falls back to the object's .mask property when no mask arg is passed (matching the documented behavior), with a clear error if neither is set. Added a unit test. - Walkthrough (CanlabCore/docs/fmri_surface_data_walkthrough.m): use the bundled HCP myelin map as the primary object so it runs with only CanlabCore; guard the subcortex / atlas / parcellation sections on NPM availability; fix the parcellation demo (previously skipped because spaces never matched). - How-to (docs/workflows/fmri_surface_data_howto.md): fix Section E/F snippets that referenced undefined variables (mydata, subj1..3, scores) and called regress before setting the design; they now match the runnable walkthrough. - Docs accuracy: regress is native OLS (not "delegated"); cluster-extent threshold is implemented (not "planned"); ica is a class override (not inherited); canlab_surface_vertexcolors signature includes graycolor. - Method help examples (vol2surf, surface, surf2vol, canlab_read_cifti): use the bundled emotionreg / myelin sample data instead of a private-repo image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmri_surface_data: mask volume-only methods; redirect orthviews/montage/slices; doc links - Volume-only image_vector methods (flip, isosurface, interpolate, resample_space, extract_gray_white_csf, searchlight, slice_movie, trim_mask, read_from_file, extract_roi_averages, ...) are overridden in a methods(Hidden) block: they no longer clutter methods(obj)/tab-completion, and calling one raises a clear fmri_surface_data:unsupportedMethod error pointing to the surface equivalent. The class stays a full image_vector subclass. - orthviews / montage / slices now route the subcortical grayordinates through to_fmri_data and call the fmri_data method (meaningful subcortex display); cortex-only objects get a clear "use surface(obj)" error. - Added test_volume_only_methods_hidden; documented in the methods page ("Volume-only methods (redirected or masked)"). - Made the walkthrough / design-plan references in the docs into links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmri_surface_data: rename property intent -> imagetype; label_table now a MATLAB table - Object property `intent` renamed to `imagetype` (holds dscalar/dtseries/dlabel/func/shape/label). The CIFTI/GIFTI reader & writer structs keep the faithful `intent`/`intents` field names at the I/O boundary; the constructor/write map between `obj.imagetype` and the CIFTI intent. - `label_table` property is now a MATLAB `table` (variables key, name, rgba) instead of a struct array — nicer to view/query. Boundary converters (private struct2labeltable / labeltable2struct) translate to/from the native readers' struct arrays; apply_parcellation accepts either format. - Updated tests (assert imagetype + istable(label_table)) and the methods docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fmri_surface_data: harmonize surface() colours with the volume visualization pipeline surface() / render_on_surface() now accept the same colour vocabulary as the master visualization pipeline (addblobs / set_colormap): clim / cmaprange, colormap / colormapname (single sequential map), pos_colormap / neg_colormap, splitcolor, maxcolor / mincolor, and color (solid). surface() forwards any colour option straight through to render_on_surface, and the via-volume path forwards the compatible options to image_vector/render_on_surface. So the same options colour surface data and volume blobs. Added test_harmonized_color_options; updated surface() help and the methods page. (Level 1 of the visualization harmonization; the stateful fmridisplay-layer integration is the next step.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Integrate fmri_surface_data into the stateful fmridisplay (surface-native layers) Level 2 of the visualization harmonization: an fmri_surface_data can now be added to a managed fmridisplay and driven by the same controls as volume data. - @fmridisplay/addblobs: detects an fmri_surface_data and dispatches to a new @fmridisplay/add_surface_blobs, which registers it as a SURFACE-NATIVE layer (source_surface) rather than converting to a volume region. - @fmridisplay/render_layer_surfaces: paints surface-native layers by colouring the per-vertex data DIRECTLY on any registered mesh whose vertex count matches the object's space (fs_LR-32k / fsaverage-164k), at full fidelity, using the same central canlab_colormap value->colour map as montages (so colours match). set_colormap / set_opacity / removeblobs / refresh / the controller act on the layer like a volume layer. Non-matching meshes are skipped; a surface layer has no montage representation (use to_fmri_data / surf2vol for that). - Fix @fmri_surface_data/isempty: image_vector/isempty treats an empty volInfo as empty, but a cortex-only surface object legitimately has an empty volInfo, so a fully-populated cortical map wrongly reported empty. Now empty iff .dat has no rows. - Added canlab_test_surface_fmridisplay (addblobs paints, set_colormap/removeblobs propagate, non-matching/no-surface handled). Full surface suite: 47 passing. - Documented the managed-display integration in the methods page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Surface viz: surface() returns a managed fmridisplay and renders under the controller Redesign the fmri_surface_data surface() UX so the stateful display is the single, self-consistent entry point: - fmri_surface_data/surface(obj) native mode now returns a managed fmridisplay (not a raw-handle struct): it adds the surface set that MATCHES the object's space (fsLR_32k -> foursurfaces_hcp, fsaverage_164k -> foursurfaces_freesurfer) and paints the data as a managed surface-native layer. This is what makes the colormap changeable after rendering (set_colormap / removeblobs / refresh now act on the surfaces). Non-default surftype (midthickness/sphere) is honored by swapping the managed patches' geometry. - fmridisplay/surface(o2, obj[, keyword]) detects an fmri_surface_data argument, adds its native surfaces (matching set by default, or an explicit keyword) and paints it via add_surface_blobs, all under controller management -- so "o2 = surface(o2, obj)" actually renders instead of silently dropping obj into the addbrain pass-through. - Fix a hemisphere-assignment bug: addbrain relabels the foursurfaces_* patches with the group keyword, erasing their left/right tags, which made the native layer paint LEFT data onto BOTH hemispheres. render_layer_surfaces now resolves hemisphere by tag, falling back to vertex x-centroid sign (left < 0 < right). - Cross-space request (e.g. fsaverage meshes for fs_LR data) now emits a clear fmridisplay:render_layer_surfaces:spacemismatch warning instead of rendering something wrong -- a surface object has no volume to resample onto a foreign mesh, so the fix is to add the matching surfaces (surface(obj) does so). - add_surface_blobs normalizes color aliases (clim -> cmaprange, colormapname -> colormap, named colormap string -> numeric LUT) so a managed surface layer honors the same color vocabulary as the volume path. - Clarify the .geom property doc: it holds ATTACHED custom mesh geometry from a .surf.gii and is empty for a data-only CIFTI (that is expected) -- standard meshes are loaded on demand by surface_space, not cached in .geom. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Tests: cover managed surface() return, hemisphere correctness, space-mismatch warning - canlab_test_surface_fmridisplay: add tests that surface(obj) returns a managed fmridisplay and paints each hemisphere with ITS OWN data (regression for the foursurfaces tag/x-centroid hemisphere fix); that surface(o2, obj) adds+paints on an existing display; and that an explicit cross-space request warns with fmridisplay:render_layer_surfaces:spacemismatch and does not mispaint. - canlab_test_surface_render: update the native-render tests to the managed fmridisplay return (via a cortex_patches helper) instead of the old struct. All surface_data tests pass (50 passed / 0 failed; 1 incomplete = ICA toolbox absent). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs: document surface() managed return, set_colormap workflow, space-mismatch behavior Update the method reference (dev + readthedocs copies), the runnable walkthrough, and the how-to to show that surface(obj) returns a managed fmridisplay, how to recolor live with set_colormap, how surface(o2, obj) adds+paints on an existing display, and the spacemismatch warning for cross-space surfaces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: make the managed surface layer fully stateful (controller, opacity, rethreshold, colormap) The surface-native layer was bolted onto the stateful display but did not integrate with the controller-driven machinery. Fixes so it behaves like a volume layer: - Controller sync: add_surface_blobs now calls update_controller (like addblobs), so the auto-launched controller reflects the new surface layer instead of keeping its empty state until the user reopens it by hand. - Opacity: paint_surface_native_layer now saves the anatomy gray to the patch UserData on first paint, and composite_surfaces resets to gray when ANY patch in a view carries saved anatomy. Previously the layer blended against its OWN colour (base == painted colour), so set_opacity was a no-op. Now opacity blends toward gray and restores fully at 1. - Rethreshold: add a surface-native branch that stores the threshold as a per-vertex magnitude cutoff (applied at paint time) instead of building a region from the object -- a cortical surface object has no matching volInfo, so the old image_vector path produced "Illegal size for mask.dat" / "Mask has multiple images" warnings and did not actually threshold the surface. - Colormap keeps the threshold: canlab_colormap.map preserved NaN for the split map but not for 'single' (maxcolor/mincolor) or 'solid' (color) -- clamp01(NaN) became 0, painting uncoloured/thresholded vertices with the min endpoint. Now both preserve NaN, so set_colormap no longer resets a rethreshold and the medial wall stays gray under a single/solid map. - refresh skips the montage render_blobs path for surface-native layers (they have no volume mapdata); the surfaces are redrawn by composite_surfaces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: render subcortical grayordinates for mixed objects (surface + montage) A mixed grayordinate object (cortex + subcortical voxels, e.g. a 91k dscalar) previously rendered only its cortex; the subcortical data was dropped. - surface(obj) / surface(o2, obj): when the object has a subcortical sub-block, add its voxels as a second managed VOLUME layer (via to_fmri_data), so the subcortical anatomical meshes the foursurfaces set draws (thalamus, brainstem, cerebellum, ...) are painted. The layer is confined to the subcortical meshes via skip_cortex_nv (in render_layer_surfaces) so it never bleeds onto the cortical surfaces / medial wall, which the surface-native cortex layer owns. Cortex and subcortex share one colour range unless the caller passes clim/cmaprange, and both respond to set_colormap/rethreshold/set_opacity/the controller together. - montage(o2, obj): detect an fmri_surface_data argument, build the montage, and render the subcortical grayordinates on the slices (instead of ignoring obj). Cortex-only objects warn and point to surface(obj). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Tests + docs: managed surface layer statefulness and subcortical rendering - Tests: opacity blends toward gray and restores; rethreshold applies a per-vertex magnitude cutoff with no volInfo warning; set_colormap preserves the threshold; a mixed object renders cortex + subcortex without bleeding onto the medial wall; montage(o2, obj) draws the subcortical grayordinates. Update the render tests for the cortex+subcortex two-layer result. - Docs: document mixed-object subcortical rendering and the surface rethreshold (magnitude cutoff) in the method reference. All surface_data tests pass (54 passed / 0 failed; 1 incomplete = ICA toolbox absent); canlab_colormap (19) and fmridisplay_handle (47) suites pass unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: strip view-selectors before render_blobs; keep the controller in sync when adding surfaces - addblobs consumed 'wh_surface'/'wh_montage' (and variants) but still forwarded them verbatim to render_blobs, which warned "Unknown input string option: wh_surface" whenever a surface layer was added to a display that also has montages (e.g. surface(o2, obj, 'foursurfaces_hcp') after montage(o2, obj), or the subcortical volume layer surface() adds for a mixed object). Strip these view-selectors from varargin after parsing, so they never reach render_blobs nor persist in render_args (where refresh would forward them again). - @fmridisplay/surface now calls update_controller after adding a surface view (both the single-surface and foursurfaces_* multi paths), so an open controller stays bound to the same object and reflects the current views. No new controller is ever created (update_controller is a no-op when none is open; fmridisplay is a handle class so surface(o2, ...) mutates o2 in place). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Colormap: collapse a 4-element split cmaprange to full span for single/continuous maps Recolouring a split-range layer to a single (maxcolor/mincolor) or continuous (numeric 'colormap', e.g. hot) map reused the layer's 4-element split cmaprange verbatim, so the map took [lo hi] = range(1:2) -- the NEGATIVE arm only. Every value above the negative arm's top then clamped to the max colour: with hot that is white, so a mixed grayordinate object's subcortical surfaces (whose values sit in the positive arm) rendered ~96% white after set_colormap(o, 'colormap', hot). canlab_colormap.single / .continuous now collapse a 4-element range to its full span [range(1) range(4)] (mirroring how .split calls expand_split_range). Verified visually across surface + volume, cortex + subcortex, with hot / parula / maxcolor-mincolor / solid: no white artifacts, and the standard volume path is unchanged (set_colormap hot on a volume montage+surface stays correct). Adds a regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * load_image_set: add surface/volumetric map sets and a categorized 'list' New loadable keywords (files in Neuroimaging_Pattern_Masks/spatial_basis_functions): - hcp_ica15 / hcp_ica25 / hcp_ica50 (aliases hcp15/hcp25/hcp50): HCP resting-state group-ICA components, returned as fmri_surface_data (91k grayordinates). - spectral_bases (alias hcp_bases): 200 spectral (Laplacian eigenmap) basis functions -> fmri_surface_data. - mito_maps (alias mito): mitochondrial energetic-capacity maps (CI, CII, CIV, MitoD, MRC, TRC) -> fmri_data (6 maps), with names. CIFTI/grayordinate sets return fmri_surface_data; volumetric sets return fmri_data. load_image_set('list') now prints a series of CATEGORIZED tables, each under a descriptive title -- multivariate signatures, person-level datasets, network/ICA/ topic maps, surface (CIFTI) map sets, gradients & basis maps, and meta-analysis/ receptor maps -- and RETURNS a struct whose fields are those tables (previously it returned a single table). The signatures table is unchanged and is the .signatures field. Help header documents 'list' prominently and the new keywords. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * load_atlas: register Tian subcortical scale atlases and add a categorized 'list' - Register the Tian 3T subcortical atlas at explicit granularity scales: tian_s1 / tian_s2 / tian_s3 / tian_s4 (16 / 32 / 50 / 54 regions), each with an _fsl6 (MNI152NLin6Asym) variant; default is fmriprep20. The existing tian_3t keyword already loads the finest scale (S4). Removes a stale commented-out schaefer400 block (that file is a 32-region network-level parcellation, already covered by yeo17networks, not the 400-parcel Schaefer). - load_atlas('list') prints categorized tables of atlas keywords (combined, cortex, subcortical, thalamus/hypothalamus, brainstem/cerebellum, networks) and returns a struct of those tables, mirroring load_image_set('list'). Help header documents it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs + tests for the load_image_set / load_atlas registry updates - New workflow page docs/workflows/load_image_set_howto.md: discovering sets with load_image_set('list'), loading datasets, applying signatures (apply_mask), image similarity to networks/topics (image_similarity_plot), dual regression (dual_regression), and surface (CIFTI) map sets. Linked from docs/Workflows.md. - canlab_test_load_registries.m: verifies the new surface/volumetric keywords return the right object types and map counts, that both 'list' options return a struct of tables, and that the Tian S1-S4 atlases load with the expected region counts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Test: skip canlab_test_load_registries under GitHub Actions This registry test needs the full Neuroimaging_Pattern_Masks data (large CIFTI / .mat binaries). It is headless-safe and its per-test assumeFail guards already prevent CI failures, but by request it is now kept out of the CI unit tier: setupOnce assumeFail's the whole file when GITHUB_ACTIONS is set, so it is skipped (Incomplete) in CI and runs normally when executed locally. teardownOnce is guarded so the early skip does not error on the unset figvis field (which would otherwise flip filtered tests to failed). Local: 6 passed / 0 failed. Simulated CI: 0 failed / 6 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Test: include canlab_test_load_registries in the GitHub Actions suite The required data files (HCP-ICA / spectral CIFTI, mito_maps.mat, and the Tian S1-S4 atlas objects) are all committed to the public Neuroimaging_Pattern_Masks repo that CI checks out, and the test is headless-safe, so it runs in the CI unit tier. Removes the GITHUB_ACTIONS skip added earlier; the per-test assumeFail guards remain so a missing file is skipped (Incomplete) rather than failing CI, and the teardownOnce guard stays as defensive cleanup. Verified with GITHUB_ACTIONS set: 6 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs: make explicit that vol2surf/surf2vol ARE the CBIG registration-fusion mappers State clearly, in the method help headers and the surface docs, that vol2surf and surf2vol are native reimplementations of the CBIG Registration-Fusion (RF-ANTs) MNI152<->fsaverage mappers (Wu et al. 2018, Human Brain Mapping): vol2surf is a line-for-line equivalent of CBIG_RF_projectMNI2fsaverage using the identical vendored warp, driven by SPM's volInfo.mat instead of FreeSurfer's MRIread, so no FreeSurfer or Connectome Workbench is needed. surf2vol is the native inverse using the same warp. Also note that CBIG's heavier fsaverage2Vol ribbon-fill script requires FreeSurfer + the CBIG MARS toolbox + external mask geometry (not bundled) and is intentionally not wired in. Adds the Wu et al. 2018 reference. Docs only; no code changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add resample_surface: native surface-to-surface resampling for fmri_surface_data New method resample_surface(obj, target_space) resamples cortical data between surface spaces natively (no FreeSurfer / Connectome Workbench): - fsaverage_164k <-> fs_LR_32k (HCP CIFTI) via the vendored HCP registration ("deformed") sphere that expresses fsaverage vertices in the fs_LR-aligned frame, so both meshes share one spherical frame; resampled with the bundled spherical barycentric / nearest-neighbor engine. - fsaverage_164k <-> fsaverage6/5/4 as the exact nested icosahedral subset (verified: first 40962 verts form a valid ic6 mesh, 81920 faces). Accuracy/speed: barycentric ("linear") is the default for continuous data (round-trip corr ~0.9999 vs ~0.998 for nearest); interpolation weights depend only on geometry, so they are computed once and applied to all maps as a sparse matrix multiply -- a 50-map object costs about the same as one map (~10s one-time weight build for fsaverage<->fs_LR). Binary masks and .dlabel images auto-select nearest to preserve discrete values; 'interp' overrides. Subcortical (voxel) models are carried through unchanged. resample_surface(obj,'list') prints the available target spaces + aliases. Tests (canlab_test_surface_resample, 8/8 pass; core cases use a synthetic fsaverage object from bundled assets, so no NPM needed): fsaverage->fs_LR, exact nested downsample, round-trip accuracy, binary->nearest auto, interp override, multi-map weight reuse, subcortex carried through. Docs: a prominent "Surface spaces" section (fsaverage164k/6/5/4, fs_LR-32k, the 91k grayordinate model) with resolutions and references (Fischl 1999, Van Essen 2012, Glasser 2013); clarified that .geom holds ATTACHED CUSTOM meshes only and standard meshes load on demand; and resample_surface method entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add onavg (OpenNeuro Average) as a resample_surface template space onavg (Feilong et al. 2024, Nature Methods; CC0) is an equal-area cortical surface template. TemplateFlow tpl-onavg ships onavg spheres registered to the fs_LR frame (space-fsLR), which is the same common frame resample_surface already uses -- so onavg resamples to fsaverage / fs_LR with the existing native spherical engine, no FreeSurfer/Workbench. - Vendor the space-fsLR onavg registration spheres (den-41k = 40962 verts/hemi, den-10k = 10242) as .mat (single verts / uint32 faces, ~2.35 MB total) under canlab_canonical_brains/Canonical_brains_surfaces/onavg/, with a provenance README (source, CC0 license, reference). - resample_surface gains target/source spaces 'onavg' / 'onavg_41k' and 'onavg_10k' (aliases onavg41k / onavg10k). onavg is a non-nested tessellation, so it always interpolates (no exact-subset fast path). Verified: fsaverage -> onavg -> fsaverage round-trip corr 0.9999; fs_LR -> onavg works. - Test + docs: onavg round-trip test; onavg rows in the 'list' output and the Surface spaces table (with reference). Only the registration spheres are vendored (enough to resample). onavg group statistics (sulcal depth, curvature, vertex area) on GIN are not bundled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: auto-resample surface data onto a mismatched standard mesh Previously, rendering an fmri_surface_data layer onto a surface in a different space (e.g. fs_LR data on an fsaverage foursurfaces_freesurfer mesh) skipped the mesh with a spacemismatch warning. Now the painter resamples the data onto that mesh's space automatically and paints it. - resample_surface now accepts an isosurface PATCH HANDLE or a mesh struct (with .vertices) as the target, resolving the space by per-hemisphere vertex count (32492 -> fs_LR, 163842 -> fsaverage_164k, 40962/10242/2562 -> fsaverage6/5/4). A non-standard vertex count errors clearly (cannot resample onto an arbitrary mesh; use surf2vol + render_on_surface). - render_layer_surfaces/paint_surface_native_layer: a cortical patch whose vertex count differs from the data but is a recognized standard mesh triggers an on-the-fly resample_surface (nearest, for render speed), CACHED on the layer (obj.activation_maps{k}.resampled) so set_colormap / rethreshold / set_opacity re-renders are instant (verified 0.4s first paint, 0.02s cached re-render). Only truly non-standard meshes remain unpainted (spacemismatch warning, reworded). Threshold is applied per hemisphere via a shared helper so native and resampled meshes behave identically. Tests: canlab_test_surface_fmridisplay's mismatch test now asserts auto-resample + paint (was: warn + not painted); a resample test covers patch/mesh-struct targets and the non-standard-count error. Full surface suite 64 passed / 0 failed (1 incomplete = ICA); volume fmridisplay_handle 47/47 unaffected. Docs: document rendering auto-resample and the patch/mesh target of resample_surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: render surface data onto ARBITRARY MNI isosurfaces via volume projection Auto-resampling handled cross-space *standard* meshes (fs_LR<->fsaverage), but arbitrary MNI isosurfaces with no spherical registration (addbrain 'hires left', 'cutaway', pial meshes) still warned and went unpainted. Now they are painted by projecting the data to a volume and sampling it at the mesh vertices. - New public method to_display_volume(obj): project any surface/grayordinate object to an MNI fmri_data -- cortex resampled to fsaverage (resample_surface, auto nearest for labels) then surf2vol, MERGED with subcortex (to_fmri_data). The private obj_to_volume now delegates to it, so render_on_surface(any_surf, arbitrary patch) works for fs_LR data too (previously fsaverage-only) and includes subcortex. - render_layer_surfaces/paint_surface_native_layer: a patch whose vertex count is not a recognized standard cortical mesh now projects via to_display_volume (cached on the layer as display_volume) and paints with image_vector.render_on_surface using the layer's truecolor map + opacity; the layer threshold is applied to the volume. Only a genuinely failing projection leaves a mesh unpainted. Verified on the Gordon atlas: 'hires left' and 'cutaway' render cortex + subcortex, no warning, ~1.7-3.6s first paint, cached re-renders fast. Tests: to_display_volume returns a cortical MNI volume; rendering onto an addbrain 'hires left' mesh paints it (guarded). Full surface suite 64 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Surface viz: 'unique'/'solid' color modes + a single source of truth for auto-selection - New helper canlab_color_mode(dat) is the single source of truth for choosing a blob colour mode: 'solid' (<=2 distinct nonzero values, i.e. a binary mask), 'unique' (an atlas / .dlabel parcellation, or a few integer labels), or 'colormap' (continuous / many values). Cutoff 1000 for a generic integer map; atlas and .dlabel/.label objects are always 'unique' regardless of region count (Gordon-333, Julich-314, etc. exceed the old 300 orthviews cutoff). - fmri_surface_data surface rendering (add_surface_blobs) honours an explicit 'unique' or 'solid' flag and, when no colour spec is given, auto-selects the mode via canlab_color_mode. 'unique' -> one solid colour per region (scn_standard_colors as an indexed colormap); 'solid' -> a single colour for all in-data vertices. surface(o2, surf_obj) / surface(surf_obj) / addblobs route the flags through. Verified: surface(gordon_dlabel) now auto-renders a unique-colour parcellation (cortex + subcortex); surface(obj,'solid') and 'unique' work explicitly; continuous maps still default to a colormap. Tests: canlab_color_mode classification + surface unique/solid/auto (14/14 in the fmridisplay suite; full surface suite 66 passed / 0 failed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * render_blobs: ignore 'unique'/'solid' color-mode flags; controller bg -> light sea green - render_blobs now treats 'unique' and 'solid' as recognized (ignored) options instead of emitting "Unknown input string option:unique". These flags are handled upstream (region/montage colortype, add_surface_blobs), so montage(atl,'unique') no longer prints the spurious warning. addblobs already suppresses its own unknown-option warning. - fmridisplay controller window background changed from orange to light sea green ([0.125 0.698 0.667]). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fmridisplay controller: never change the current figure (montage axes consistency) Root cause of montage slices intermittently rendering in the wrong figure/axes: the controller is a uifigure, and opening OR rebuilding it (auto-launch in the constructor, update_controller after addblobs/set_colormap, or an explicit controller() call) could leave it as gcf, so the next gca/gcf-based slice drawing targeted the controller or a stale figure. The constructor already guarded its own launch; this moves the guard INTO controller() via onCleanup so EVERY entry path (including the early update-in-place return) restores the caller's current figure. Timing-neutral (the controller still opens when it did before), so it does not disturb figure-timing behavior -- verified the full fmridisplay_handle suite (47/47, incl. test_foursurfaces_uses_dedicated_figure) and the surface suite (67 passed) still pass. The bug is interactive (real-window focus) and does not reproduce headless, but this addresses the mechanism directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fmridisplay controller: working 'unique (per region)' colormap dropdown item The colormap dropdown listed 'indexed (atlas)' but on_colormap had no case for it (a no-op). Renamed it to 'unique (per region)' and wired it up: selecting it applies one solid colour per region via set_colormap('indexmap', scn_standard_colors(n)) on the layer, and current_colormap_label reports 'unique (per region)' for an indexed layer. The dropdown now offers colormap options, 'unique (per region)', and 'solid colour…'. Verified: option present, initial value mapping, and the underlying set_colormap(indexmap) action; fmridisplay_handle (47/47) and surface_fmridisplay (14/14) suites pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * region/surface: 'unique'/'solid' colour modes + auto unique for atlases surface() on a volumetric atlas (via image_vector/surface -> region/surface) rendered the integer region indices through a CONTINUOUS colormap (blended at borders), and 'unique' produced an "Unknown input string option" warning. region/surface now: - accepts 'unique'/'solid' flags (no warning); - 'unique' -> one solid colour per region (scn_standard_colors as an indexed colormap; render_on_surface uses nearest automatically), matching region/montage; - 'solid' -> a single colour; - when no explicit colour spec is given, auto-selects the mode via the shared canlab_color_mode. region2imagevec stores integer region indices for an atlas (-> 'unique') but continuous .Z values for a statistic map (-> 'colormap'), so surface(atlas) now auto-renders unique colours while surface(t) keeps its colormap (verified: no regression on continuous maps). Test added (guarded on the julich atlas); surface_data suite 9/9, full surface + fmridisplay_handle suites 114 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No description provided.