Dependencies & provenance
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.thresholdexists in@atlas,@glm_map,@image_vectorand@statistic_image; deciding which one runs needs type inference these workspace-chained scripts do not support. All candidates are listed.dotcall— called asobj.name(...)and matched to a class method by name; it could also be a struct field.dynamic— the file usesfeval/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 ownrequiredFilesAndProductsis not used: it aborts on the first one it meets.