This vignette collects small, task‑oriented examples (“cookbook”
style) for common NeuroVec workflows that don’t warrant a
full standalone vignette.
We assume basic familiarity with NeuroVec from
vignette("NeuroVector").
Reducing a 4D NeuroVec over time into a NeuroVol
Sometimes you want to take a 4D time‑series (NeuroVec)
and collapse the time dimension into a single 3D volume by applying a
reduction function to each voxel’s time‑series (e.g., temporal mean,
standard deviation, or any custom summary).
The pattern is:
- Convert the
NeuroVecto a voxel × time matrix withas.matrix(). - Apply a function over the time axis for each voxel (row).
- Reshape back to 3D and wrap as a
NeuroVol.
file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
vec <- read_vec(file_name) # 4D NeuroVec
reduce_ts_to_vol <- function(x, FUN) {
dm <- dim(x)
stopifnot(length(dm) == 4)
mat <- as.matrix(x) # voxels × time
vals <- apply(mat, 1, FUN) # one value per voxel
NeuroVol(array(vals, dm[1:3]), drop_dim(space(x)))
}
# Example: temporal mean volume
mean_vol <- reduce_ts_to_vol(vec, mean)
mean_vol
#> <DenseNeuroVol> [806.6 Kb]
#> ── Spatial ─────────────────────────────────────────────────────────────────────
#> Dimensions : 64 x 64 x 25
#> Spacing : 3.5 x 3.5 x 3.7 mm
#> Origin : 112, -108, -46.2
#> Orientation : LAS
#> ── Data ────────────────────────────────────────────────────────────────────────
#> Range : [0.000, 1.000]You can plug in any summary function that maps a numeric vector to a
single value (e.g. median, a trimmed mean, robust
summaries, etc.).
Splitting a NeuroVec into blocks by an index vector
If you have a single concatenated NeuroVec with multiple
logical blocks along the time dimension (e.g., runs or sessions), you
can “unconcatenate” it into one NeuroVec per block using
split_blocks().
Create an index vector of length dim(vec)[4] indicating
which block each timepoint belongs to, then split the time index and
convert each subset to a sub-vector (this is what does internally):
space4 <- NeuroSpace(c(10, 10, 10, 9), c(1, 1, 1))
vec4d <- NeuroVec(array(rnorm(10 * 10 * 10 * 9),
dim = c(10, 10, 10, 9)),
space4)
# Suppose timepoints 1–3 belong to block 1, 4–6 to block 2, 7–9 to block 3
block_idx <- c(1, 1, 1,
2, 2, 2,
3, 3, 3)
idx_list <- split(seq_len(dim(vec4d)[4]), block_idx)
blocks <- lapply(idx_list, function(ii) sub_vector(vec4d, ii))
length(blocks) # 3 blocks
#> [1] 3
dim(blocks[[1]]) # first block: 10×10×10×3
#> [1] 10 10 10 3
dim(blocks[[2]]) # second block: 10×10×10×3
#> [1] 10 10 10 3
dim(blocks[[3]]) # third block: 10×10×10×3
#> [1] 10 10 10 3The result is a list of NeuroVec objects; access
elements with blocks[[i]] for block i. This is
effectively the inverse of concatenation by time. The helper exposes
this pattern with method dispatch for NeuroVec.
Converting a NeuroVec to a memory‑mapped MappedNeuroVec
For large 4D datasets or shared‑memory workflows on HPC, it is often
useful to back a NeuroVec by a memory‑mapped file using
MappedNeuroVec. The as_mmap() helper converts
common vector types (NeuroVec, SparseNeuroVec,
FileBackedNeuroVec) into a MappedNeuroVec,
writing an uncompressed NIfTI file if needed.
file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
vec <- read_vec(file_name) # DenseNeuroVec in memory
# Convert to a memory-mapped representation backed by a temporary .nii file
mvec <- as_mmap(vec)
mvec
#> <MappedNeuroVec> [7.3 Kb]
#> ── Spatial ─────────────────────────────────────────────────────────────────────
#> Dimensions : 64 x 64 x 25
#> Time Points : 4
#> Spacing : 3.5 x 3.5 x 3.7
#> Origin : 112, -108, -46.2
#> ── Mapping ─────────────────────────────────────────────────────────────────────
#> Offset : 88
#> Label : file2b964c8db534.nii
# Or explicitly choose an output file (must be uncompressed for mmap)
tmp_nii <- tempfile(fileext = ".nii")
mvec2 <- as_mmap(vec, file = tmp_nii, overwrite = TRUE)
inherits(mvec2, "MappedNeuroVec")
#> [1] TRUEFor sparse data (SparseNeuroVec), as_mmap()
first densifies the vector and then writes a full 4D NIfTI before
mapping it, trading memory once for much more efficient subsequent
access and multi‑process sharing.
Mapping a kernel over a 3D NeuroVol with mapf
To apply a spatial kernel (e.g. a 3×3×3 mean filter) over a 3D
volume, use mapf() with a Kernel object. This
keeps the familiar NeuroVol/NeuroSpace
metadata while doing neighborhood computations.
bspace <- NeuroSpace(c(10, 10, 10), c(1, 1, 1))
vol <- NeuroVol(array(rnorm(10 * 10 * 10), c(10, 10, 10)), bspace)
# Simple 3×3×3 mean kernel
kern <- Kernel(c(3, 3, 3), vdim = c(3, 3, 3))
smoothed_vol <- mapf(vol, kern)
smoothed_vol
#> <DenseNeuroVol> [14.4 Kb]
#> ── Spatial ─────────────────────────────────────────────────────────────────────
#> Dimensions : 10 x 10 x 10
#> Spacing : 1 x 1 x 1 mm
#> Origin : 0, 0, 0
#> Orientation : RAS
#> ── Data ────────────────────────────────────────────────────────────────────────
#> Range : [-2.413, 2.801]You can also pass a logical mask to restrict computation to a region while keeping the output volume in the original space.
Splitting a NeuroVec into ROIs by voxel clusters
Given voxelwise cluster labels (for example, from a parcellation),
split_clusters() can turn a NeuroVec into a
list of ROIVec objects—one per cluster—each containing the
time‑series of voxels in that cluster.
file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
vec <- read_vec(file_name)
# Fake cluster labels over the full 3D grid
n_vox <- prod(dim(vec)[1:3])
cl_lab <- sample(1:5, n_vox, replace = TRUE)
roi_list <- split_clusters(vec, cl_lab)
length(roi_list) # number of non-empty clusters
#> [1] 5
roi_list[[1]] # ROIVec for cluster "1"
#> <ROIVec> [20453 voxels x 4 features]
#> ── Structure ───────────────────────────────────────────────────────────────────
#> Voxels : 20453
#> Features : 4
#> Size : 885.8 Kb
#> ── Data ────────────────────────────────────────────────────────────────────────
#> Range : [0.000, 1.000]
coords(roi_list[[1]])[1:5, ] # first few voxel coordinates
#> [,1] [,2] [,3]
#> [1,] 3 1 1
#> [2,] 14 1 1
#> [3,] 22 1 1
#> [4,] 23 1 1
#> [5,] 24 1 1
dim(values(roi_list[[1]])) # time × voxels in that cluster
#> [1] 4 20453This is useful when you want to work directly with voxel‑level ROIs
per cluster rather than first building a
ClusteredNeuroVec.
Group‑wise voxel reduction with split_reduce
When you have a voxel‑wise grouping (e.g. tissue classes, parcels, or
custom regions), split_reduce() can compute one summary
time‑series per group directly from a NeuroVec.
file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
vec <- read_vec(file_name)
n_vox <- prod(dim(vec)[1:3])
# Assign each voxel to one of three arbitrary groups
fac <- factor(sample(1:3, n_vox, replace = TRUE))
# Default: mean over voxels in each group (per timepoint)
group_ts <- split_reduce(vec, fac)
dim(group_ts) # groups × timepoints
#> [1] 3 4
rownames(group_ts) # "1", "2", "3"
#> [1] "1" "2" "3"Here each row of group_ts is the mean time‑series of all
voxels in that group; you can supply any function FUN that
maps a numeric vector to a scalar (e.g. median, robust
summaries, etc.).
Concatenating NeuroVols into a NeuroVec
To build a 4D NeuroVec from multiple 3D
NeuroVol objects that share the same space, use
concat() along the time dimension.
sp3 <- NeuroSpace(c(10, 10, 10), c(1, 1, 1))
vol1 <- NeuroVol(array(rnorm(10 * 10 * 10), c(10, 10, 10)), sp3)
vol2 <- NeuroVol(array(rnorm(10 * 10 * 10), c(10, 10, 10)), sp3)
vol3 <- NeuroVol(array(rnorm(10 * 10 * 10), c(10, 10, 10)), sp3)
# Concatenate volumes into a 4D NeuroVec (time dimension length 3)
vec_3 <- concat(vol1, vol2, vol3)
dim(vec_3) # 10 × 10 × 10 × 3
#> [1] 10 10 10 3
space(vec_3) # inherits spatial metadata from inputs
#> <NeuroSpace> [4D]
#> ── Geometry ────────────────────────────────────────────────────────────────────
#> Dimensions : 10 x 10 x 10 x 3
#> Spacing : 1 x 1 x 1 mm
#> Origin : 0, 0, 0
#> Orientation : RAS
#> Voxels : 3,000All input volumes must have identical spatial dimensions and
NeuroSpace; otherwise concat() will error.
Concatenating NeuroVecs along the time dimension
You can also concatenate multiple NeuroVec objects
(e.g. runs or sessions) into a longer 4D vector, again with
concat().
file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
run1 <- read_vec(file_name) # 4D NeuroVec
run2 <- read_vec(file_name) # same space and shape
# Concatenate timepoints: result has dim(...)[4] = dim(run1)[4] + dim(run2)[4]
run12 <- concat(run1, run2)
dim(run1)
#> [1] 64 64 25 4
dim(run2)
#> [1] 64 64 25 4
dim(run12)
#> [1] 64 64 25 8This is the natural inverse of splitting by blocks: you can break a
long NeuroVec into segments with
split_blocks() and then reconstruct a longer series again
with concat().
Connected components in a 3D mask
To identify spatially contiguous clusters in a 3D volume or mask, use
conn_comp() on a NeuroVol. This is handy for
summarizing thresholded statistical maps or cleaning up binary
masks.
sp <- NeuroSpace(c(10, 10, 10), c(1, 1, 1))
arr <- array(0, c(10, 10, 10))
# Two small 2×2×2 clusters in opposite corners
arr[1:2, 1:2, 1:2] <- 1
arr[8:9, 8:9, 8:9] <- 1
vol <- NeuroVol(arr, sp)
# Find connected components above threshold 0 (26-connectivity by default)
cc <- conn_comp(vol, threshold = 0)
max(cc$index) # number of clusters (should be 2)
#> [1] 2
cc$size[cc$size > 0] # cluster sizes in voxels
#> [1] 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8You can also request a cluster_table of summary
statistics or local_maxima for peak finding: