---
title: 'Surface Templates: Geometry vs. Data'
author: neuroatlas Dev Team
date: '`r Sys.Date()`'
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Surface Templates: Geometry vs. Data}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
params:
family: red
css: albers.css
resource_files:
- albers.css
- albers.js
includes:
in_header: |-
---
```{r setup, include = FALSE}
if (requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("albersdown", quietly = TRUE)) ggplot2::theme_set(albersdown::theme_albers(params$family))
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
echo = TRUE,
eval = FALSE # avoid downloads during CRAN builds
)
suppressWarnings(suppressMessages(library(neuroatlas)))
templateflow_ok <- FALSE
try({
templateflow_ok <- reticulate::py_available(initialize = TRUE) &&
reticulate::py_module_available("templateflow")
}, silent = TRUE)
```
## Why 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 `data` slot of a
`neurosurf::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.gii` file (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()`) →
`LabeledNeuroSurface` with integer labels in `data` and a mesh in
`geometry`.
- Packaged fsaverage6 meshes live in `data/fsaverage.rda` and are used
automatically by `schaefer_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.
```{r get-geometry, eval=FALSE}
# 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_l
```
`geom_l` is a `SurfaceGeometry` with:
- `mesh`: `rgl::mesh3d` containing vertices (`vb`) and faces (`it`).
- `graph`: `igraph` adjacency of the mesh.
- `hemi`: `"left"` or `"right"`.
No per-vertex values are present yet.
### Both hemispheres at once
```{r both-hemi, eval=FALSE}
# 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:
```{r build-neurosurface, eval=FALSE}
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:
```{r schaefer-surf, eval=FALSE}
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 vertex
```
`slot(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`/`space` not packaged (e.g.,
fsaverage, fsaverage5/6, fsLR) needs TemplateFlow plus network access
on first use.
- fsaverage6 geometry is bundled; set `space = "fsaverage6"` in
`schaefer_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.
```{r snapshot, eval=FALSE, cache=TRUE}
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_path
```
```{r snapshot-show, echo=FALSE, eval=FALSE, fig.alt="fsLR 32k inflated left hemisphere surface snapshot"}
knitr::include_graphics(file.path("figures", "fslr32k_inflated_L.png"))
```
If 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:
```{r snapshot-batch, eval=FALSE}
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.