Skip to content

TravisBeckwith/fsglean

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 

Repository files navigation

fsglean

DOI

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.


The problem

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:

  1. Find every subject and session directory
  2. Parse the # ColHeaders line from each file to get column names
  3. Handle left and right hemispheres separately but consistently
  4. Merge across subjects and sessions with sub_id and ses_id keys
  5. Handle missing files without silent data loss
  6. Produce both long format (for lme4 in R) and wide format (for cross-sectional analyses)
  7. Document what every column means

fsglean does all of this in one command.


Features

  • 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/nlme in R or statsmodels in 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 labeled bilateral
  • Multiple atlases — supports aparc.stats (Desikan-Killiany), aparc.a2009s.stats (Destrieux), aparc.DKTatlas.stats (DKT), aseg.stats, and wmparc.stats
  • Infant FreeSurfer compatible — output format is identical to standard FreeSurfer; no special handling required

Installation


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


Quick start

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

Supported input files

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.stats extraction is supported but the metrics table below covers only aparc.stats and aseg.stats. wmparc.stats columns follow the same format as aseg.stats (NVoxels, Volume_mm3, normMean, normStd, normMin, normMax, normRange).


Output format examples

Long format (cortical_long.csv)

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)

Long format (subcortical_long.csv)

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

Wide format (merged_wide.csv)

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-HippocampusLeft_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,...

Data dictionary (data_dictionary.csv)

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

Manifest (manifest.csv)

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

FreeSurfer stats file metrics

aparc.stats (cortical, per hemisphere)

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

aseg.stats (subcortical segmentation)

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

CLI reference

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.

Python API

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)

BIDS directory layout

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.


Notes on Infant FreeSurfer and the dHCP pipeline

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.


Comparison with existing tools

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

Roadmap

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

Contributing

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/

License

MIT

About

BIDS-aware extraction of FreeSurfer stats files into tidy, longitudinal tables.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages