Skip to contents

This vignette shows how to display cortical geometry, curvature, and vertex values with RGL. It uses the small std.8 surfaces bundled with neurosurf; no downloads or browser processes are needed to build the article. The first figure is an interactive RGL scene. The later static illustrations use neurosurf’s CPU renderer, rather than RGL snapshots, to keep the installed vignette compact and usable on machines without OpenGL.

Open a scene and close it when finished

view_surface() draws into the current RGL device when new_window = FALSE. This small helper gives each example a private NULL device and closes it even if the plotting call fails. A NULL device records a scene for WebGL without opening a desktop window. In an interactive session, use open3d() to open a normal RGL window instead.

with_rgl_scene <- function(code) {
  device <- rgl::open3d(useNULL = TRUE)
  on.exit(rgl::close3d(device), add = TRUE)
  force(code)
}

A SurfaceGeometry records its hemisphere, so viewpoint = "lateral" selects the appropriate left or right view. Drag the scene to rotate it.

with_rgl_scene({
  view_surface(left, viewpoint = "lateral", bgcol = "lightgray",
               new_window = FALSE)
  rgl::rglwidget()
})

Curvature and scalar overlays

Curvature distinguishes gyri from sulci. curv_cols_smooth() turns it into a gray background; pass vertex values separately in vals. irange sets the color scale, and thresh = c(-1, 1) makes values inside that band transparent. Each vector must have one entry per vertex.

with_rgl_scene({
  view_surface(left, vals = vertex_values, cmap = rainbow(256),
               irange = c(-3, 3), thresh = c(-1, 1),
               bgcol = curv_cols_smooth(curv), viewpoint = "lateral",
               specular = "black", new_window = FALSE)
  invisible(NULL)
})

For a deterministic static illustration of the same values and threshold, use render_surface_rgba(). Its lighting and color mapping are independent of RGL; this is a CPU rendering, not a screenshot of the preceding scene.

panel <- render_surface_rgba(
  left, vertex_values, anatomy_metric = curv,
  camera = "lateral", threshold = 1, limits = c(-3, 3),
  palette = rainbow(256), width = 640, height = 400, antialias = 2
)

Vertex colors, transparency, and material

vert_clrs supplies explicit colors and overrides scalar color mapping. alpha controls opacity; specular = "black" gives a matte surface, while other colors add highlights. Here the lower half of the x coordinates is blue and the upper half is red.

x <- coords(left)[, 1]
colors <- ifelse(x > median(x), "#FF0000", "#0000FF")
with_rgl_scene({
  view_surface(left, vert_clrs = colors, alpha = 0.6,
               viewpoint = "ventral", specular = "black",
               new_window = FALSE)
  invisible(NULL)
})

The lit argument controls whether lighting affects the material. These choices change the displayed scene, not the stored geometry or vertex data.

Change viewpoints and combine hemispheres

Supported views include "lateral", "medial", "ventral", and "posterior". Lateral views of opposite hemispheres face different orientations, so use separate panels for them. For a posterior view, both hemispheres can share one scene because their coordinates separate along x.

with_rgl_scene({
  view_surface(left, viewpoint = "posterior", offset = c(-5, 0, 0),
               bgcol = curv_cols_smooth(curvature(left)),
               new_window = FALSE)
  view_surface(right, viewpoint = "posterior", offset = c(5, 0, 0),
               bgcol = curv_cols_smooth(curvature(right)),
               new_window = FALSE)
  invisible(NULL)
})

For static multi-view output, surface_figure() combines hemispheres and adds labels. See vignette("surface-figures") for a full workflow.

Add markers and plot surface data objects

Markers can refer to vertices, with a radius and color for each marker. These three markers use deterministic vertex indices.

markers <- data.frame(vertex = c(10, 100, 300), radius = 4.5,
                      color = c("yellow", "cyan", "magenta"))
with_rgl_scene({
  view_surface(left, spheres = markers, spheres_as_vertices = TRUE,
               bgcol = curv_cols_smooth(curv), viewpoint = "lateral",
               new_window = FALSE)
  invisible(NULL)
})

NeuroSurface stores values and their vertex indices. Its plot() method passes them to the same rendering machinery, so you can override color, threshold, opacity, and viewpoint in the plotting call.

surface <- NeuroSurface(left, indices = seq_along(vertex_values),
                        data = vertex_values)
with_rgl_scene({
  plot(surface, cmap = heat.colors(128), irange = c(-3, 3),
       viewpoint = "lateral", new_window = FALSE)
  invisible(NULL)
})

Smooth and threshold an activation map

Smoothing and cluster-size thresholding operate on surface data before rendering. This synthetic map is an illustration of the workflow, not a statistical inference result.

smoothed <- smooth(surface)
clustered <- cluster_threshold(smoothed, size = 30,
                               threshold = c(-0.2, 0.2))
with_rgl_scene({
  plot(clustered, cmap = rainbow(100), irange = c(-3, 3),
       thresh = c(-0.2, 0.2), new_window = FALSE)
  invisible(NULL)
})

surface_montage() accepts a list of surfaces or list(surface, ...overrides) panels. Its static RGL captures require a rendering backend. For example, run this in a session where RGL snapshots are available:

surface_montage(
  list(smoothed, list(smoothed, thresh = c(-0.2, 0.2)),
       list(clustered, thresh = c(-0.2, 0.2))),
  cmap = rainbow(100), irange = c(-3, 3), ncol = 3
)

Save an RGL snapshot

snapshot_surface() saves a PNG through the available RGL rendering backend. On a NULL device, it requires webshot2 and a headless browser. It may return an empty path when no usable snapshot is available. The following is an interactive export recipe; building this article does not launch a browser.

file <- tempfile(fileext = ".png")
path <- snapshot_surface(left, file = file, vals = vertex_values,
                         cmap = rainbow(256), viewpoint = "lateral")
if (length(path)) {
  png::readPNG(path)
}
unlink(file)

For browser-free PNG output, use the CPU renderer shown above. For authored interactive reports with multiple selectable maps, see vignette("interactive-surfaces").

The bundled templates retain the FreeSurfer terms in system.file("extdata", "LICENSE-FreeSurfer.txt", package = "neurosurf").