--- title: Shared Spatial Dictionaries Across Subjects output: rmarkdown::html_vignette: toc: yes toc_depth: 2.0 css: albers.css includes: in_header: albers-header.html resource_files: - albers.css - albers.js - albers-header.html params: family: red preset: homage vignette: | %\VignetteIndexEntry{Shared Spatial Dictionaries Across Subjects} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{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 ) ``` ```{r albers-classes, echo=FALSE, results='asis'} cat(sprintf( paste0( '' ), params$family, params$preset )) ``` ```{r library} library(fmrilatent) ``` Shared spatial dictionaries are useful when multiple subjects should be represented in the same spatial coordinates. The dictionary is built once from a template reduction or training sample; each subject gets its own coefficient time series and offset. ## What is shared? We start with a nine-voxel mask and three parcels. The training matrix has one dominant signal in each parcel. ```{r make-template-data} mask <- array(TRUE, dim = c(3, 3, 1)) mask_vol <- neuroim2::LogicalNeuroVol(mask, neuroim2::NeuroSpace(dim(mask))) parcel_map <- rep(1:3, each = 3L) reduction <- make_cluster_reduction(mask_vol, parcel_map) n_time <- 8L t <- seq_len(n_time) signals <- cbind( sin(2 * pi * t / n_time), cos(2 * pi * t / n_time), seq(-1, 1, length.out = n_time) ) weights <- matrix(c( 1.0, 0.8, 1.2, 1.1, 0.9, 1.3, 0.7, 1.0, 1.4 ), nrow = 3, byrow = TRUE) X_train <- matrix(0, nrow = n_time, ncol = sum(mask)) for (parcel in 1:3) { cols <- which(parcel_map == parcel) X_train[, cols] <- signals[, parcel] %*% t(weights[parcel, ]) } ``` Build the template once. This example uses one PCA component per parcel so the render is deterministic and fast. ```{r build-template} tmpl <- parcel_basis_template( reduction, basis_pca(k = 1), data = X_train, center = TRUE ) validate_template_protocol(tmpl)[c("rank", "n_features", "support_cardinality")] ``` ```{r validate-template, include = FALSE} protocol <- validate_template_protocol(tmpl) stopifnot(protocol$rank == 3L, protocol$n_features == sum(mask)) ``` ## How do subjects differ? Two subject matrices can share the same dictionary while having different coefficient time series. ```{r encode-subjects} set.seed(19) X_subject1 <- X_train + matrix(rnorm(length(X_train), sd = 0.01), nrow = n_time) X_subject2 <- 0.8 * X_train + matrix(rnorm(length(X_train), sd = 0.01), nrow = n_time) lat1 <- encode(X_subject1, spec_space_parcel(tmpl), mask = mask_vol) lat2 <- encode(X_subject2, spec_space_parcel(tmpl), mask = mask_vol) data.frame( subject = c("subject1", "subject2"), coefficient_rows = c(nrow(coef_time(lat1)), nrow(coef_time(lat2))), coefficient_cols = c(ncol(coef_time(lat1)), ncol(coef_time(lat2))), rmse = round(c( sqrt(mean((as.matrix(lat1) - X_subject1)^2)), sqrt(mean((as.matrix(lat2) - X_subject2)^2)) ), 6) ) ``` The spatial loadings are the shared part. The coefficients are the subject-specific part. ```{r shared-loadings} data.frame( same_loading_shape = identical(dim(loadings(lat1)), dim(loadings(lat2))), max_loading_difference = max(abs(loadings(lat1) - loadings(lat2))), coefficient_difference = max(abs(coef_time(lat1) - coef_time(lat2))) ) ``` ```{r validate-subjects, include = FALSE} stopifnot( max(abs(loadings(lat1) - loadings(lat2))) < 1e-10, max(abs(coef_time(lat1) - coef_time(lat2))) > 0 ) ``` Use `vignette("shared-structure-boldzip")` when you need the lower-level shared-structure contracts and archive handoff boundaries.