Reading FreeSurfer stats files

FreeSurfer’s recon-all produces stats files with region-level measures – cortical thickness, surface area, volume. ggseg includes readers that pull these files directly into data frames shaped for plotting.

All three functions below come from the ggseg.formats package, which ggseg re-exports.

library(ggseg)
library(ggplot2)
library(dplyr)

Reading a single stats file

After recon-all finishes, each subject has a stats/ folder with parcellation data. read_freesurfer_stats() reads one file at a time:

subjects_dir <- Sys.getenv("SUBJECTS_DIR")
stats_file <- file.path(subjects_dir, "bert/stats/lh.aparc.stats")

data <- read_freesurfer_stats(stats_file)
data

The hemisphere is encoded in the filename (lh. or rh.). To match the atlas, prepend it to the label column:

ggplot(data |> mutate(label = paste0("lh_", label))) +
  geom_brain(atlas = dk(), mapping = aes(fill = ThickAvg))

Reading stats files across subjects

read_atlas_files() reads all matching stats files from a subjects directory. Pass a regular expression to select the right files:

dat <- read_atlas_files(subjects_dir, "aparc.stats$")
dat

The trailing $ matters – it ensures you get aparc.stats but not aparc.a2009s.stats.

The function adds hemisphere prefixes automatically, so the data is ready for plotting:

ggplot(dat) +
  geom_brain(atlas = dk(), mapping = aes(fill = ThickStd))

To show all metrics at once, pivot and facet:

library(tidyr)

dat |>
  pivot_longer(-c(subject, label), names_to = "stat", values_to = "val") |>
  ggplot() +
  geom_brain(atlas = dk(), mapping = aes(fill = val)) +
  facet_wrap(~stat)

Reading FreeSurfer stats tables

FreeSurfer’s aparcstats2table and asegstats2table commands create summary tables across subjects. Read these with read_freesurfer_table():

table_path <- "path/to/aparc.volume.table"

read_freesurfer_table(table_path)

The output has three columns: subject, label, and value.

Cleaning label suffixes

Stats tables append the measure name to each label (e.g. lh_bankssts_volume). The measure argument strips that suffix and renames the value column:

dat <- read_freesurfer_table(table_path, measure = "volume")
dat

Filtering non-region rows

FreeSurfer tables include summary measures (total volume, ICV) that don’t correspond to atlas regions. Filter them before plotting:

ggplot(dat |> filter(grepl("lh|rh", label))) +
  geom_brain(atlas = dk(), mapping = aes(fill = volume))

The grepl("lh|rh", label) pattern keeps only hemisphere-specific rows.