--- title: "How the clustering methods differ" name: method-deep-dives description: Compare algorithmic work units and controls without turning them into universal rankings. 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{How the clustering methods differ} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, fig.align = "center", fig.retina = 2, out.width = "100%", fig.width = 7, fig.asp = 0.58 ) if (requireNamespace("albersdown", quietly = TRUE) && requireNamespace("ggplot2", quietly = TRUE)) { ggplot2::theme_set(albersdown::theme_albers(family = params$family, preset = params$preset)) } ``` ```{r albers-classes, echo=FALSE, results='asis'} cat(sprintf( paste0(''), params$family, params$preset, params$content_width )) ``` All methods turn voxel-wise features and spatial support into parcels, but they do not optimize the same objective. Choose a family because its assumptions and controls match the analysis—not because an unscoped label calls it “fast” or “high quality.” ## Where does each family spend its work? ```{r method-map-data, include=FALSE} taxonomy <- data.frame( key = c("supervoxels", "snic", "slic", "corr_slic", "brs_slic", "slice_msf", "flash3d", "g3s", "rena", "rena_plus", "mcl", "acsc", "commute"), method = c("Supervoxels", "SNIC", "SLIC", "Corr-SLIC", "BRS-SLIC", "Slice-MSF", "FLASH-3D", "G3S", "ReNA", "ReNA+", "MCL", "ACSC", "Commute"), work_unit = c( "local heat kernels", "seeded local growth", "local assignment", "correlation embedding", "sketch plus exact boundary", "spatial graph", "compressed signatures", "gradient geodesic graph", "spatial graph", "spatial graph plus refinement", "weighted graph diffusion", "block community graph", "resistance embedding" ), update = c( "iterative", "single growth", "iterative", "iterative", "staged", "single construction", "iterative", "growth plus refinement", "agglomerative", "agglomerative", "iterative", "community plus refinement", "spectral then cluster" ), family = c("local", "local", "local", "embedded", "embedded", "graph", "embedded", "graph", "graph", "graph", "graph", "community", "spectral"), direct = c( "cluster4d_supervoxels", "cluster4d_snic", "cluster4d_slic", NA, NA, "cluster4d_slice_msf", "cluster4d_flash3d", "cluster4d_g3s", "cluster4d_rena", "cluster4d_rena_plus", "cluster4d_mcl", "cluster4d_acsc", "cluster4d_commute" ), stringsAsFactors = FALSE ) method_choices <- eval(formals(neurocluster::cluster4d)$method) package_exports <- getNamespaceExports("neurocluster") stopifnot( identical(taxonomy$key, method_choices), nrow(taxonomy) == 13L, !anyDuplicated(taxonomy$key), all(stats::na.omit(taxonomy$direct) %in% package_exports), all(c("cluster4d", "validate_cluster4d", "compare_cluster4d") %in% package_exports) ) family_order <- c("local", "embedded", "graph", "community", "spectral") taxonomy$x <- match(taxonomy$family, family_order) taxonomy$y <- ave(seq_len(nrow(taxonomy)), taxonomy$family, FUN = seq_along) ``` ```{r taxonomy-table, echo=FALSE} knitr::kable( taxonomy[c("method", "work_unit", "update")], col.names = c("Unified method", "Dominant work unit", "Update pattern"), caption = "All 13 choices accepted by the installed cluster4d() interface." ) ``` ```{r method-map, echo=FALSE, fig.cap='All 13 unified methods grouped by their dominant representation. Vertical position only separates labels within a group; it is not a score.', fig.alt='Five grouped columns labeled local, embedded, graph, community, and spectral containing all thirteen method names.'} palette_cb <- c(local = "#0072B2", embedded = "#D55E00", graph = "#009E73", community = "#CC79A7", spectral = "#E69F00") plot(NA, xlim = c(0.5, 5.5), ylim = c(0.5, 5.2), axes = FALSE, xlab = "dominant representation", ylab = "") axis(1, at = seq_along(family_order), labels = family_order) abline(v = seq_along(family_order), col = "#E5E5E5", lty = 3) points(taxonomy$x, taxonomy$y, pch = 21, cex = 2, bg = palette_cb[taxonomy$family], col = "white", lwd = 1.3) text(taxonomy$x, taxonomy$y, labels = taxonomy$method, pos = 3, cex = 0.72) box() ``` **What to notice.** The methods differ first in representation and update strategy. The axes are categories, not claims about runtime, memory, or accuracy. ## What is distinctive about each method? - **SNIC** grows clusters from seeds with a priority queue in a non-iterative pass. Its native control is compactness. - **SLIC** alternates local assignment and center updates. Its controls include compactness, iteration count, seeding, connectivity, and optional exact-K repair subject to mask topology. - **Corr-SLIC** performs local SLIC updates in a compact correlation embedding; its approximation and exact-refinement controls must be reported. - **BRS-SLIC** deliberately separates a coarse sketch stage from exact correlation refinement at parcel boundaries. - **Supervoxels** iteratively combines feature and spatial heat-kernel similarities. It exposes bandwidths and convergence controls. - **Slice-MSF (experimental)** builds normalized DCT sketches and segments a masked spatial graph with cosine dissimilarity; it can stitch across axial slices and combine seeded ensemble runs. It is excluded from automatic recommendations pending release recertification; see the [evaluation contract](slice-msf.html). - **FLASH-3D** uses compact temporal signatures and iterative refinement. Hash length and DCT dimension are approximation controls, not generic quality scores. - **G3S** uses functional gradients and geodesic growth followed by connected refinement. - **ReNA** recursively agglomerates neighbouring regions on a spatial graph; **ReNA+** adds its own refinement path. Their weights are method-native rather than the unified spatial blend. - **MCL** alternates expansion and inflation on a sparse weighted voxel graph; inflation controls granularity rather than a target-K objective. - **ACSC** builds a block graph, applies community detection, and can refine boundaries using a chosen correlation definition. - **Commute** embeds the voxel graph using effective-resistance geometry before clustering; its spectral representation has different scaling constraints from local assignment methods. ## What should drive the final choice? Decide which properties are required: connected parcels, an exact target K, cross-slice edges, a particular similarity definition, deterministic output, or a memory ceiling. Then test candidate methods on the same mask and features with defined estimands. [Compare clustering methods](compare-methods.html) shows that workflow; [Benchmark with a reproducible receipt](benchmarks.html) shows how to attach runtime evidence.