Tidy, BIDS-aware extraction of FreeSurfer stats files into analysis-ready tabular datasets.
FreeSurfer ships aparcstats2table and asegstats2table, but they require you to enumerate subject IDs manually, don't understand BIDS session structure, and produce flat tables that need further wrangling before they're usable in longitudinal mixed-effects models. Every lab ends up writing the same one-off parsing script. fsglean replaces that script with a documented, tested, reusable tool.
After running FreeSurfer (or Infant FreeSurfer, or the dHCP pipeline) on a longitudinal cohort, you have per-subject stats files:
derivatives/freesurfer/
sub-01/ses-V1/stats/lh.aparc.stats
sub-01/ses-V1/stats/rh.aparc.stats
sub-01/ses-V1/stats/aseg.stats
sub-01/ses-V2/stats/lh.aparc.stats
...
sub-87/ses-V3/stats/aseg.stats
To get from those files to a table you can actually model, you need to:
- Find every subject and session directory
- Parse the
# ColHeadersline from each file to get column names - Handle left and right hemispheres separately but consistently
- Merge across subjects and sessions with
sub_idandses_idkeys - Handle missing files without silent data loss
- Produce both long format (for
lme4in R) and wide format (for cross-sectional analyses) - Document what every column means
fsglean does all of this in one command.
- BIDS-aware directory traversal — finds all subjects and sessions automatically; handles both nested (
sub-01/ses-V1/) and flat (sub-01_ses-V1/) layouts - Long and wide format output — long format is ready for
lme4/nlmein R orstatsmodelsin Python; wide format is ready for cross-sectional analyses - Auto-generated data dictionary — one row per variable documenting name, description, units, atlas, and source file
- Subject-session manifest — records which files were found, which are missing, and why
- QC-flag column — sessions with suspiciously extreme values (>4 SD from cohort mean per metric) are flagged for review; final inclusion decisions remain yours
- Hemisphere handling — left and right are kept as separate rows (long) or prefixed columns (
lh_,rh_) (wide); aseg structures labeledbilateral - Multiple atlases — supports
aparc.stats(Desikan-Killiany),aparc.a2009s.stats(Destrieux),aparc.DKTatlas.stats(DKT),aseg.stats, andwmparc.stats - Infant FreeSurfer compatible — output format is identical to standard FreeSurfer; no special handling required
git clone https://github.com/TravisBeckwith/fsglean
cd fsglean
pip install -e .
Dependencies: Python >= 3.9, pandas >= 1.3, click >= 8.0, pybids >= 0.15
fsglean /path/to/derivatives/freesurfer/ \
--stats aparc aseg \
--metrics ThickAvg GrayVol SurfArea Volume_mm3 \
--output ./tables/This produces five files in ./tables/:
| File | Contents |
|---|---|
cortical_long.csv |
Long-format cortical stats (aparc) |
subcortical_long.csv |
Long-format subcortical volumes (aseg) |
merged_wide.csv |
Wide-format merged table, one row per sub-ses |
data_dictionary.csv |
Variable documentation |
manifest.csv |
Subject-session inventory with missing-file flags |
| File | Atlas | Structure |
|---|---|---|
lh.aparc.stats |
Desikan-Killiany | Left hemisphere cortical ROIs |
rh.aparc.stats |
Desikan-Killiany | Right hemisphere cortical ROIs |
lh.aparc.a2009s.stats |
Destrieux | Left hemisphere cortical ROIs |
rh.aparc.a2009s.stats |
Destrieux | Right hemisphere cortical ROIs |
lh.aparc.DKTatlas.stats |
DKT | Left hemisphere cortical ROIs |
rh.aparc.DKTatlas.stats |
DKT | Right hemisphere cortical ROIs |
aseg.stats |
Subcortical segmentation | Bilateral subcortical volumes |
wmparc.stats |
White matter parcellation | White matter ROI volumes |
Note:
wmparc.statsextraction is supported but the metrics table below covers onlyaparc.statsandaseg.stats.wmparc.statscolumns follow the same format asaseg.stats(NVoxels, Volume_mm3, normMean, normStd, normMin, normMax, normRange).
One row per subject × session × hemisphere × region × metric:
sub_id,ses_id,hemi,atlas,region,metric,value,units
sub-01,ses-V1,lh,desikan-killiany,bankssts,ThickAvg,2.571,mm
sub-01,ses-V1,lh,desikan-killiany,bankssts,SurfArea,1088,mm2
sub-01,ses-V1,lh,desikan-killiany,bankssts,GrayVol,3000,mm3
sub-01,ses-V1,rh,desikan-killiany,bankssts,ThickAvg,2.643,mm
sub-01,ses-V2,lh,desikan-killiany,bankssts,ThickAvg,2.489,mm
Load directly into lme4:
library(tidyverse)
library(lme4)
df <- read_csv("cortical_long.csv") |>
filter(atlas == "desikan-killiany", metric == "ThickAvg") |>
left_join(demographics, by = c("sub_id", "ses_id"))
model <- lmer(value ~ age_at_scan * group + (1 + age_at_scan | sub_id),
data = df)One row per subject × session × structure × metric:
sub_id,ses_id,hemi,atlas,region,metric,value,units
sub-01,ses-V1,bilateral,aseg,Left-Hippocampus,Volume_mm3,4128.5,mm3
sub-01,ses-V1,bilateral,aseg,Right-Hippocampus,Volume_mm3,4052.1,mm3
sub-01,ses-V1,bilateral,aseg,Left-Amygdala,Volume_mm3,1643.2,mm3
One row per subject × session, all regions as columns. Structure names are sanitized for R and Python compatibility: hyphens are replaced with underscores (e.g., Left-Hippocampus → Left_Hippocampus):
sub_id,ses_id,lh_bankssts_ThickAvg,lh_bankssts_SurfArea,rh_bankssts_ThickAvg,...,Left_Hippocampus_Volume_mm3,...
sub-01,ses-V1,2.571,1088,2.643,...,4128.5,...
sub-01,ses-V2,2.489,1071,2.601,...,4089.3,...
column_name,description,units,atlas,source_file,freesurfer_metric
lh_bankssts_ThickAvg,Mean cortical thickness in the left banks of the superior temporal sulcus,mm,desikan-killiany,lh.aparc.stats,ThickAvg
lh_bankssts_SurfArea,Surface area of the left banks of the superior temporal sulcus,mm2,desikan-killiany,lh.aparc.stats,SurfArea
Left_Hippocampus_Volume_mm3,Volume of the left hippocampus,mm3,aseg,aseg.stats,Volume_mm3
sub_id,ses_id,lh_aparc_found,rh_aparc_found,aseg_found,notes
sub-01,ses-V1,True,True,True,
sub-01,ses-V2,True,True,False,aseg.stats missing
sub-03,ses-V1,True,False,True,rh.aparc.stats missing
| Metric | Description | Units |
|---|---|---|
NumVert |
Number of vertices in the ROI | vertices |
SurfArea |
Surface area | mm² |
GrayVol |
Gray matter volume | mm³ |
ThickAvg |
Mean cortical thickness | mm |
ThickStd |
Standard deviation of cortical thickness | mm |
MeanCurv |
Mean curvature | mm⁻¹ |
GausCurv |
Gaussian curvature | mm⁻² |
FoldInd |
Folding index | unitless |
CurvInd |
Intrinsic curvature index | unitless |
| Metric | Description | Units |
|---|---|---|
NVoxels |
Number of voxels in the structure | voxels |
Volume_mm3 |
Volume of the structure | mm³ |
normMean |
Mean intensity of normalized volume | unitless |
normStd |
Standard deviation of intensity | unitless |
normMin |
Minimum intensity | unitless |
normMax |
Maximum intensity | unitless |
Usage: fsglean [OPTIONS] DERIVATIVES_DIR
Extract FreeSurfer stats files from a BIDS derivatives directory into
tidy tabular datasets.
Arguments:
DERIVATIVES_DIR Path to the FreeSurfer BIDS derivatives directory.
Options:
--stats TEXT Stats files to extract. Choices: aparc, aparc.a2009s,
aparc.DKTatlas, aseg, wmparc. Can be specified
multiple times. Default: aparc aseg
--metrics TEXT Specific metrics to include. If omitted, all metrics
are included. Can be specified multiple times.
--subjects TEXT Subject IDs to include (e.g. sub-01 sub-02). If
omitted, all subjects are included.
--sessions TEXT Session IDs to include (e.g. ses-V1 ses-V2). If
omitted, all sessions are included.
--output PATH Output directory. Created if it does not exist.
Default: ./fsglean_output/
--format [long|wide|both]
Output format. Default: both
--no-dict Skip data dictionary generation.
--no-manifest Skip manifest generation.
--qc-threshold FLOAT Flag values more than N standard deviations from the
cohort mean. Default: 4.0. Set to 0 to disable.
--help Show this message and exit.
from fsglean import FSGlean
extractor = FSGlean(
derivatives_dir="/path/to/derivatives/freesurfer/",
stats=["aparc", "aseg"],
metrics=["ThickAvg", "GrayVol", "Volume_mm3"],
)
long_df = extractor.to_long()
wide_df = extractor.to_wide()
dictionary = extractor.data_dictionary()
manifest = extractor.manifest()
long_df.to_csv("cortical_long.csv", index=False)fsglean expects FreeSurfer output organized under a BIDS derivatives root. Both nested and flat session layouts are supported:
Nested (preferred):
derivatives/freesurfer/
sub-01/
ses-V1/
stats/
lh.aparc.stats
rh.aparc.stats
aseg.stats
ses-V2/
stats/
...
sub-02/
...
Flat (also supported):
derivatives/freesurfer/
sub-01_ses-V1/
stats/
lh.aparc.stats
...
sub-01_ses-V2/
...
Cross-sectional (no session level):
derivatives/freesurfer/
sub-01/
stats/
lh.aparc.stats
...
In the cross-sectional case, ses_id is set to ses-01 in all output tables. If you prefer NA, pass --no-session-fallback and the column will be empty.
The stats file format produced by Infant FreeSurfer and the dHCP structural pipeline is identical to standard FreeSurfer. fsglean parses these outputs without modification.
One practical note for infant data: cortical thickness, surface area, and subcortical volumes change rapidly across the first two years of life. When modeling these trajectories, age at scan (in days or weeks post-menstrual age for preterm infants) is a more appropriate time variable than session label. The manifest output includes ses_id but does not attempt to extract age — that should come from your participants.tsv or REDCap export and be merged on sub_id × ses_id keys.
| Feature | aparcstats2table |
asegstats2table |
fsglean |
|---|---|---|---|
| BIDS directory traversal | ✗ | ✗ | ✓ |
| Longitudinal session handling | ✗ | ✗ | ✓ |
| Long format output | ✗ | ✗ | ✓ |
| Wide format output | ✓ (limited) | ✓ (limited) | ✓ |
| Data dictionary | ✗ | ✗ | ✓ |
| Subject-session manifest | ✗ | ✗ | ✓ |
| Missing file handling | ✗ | ✗ | ✓ |
| Python API | ✗ | ✗ | ✓ |
| Multi-atlas in one call | ✗ | ✗ | ✓ |
fsglean is the first module in a planned suite of infant neuroimaging output extractors. Upcoming modules follow the same design: BIDS-aware input, tidy output, auto-generated data dictionaries.
- v0.2 — MRIQC output extractor (
group_T1w.tsv,group_bold.tsv, individual JSON IQMs) - v0.3 — QSIRecon/NODDI extractor (NDI, ODI, FWF, FA, MD per bundle; tract profiles; infant diffusivity parameter logging)
- v0.4 — NiBabies/xcp-d functional connectivity extractor (connectivity matrices using Myers-Labonte, Gordon, and Glasser parcellations; QC summaries from confounds TSV)
- v0.5 — DESPOT/QUIT quantitative MRI extractor (T1, T2, myelin water fraction per atlas ROI)
- v1.0 — Unified CLI that orchestrates all modules and merges outputs into a single analysis-ready dataset
Contributions welcome. Please open an issue before submitting a pull request for new features. Bug reports with a minimal reproducible example are especially helpful.
git clone https://github.com/TravisBeckwith/fsglean
cd fsglean
pip install -e ".[dev]"
pytest tests/MIT