---
title: Getting started with neurocluster
name: getting-started
description: A first spatially constrained clustering, from data to a checked result.
output:
rmarkdown::html_vignette:
toc: yes
toc_depth: 2
css: albers.css
includes:
in_header: albers-header.html
params:
family: red
base_size: 13
content_width: 72
preset: homage
resource_files:
- albers.css
- albers.js
- albers-header.html
- fonts
vignette: |
%\VignetteIndexEntry{Getting started with neurocluster}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
if (requireNamespace("albersdown", quietly = TRUE)) albersdown::albers_register_fonts()
knitr::opts_chunk$set(
collapse = TRUE, comment = "#>", fig.align = "center", fig.retina = 2,
out.width = "100%", fig.width = 8, fig.asp = 0.58,
message = FALSE, warning = FALSE
)
options(width = 80)
suppressPackageStartupMessages({
library(neurocluster)
library(neuroim2)
})
parcel_colors <- c("#0072B2", "#E69F00", "#009E73", "#CC79A7",
"#56B4E9", "#D55E00", "#F0E442", "#000000")
align_labels <- function(labels, truth) {
ids <- sort(unique(labels))
truth_ids <- sort(unique(truth))
stopifnot(length(ids) == length(truth_ids))
overlap <- table(factor(labels, levels = ids),
factor(truth, levels = truth_ids))
map <- truth_ids[as.integer(clue::solve_LSAP(overlap, maximum = TRUE))]
aligned <- unname(map[match(labels, ids)])
stopifnot(length(unique(aligned)) == length(ids))
aligned
}
draw_slice <- function(labels, dims, z, title) {
slice <- array(labels, dims)[, , z]
image(t(slice[, ncol(slice):1]), col = parcel_colors, axes = FALSE,
useRaster = TRUE, asp = 1, main = title)
box(col = "#4A4A4A")
}
```
```{r albers-classes, echo=FALSE, results='asis'}
cat(sprintf(paste0(
''
), params$family, params$preset, params$content_width))
```
# What problem are you solving? {#problem}
A 4D neuroimaging dataset gives you a time series at every voxel. Many analyses
become easier when neighboring voxels with similar signals are replaced by a
smaller set of spatially coherent parcels. `cluster4d()` performs that reduction
while retaining the mask geometry, final labels, feature means, and physical
centroids needed downstream.
This first workflow uses a small simulated brain volume with known parcels. The
known answer lets you learn the object model and verify the result before moving
to your own NIfTI data.
# What goes in? {#inputs}
You need a `NeuroVec` containing x × y × z × time data and a `NeuroVol` mask.
Only finite mask values greater than zero are included. Here, the generator
returns both objects plus one ground-truth label per included voxel.
```{r make-example}
syn <- generate_synthetic_volume(
scenario = "gaussian_blobs", dims = c(16, 16, 8),
n_clusters = 4, n_time = 40, noise_sd = 0.08, seed = 42
)
c(spatial_dimensions = paste(syn$dims, collapse = " x "),
time_points = dim(syn$vec)[4], masked_voxels = length(syn$truth))
```
# How do you make a first parcellation? {#cluster}
Choose a target number of parcels and a method. ReNA is a useful first pass for
this example because it aggregates adjacent regions directly. The target is not
a promise for every method, so inspect `actual_k` after fitting.
```{r first-fit}
fit <- cluster4d(
syn$vec, syn$mask, n_clusters = 4, method = "rena",
connectivity = 26, parallel = FALSE
)
c(method = fit$method, requested_k = fit$parameters$n_clusters_requested,
actual_k = fit$actual_k)
```
```{r check-first-fit, include=FALSE}
fit_check <- validate_cluster4d(fit, syn$vec, syn$mask)
fit_metrics <- cluster_metrics(fit, truth = syn$truth)
stopifnot(
fit_check$valid,
identical(fit$actual_k, 4L),
is.finite(fit_metrics$ari_truth),
fit_metrics$ari_truth > 0.99,
identical(fit$labels, fit$cluster)
)
aligned_fit <- align_labels(fit$labels, syn$truth)
```
```{r first-result, echo=FALSE, fig.cap='Known simulated parcels and the fitted ReNA partition on the middle axial slice. A one-to-one maximum-overlap assignment aligns arbitrary fitted IDs to the known colors only for this diagnostic view.', fig.alt='Two axial brain-grid panels. The known four-region partition is on the left and the fitted partition on the right; all four parcel colors and shapes match.'}
old_par <- par(mfrow = c(1, 2), mar = c(1, 1, 3, 1))
draw_slice(syn$truth, syn$dims, z = 4, "Known parcels")
draw_slice(aligned_fit, syn$dims, z = 4, "Fitted parcels")
par(old_par)
```
On this deliberately easy fixture, the fitted partition matches the known
partition exactly (adjusted Rand index = `r sprintf("%.3f", fit_metrics$ari_truth)`).
That result checks the example; it is not a general performance claim about
ReNA or evidence that four parcels is appropriate for real data.
# How do you check the object? {#validate}
`validate_cluster4d()` checks the result schema and independently recomputes
centers and geometry when you supply the original inputs. It answers “is this a
consistent result?”, not “is this a scientifically useful parcellation?”
```{r validate-result}
check <- validate_cluster4d(fit, syn$vec, syn$mask)
check[c("valid", "warnings", "errors")]
```
The object contains contiguous voxel labels in mask order, a clustered volume
for plotting or export, and final centers in the original time-series space.
```{r inspect-result}
c(n_labels = length(fit$labels), n_centers = nrow(fit$centers),
center_time_points = ncol(fit$centers))
```
# Where should you go next? {#next}
- Read [Choose parameters](choose-parameters.html) to learn what K and spatial weighting change.
- Read [Compare methods](compare-methods.html) to compare methods on identical inputs.
- Read [Validate and compare](validate-compare.html) to separate structural validity from quality estimands.
- Read [Visualize and export](visualize-export.html) to inspect slices and write a NIfTI label map.