This repository contains two Bash wrappers to run the FastSurfer cross-sectional and longitudinal p- fsqc's surfaces module needs a graphical display (OpenGL). If no DISPLAY is set, scripts/analyse_qdec.py automatically disables fsqc surfaces and logs an info message. Screenshots + HTML work headlessly.pelines on BIDS datasets via Singularity containers. They add BIDS-aware discovery, JSON-based configuration, and quality-of-life flags like dry-run, debug, and pilot sampling.
bids_faststurfer.sh— Cross-sectional wrapper (note the file name spelling)bids_long_fastsurfer.sh— Longitudinal wrapper (auto-detects longitudinal subjects by default)
The scripts are designed to behave like lightweight BIDS apps: you pass the input dataset, an output directory, and a config JSON file; the scripts discover inputs and build the correct Singularity commands for you.
- A Linux environment with Singularity installed (or Apptainer; if using Apptainer, you can alias
singularitytoapptainer). On macOS, run within a Linux VM or environment that provides Singularity/Apptainer. jqfor JSON parsing- FastSurfer GPU Singularity image (
.sif) - A valid FreeSurfer license file
Additionally for the R analysis environment (this repo includes an installer under scripts/install.sh):
- A local FreeSurfer installation is required and must be discoverable via
FREESURFER_HOME(the installer will stop if not found). - Several GB of free space for the micromamba env, package cache, and temporary downloads.
Quick checks:
# should print a version
singularity --version
# should print a path after install
## Usage
See `bids_long_fastsurfer.sh` for full help and options.
### Example: Manual subject specification
```bash
bash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --tid sub-001 \
--tpids sub-001_ses-01 sub-001_ses-02 --dry_run --debugbash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --dry_runbash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --pilot --dry_runbash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --re-run missing_subjects.json --dry_runbash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --re-run missing_subjects.json --nohupbash bids_long_fastsurfer.sh /data/BIDS /data/derivatives/fastsurfer_long \
-c fastsurfer_options.json --re-run missing_subjects.json --batch_size 4-
--nohupruns each subject in the background with output redirected to log files. -
--batch_size Ntriggers batch processing, running up to N subjects in parallel per batch (usingbatch_fastsurfer.sh). -
Monitor batch progress with:
tail -f <OUTPUT_DIR>/batch_processing.log -
Monitor individual subject logs with:
tail -f <OUTPUT_DIR>/long_fastsurfer_<subject>.log -
Python: openneuro-py (https://pypi.org/project/openneuro-py/)
# Create/activate a virtual environment (optional)
python3 -m venv ~/.venvs/openneuro && source ~/.venvs/openneuro/bin/activate
pip install --upgrade pip openneuro-py
# Download longitudinal dataset ds004937 v1.0.1
python - <<'PY'
from openneuro import download
download(dataset='ds004937', target_dir='/path/to/BIDS-ds004937', include='*', tag='1.0.1')
PY
# Download cross-sectional dataset ds004965 v1.0.1
python - <<'PY'
from openneuro import download
download(dataset='ds004965', target_dir='/path/to/BIDS-ds004965', include='*', tag='1.0.1')
PYAfter downloading, use the dataset root directories (/path/to/BIDS-ds004937 or /path/to/BIDS-ds004965) as <BIDS_ROOT> in the examples below.
Use scripts/install.sh to create a micromamba environment with R packages and fsqc. FreeSurfer must be installed locally and FREESURFER_HOME should point to it (the installer will stop if it cannot find FreeSurfer; you can override with --allow-no-fs if you know what you’re doing).
Basic usage
- Standard install (requires FreeSurfer):
bash scripts/install.sh
- Low-storage install (put cache/tmp on a large disk and skip compilers):
bash scripts/install.sh --no-compilers --pkgs-dir /big/.mamba-cache --tmpdir /big/tmp
Expert usage
- Custom environment name and R version:
bash scripts/install.sh --env my-r-env --r 4.5
- Proceed without FreeSurfer (advanced; FS-dependent features won’t work):
bash scripts/install.sh --allow-no-fs
- Auto-install missing CLI prerequisites on Debian/Ubuntu:
bash scripts/install.sh --auto-apt
Options (with examples)
--env NAME— Environment name (default:fastsurfer-r).--r VERSION— R version override; default comes fromenvironment.yml.--no-fsqc— Skip installing fsqc into the env. You can add later:source scripts/mamba_activate.sh && python -m pip install fsqc.--no-compilers— Skip C/C++/Fortran compilers to save space.--pkgs-dir DIR— Package cache directory. Example:--pkgs-dir /data/cache/.mamba-cache.--tmpdir DIR— Temporary downloads directory. Example:--tmpdir /data/tmp.--min-tmp-gb N— Require at least N GB free in TMPDIR (default: 3). Example:--min-tmp-gb 5.--min-cache-gb N— Require at least N GB free in package cache (default: 5). Example:--min-cache-gb 8.--require-fs(default) — Fail if FreeSurfer is not detected.--allow-no-fs— Proceed without FreeSurfer (not recommended).--auto-apt— apt-get missing tools (curl,tar,grep,awk,sed,head) on Debian/Ubuntu.--use-specs— Use explicit conda specs instead ofenvironment.yml.
After installation
- Activate:
source scripts/mamba_activate.sh - Verify tools:
which Rscriptandrun_fsqc --help - Ensure FreeSurfer is active: set
FREESURFER_HOMEand sourceSetUpFreeSurfer.shif not already done by your environment.
Headless servers note
- fsqc’s surfaces module needs a graphical display (OpenGL). If no
DISPLAYis set,scripts/prep_long.pyautomatically disables fsqc surfaces and logs an info message. Screenshots + HTML work headlessly.
Both scripts read a single JSON file with top-level container paths and per-pipeline option sections. Replace placeholder paths below with your actual locations.
{
"fs_license": "/abs/path/to/license.txt",
"sif_file": "/abs/path/to/fastsurfer-gpu.sif",
"cross": {
"vox_size": "min",
"seg_only": true,
"3T": true,
"reg_mode": "coreg",
"qc_snap": false
// ... more cross-sectional options (see below)
},
"long": {
"parallel": null,
"parallel_seg": null,
"parallel_surf": null,
"reg_mode": "coreg",
"3T": true,
"qc_snap": false
// ... more longitudinal options (see below)
}
}-
Notes:
-
Top-level keys:
fs_license— Absolute path to your FreeSurfer license file.sif_file— Absolute path to the FastSurfer GPU Singularity image.
-
The
crossandlongsections mirror FastSurfer flags. Values are translated as follows:- Boolean
true→ adds--flag - Boolean
falseornull→ omitted - Strings/numbers → adds
--flag value
- Boolean
-
Unknown key behavior differs:
- Longitudinal (
long): unknown keys are ignored; run with--debugto see warnings. - Cross-sectional (
cross): keys are forwarded as flags to FastSurfer; unknown ones may cause errors. Keep thecrosssection minimal and only include supported flags.
- Longitudinal (
Common option keys you can use in cross/long:
- Parallelization and threading:
parallel,parallel_seg,parallel_surf,threads,threads_seg,threads_surf,batch - Device selection:
device,viewagg_device(GPU index if applicable) - MRI specifics and modes:
3T,reg_mode,surf_only,no_fs_T1,no_surfreg - Additional toggles:
qc_snap,ignore_fs_version,fstess,fsqsphere,fsaparc,allow_root,base
Tip: Keep only the options you actually need; remove or set unneeded keys to null.
This section lists commonly used keys you can set in fastsurfer_options.json. Not every key is required—omit what you don’t need.
-
Top-level (required):
fs_license(string): Absolute path to your FreeSurfer license file (license.txt).sif_file(string): Absolute path to your FastSurfer GPU.sifcontainer.
-
Cross (
crosssection) — forwarded to/fastsurfer/run_fastsurfer.sh:3T(bool): Enable 3T-specific heuristics.seg_only(bool): Run segmentation only (no surfaces).surf_only(bool): Run surface pipeline only.reg_mode(string): Registration mode, e.g.coreg.qc_snap(bool): Generate QC snapshots.device,viewagg_device(string/int): GPU selection if multiple GPUs.threads,threads_seg,threads_surf(int): Thread counts.parallel,parallel_seg,parallel_surf(bool): Enable parallel execution where supported.batch(int): Batch size or job parallelism (depends on stage).ignore_fs_version(bool): Skip FreeSurfer version checks.fstess,fsqsphere,fsaparc(bool): Advanced FreeSurfer toggles.no_fs_T1,no_surfreg(bool): Disable specific steps.vox_size(string): Voxel size handling (e.g.,min).no_biasfield,tal_reg,no_asegdkt,no_cereb,no_hypothal(bool): Advanced toggles.t2(string): Path to a T2 image (container path). Note: the wrapper also tries to auto-detect--t2; avoid setting both to prevent duplicates.- Avoid including runtime flags the wrapper sets:
t1,sid,sd,fs_license,sif_file,py.
-
Longitudinal (
longsection) — filtered and mapped to/fastsurfer/long_fastsurfer.sh:parallel,parallel_seg,parallel_surf(bool): Enable parallel execution.reg_mode(string): Registration mode (e.g.,coreg).qc_snap(bool): Generate QC snapshots.surf_only(bool): Surface pipeline only.3T(bool): 3T-specific heuristics.device,viewagg_device(string/int): GPU selection.threads,threads_seg,threads_surf(int): Thread counts.batch(int): Batch size or job parallelism.ignore_fs_version(bool): Skip FreeSurfer version checks.fstess,fsqsphere,fsaparc(bool): Advanced FreeSurfer toggles.no_fs_T1,no_surfreg(bool): Disable specific steps.allow_root(bool): Allow running as root inside the container.base(string): Advanced: base name control (used by some workflows).
For any option not listed here, consult FastSurfer’s upstream documentation. If an option isn’t recognized by the underlying scripts, it will be ignored in the longitudinal wrapper and may error in the cross wrapper.
Run FastSurfer cross-sectionally across a BIDS dataset, a specific subject, or a specific session across all subjects.
Usage:
bash bids_faststurfer.sh <BIDS_ROOT> <OUTPUT_DIR> -c <config.json> [--sub <sub-XXX>] [--ses <ses-YYY>] [--pilot] [--dry_run] [--debug]<BIDS_ROOT>: Path to your BIDS dataset root.<OUTPUT_DIR>: Destination directory for FastSurfer outputs (bind-mounted at/output).-c/--config: Path to your JSON config file (see above).--sub sub-XXX: Restrict to a single subject.--ses ses-YYY: Restrict to a session; can be used without--subto process that session for all subjects.--pilot: Randomly picks a single T1w to test the pipeline quickly.--dry_run: Prints the constructed Singularity command(s) without executing.--debug: Prints extra debug information (resolved paths, options, etc.).
What it does:
- Scans for T1w images (
*_T1w.nii.gz,*_T1w.nii, or*_desc-preproc_T1w.nii.gz) respecting--sub/--seslimits if provided. - Optionally attempts to find a matching T2w (
*_T2w.nii.gz) per T1w and adds--t2if found. - Builds and executes the FastSurfer cross-sectional pipeline with the appropriate
--t1,--sid, and other options derived from your BIDS dataset and config. - The wrapper takes care of container bindings internally; you only provide host paths for
<BIDS_ROOT>and<OUTPUT_DIR>.
Examples:
# Dry run across whole BIDS dataset
bash bids_faststurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer \
-c fastsurfer_options.json --dry_run
# Only subject sub-001 session ses-01
bash bids_faststurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer \
-c fastsurfer_options.json --sub sub-001 --ses ses-01
# Session-only across all subjects
bash bids_faststurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer \
-c fastsurfer_options.json --ses ses-01 --dry_run
# Pilot run (pick one random T1w)
bash bids_faststurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer \
-c fastsurfer_options.json --pilot --dry_run --debugNotes:
- Use a proper BIDS tree (e.g.,
sub-XXX/ses-YYY/anat/). The OpenNeuro examples above are already BIDS-compliant.
Run FastSurfer’s longitudinal pipeline either automatically (default) or manually.
Usage (auto mode, default):
bash bids_long_fastsurfer.sh <BIDS_ROOT> <OUTPUT_DIR> -c <config.json> [--pilot] [--dry_run] [--debug]Usage (manual mode):
bash bids_long_fastsurfer.sh <BIDS_ROOT> <OUTPUT_DIR> -c <config.json> \
--tid <subject> --tpids <sub-XXX_ses-YYY> [<sub-XXX_ses-ZZZ> ...] [--py <python>] [--dry_run] [--debug]- Auto mode:
- Scans each
sub-*forses-*and requires at least 2 sessions with valid T1w inanat/. - Builds timepoint lists and runs
long_fastsurfer.shper eligible subject. --pilotrandomly selects one eligible subject to process.
- Scans each
- Manual mode:
--tidis the template subject (with or withoutsub-prefix).--tpidsare timepoint IDs likesub-001_ses-01(one or more).- The script locates the T1w for each TPID under
sub-XXX/ses-YYY/anat/.
--pylets you choose the Python executable inside the container (default:python3).
What it does:
- Builds and executes the FastSurfer longitudinal pipeline with the appropriate
--tid,--t1s,--tpids, and options based on your dataset and config. The wrapper handles container bindings internally.
Examples:
# Auto-detect all longitudinal subjects (>= 2 sessions)
bash bids_long_fastsurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer_long \
-c fastsurfer_options.json --dry_run
# Auto + pilot (random one subject)
bash bids_long_fastsurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer_long \
-c fastsurfer_options.json --pilot --dry_run
# Manual subject with 2 sessions
bash bids_long_fastsurfer.sh /path/to/BIDS /path/to/derivatives/fastsurfer_long \
-c fastsurfer_options.json --tid sub-001 \
--tpids sub-001_ses-01 sub-001_ses-02 --dry_run --debugNotes:
--pilotis only valid in auto mode.- Unknown keys in the
longsection are ignored, with warnings printed in--debugmode.
- All outputs go to
<OUTPUT_DIR>. - Cross-sectional runs create one FreeSurfer subject directory per input (named by
--sid). - Longitudinal runs create base and TP directories under the same
<OUTPUT_DIR>according to FastSurfer’s conventions.
- “singularity: command not found”
- Install Singularity/Apptainer or run inside a suitable environment; on Apptainer, you can create an alias:
alias singularity=apptainer.
- Install Singularity/Apptainer or run inside a suitable environment; on Apptainer, you can create an alias:
- “jq: command not found”
- Install jq (e.g.,
sudo apt-get install jq).
- Install jq (e.g.,
- “Config or license not found”
- Ensure
fastsurfer_options.jsonexists and contains valid absolute paths forfs_licenseandsif_file.
- Ensure
- “No T1w found”
- Verify your BIDS tree and that
anat/*_T1w.nii[.gz]files exist for the targeted subjects/sessions.
- Verify your BIDS tree and that
- Permission issues writing to output
- Use an output directory you own; avoid binding network or read-only locations.
- GPU access inside Singularity
- These wrappers pass
--nv. Ensure the host has NVIDIA drivers and the container supports GPU.
- These wrappers pass
- On servers without a graphical display (
DISPLAYnot set), the fsqc "surfaces" module requires OpenGL and will fail with a GLFW error. Our prep script automatically disables fsqc surfaces when it detects no DISPLAY and will log an info message. You can still generate headless-friendly outputs by enabling screenshots/metrics (e.g.,--qc --qc-screenshots --qc-html).
- Start with
--dry_run --debugto inspect the constructed commands. - Use
--pilotto quickly validate the environment on a small subset before scaling up. - Keep your config minimal and explicit; remove unused keys to reduce ambiguity.
To run FreeSurfer-style longitudinal statistics (e.g., with the R package fslmer), generate a Qdec file from your BIDS participants.tsv and your FastSurfer/FreeSurfer subjects directory.
Script: scripts/generate_qdec.py
Inputs:
--participants: Path toparticipants.tsvwith at leastparticipant_idand optionallysession_id, plus covariates (age,sex,group, ...).--subjects-dir: Path to the FastSurfer/FreeSurfer subjects directory. Expect directories likesub-1291003(base) andsub-1291003_ses-1(timepoint).--output: Output path for the Qdec TSV (default:qdec.table.dat).--include-columns: Optional explicit list of covariates to include fromparticipants.tsv. If omitted, all columns except participant/session are included.--strict: If set, the script fails when a timepoint has no matching participants row; otherwise fillsn/a.--inspect: Printparticipants.tsvcolumn names and exit (useful for column selection).--bids: Optional BIDS root; if provided, the script prints a consistency summary comparingparticipants.tsv,subjects_dir, and the BIDS tree.--list-limit: Maximum number of subject IDs to print per list in the summary (default: 20). The summary avoids printing the same list twice when it’s identical across sources.
Output columns:
fsid— timepoint subject id (e.g.,sub-1291003_ses-1)fsid-base— base/template id (e.g.,sub-1291003)tp— numeric timepoint derived fromses-<number>(orn/aif not parseable)- Covariates — selected from
participants.tsv
Example:
python scripts/generate_qdec.py \
--participants /path/to/BIDS/participants.tsv \
--subjects-dir /path/to/derivatives/fastsurfer_long \
--output /path/to/qdec.table.dat \
--include-columns age sex groupYou can also inspect columns or print a consistency summary:
# See columns in participants.tsv
python scripts/generate_qdec.py --participants /path/to/participants.tsv --subjects-dir /path/to/subjects --inspect
# Generate Qdec and print a summary of overlaps/missing subjects across sources
python scripts/generate_qdec.py --participants /path/to/participants.tsv --subjects-dir /path/to/subjects --bids /path/to/BIDSUse the resulting qdec.table.dat with FreeSurfer longitudinal statistics and tools like fslmer:
- FreeSurfer Longitudinal: https://surfer.nmr.mgh.harvard.edu/fswiki/LongitudinalStatistics
- fslmer: https://github.com/Deep-MI/fslmer
A flexible R helper is included to run univariate models on aseg/aparc tables using:
- Mixed-effects via
fslmer - Generalized Additive Models via
mgcv - Generalized Linear Models via base
stats
Script: scripts/fslmer_univariate.R
Basic usage:
Rscript scripts/fslmer_univariate.R \
--qdec /path/to/qdec.table.dat \
--aseg /path/to/aseg.long.table \
--roi Left.Hippocampus \
--formula "~ tp*group" \
--zcols 1,2 \
--contrast 0,0,0,1 \
--outdir results_univar --save-mergedFlags:
--qdec: Qdec TSV generated by this repo.--aseg: aseg/aparc table made withasegstats2table --qdec-longoraparcstats2table --qdec-long.--roi: ROI column name (R uses.instead of-, e.g.,Left.Hippocampus).--formula: R fixed-effect formula (default~ tp). Add covariates/interaction as needed.--zcols: Random-effect columns (in design matrix order).1,2means random intercept and random slope for time.--contrast: Comma-separated vector matchingncol(X)to test a specific effect (optional).--time-col: If your time variable is nottporTime.From.Baseline, set it here.--id-col: If the ID column in aseg is notMeasure:volume, specify it.--print-cols: Print column names of qdec and aseg and exit.--outdir: Output directory (defaultfslmer_out).--save-merged: Save merged design/response as CSV.
Outputs in --outdir:
fit.rds— model fit fromlme_fit_FS(RDS).Bhat.txt— fixed-effect coefficients.F_test.txt— if--contrastgiven, F-values, p-values, sign, and df.merged_data.csv— optional merged data dump.
This maps closely to the fslmer README tutorial while automating the data loading/merging and making the model specification configurable via flags.
Config-driven usage and reproducibility:
- Instead of passing many flags, you can provide a JSON config via
--config(seeconfigs/fslmer_univariate.example.json). Any CLI flag overrides the same field in the JSON. The script writes the merged settings to<outdir>/used_config.json. - Example JSON fields:
qdec,aseg,roi,formula,zcols(array of integers),contrast(array),outdir,time_col,id_col. - To run: point
--configto your JSON file; optionally add--outdiror other flags to override.
Additional flag:
--config: Path to a JSON file with the same keys as the CLI flags. Useful to version-control analyses.
Set up everything needed for the R analysis helper with micromamba:
bash scripts/install.shWhat it does:
- Bootstraps micromamba under
~/.local/micromambaif missing - Creates/updates an env (default
fastsurfer-r) fromscripts/environment.ymlin strict mode - Installs/validates R packages in the right order (checkmate/backports → bettermc → fslmer; plus mgcv, optparse, jsonlite)
Activate, verify, and use:
# Activate the env
source scripts/mamba_activate.sh
# Verify packages
Rscript -e 'pkgs <- c("optparse","jsonlite","mgcv","checkmate","bettermc","fslmer"); print(sapply(pkgs, requireNamespace, quietly=TRUE))'
# See helper usage
Rscript scripts/fslmer_univariate.R --help
# Deactivate later
source scripts/mamba_deactivate.shAdvanced install options:
# Use spec-based installation (alternative to YAML), skip compilers on macOS
bash scripts/install.sh --use-specs --no-compilers
# Choose a custom env name and R version
bash scripts/install.sh --env my-fast-env --r 4.4Some FreeSurfer utilities (e.g., asegstats2table --qdec-long) expect timepoints to be arranged as <fsid>.long.<fsid-base>/stats/aseg.stats. FastSurfer’s longitudinal pipeline may not create these .long.* directories by default.
To make FreeSurfer tools work without re-running with FreeSurfer, this repo provides a safe symlink workflow:
# Preview/verify which links would be needed (no changes)
python scripts/generate_qdec.py \
--participants /path/to/participants.tsv \
--subjects-dir /path/to/fastsurfer_subjects \
--verify-long --list-limit 10
# Create .long.<base> symlinks that point to each timepoint directory (dry-run)
python scripts/generate_qdec.py \
--participants /path/to/participants.tsv \
--subjects-dir /path/to/fastsurfer_subjects \
--link-long --link-dry-run
# Actually create/update links (careful):
python scripts/generate_qdec.py \
--participants /path/to/participants.tsv \
--subjects-dir /path/to/fastsurfer_subjects \
--link-long
# If an existing symlink points elsewhere, allow updating it:
python scripts/generate_qdec.py \
--participants /path/to/participants.tsv \
--subjects-dir /path/to/fastsurfer_subjects \
--link-long --link-forceAfter creating the links, asegstats2table --qdec-long will be able to find stats/aseg.stats under the expected names.
- FastSurfer is developed by the FastSurfer team; see their documentation for details and licensing.
- This repository’s scripts were created for the 2025 workshop. See
license.txtfor terms applying to this repo.