Surface Templates: Geometry vs. Data
neuroatlas Dev Team
2026-02-26
Source:vignettes/surface-templates.Rmd
surface-templates.RmdWhy this vignette
Surface work in neuroatlas uses two key pieces:
-
Geometry (mesh and topology): stored as a
neurosurf::SurfaceGeometry. -
Per-vertex data (numbers or labels): stored as the
dataslot of aneurosurf::NeuroSurface(one value per vertex).
This vignette shows how to fetch surface geometry from
TemplateFlow, what each helper returns, and how to attach data when you
need a full NeuroSurface.
Quick reference
-
get_surface_template()→ character path to a.surf.giifile (one hemi). No mesh loaded yet. -
load_surface_template()→SurfaceGeometry(mesh + graph + hemi), still no per-vertex data. Can return L/R or both as a list. - Atlas helpers (e.g.,
schaefer_surf(),glasser_surf()) →LabeledNeuroSurfacewith integer labels indataand a mesh ingeometry. - Packaged fsaverage6 meshes live in
data/fsaverage.rdaand are used automatically byschaefer_surf(..., space = "fsaverage6").
Fetching geometry only
Note: load_surface_template() requires TemplateFlow. The
fsaverage template may have limited availability for
surface files. Check tflow_spaces() for available
templates.
# fsaverage6 pial surface, left hemi (requires TemplateFlow)
geom_l <- load_surface_template(
template_id = "fsaverage",
surface_type = "pial",
hemi = "L",
density = "41k",
resolution = "06"
)
geom_lgeom_l is a SurfaceGeometry with:
-
mesh:rgl::mesh3dcontaining vertices (vb) and faces (it). -
graph:igraphadjacency of the mesh. -
hemi:"left"or"right".
No per-vertex values are present yet.
Both hemispheres at once
# fsLR is more commonly available in TemplateFlow than fsaverage
geoms <- load_surface_template(
"fsLR", "inflated", hemi = "both", density = "32k"
)
str(geoms, max.level = 1)Returns a named list with L and R
SurfaceGeometry objects if the template is available in
TemplateFlow.
Attaching per-vertex data
To get a full NeuroSurface, supply data explicitly:
geom_l <- load_surface_template("fsaverage", "pial", hemi = "L",
density = "41k", resolution = "06")
# Example: all zeros (same length as vertices)
vals <- rep(0, length(neurosurf::nodes(geom_l)))
surf_l <- neurosurf::NeuroSurface(
geometry = geom_l,
indices = neurosurf::nodes(geom_l),
data = vals
)surf_l@data now holds one value per vertex; you can
replace vals with cortical thickness, activation, etc.
Getting labeled surfaces (parcellations)
If you want labels already attached, use the atlas helpers; they combine geometry with per-vertex label IDs:
atl <- schaefer_surf(parcels = 200, networks = 7,
space = "fsaverage6", surf = "inflated")
class(atl$lh_atlas)
#> "LabeledNeuroSurface" "NeuroSurface" ...
head(slot(atl$lh_atlas, "data"))
# integer labels per vertexslot(atl$lh_atlas, "data") holds parcel IDs; metadata in
atl$labels maps those IDs to names.
When TemplateFlow is required
- Any call that specifies
template_id/spacenot packaged (e.g., fsaverage, fsaverage5/6, fsLR) needs TemplateFlow plus network access on first use. - fsaverage6 geometry is bundled; set
space = "fsaverage6"inschaefer_surf()to avoid TemplateFlow.
Common patterns
- Get a mesh path only (no R object):
get_surface_template(...). - Get a mesh object:
load_surface_template(...). - Get mesh + labels:
schaefer_surf()/glasser_surf(). - Add your own values: wrap the geometry with
NeuroSurface(...).
Sanity check: surface renders
We generate a small snapshot to prove the geometry loads and is
displayable. This chunk only runs when TemplateFlow is available; the
PNG is written to figures/ and then shown below.
dir.create("figures", showWarnings = FALSE)
png_path <- file.path("figures", "fslr32k_inflated_L.png")
geom_l <- load_surface_template(
"fsLR",
"inflated",
hemi = "L",
density = "32k"
)
neurosurf::snapshot_surface(geom_l, file = png_path)
png_pathIf the image renders, the template fetched correctly.
Template roster & snapshots
Below are the surface templates we currently target in code/tests.
Densities are TemplateFlow defaults; surface types are those exposed by
get_surface_template() (and used by our atlas helpers).
| template_id | density/res | surface types | packaged? |
|---|---|---|---|
| fsaverage | 164k | white, pial, inflated, midthickness, sphere | TF |
| fsaverage6 | 41k (res-06) |
white, pial, inflated | yes (bundled) |
| fsaverage5 | 10k (res-05) |
white, pial, inflated | TF |
| fsLR | 32k | white, pial, inflated, midthickness, sphere | TF |
Batch snapshot script (run locally)
To create a thumbnail per template/hemisphere/type for quick QA:
templates <- list(
list(id = "fsaverage", den = "164k", res = NULL, types = c("pial", "inflated")),
list(id = "fsaverage6", den = "41k", res = "06", types = c("pial", "inflated")),
list(id = "fsaverage5", den = "10k", res = "05", types = c("pial")),
list(id = "fsLR", den = "32k", res = NULL, types = c("pial", "inflated"))
)
for (tpl in templates) {
for (stype in tpl$types) {
geom_l <- load_surface_template(
template_id = tpl$id,
surface_type = stype,
hemi = "L",
density = tpl$den,
resolution = tpl$res
)
fname <- sprintf("%s_%s_L.png", tpl$id, stype)
neurosurf::snapshot_surface(geom_l, file = fname)
}
}Enable and run locally to generate PNGs you can embed or eyeball to verify each template is present and renderable.
That’s it—use geometry-only helpers when you want maximal control, and atlas helpers when you want labeled surfaces out of the box.