Owner LaBGAS Repository LaBGAScore Folder clean/

Analysis scripts are copied into a study’s code subdataset and adapted there, so every project keeps its own frozen copy. Their dependencies are not frozen: CanlabCore, our fork of CANlab_help_examples, CanlabPrivate, canlab_single_trials and the rest are shared clones that keep moving underneath the scripts.

That gap is the practical reproducibility problem in this workflow. A result from eighteen months ago can be re-read in its HTML report, but until recently nothing recorded which version of CanlabCore produced it.

Two questions, two tools

Going forward — record it at run time. Publish a script with LaBGAScore_prov_publish instead of MATLAB’s publish, and the HTML report gains a Provenance section listing the commit of every dependency the script reaches, plus a machine-readable copy in the model’s results/notes/.

% instead of
publish('cfs_secondlevel_m14_s7_c2a_second_level_regression', 'outputDir', htmlsavedir)

% use
LaBGAScore_prov_publish('cfs_secondlevel_m14_s7_c2a_second_level_regression', htmlsavedir)

In practice you rarely call it by hand. Scripts are normally run headless, from the Linux command line, which publishes through LaBGAScore_prov_publish and additionally checks that the run actually worked:

labgascore_run_headless.sh -d /data/proj_xxx \
    -s proj_secondlevel_m1_s0_a_set_up_paths_always_run_first \
    -a data_objects.mat \
    proj_secondlevel_m1_s4_prep_2_load_image_data_and_save

That last point is easy to miss and expensive: publish catches a script’s error into the HTML report and then returns normally. No exception, exit status 0, report file present. A chain of scripts can report success from beginning to end while one of them died partway — sometimes after an hour of computation and before anything was saved. LaBGAScore_run_reports, which the wrapper calls, reads each report back and fails on the error markup, and can assert that the results file the script should have written exists.

Looking back — reconstruct it. For analyses already run, LaBGAScore_prov_resolve_retrospective recovers the same information after the fact. It works because two records survive independently. Every artifact carries its own date: a published report has its run date, the MATLAB version and its complete source embedded in the HTML; a result .mat has a Created on: stamp in its file header, with a time of day, which survives git-annex. And every clone keeps a git reflog recording which commit it actually had checked out, and when. Intersecting the two gives the commit that was genuinely in place.

This matters more than it sounds: only a handful of scripts per model are ever published to HTML — the prep_ scripts write .mat files instead — so resolving reports alone would leave most of the pipeline undocumented. Output is one page per run, grouping a script’s report with the .mat files it wrote. Existing files are never modified.

Protect the reflogs first. Git prunes reflog entries after 90 days by default, and it happens silently during garbage collection. The reflog cannot be reconstructed afterwards — a commit’s own date says nothing about when your clone moved to it. Run clean/labgascore_prov_protect_reflogs.sh once per machine to disable expiry.

Figures depend on how you ran the script

Headless — the default — publish() prints figures rather than capturing them from a screen, so no display setting affects them and their size is not limited by the 1024×768, 72 dpi virtual screen headless MATLAB reports. The only thing you give up is pixel density.

Run interactively in X2go when you want higher-resolution figures (96–144 dpi). There publish() captures figures from the screen, so the size and DPI of your session decide how they come out — and a figure larger than the session window is captured at display size instead. Two consequences the workflow handles explicitly:

  • Figure size is set in inches, not pixels, because MATLAB font sizes are in points. The request is treated as a maximum and scaled down to fit the display, preserving the aspect ratio.
  • Every report records the session’s screen geometry, DPI and the resulting figure dimensions, and flags figures whose size was decided by the display rather than the script — so a difference between two people’s reports is measurable rather than puzzling.

Run LaBGAScore_check_display to see what your own session can produce — it applies to the interactive route only. Both routes, and the recommended X2go settings per screen size, are in section 2 of the fMRI analysis workflow.

What a commit hash does and does not tell you

A hash alone is not enough, because shared clones accumulate uncommitted local edits. The tooling therefore also reports which modified files a given script actually reaches, using a call graph built with mtree. In practice most repository/script pairs come back clean, and the few that do not are named precisely rather than left as a blanket warning.

What genuinely cannot be recovered is whether a dependency was dirty at the time — uncommitted work is not timestamped anywhere. That limit is stated in the output rather than glossed over.

Dependency overview

The tables below are generated by LaBGAScore_dep_report, which parses each script with mtree and resolves every called name against an index of the installed toolboxes. Full per-script detail lives in each repository’s DEPENDENCIES.md — at the root for LaBGAScore, and in Second_level_analysis_template_scripts/ for the CANlab fork, where it covers the 20 second-level templates LaBGAS actively maintains rather than the ~113 scripts present.

Resolution is deliberately conservative: only an unambiguous, repository-unique call can introduce a dependency. Names that several toolboxes define, MATLAB’s own builtins, and obj.name reads (which are usually object properties, not method calls) can reinforce a dependency the script already has, but never create one.

CANlab_help_examples

20 scripts documented, generated 2026-09-23.

Script Domain Depends on Direct calls
a2_set_default_options Second_level_analysis_template_scripts   0
a_set_up_paths_always_run_first Second_level_analysis_template_scripts LaBGAScore 2
c2_SVM_contrasts_masked Second_level_analysis_template_scripts CanlabCore, LaBGAScore 12
c2a_second_level_regression Second_level_analysis_template_scripts CanlabCore, LaBGAScore 19
c2f_run_MVPA_regression_single_trial Second_level_analysis_template_scripts CanlabCore, LaBGAScore, ooFmriDataObjML 23
c2g_run_multivariate_mediation_single_trial Second_level_analysis_template_scripts CanlabCore, LaBGAScore, MediationToolbox 13
c2h_run_multivariate_mediation Second_level_analysis_template_scripts CanlabCore, MediationToolbox 8
d10_signature_riverplots Second_level_analysis_template_scripts CanlabCore, LaBGAScore 4
d_signature_responses_generic Second_level_analysis_template_scripts   0
e1_corr_patterns Second_level_analysis_template_scripts CanlabCore, LaBGAScore 11
h_signature_responses_group_diff Second_level_analysis_template_scripts CanlabCore, LaBGAScore 9
prep_1_set_conditions_contrasts_colors Second_level_analysis_template_scripts CanlabCore 2
prep_1b_prep_behavioral_data Second_level_analysis_template_scripts   0
prep_2_load_image_data_and_save Second_level_analysis_template_scripts CanlabCore, ComBatHarmonization, LaBGAScore, canlab_single_trials, spm12 11
prep_3_calc_univariate_contrast_maps_and_save Second_level_analysis_template_scripts CanlabCore, ComBatHarmonization, LaBGAScore 6
prep_3a_run_second_level_regression_and_save Second_level_analysis_template_scripts CanlabCore, LaBGAScore 33
prep_3c_run_SVMs_on_contrasts_masked Second_level_analysis_template_scripts CanlabCore, LaBGAScore, ooFmriDataObjML 23
prep_3f_create_fmri_data_single_trial_object Second_level_analysis_template_scripts CanlabCore, LaBGAScore, canlab_single_trials 4
prep_3g_create_fmri_data_runwise_contrast_object Second_level_analysis_template_scripts CanlabCore, canlab_single_trials 2
prep_4_apply_signatures_and_save Second_level_analysis_template_scripts CanlabCore, MasksPrivate, Neuroimaging_Pattern_Masks 3

LaBGAScore

146 scripts documented, generated 2026-09-17.

Script Domain Depends on Direct calls
LaBGAScore_atlas_binary_mask_from_atlas atlas_mask_tools CANlab_help_examples, CanlabCore 6
LaBGAScore_atlas_rois_from_atlas atlas_mask_tools CANlab_help_examples, CanlabCore 10
LaBGAScore_check_all_scripts clean   0
LaBGAScore_check_display clean   0
LaBGAScore_clean_gzip_all_nii clean   0
LaBGAScore_clean_sourcedata clean   0
LaBGAScore_dep_build_index clean   0
LaBGAScore_dep_map clean   0
LaBGAScore_dep_report clean   0
LaBGAScore_move_repos_matlabpath clean   0
LaBGAScore_prov_gitinfo clean   0
LaBGAScore_prov_gitstatus clean   0
LaBGAScore_prov_protect_reflogs clean   0
LaBGAScore_prov_publish clean   0
LaBGAScore_prov_resolve_retrospective clean   0
LaBGAScore_prov_snapshot clean   0
LaBGAScore_run_reports clean   0
LaBGAScore_smart_parallel_pool_setup clean   0
LaBGAScore_cosmomvpa_searchlight_rsa cosmomvpa CANlab_help_examples, CanlabCore, CoSMoMVPA 25
LaBGAScore_decoding_SVM_between_subjects decoding_toolbox CANlab_help_examples, CanlabCore, ComBatHarmonization, spm12 10
LaBGAScore_decoding_template_xclass_acc decoding_toolbox   0
LaBGAScore_export_scaled_contrasts decoding_toolbox   0
canlabCmap figures   0
cluster_surface_plots figures spm12 2
plugin_set_figure_size figures   0
save_all_open_figures_smart figures   0
LaBGAScore_firstlevel_find_events firstlevel   0
LaBGAScore_firstlevel_refit_motion_comparison firstlevel spm12 8
LaBGAScore_firstlevel_s1_options_dsgn_struct firstlevel   0
LaBGAScore_firstlevel_s1a_options_dsgn_multisess_multitask firstlevel   0
LaBGAScore_firstlevel_s1b_fit_phMRI_model firstlevel spm12 4
LaBGAScore_firstlevel_s2_fit_model firstlevel CanlabCore, CanlabPrivate, spm12 13
LaBGAScore_firstlevel_s2a_fit_model_multisess_multitask firstlevel CanlabCore, CanlabPrivate, spm12 13
LaBGAScore_firstlevel_s2b_phMRI_covar_contrasts firstlevel spm12 2
LaBGAScore_firstlevel_s3_diagnose_model firstlevel CanlabCore 11
LaBGAScore_firstlevel_s3b_phMRI_signature_responses firstlevel CanlabCore 3
LaBGAScore_firstlevel_s3c_phMRI_neurotransmitter_maps firstlevel CanlabCore, canlab_single_trials 5
LaBGAScore_firstlevel_task_motion_diagnostics firstlevel   0
canlab_glm_subject_levels_old firstlevel CanlabCore, spm12 5
canlab_glm_subject_levels_run1subject_old firstlevel CanlabCore, spm12 10

Showing 40 of 146. See DEPENDENCIES.md for the rest.

Reading the caveats

Resolution is static analysis, and MATLAB makes some of it genuinely undecidable. The generated documents mark every uncertain edge rather than guessing:

  • ambiguous — several classes define the name. threshold exists in @atlas, @glm_map, @image_vector and @statistic_image; deciding which one runs needs type inference these workspace-chained scripts do not support. All candidates are listed.
  • dotcall — called as obj.name(...) and matched to a class method by name; it could also be a struct field.
  • dynamic — the file uses feval/eval/str2func, so its real call set cannot be recovered statically at all.
  • unparseable — the file has a syntax error. There are a handful of these in the CANlab tree, which is why MATLAB’s own requiredFilesAndProducts is not used: it aborts on the first one it meets.

Updated: