Introduction to NeuroSurf Data Structures
Bradley Buchsbaum
2026-02-03
Source:vignettes/introduction-to-neurosurf.Rmd
introduction-to-neurosurf.RmdThis vignette introduces the fundamental data structures used in the
neurosurf package for representing and working with brain
surface geometries and associated data.
For visualization, see the companion vignettes:
-
Displaying Surfaces with RGL for low-level static plots
based on
plot()andview_surface(). - Interactive Surface Visualization with surfwidget for HTML-widget based exploration.
-
Surfplot-style Figures with neurosurf for multi-view,
publication-style layouts built with
surface_plot()and friends.
SurfaceGeometry: Representing the Mesh
The core building block for any surface analysis is the geometry
itself. In neurosurf, this is represented by the
SurfaceGeometry class.
An object of this class encapsulates:
-
mesh: Anrgl::mesh3dobject containing the raw vertex coordinates and the triangular faces that define the surface shape. -
graph: Anigraphobject representing the connectivity of the mesh vertices. Edges connect adjacent vertices along the surface. -
hemi: A character string indicating the hemisphere, typically “lh” (left) or “rh” (right).
Loading a SurfaceGeometry
You can load a surface geometry from various file formats (Freesurfer
binary, Freesurfer ASCII .asc, GIFTI .gii)
using the read_surf_geometry() function.
# Load the example left hemisphere white matter surface (.asc format)
lh_geom <- read_surf_geometry(white_lh_asc)
#> loading /private/var/folders/9h/nkjq6vss7mqdl4ck7q1hd8ph0000gp/T/Rtmp0Nbniq/temp_libpath80e31f94425b/neurosurf/extdata/std.8_lh.smoothwm.asc
# Display summary information about the loaded geometry
show(lh_geom)
#>
#> SurfaceGeometry
#>
#> /\
#> / \
#> /____\
#> / \
#> / \
#>
#> Basic Information:
#> Hemisphere: left
#> Vertices: 642
#> Faces: 1,280
#> Edges: 1,920
#>
#> Geometry Metrics:
#> Euler Characteristic: 2
#> Genus: 0
#> Surface Area: 36956
#> Avg Edge Length: 10.31Accessing Geometry Properties
Several methods allow you to access information within the
SurfaceGeometry object:
# Get the total number of vertices (nodes) in the geometry
num_nodes <- length(nodes(lh_geom))
cat("Number of vertices:", num_nodes, "\n")
#> Number of vertices: 642
# Get the coordinates of all vertices as a matrix (N x 3)
vertex_coords <- coords(lh_geom)
cat("Dimensions of coordinate matrix:", dim(vertex_coords), "\n")
#> Dimensions of coordinate matrix: 642 3
cat("Coordinates of the first 3 vertices:\n")
#> Coordinates of the first 3 vertices:
print(head(vertex_coords, 3))
#> [,1] [,2] [,3]
#> [1,] -32.43124 -5.756156 -26.85239
#> [2,] -38.78497 10.942583 52.54661
#> [3,] -26.23813 -35.324768 58.41325
# Access the underlying igraph object
g <- neurosurf::graph(lh_geom)
cat("Graph summary:", "\n")
#> Graph summary:
g
#> IGRAPH aa49dec U--- 642 1920 --
#> + attr: x (v/n), y (v/n), z (v/n), dist (e/n)
#> + edges from aa49dec:
#> [1] 1-- 13 1-- 27 1-- 55 1-- 90 1--125 2--188 2--195 2--223 2--258
#> [10] 2--293 3--264 3--271 3--300 3--356 3--391 4-- 96 4--103 4--132
#> [19] 4--517 4--545 5-- 19 5-- 26 5-- 62 5--201 5--334 6--397 6--404
#> [28] 6--426 6--482 6--551 7-- 20 7-- 33 7--166 7--194 7--230 8--362
#> [37] 8--369 8--398 8--510 8--523 9--229 9--236 9--265 9--363 9--600
#> [46] 10-- 61 10-- 68 10-- 97 10--460 10--488 11--299 11--306 11--328 11--432
#> [55] 11--454 12--131 12--138 12--160 12--516 12--594 13-- 14 13-- 27 13-- 34
#> [64] 13-- 55 13-- 69 14-- 15 14-- 34 14-- 40 14-- 69 14-- 70 15-- 16 15-- 40
#> + ... omitted several edges
# Access the underlying mesh3d object
mesh <- lh_geom@mesh
cat("Mesh object class:", class(mesh), "\n")
#> Mesh object class: mesh3d shape3d
# Access the hemisphere label
hemi_label <- lh_geom@hemi
cat("Hemisphere:", hemi_label, "\n")
#> Hemisphere: lh
NeuroSurface: Mapping Data to Geometry
Often, we want to associate data values (like cortical thickness,
fMRI activation, etc.) with each vertex on the surface. The
NeuroSurface class links a single vector of data
to a SurfaceGeometry.
It contains:
-
geometry: The associatedSurfaceGeometryobject. -
indices: An integer vector specifying which vertices in the geometry have corresponding data values. This allows for representing data defined only on a subset of the surface. -
data: A numeric vector containing the data values. Its length must match the length ofindices.
Creating a NeuroSurface
You typically create a NeuroSurface using its
constructor, providing the geometry, indices, and data.
# Generate some example data (e.g., based on x-coordinate)
# Use all vertices from lh_geom
vertex_indices <- nodes(lh_geom)
example_data <- coords(lh_geom)[, 1] # Use x-coordinate as data
# Create the NeuroSurface object
lh_surf_data <- NeuroSurface(geometry = lh_geom,
indices = vertex_indices,
data = example_data)
# Display summary
show(lh_surf_data)
#>
#> NeuroSurface
#>
#> Geometry & Data Mapping:
#> Hemisphere: lh
#> Total Vertices: 642
#> Vertices w/ Data:642
#>
#> Data Summary:
#> Min: -65.39
#> Median:-29.95
#> Mean: -29.35
#> Max: 1.209Loading Data with read_surf (Optional Data File)
If your data is stored in a separate file (e.g., AFNI
.1D.dset, NIML .niml.dset), you can load both
the geometry and the data using read_surf. Here we create a
temporary .1D.dset file for demonstration.
# 1. Prepare sample data for a subset of nodes
sample_nodes_indices_R <- sample(nodes(lh_geom), size = 500) # R indices (1-based)
sample_nodes_indices_0based <- sample_nodes_indices_R - 1 # 0-based for .1D file
sample_data <- rnorm(500)
# 2. Create a temporary file
temp_dset_file <- tempfile(fileext = ".1D.dset")
# 3. Write data in AFNI .1D format (node_index value)
write.table(cbind(sample_nodes_indices_0based, sample_data),
file = temp_dset_file,
row.names = FALSE,
col.names = FALSE,
sep = " ")
# 4. Load geometry and the data from the temporary file
# read_surf will match nodes in the file to the geometry
lh_surf_loaded <- read_surf(surface_name = white_lh_asc,
surface_data_name = temp_dset_file)
#> loading /private/var/folders/9h/nkjq6vss7mqdl4ck7q1hd8ph0000gp/T/Rtmp0Nbniq/temp_libpath80e31f94425b/neurosurf/extdata/std.8_lh.smoothwm.asc
# Display summary of the loaded NeuroSurface
show(lh_surf_loaded)
#>
#> NeuroSurface
#>
#> Geometry & Data Mapping:
#> Hemisphere: lh
#> Total Vertices: 642
#> Vertices w/ Data:500
#>
#> Data Summary:
#> Min: -2.82
#> Median:-0.01911
#> Mean: -0.03667
#> Max: 2.689
# Verify the number of loaded data points
cat("Number of data points loaded:", length(lh_surf_loaded@data), "\n")
#> Number of data points loaded: 500
cat("Number of non-zero data points:", sum(lh_surf_loaded@data != 0), "\n") # Should match size=500
#> Number of non-zero data points: 500
# Clean up the temporary file (optional, R usually handles temp files)
# file.remove(temp_dset_file) Accessing NeuroSurface Properties
# Access the geometry
geom_from_ns <- geometry(lh_surf_data)
cat("Is geometry the same?", identical(geom_from_ns, lh_geom), "\n")
#> Is geometry the same? TRUE
# Access the data vector
data_vec <- lh_surf_data@data # Direct slot access
# Alternatively, convert to a simple vector
data_vec_as <- as.vector(lh_surf_data)
cat("Length of data vector:", length(data_vec), "\n")
#> Length of data vector: 642
cat("First 5 data values:", head(data_vec, 5), "\n")
#> First 5 data values: -32.43124 -38.78497 -26.23813 -29.04933 -39.65442
# Access the associated indices
index_vec <- indices(lh_surf_data)
cat("Length of index vector:", length(index_vec), "\n")
#> Length of index vector: 642
cat("First 5 indices:", head(index_vec, 5), "\n")
#> First 5 indices: 1 2 3 4 5
NeuroSurfaceVector: Mapping Multiple Data Vectors
When you have multiple measurements per vertex (e.g., time series
data, multiple features), the NeuroSurfaceVector class is
used. It links a matrix of data to a
SurfaceGeometry.
It contains:
-
geometry: The associatedSurfaceGeometryobject. -
indices: An integer vector specifying which vertices have data (same asNeuroSurface). -
data: AMatrix(from theMatrixpackage, often sparse) where rows correspond to vertices (matchingindices) and columns represent different measurements or time points.
Creating a NeuroSurfaceVector
# Create example matrix data (e.g., x, y, z coordinates as 3 'features')
# Use all vertices
num_vertices <- length(nodes(lh_geom))
vertex_indices_vec <- nodes(lh_geom)
# Create a dense matrix first
example_matrix_data <- coords(lh_geom)
# Convert to a Matrix object (can be sparse or dense)
example_matrix <- Matrix(example_matrix_data)
# Create the NeuroSurfaceVector
lh_surf_vec <- NeuroSurfaceVector(geometry = lh_geom,
indices = vertex_indices_vec,
mat = example_matrix)
# Display summary
show(lh_surf_vec)
#>
#> NeuroSurfaceVector
#>
#> Geometry & Data Mapping:
#> Hemisphere: lh
#> Total Vertices: 642
#> Vertices w/ Data:642
#>
#> Data Matrix Information:
#> Number of Vectors:3
#> Data Dimensions: [642 rows x 3 cols]
#> Matrix Class: dgeMatrixAccessing NeuroSurfaceVector Properties
# Access the geometry
geom_from_nsv <- geometry(lh_surf_vec)
cat("Is geometry the same?", identical(geom_from_nsv, lh_geom), "\n")
#> Is geometry the same? TRUE
# Access the data matrix
data_mat <- lh_surf_vec@data # Direct slot access
# Or convert to a standard matrix
data_mat_std <- as.matrix(lh_surf_vec)
cat("Dimensions of data matrix:", dim(data_mat), "\n")
#> Dimensions of data matrix: 642 3
# Access data for specific vertices/columns using standard matrix indexing
cat("Data for vertex 10 (all columns):\n")
#> Data for vertex 10 (all columns):
print(data_mat[10, ])
#> [1] -48.594086 -23.064110 -8.115485
cat("Data for column 2 (all vertices):\n")
#> Data for column 2 (all vertices):
print(head(data_mat[, 2]))
#> [1] -5.756156 10.942583 -35.324768 -73.917900 20.463083 -70.911591
# Access the associated indices
index_vec_nsv <- indices(lh_surf_vec)
cat("Length of index vector:", length(index_vec_nsv), "\n")
#> Length of index vector: 642Specialized NeuroSurface Classes
neurosurf also provides specialized classes that inherit
from NeuroSurface and add features primarily for
visualization:
-
LabeledNeuroSurface: Associates categorical labels and corresponding colors with vertices (useful for atlases or parcellations). Containslabelsandcolsslots. -
ColorMappedNeuroSurface: Stores data along with a specific colormap (cmap), data range (irange), and thresholds (thresh) to pre-define how the data should be visualized. -
VertexColoredNeuroSurface: Stores specific hex color codes directly for each vertex (colorsslot), bypassing data mapping.
These classes facilitate plotting with pre-set visual parameters
using the plot() method discussed in the
displaying-surfaces vignette.
Conclusion
Understanding SurfaceGeometry,
NeuroSurface, and NeuroSurfaceVector is key to
using the neurosurf package effectively.
SurfaceGeometry holds the mesh structure, while
NeuroSurface and NeuroSurfaceVector link
single or multiple data vectors to this geometry, respectively. These
structures provide the foundation for various surface-based analyses and
visualizations.