Stage 10: FreeSurfer anatomical reconstruction
Author: Hadi Zaatiti <hadi.zaatiti@nyu.edu>
What & why
Stage 10 of the KIT general pipeline is the first anatomy stage. For
every selected subject with a T1-weighted MRI it runs FreeSurfer’s
recon-all -all (skull stripping, white and pial surfaces, cortical
parcellation, subcortical segmentation) and then builds the scalp
surface surf/lh.seghead with FreeSurfer’s mkheadsurf, the same
call MNE’s make_scalp_surfaces makes, and the BEM surfaces (brain,
inner skull, outer skull, outer skin) with mri_watershed, the call
MNE’s make_watershed_bem makes.
Both products are needed later: the manual MEG-MRI coregistration of stage 12 fits the digitised head shape onto the scalp surface, and the forward model of stage 14 builds the BEM and the source space on the FreeSurfer surfaces. Stage 11 checks every reconstruction before stage 12 may use it.
The stage runs on Linux (workstation, HPC node) or in WSL on Windows:
FreeSurfer is a Linux package. On Windows, the Python entry point is started
with the neurowaves interpreter and forwards the whole run into the WSL
distribution, where only python3 and PyYAML are needed. The MNE
environment is not used by this stage.
Subjects without a T1w image are not an error: they are recorded as
template_fsaverage (default policy) so that stage 12 coregisters them to
the fsaverage template, or reported as an error when
freesurfer.missing_anat: error.
What you need to provide
One T1w image per subject in the BIDS dataset:
sub-<id>/anat/sub-<id>_T1w.nii[.gz]orsub-<id>/ses-<id>/anat/sub-<id>_ses-<id>_T1w.nii[.gz]. Exactly one image is used per subject; several candidates are reported so you can choose (the stage does not pick for you).FreeSurfer 7.x installed on the Linux host or in the WSL distribution, with its license (
FS_LICENSEin the environment orfreesurfer.license_filein the YAML). See Optional: FreeSurfer for stage 10 on the setup page.Disk space of roughly 1 GB per subject and several hours of CPU time per subject (
recon-alltakes 4 to 12 hours per subject on one core; the stage runs subjects in parallel when the machine allows it).
Configuration file
freesurfer:
enabled: true
missing_anat: fsaverage # fsaverage = template in stage 12 for subjects without a T1w; error = stop
subjects_dir: null # null -> <bids_root>/derivatives/10-freesurfer_reconstruction/subjects
freesurfer_home: null # null -> environment or auto-discovery of a Linux install
license_file: null # null -> FS_LICENSE
jobs: null # null -> auto-plan from CPUs and RAM; a number overrides
threads_per_subject: null # null -> auto-plan; 1 disables OpenMP
force: false # rerun verified subjects
dry_run: false # validate discovery and resources, launch nothing
auto_cw256: false # retry a field of view > 256 with -cw256 (stage 11 checks the crop)
scalp_surface: true # mkheadsurf -> surf/lh.seghead after recon-all
scalp_threshold: 20 # mkheadsurf -thresh1/-thresh2 (MNE's default)
project and subjects select the BIDS root and the subjects as in
every stage; an empty subjects.include processes every BIDS subject and
subjects.exclude is always applied.
Resource planning
With jobs and threads_per_subject both null, the stage measures
the CPUs and the memory available to the process at launch (Linux affinity
and cgroup limits, Slurm allocations) and plans on every machine and run:
one CPU and at least 10 % of the RAM (or 2 GiB) are left free; 8 GiB are
budgeted for the first concurrent subject and 4 GiB for each further one
(FreeSurfer’s system requirements); the remaining CPUs are spread across as
many memory-safe subject jobs as possible, and the threads of each job go to
recon-all -openmp. A single subject therefore uses most of the machine;
a cohort runs several subjects at once. --jobs / --threads or the
YAML values override the plan.
How to run
On Linux or HPC, from the repository root (the stage does not import the
pipeline package, so it also runs by file path):
python3 pipeline/mne_pipelines/kit_general_pipelines/10-freesurfer_reconstruction.py \
-c pipeline/mne_pipelines/kit_general_pipelines/pipeline_config_files/<project>_config.yml
On Windows, run the same command with the neurowaves interpreter, or use
the VS Code launch entries <project> 10 FreeSurfer reconstruction (T1w,
runs in WSL), ... dry run and ... FORCE rerun. The entry point
translates the Windows paths (C:\... to /mnt/c/...), starts the run
inside the distribution named by FREESURFER_WSL_DISTRO (default
Ubuntu-22.04) and streams its output. MEG_DATA must be set in the
environment before VS Code starts.
Options:
--dry-run: discover the T1w images, plan the resources, write the audit files and launch nothing. Run this first on a new dataset.--force: rerun subjects whose reconstruction is verified complete, without deleting anything (recon-allresumes over the existing folder).--jobs Nand--threads N: override the plan.--subjects-dir,--freesurfer-home,--license-file,--bids-root: override the YAML values.
What the script does
Resolve the BIDS root and the subjects, find the T1w of each subject, and classify it: T1w found, no T1w (
template_fsaverageor error), several T1w (reported).Check the existing
SUBJECTS_DIRentry of each subject. A subject is complete only whenscripts/recon-all.doneexists,recon-all.errordoes not, the lastrecon-all.logrecords a successful run, and the required MRI, surface, annotation and statistics files are non-empty. Complete subjects areskipped_completeunless--force; incomplete ones are resumed (recon-allcontinues from its last finished step).Plan jobs and threads, then run
recon-all -allfor the pending subjects, several at a time. The live FreeSurfer output is printed with the subject as prefix and saved in full to the run’s subject log; a quiet job prints a heartbeat every 30 seconds.If
recon-allfails because the field of view exceeds 256 andauto_cw256is true, back up the oversizedmri/orig.mgzunderscripts/nw_cw256_backups/and retry with-cw256(FreeSurfer crops the conformed image to 256 mm; stage 11 later measures whether the scalp touches the faces of the cropped volume).After every verified reconstruction, including skipped ones, create
surf/lh.segheadwithmkheadsurf -subjid <subject> -srcvol T1.mgz -thresh1 20 -thresh2 20when it is missing or older thanmri/T1.mgz. Then, when missing or older thanmri/T1.mgz(freesurfer.bem_surfaces: true), runmri_watershed -useSRAS -surf bem/watershed/<subject> mri/T1.mgz bem/watershed/ws.mgz. The last step of MNE’smake_watershed_bem(the T1 volume header written into each surface and the copy tobem/<name>.surf) needs MNE, which the FreeSurfer environment does not have; stage 14 does it the first time it needsbem/inner_skull.surf.Record each subject in the run summary with the FreeSurfer version of the run and the build stamps found in its
recon-all.log, so a subject reconstructed with an older version keeps its true version history; copy the run summary to the stage root; exit with code 1 when any subject failed (the console lists them with the reason).
Outputs
Under <bids_root>/derivatives/10-freesurfer_reconstruction/:
subjects/ # SUBJECTS_DIR
sub-<id>/
mri/, surf/, label/, stats/, scripts/ # recon-all
surf/lh.seghead # scalp surface (mkheadsurf)
bem/watershed/sub-<id>_{brain,inner_skull,outer_skull,outer_skin}_surface # mri_watershed
bem/{brain,inner_skull,outer_skull,outer_skin}.surf # finalized by stage 14
bem/sub-<id>-head-{dense,medium,sparse}.fif # written later by stage 11
freesurfer_summary.csv # latest run, one row per subject
runs/<run-id>/
freesurfer_summary.csv
run_metadata.txt # command line, host, Slurm allocation, FreeSurfer version
config.yml # resolved config of the run
logs/sub-<id>.log # full recon-all output per subject
freesurfer_summary.csv has one row per subject: run_id,
subject, mri_source (individual | template_fsaverage),
t1_path, freesurfer_subject_dir, status (success |
skipped_complete | failed | template_fsaverage | missing_t1
| ambiguous_t1 | dry_run),
action, exit_code, elapsed_seconds, the log paths, the
command, parallel_jobs, threads_per_subject,
resource_plan, hostname, slurm_job_id,
freesurfer_version, reconstruction_versions, and the scalp surface
columns (head_surface, head_surface_status,
head_surface_message).
Report
The report’s stage-10 section shows the summary table with the resource
plan of the latest run, the FreeSurfer version of each reconstruction, and
the subjects that will use the template. On a workstation, re-render the
report after a run on a compute node with the report_generation.refresh
command of the setup page (the compute node has no Quarto).
Edge cases & safeguards
A present T1w with a failed reconstruction is an incomplete individual reconstruction, never a template subject: fix it and rerun stage 10. The template is used only when there is no T1w at all (or when stage 11 gives a
use_fsaverageverdict).Interrupted run: start the stage again;
recon-allresumes and the summary records the resume.Several T1w images for a subject:
ambiguous_t1, the stage lists them and processes nothing for that subject until one is kept (or renamed out of theanatfolder).Field of view above 256:
recon-allfails with a message aboutcw256; setauto_cw256: trueand rerun, then look at the coverage figure of stage 11 for that subject.Old folder name: reconstructions made before the 2026-10 renumbering in
7-freesurfer_reconstruction/or8-freesurfer_reconstruction_script/are still found by stages 11 to 15 when the new folder does not exist. Rename them withrename_legacy_derivatives.py -c <config> --applyonce norecon-allis running.
Troubleshooting
``No module named yaml`` inside WSL:
sudo apt install python3-yamlin the distribution.FreeSurfer not found: set
freesurfer.freesurfer_homeor sourceSetUpFreeSurfer.shin the shell that starts the stage; in WSL the stage auto-discovers/usr/local/freesurfer/<version>.License error: set
FS_LICENSEorfreesurfer.license_fileto thelicense.txtobtained from the FreeSurfer website.Out of memory: lower
jobs(each concurrent subject needs several GB) or setthreads_per_subject: 1.Slow on a syncing drive: a
SUBJECTS_DIRunder Box or OneDrive is slow and may corrupt files; setfreesurfer.subjects_dirto a local disk and copy the results afterwards, or exclude the folder from syncing.