---
title: ROIs, Searchlights, and Pipelines
output: rmarkdown::html_vignette
vignette: |
%\VignetteIndexEntry{ROIs, Searchlights, and Pipelines}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
params:
family: red
preset: homage
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(family = params$family, preset = params$preset))
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
message = FALSE,
warning = FALSE
)
suppressPackageStartupMessages({
library(neuroim2)
library(purrr)
})
```
Most analysis work in `neuroim2` reduces to the same pattern: define a spatial
support, extract values from it, then summarize or map those values into a new
result. This article shows the three main ways to do that.
## Extract a time series from one ROI
Start with a 4D image and define one spherical region of interest.
```{r load-demo}
vec_file <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
vol <- read_vol(vec_file)
vec <- read_vec(vec_file)
```
```{r roi-example}
roi <- spherical_roi(vol, c(12, 12, 12), radius = 6)
roi_ts <- series_roi(vec, roi)
dim(values(roi_ts))
```
```{r roi-example-check}
stopifnot(length(roi) > 0L)
stopifnot(nrow(values(roi_ts)) == dim(vec)[4])
```
That gives you one compact object containing the time series from every voxel
inside the ROI.
## Split a masked volume into parcels
When you already have a parcellation or cluster assignment, `split_clusters()`
turns one `NeuroVec` into a list of region-wise objects.
```{r cluster-setup}
set.seed(1)
mask_vol <- vol > 0
cluster_ids <- sample(1:4, sum(mask_vol), replace = TRUE)
clustered <- ClusteredNeuroVol(mask_vol, cluster_ids)
parts <- split_clusters(vec, clustered)
length(parts)
```
```{r cluster-summary}
part_means <- map_dbl(parts, ~ mean(values(.)))
part_means
```
```{r cluster-check}
stopifnot(length(parts) == 4L)
stopifnot(all(is.finite(part_means)))
```
This is the right pattern when the support is fixed in advance by an atlas,
parcel map, or clustering step.
## Build searchlights lazily
Searchlights define many overlapping ROIs, one centered at each voxel in a
mask. The lazy form is useful because you only realize the neighborhoods you
actually touch.
```{r searchlight-demo}
sl <- searchlight(mask_vol, radius = 4, eager = FALSE, nonzero = FALSE)
first_sl <- sl[[1]]
nrow(coords(first_sl))
```
```{r searchlight-check}
stopifnot(nrow(coords(first_sl)) > 0L)
```
The eager form is better when you want to iterate repeatedly over the full set
and can afford the up-front construction cost.
## Map a simple statistic over the first few searchlights
You do not need a specialized pipeline framework to use split-map-reduce style
workflows. A small list of ROIs plus `purrr::map_*()` is often enough.
```{r map-demo}
first_five <- lapply(seq_len(5), function(i) sl[[i]])
first_five_means <- map_dbl(first_five, ~ mean(values(.)))
first_five_means
```
```{r map-check}
stopifnot(length(first_five_means) == 5L)
stopifnot(all(is.finite(first_five_means)))
```
The pattern is the same whether the pieces come from parcels, ROIs, connected
components, or searchlights:
1. define the spatial pieces
2. extract the values you need
3. reduce them into a scalar, vector, or model result
## Where to go next
- `vignette("VolumesAndVectors")` for the core data containers
- `vignette("regionOfInterest")` for the full ROI API surface
- `vignette("pipelines")` for older split-map-reduce examples
- `vignette("clustered-neurovec")` for parcel-based data structures