| Title: | Least Squares Separate (LSS) Analysis for fMRI Data |
|---|---|
| Description: | Implements efficient least squares separate (LSS) analysis for functional magnetic resonance imaging (fMRI) data, following Mumford et al. (2012) <doi:10.1016/j.neuroimage.2011.08.076>. LSS estimates trial-by-trial activation patterns in event-related designs. The package provides reference R implementations, optimized C++ backends, prewhitening, and methods for voxel-specific haemodynamic response functions, including a fast implementation of GLMsingle (Prince et al., 2022) <doi:10.7554/eLife.77599>. |
| Authors: | Brad Buchsbaum [aut, cre] (ORCID: <https://orcid.org/0000-0002-1108-4866>) |
| Maintainer: | Brad Buchsbaum <[email protected]> |
| License: | GPL-3 |
| Version: | 0.2.0 |
| Built: | 2026-10-16 13:22:02 UTC |
| Source: | https://github.com/bbuchsbaum/fmrilss |
Compare performance between the standard mixed_solve implementation and the
optimized mixed_solve_optimized version.
benchmark_mixed_solve(X, Z, K = NULL, Y, n_reps = 5)benchmark_mixed_solve(X, Z, K = NULL, Y, n_reps = 5)
X |
Fixed effects design matrix |
Z |
Random effects design matrix |
K |
Kinship matrix (optional, defaults to identity) |
Y |
Response matrix (n x V) |
n_reps |
Number of repetitions for benchmarking |
Data frame with timing results
X <- matrix(rnorm(100 * 2), 100, 2) Z <- matrix(rnorm(100 * 3), 100, 3) Y <- matrix(rnorm(100 * 5), 100, 5) benchmark_mixed_solve(X, Z, Y = Y, n_reps = 2)X <- matrix(rnorm(100 * 2), 100, 2) Z <- matrix(rnorm(100 * 3), 100, 3) Y <- matrix(rnorm(100 * 5), 100, 5) benchmark_mixed_solve(X, Z, Y = Y, n_reps = 2)
Evaluates how well each method recovered the true HRF
calculate_recovery_metrics(results, true_hrf)calculate_recovery_metrics(results, true_hrf)
results |
Output from compare_hrf_recovery |
true_hrf |
Ground truth HRF sampled on |
Data frame with recovery metrics
onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) calculate_recovery_metrics(res, sim$true_hrf)onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) calculate_recovery_metrics(res, sim$true_hrf)
Single-trial betas from a glmsingle fit
## S3 method for class 'glmsingle_fit' coef(object, type = c("d", "c", "b", "a"), ...)## S3 method for class 'glmsingle_fit' coef(object, type = c("d", "c", "b", "a"), ...)
object |
A |
type |
Model type: |
... |
Unused. |
Trials x voxels matrix (a vector for type "a").
example("glmsingle", echo = FALSE) beta <- coef(fit) dim(beta)example("glmsingle", echo = FALSE) beta <- coef(fit) dim(beta)
Compares OASIS, SPMG1, SPMG3, and FIR for HRF recovery
compare_hrf_recovery(data, hrf_grid = NULL)compare_hrf_recovery(data, hrf_grid = NULL)
data |
Synthetic data from generate_lwu_data |
hrf_grid |
Optional pre-computed HRF grid for OASIS |
List with results from all methods
onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) names(res)onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) names(res)
Generates a grid of LWU HRF models with varying parameters
create_lwu_grid( tau_range = c(4, 8), sigma_range = c(1.5, 3.5), rho_range = c(0.1, 0.6), n_tau = 5, n_sigma = 3, n_rho = 3 )create_lwu_grid( tau_range = c(4, 8), sigma_range = c(1.5, 3.5), rho_range = c(0.1, 0.6), n_tau = 5, n_sigma = 3, n_rho = 3 )
tau_range |
Range of tau values to test |
sigma_range |
Range of sigma values to test |
rho_range |
Range of rho values to test |
n_tau |
Number of tau values in grid |
n_sigma |
Number of sigma values in grid |
n_rho |
Number of rho values in grid |
An object of class "fmrilss_lwu_grid": a list containing the HRF
functions in hrfs and their parameter data frame in parameters. The
object can be supplied directly as library_spec$pgrid to sbhm_build().
grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) nrow(grid$parameters)grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) nrow(grid$parameters)
Fits a common-amplitude GLM to estimate one pooled HRF shape for every voxel. The HRF basis and response are residualized against the complete fixed and nuisance span before fitting. Estimation fails when that span makes the HRF basis unidentifiable or rank deficient.
estimate_voxel_hrf( Y, events, basis, nuisance_regs = NULL, sframe = NULL, fixed_regs = NULL, ref_hrf = NULL )estimate_voxel_hrf( Y, events, basis, nuisance_regs = NULL, sframe = NULL, fixed_regs = NULL, ref_hrf = NULL )
Y |
Numeric matrix of BOLD data (time x voxels). |
events |
Data frame with |
basis |
HRF object from the |
nuisance_regs |
Optional finite numeric matrix of nuisance regressors. |
sframe |
Explicit |
fixed_regs |
Optional finite numeric matrix of fixed/common regressors. An intercept is added when it is not already in their span. |
ref_hrf |
HRF used to orient the sign of each estimated shape.
Defaults to the canonical |
A VoxelHRF object containing at least:
coefficients |
Matrix of positive-peak-normalized HRF shape weights with one row per basis function and one column per voxel. |
amplitude_scale |
The signed scale removed from each raw pooled-fit coefficient column. |
degenerate |
Logical, one per voxel: the estimated shape has no positive peak after orientation, or one smaller than 5% of its largest absolute deflection (typically a voxel without signal). Such shapes are scaled by their largest absolute value instead, and a warning reports how many there are; estimation does not fail. |
basis |
The HRF basis object used. |
conditions |
Observed event labels. Labels are metadata: all events are pooled into one shape per voxel. |
condition_pooling |
The literal string "all-events". |
set.seed(1) Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) times <- fmrihrf::samples(sframe, global = TRUE) rset <- fmrihrf::regressor_set(onsets = events$onset, fac = factor(rep("all events", nrow(events))), hrf = basis, duration = events$duration, span = 30) X <- fmrihrf::evaluate(rset, grid = times, precision = 0.1, method = "conv") coef <- matrix(rnorm(ncol(X) * ncol(Y)), ncol(X), ncol(Y)) Y <- X %*% coef + Y * 0.1 est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) str(est)set.seed(1) Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) times <- fmrihrf::samples(sframe, global = TRUE) rset <- fmrihrf::regressor_set(onsets = events$onset, fac = factor(rep("all events", nrow(events))), hrf = basis, duration = events$duration, span = 30) X <- fmrihrf::evaluate(rset, grid = times, precision = 0.1, method = "conv") coef <- matrix(rnorm(ncol(X) * ncol(Y)), ncol(X), ncol(Y)) Y <- X %*% coef + Y * 0.1 est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) str(est)
Selects an LWU HRF by joint-model profile fit, then estimates OASIS betas.
fit_oasis_grid(Y, onsets, sframe, hrf_grid, ridge_x = 0.01, ridge_b = 0.01)fit_oasis_grid(Y, onsets, sframe, hrf_grid, ridge_x = 0.01, ridge_b = 0.01)
Y |
Data matrix (time x voxels) |
onsets |
Event onset times |
sframe |
Sampling frame |
hrf_grid |
List of HRF models to test |
ridge_x |
Ridge parameter for design matrix |
ridge_b |
Ridge parameter for aggregator |
Each candidate is scored by pooled R-squared from unpenalized joint least squares with a voxel-specific intercept and all trial columns. The denominator sums squared deviations from each voxel's own mean. Ridge parameters affect only the final LSS estimates, not HRF selection. Simulation, scoring, and fitting use the same event construction at 0.1-second precision. This is an in-sample selection criterion; overlapping events can leave HRF parameters weakly identifiable.
List with best HRF index, parameters, and beta estimates
onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) fit <- fit_oasis_grid(sim$Y, sim$onsets, sim$sframe, grid) fit$best_paramsonsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) fit <- fit_oasis_grid(sim$Y, sim$onsets, sim$sframe, grid) fit$best_params
These helpers create validated option lists for lss() and friends.
No value itself. This topic groups the documented constructors
stglmnet_options(), oasis_options(), and prewhiten_options().
stglmnet_options(mode = "fixed", lambda = 0.1) oasis_options(ridge_x = 0.1, ridge_b = 0.1) prewhiten_options(method = "ar", p = 1)stglmnet_options(mode = "fixed", lambda = 0.1) oasis_options(ridge_x = 0.1, ridge_b = 0.1) prewhiten_options(method = "ar", p = 1)
Creates synthetic fMRI time series using specified LWU HRF parameters
generate_lwu_data( onsets, tau = 6, sigma = 2.5, rho = 0.35, TR = 1, total_time = 300, n_voxels = 10, amplitudes = NULL, noise_sd = 0.2, seed = NULL )generate_lwu_data( onsets, tau = 6, sigma = 2.5, rho = 0.35, TR = 1, total_time = 300, n_voxels = 10, amplitudes = NULL, noise_sd = 0.2, seed = NULL )
onsets |
Vector of event onset times in seconds |
tau |
LWU tau parameter (time-to-peak) |
sigma |
LWU sigma parameter (width) |
rho |
LWU rho parameter (undershoot amplitude) |
TR |
Repetition time in seconds |
total_time |
Total scan time in seconds |
n_voxels |
Number of voxels to simulate |
amplitudes |
Event amplitudes (scalar or vector) |
noise_sd |
Standard deviation of noise |
seed |
Random seed |
List with Y (data matrix), true_hrf, true_betas, and design info
onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) dim(sim$Y)onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) dim(sim$Y)
Functions to test OASIS's ability to recover HRF parameters from rapid event-related designs with overlapping HRFs. Generate Rapid Event-Related Design
generate_rapid_design( n_events = 25, total_time = 300, min_isi = 2, max_isi = 4, seed = NULL )generate_rapid_design( n_events = 25, total_time = 300, min_isi = 2, max_isi = 4, seed = NULL )
n_events |
Number of events to generate |
total_time |
Total time in seconds |
min_isi |
Minimum inter-stimulus interval in seconds |
max_isi |
Maximum inter-stimulus interval in seconds |
seed |
Random seed for reproducibility |
Creates a rapid event-related design with specified ISI range
Numeric vector of event onset times
generate_rapid_design(n_events = 5, total_time = 40, seed = 1)generate_rapid_design(n_events = 5, total_time = 40, seed = 1)
Estimates single-trial response amplitudes with the GLMsingle procedure (Prince et al., 2022): a library-based HRF per voxel, GLMdenoise noise regressors chosen by cross-validation, and voxel-wise fractional ridge regression chosen by cross-validation over repeated conditions. The implementation reorganises the computation (per-run factorisation, a single pass of data products per stage, compiled cross-validation and sufficient-statistic R^2) so it is much faster than the reference implementation while computing the same estimator.
glmsingle( Y, design, tr, stim_dur, runs = NULL, nuisance = NULL, poly_degree = NULL, fit_hrf = TRUE, hrf_library = NULL, denoise = TRUE, max_pcs = 10L, n_pcs = NULL, pc_stop = 1.05, pc_voxel_r2 = NULL, pc_voxel_mask = NULL, noise_pool_r2 = NULL, noise_pool_brightness = c(99, 0.1), noise_pool_mask = NULL, ridge = TRUE, ridge_fracs = seq(1, 0.05, by = -0.05), ridge_rescale = TRUE, ridge_alpha = c("grid", "exact"), cv_folds = NULL, sessions = NULL, percent_signal = TRUE, nuisance_in_denoise = c("always", "with_pcs"), cv_zero_variance = c("ignore", "glmsingle"), singular = c("error", "pinv"), pc_cv_all_voxels = FALSE, chunk_size = 50000L, n_threads = 1L, verbose = TRUE )glmsingle( Y, design, tr, stim_dur, runs = NULL, nuisance = NULL, poly_degree = NULL, fit_hrf = TRUE, hrf_library = NULL, denoise = TRUE, max_pcs = 10L, n_pcs = NULL, pc_stop = 1.05, pc_voxel_r2 = NULL, pc_voxel_mask = NULL, noise_pool_r2 = NULL, noise_pool_brightness = c(99, 0.1), noise_pool_mask = NULL, ridge = TRUE, ridge_fracs = seq(1, 0.05, by = -0.05), ridge_rescale = TRUE, ridge_alpha = c("grid", "exact"), cv_folds = NULL, sessions = NULL, percent_signal = TRUE, nuisance_in_denoise = c("always", "with_pcs"), cv_zero_variance = c("ignore", "glmsingle"), singular = c("error", "pinv"), pc_cv_all_voxels = FALSE, chunk_size = 50000L, n_threads = 1L, verbose = TRUE )
Y |
Data: a list of time x voxel matrices (one per run), or a single
time x voxel matrix with |
design |
Either a list of time x condition 0/1 matrices, one per run
(GLMsingle's format; a 1 marks a trial onset), or a data frame with
columns |
tr |
Repetition time in seconds. |
stim_dur |
Trial duration in seconds (sets the duration of the canonical HRF and of the HRF library). |
runs |
Run identifier for each row of |
nuisance |
Optional nuisance regressors (e.g. motion, physiology):
a list with one time x regressor matrix (or |
poly_degree |
Degree of the per-run polynomial drift model: a single
integer or one per run. Default ( |
fit_hrf |
If |
hrf_library |
Optional time x HRF matrix sampled at the TR (first row
at trial onset); columns are scaled to unit peak. Default ( |
denoise |
If |
max_pcs |
Largest number of noise PCs considered (default 10). Capped, with a warning, at the noise pool's rank across runs; an empty or rank-zero pool uses zero PCs and skips the PC-count cross-validation. |
n_pcs |
Use exactly this many noise PCs instead of choosing the count
by cross-validation. Default ( |
pc_stop |
Stopping factor for the cross-validated PC count (default
1.05): the smallest count whose improvement over zero PCs is at least
|
pc_voxel_r2 |
ON-OFF R^2 (percent) above which a voxel contributes to
choosing the PC count. Default ( |
pc_voxel_mask |
Optional logical vector (one per voxel); only |
noise_pool_r2 |
ON-OFF R^2 (percent) below which a voxel may enter the
noise pool. Default ( |
noise_pool_brightness |
Two numbers |
noise_pool_mask |
Optional logical vector (one per voxel); only |
ridge |
If |
ridge_fracs |
Ridge fractions in (0, 1] to choose from by cross-validation; a fraction is the length of the ridge solution relative to the OLS solution (1 = no shrinkage). A single value skips cross-validation and uses that fraction everywhere. Default: 1 to 0.05 in steps of 0.05. |
ridge_rescale |
If |
ridge_alpha |
How fractions are converted to ridge penalties:
|
cv_folds |
Cross-validation folds: a list of integer vectors of runs
held out together. Default ( |
sessions |
Session of each run (one entry per run). Betas are
z-scored within session before cross-validation, removing
session-level gain differences. Default ( |
percent_signal |
If |
nuisance_in_denoise |
How |
cv_zero_variance |
Treatment, in cross-validation, of voxels whose
reference betas have zero variance within a session (e.g. all-zero
voxels): |
singular |
What to do when a run's trial regressors are linearly
dependent (e.g. two trials with the same onset): |
pc_cv_all_voxels |
If |
chunk_size |
Number of voxels processed at a time (default 50000). Lower it to reduce peak memory; results do not depend on it. |
n_threads |
Threads for the per-voxel C++ loops (default 1; |
verbose |
Print progress messages (default |
Four models are returned, matching GLMsingle:
ON-OFF model: one canonical-HRF regressor for all trials.
Single-trial OLS with the best library HRF per voxel.
Type B plus GLMdenoise noise regressors.
Type C plus voxel-wise fractional ridge regression.
Arguments are grouped by stage: HRF (fit_hrf, hrf_library), noise
regressors (denoise, max_pcs, n_pcs, pc_*, noise_pool_*), ridge
(ridge, ridge_*) and cross-validation (cv_folds, sessions,
cv_zero_variance). The defaults are GLMsingle's defaults and are a good
choice for typical event-related designs; the arguments most often worth
setting are nuisance (e.g. motion), cv_folds/sessions for
multi-session data, and chunk_size when memory is tight.
An object of class glmsingle_fit: a list with elements typea,
typeb, typec, typed (each a list using GLMsingle's field names;
betas are trials x voxels; typec/typed are NULL when denoise/
ridge is FALSE), meanvol, hrf_library, hrf_assumed, design
(trial bookkeeping), settings (the resolved settings, including
data-driven thresholds) and timing.
fit_hrf = wantlibrary;
denoise = wantglmdenoise; ridge = wantfracridge; ridge_fracs =
fracs; ridge_rescale = wantautoscale; max_pcs = n_pcs;
pc_stop = pcstop (a fixed count, GLMsingle's pcstop = -k, is
n_pcs = k); pc_voxel_r2 = pcR2cutoff; pc_voxel_mask =
pcR2cutoffmask; noise_pool_r2 = brainR2; noise_pool_brightness =
brainthresh; noise_pool_mask = brainexclude; cv_folds =
xvalscheme (1-based run indices); sessions = sessionindicator;
nuisance = extra_regressors; poly_degree = maxpolydeg;
percent_signal = wantpercentbold; chunk_size = chunklen;
stim_dur = stimdur. Output fields keep GLMsingle's names.
Defaults reproduce GLMsingle (Python, commit 1ab54a6) except where GLMsingle is internally inconsistent:
nuisance_in_denoise = "always" keeps nuisance regressors in
every GLMdenoise and ridge fit; GLMsingle (Python) drops them when
zero noise PCs are used, including from the cross-validation
reference. "with_pcs" reproduces GLMsingle.
cv_zero_variance = "ignore" gives voxels with zero beta variance
no weight in cross-validation; GLMsingle's in-place division helper
treats them inconsistently across candidates. "glmsingle"
reproduces it.
The ON-OFF R^2 threshold uses a deterministic two-Gaussian mixture fit with a small variance floor (as in GLMsingle's MATLAB code); GLMsingle's Python fit is unseeded.
percent_signal applies to all model types (GLMsingle always
scales types C and D).
Computation is in double precision (GLMsingle uses single).
HRF indices are 1-based; betas are trials x voxels.
The FIR diagnostic model, hrfmodel = "optimize", wantlss, bootstrap
modes, figures and file outputs are not implemented.
Prince, J. S., Charest, I., Kurzawski, J. W., Pyles, J. A., Tarr, M. J., & Kay, K. N. (2022). Improving the accuracy of single-trial fMRI response estimates using GLMsingle. eLife, 11, e77599.
set.seed(1) tr <- 1; n_time <- 120; n_vox <- 40 design <- lapply(1:3, function(r) { D <- matrix(0, n_time, 6) onsets <- seq(5, 95, by = 8) D[cbind(onsets, rep_len(sample(6), length(onsets)))] <- 1 D }) Y <- lapply(design, function(D) { X <- apply(D, 2, function(s) stats::filter(s, glmsingle_hrf(3, tr), sides = 1, circular = FALSE)) X[is.na(X)] <- 0 100 + X %*% matrix(rnorm(6 * n_vox, 2), 6) + matrix(rnorm(n_time * n_vox), n_time) }) fit <- glmsingle(Y, design, tr = tr, stim_dur = 3, max_pcs = 2, verbose = FALSE) dim(fit$typed$betasmd)set.seed(1) tr <- 1; n_time <- 120; n_vox <- 40 design <- lapply(1:3, function(r) { D <- matrix(0, n_time, 6) onsets <- seq(5, 95, by = 8) D[cbind(onsets, rep_len(sample(6), length(onsets)))] <- 1 D }) Y <- lapply(design, function(D) { X <- apply(D, 2, function(s) stats::filter(s, glmsingle_hrf(3, tr), sides = 1, circular = FALSE)) X[is.na(X)] <- 0 100 + X %*% matrix(rnorm(6 * n_vox, 2), 6) + matrix(rnorm(n_time * n_vox), n_time) }) fit <- glmsingle(Y, design, tr = tr, stim_dur = 3, max_pcs = 2, verbose = FALSE) dim(fit$typed$betasmd)
Fits glmsingle() using an fmridesign::event_model() to describe the
trials. The event model must contain one event term with a single factor
whose levels are the conditions (repeated conditions drive
cross-validation). Onsets must lie on the TR grid.
glmsingle_design(Y, event_model, baseline_model = NULL, stim_dur = NULL, ...)glmsingle_design(Y, event_model, baseline_model = NULL, stim_dur = NULL, ...)
Y |
Time x voxel data matrix covering all runs of the sampling frame. |
event_model |
An |
baseline_model |
Optional |
stim_dur |
Trial duration in seconds. Default: the event durations, which must then be a single positive value. |
... |
Further arguments passed to |
A baseline_model contributes its nuisance term (e.g. motion) as
nuisance. Its drift and block terms are not used: GLMsingle
models drift with its own per-run polynomials (poly_degree).
A glmsingle_fit; see glmsingle().
set.seed(1) sf <- fmrihrf::sampling_frame(c(80L, 80L), TR = 1) events <- data.frame(onset = rep(c(5, 20, 35, 50), 2), run = rep(1:2, each = 4), stimulus = factor(rep(c("A", "B"), 4))) em <- fmridesign::event_model( onset ~ fmridesign::hrf(stimulus), data = events, block = ~run, sampling_frame = sf, durations = rep(2, nrow(events))) Y <- matrix(100 + rnorm(160 * 4), 160, 4) fit <- glmsingle_design(Y, em, denoise = FALSE, ridge = FALSE, verbose = FALSE) dim(coef(fit, type = "b"))set.seed(1) sf <- fmrihrf::sampling_frame(c(80L, 80L), TR = 1) events <- data.frame(onset = rep(c(5, 20, 35, 50), 2), run = rep(1:2, each = 4), stimulus = factor(rep(c("A", "B"), 4))) em <- fmridesign::event_model( onset ~ fmridesign::hrf(stimulus), data = events, block = ~run, sampling_frame = sf, durations = rep(2, nrow(events))) Y <- matrix(100 + rnorm(160 * 4), 160, 4) fit <- glmsingle_design(Y, em, denoise = FALSE, ridge = FALSE, verbose = FALSE) dim(coef(fit, type = "b"))
Reproduces GLMsingle's canonical HRF (getcanonicalhrf) and its library
of 20 HRFs (getcanonicalhrflibrary) for a stimulus of duration stim_dur
sampled at tr. Each HRF is peak-normalised to 1, and the first sample is
coincident with stimulus onset.
glmsingle_hrf_library(stim_dur, tr) glmsingle_hrf(stim_dur, tr)glmsingle_hrf_library(stim_dur, tr) glmsingle_hrf(stim_dur, tr)
stim_dur |
Stimulus duration in seconds (rounded to 0.1 s). |
tr |
Repetition time in seconds. |
glmsingle_hrf() returns a numeric vector; glmsingle_hrf_library()
returns a time x 20 matrix.
Prince, J. S., et al. (2022). Improving the accuracy of single-trial fMRI response estimates using GLMsingle. eLife, 11, e77599.
lib <- glmsingle_hrf_library(stim_dur = 3, tr = 1) dim(lib)lib <- glmsingle_hrf_library(stim_dur = 3, tr = 1) dim(lib)
Build and validate trial-wise design objects used by the ITEM helper layer.
The returned object has class item_bundle and carries trial-level metadata
needed by item_cv() and item_slice_fold().
item_build_design( X_t, T_target = NULL, run_id = NULL, C_transform = NULL, trial_id = NULL, trial_hash = NULL, meta = list(), diagnostics = list(), validate = TRUE )item_build_design( X_t, T_target = NULL, run_id = NULL, C_transform = NULL, trial_id = NULL, trial_hash = NULL, meta = list(), diagnostics = list(), validate = TRUE )
X_t |
Numeric trial-wise design matrix with shape |
T_target |
Optional supervised targets with |
run_id |
Optional run/session identifier of length |
C_transform |
Optional transformation matrix used to map |
trial_id |
Optional trial identifier vector of length |
trial_hash |
Optional hash used by alignment guards.
If supplied, |
meta |
Optional metadata list. |
diagnostics |
Optional diagnostics list to attach to the bundle. |
validate |
Logical; when |
An object of class item_bundle with fields:
Gamma, X_t, C_transform, T_target, U, U_by_run, run_id,
trial_id, trial_hash, trial_info, meta, and diagnostics.
bundle <- item_build_design( X_t = diag(4), T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2) ) bundle$trial_infobundle <- item_build_design( X_t = diag(4), T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2) ) bundle$trial_info
Compute the trial covariance term as the inverse of a ridge-stabilized normal-equation system, using stable solve paths with fallbacks.
item_compute_u( X_t, V = NULL, v_type = c("cov", "precision"), ridge = 0, method = c("chol", "svd", "pinv"), run_id = NULL, output = c("matrix", "by_run"), tol = sqrt(.Machine$double.eps) )item_compute_u( X_t, V = NULL, v_type = c("cov", "precision"), ridge = 0, method = c("chol", "svd", "pinv"), run_id = NULL, output = c("matrix", "by_run"), tol = sqrt(.Machine$double.eps) )
X_t |
Numeric trial-wise design matrix ( |
V |
Optional temporal covariance/precision object. Accepted forms:
|
v_type |
Whether |
ridge |
Non-negative ridge term added to the trial-wise system matrix before inversion. |
method |
Preferred solver path ( |
run_id |
Optional trial-level run ids (length |
output |
Return full |
tol |
Numerical tolerance for rank/solver fallbacks. |
Numeric matrix U by default, or a named list U_by_run when
output = "by_run". Returned object includes item_diagnostics attribute
with rank/condition/solver details.
X_t <- diag(4) item_compute_u(X_t) item_compute_u(X_t, run_id = c(1, 1, 2, 2), output = "by_run")X_t <- diag(4) item_compute_u(X_t) item_compute_u(X_t, run_id = c(1, 1, 2, 2), output = "by_run")
Run deterministic leave-one-run-out (LOSO) crossvalidation for classification or regression using ITEM decoder weights.
item_cv( Gamma, T_target = NULL, U = NULL, run_id = NULL, mode = c("classification", "regression"), metric = NULL, ridge = 0, method = c("chol", "svd", "pinv"), class_levels = NULL, trial_id = NULL, trial_hash = NULL, check_hash = FALSE )item_cv( Gamma, T_target = NULL, U = NULL, run_id = NULL, mode = c("classification", "regression"), metric = NULL, ridge = 0, method = c("chol", "svd", "pinv"), class_levels = NULL, trial_id = NULL, trial_hash = NULL, check_hash = FALSE )
Gamma |
Trial-wise beta matrix ( |
T_target |
Supervised targets ( |
U |
Trial covariance as full matrix ( |
run_id |
Run/session id vector ( |
mode |
Decoding mode: |
metric |
Optional metric name.
Classification: |
ridge |
Ridge passed to |
method |
Solver preference for |
class_levels |
Optional fixed class order for classification. |
trial_id |
Optional trial id vector (used if |
trial_hash |
Optional trial hash (used if |
check_hash |
Logical; validate stored trial hash before CV. |
Object of class item_cv_result with per-fold metrics, aggregate
metric summary, and trial-level predictions.
Gamma <- matrix( c(1, 0, 0.9, 0.1, 0.1, 0.9, 0, 1), ncol = 2, byrow = TRUE ) item_cv( Gamma = Gamma, T_target = factor(c("A", "A", "B", "B")), U = diag(4), run_id = c(1, 1, 2, 2) )Gamma <- matrix( c(1, 0, 0.9, 0.1, 0.1, 0.9, 0, 1), ncol = 2, byrow = TRUE ) item_cv( Gamma = Gamma, T_target = factor(c("A", "A", "B", "B")), U = diag(4), run_id = c(1, 1, 2, 2) )
Fit ITEM weights with a ridge-stabilized generalized least-squares solve.
item_fit( Gamma_train, T_train, U_train, ridge = 0, method = c("chol", "svd", "pinv"), tol = sqrt(.Machine$double.eps) )item_fit( Gamma_train, T_train, U_train, ridge = 0, method = c("chol", "svd", "pinv"), tol = sqrt(.Machine$double.eps) )
Gamma_train |
Numeric matrix ( |
T_train |
Numeric target matrix ( |
U_train |
Trial covariance ( |
ridge |
Non-negative ridge term added to the left-hand system. |
method |
Preferred solver path ( |
tol |
Numerical tolerance for rank/solver fallbacks. |
Numeric weight matrix W_hat (n_features x p) with
item_diagnostics attribute.
Gamma_train <- matrix( c(1, 0, 0.9, 0.1, 0.1, 0.9), ncol = 2, byrow = TRUE ) T_train <- rbind(c(1, 0), c(1, 0), c(0, 1)) W_hat <- item_fit(Gamma_train, T_train, diag(3)) item_predict(Gamma_train, W_hat)Gamma_train <- matrix( c(1, 0, 0.9, 0.1, 0.1, 0.9), ncol = 2, byrow = TRUE ) T_train <- rbind(c(1, 0), c(1, 0), c(0, 1)) W_hat <- item_fit(Gamma_train, T_train, diag(3)) item_predict(Gamma_train, W_hat)
Convenience wrapper that runs lsa() to estimate trial-wise amplitudes,
computes U, and returns an item_bundle ready for crossvalidation.
item_from_lsa( Y, X_t, T_target, run_id, Z = NULL, Nuisance = NULL, V = NULL, v_type = c("cov", "precision"), ridge = 0, lsa_method = c("r", "cpp"), solver = c("chol", "svd", "pinv"), u_output = c("matrix", "by_run"), C_transform = NULL, trial_id = NULL, trial_hash = NULL, meta = list(), validate = TRUE )item_from_lsa( Y, X_t, T_target, run_id, Z = NULL, Nuisance = NULL, V = NULL, v_type = c("cov", "precision"), ridge = 0, lsa_method = c("r", "cpp"), solver = c("chol", "svd", "pinv"), u_output = c("matrix", "by_run"), C_transform = NULL, trial_id = NULL, trial_hash = NULL, meta = list(), validate = TRUE )
Y |
Numeric data matrix ( |
X_t |
Numeric trial-wise design matrix ( |
T_target |
Supervised targets with |
run_id |
Trial-level run/session identifier ( |
Z |
Optional nuisance matrix passed to |
Nuisance |
Alias for |
V |
Optional covariance/precision for |
v_type |
Whether |
ridge |
Ridge passed to |
lsa_method |
LS-A backend ( |
solver |
Solver preference for |
u_output |
Return full |
C_transform |
Optional transform matrix used to map |
trial_id |
Optional trial id vector. |
trial_hash |
Optional trial hash. |
meta |
Optional metadata list. |
validate |
Logical; enforce strict checks before returning. |
item_bundle with fields Gamma, X_t, T_target, U/U_by_run,
run_id, meta, and diagnostics.
set.seed(1) X_t <- diag(4) Y <- X_t %*% matrix( c(1, 0, 0.8, 0.2, 0.2, 0.8, 0, 1), ncol = 2, byrow = TRUE ) + matrix(rnorm(8, sd = 0.01), 4, 2) bundle <- item_from_lsa( Y = Y, X_t = X_t, T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2), lsa_method = "r" ) names(bundle)set.seed(1) X_t <- diag(4) Y <- X_t %*% matrix( c(1, 0, 0.8, 0.2, 0.2, 0.8, 0, 1), ncol = 2, byrow = TRUE ) + matrix(rnorm(8, sd = 0.01), 4, 2) bundle <- item_from_lsa( Y = Y, X_t = X_t, T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2), lsa_method = "r" ) names(bundle)
Compute T_hat = Gamma_test %*% W_hat.
item_predict(Gamma_test, W_hat)item_predict(Gamma_test, W_hat)
Gamma_test |
Numeric matrix ( |
W_hat |
Numeric matrix ( |
Numeric matrix of predictions (n_test x p).
Gamma_test <- matrix(c(1, 0, 0, 1), ncol = 2, byrow = TRUE) W_hat <- diag(2) item_predict(Gamma_test, W_hat)Gamma_test <- matrix(c(1, 0, 0, 1), ncol = 2, byrow = TRUE) W_hat <- diag(2) item_predict(Gamma_test, W_hat)
Create deterministic leave-one-run-out slices for Gamma, T_target, and
trial covariance (U or U_by_run).
item_slice_fold(bundle, test_run, check_hash = FALSE)item_slice_fold(bundle, test_run, check_hash = FALSE)
bundle |
Object of class |
test_run |
Run/session id to hold out for testing. |
check_hash |
Logical; if |
A list with train/test slices:
Gamma_train, Gamma_test, T_train, T_test,
U_train, U_test, train_idx, test_idx, train_runs, test_run.
bundle <- item_build_design( X_t = diag(4), T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2) ) bundle$Gamma <- matrix(c(1, 0, 0.8, 0.2, 0.2, 0.8, 0, 1), 4, 2, byrow = TRUE) bundle$U <- diag(4) fold <- item_slice_fold(bundle, test_run = 2) fold$test_idxbundle <- item_build_design( X_t = diag(4), T_target = factor(c("A", "B", "A", "B")), run_id = c(1, 1, 2, 2) ) bundle$Gamma <- matrix(c(1, 0, 0.8, 0.2, 0.2, 0.8, 0, 1), 4, 2, byrow = TRUE) bundle$U <- diag(4) fold <- item_slice_fold(bundle, test_run = 2) fold$test_idx
Performs a standard multiple regression analysis where all trial regressors are fitted simultaneously. This provides a reference comparison to the Least Squares Separate (LSS) approach.
lsa(Y, X, Z = NULL, Nuisance = NULL, method = c("r", "cpp"))lsa(Y, X, Z = NULL, Nuisance = NULL, method = c("r", "cpp"))
Y |
A numeric matrix where rows are timepoints and columns are voxels/features. This is the dependent variable data. |
X |
A numeric matrix where rows are timepoints and columns are trial-specific regressors. Each column represents a single trial or event. |
Z |
A numeric matrix of nuisance regressors (e.g., motion parameters, drift terms). Defaults to NULL. |
Nuisance |
An alias for Z, provided for consistency with LSS interface. If both Z and Nuisance are provided, Z takes precedence. |
method |
Character string specifying the computational method:
|
LSA fits the model , where all trial regressors
in X are estimated simultaneously. This is in contrast to LSS, which fits
each trial separately while treating other trials as nuisance regressors.
A numeric matrix of size T × V containing the beta estimates for
each trial regressor (rows) and each voxel (columns). Supplied trial and
voxel names must be complete and unique; unnamed inputs receive canonical
Trial_i and Voxel_i names.
n_timepoints <- 100 n_trials <- 10 n_voxels <- 50 X <- matrix(0, n_timepoints, n_trials) for(i in 1:n_trials) { start <- (i-1) * 8 + 1 if(start + 5 <= n_timepoints) { X[start:(start+5), i] <- 1 } } Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) true_betas <- matrix(rnorm(n_trials * n_voxels, 0, 0.5), n_trials, n_voxels) for(i in 1:n_trials) { Y <- Y + X[, i] %*% matrix(true_betas[i, ], 1, n_voxels) } beta_estimates <- lsa(Y, X)n_timepoints <- 100 n_trials <- 10 n_voxels <- 50 X <- matrix(0, n_timepoints, n_trials) for(i in 1:n_trials) { start <- (i-1) * 8 + 1 if(start + 5 <= n_timepoints) { X[start:(start+5), i] <- 1 } } Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) true_betas <- matrix(rnorm(n_trials * n_voxels, 0, 0.5), n_trials, n_voxels) for(i in 1:n_trials) { Y <- Y + X[, i] %*% matrix(true_betas[i, ], 1, n_voxels) } beta_estimates <- lsa(Y, X)
Computes trial-wise beta estimates using the Least Squares Separate approach of Mumford et al. (2012). This method fits a separate GLM for each trial, with the trial of interest plus a single regressor formed by summing all other trials in a one-basis design. A K-basis OASIS model uses one summed other-trial regressor per basis function.
lss( Y, X, Z = NULL, Nuisance = NULL, method = c("r_optimized", "cpp_optimized", "r_vectorized", "cpp", "naive", "oasis", "stglmnet"), block_size = 96, oasis = list(), stglmnet = list(), prewhiten = NULL, trial_groups = NULL, ridge = NULL )lss( Y, X, Z = NULL, Nuisance = NULL, method = c("r_optimized", "cpp_optimized", "r_vectorized", "cpp", "naive", "oasis", "stglmnet"), block_size = 96, oasis = list(), stglmnet = list(), prewhiten = NULL, trial_groups = NULL, ridge = NULL )
Y |
A numeric matrix of size n × V where n is the number of timepoints and V is the number of voxels/variables |
X |
A numeric matrix of size n × T for a one-basis design, with one
column per trial. A raw K-basis OASIS design has n × (T K) columns and
additionally requires |
Z |
A numeric matrix of size n × F representing common fixed regressors included in every trial-wise model (e.g., intercept, condition effects, block effects). Their coefficients are not returned. If NULL, an intercept-only design is used. Defaults to NULL. |
Nuisance |
A numeric matrix of size n × N representing nuisance regressors to be projected out before LSS analysis (e.g., motion parameters, physiological noise). If NULL, no nuisance projection is performed. Defaults to NULL |
method |
Character string specifying which implementation to use. Options are:
|
block_size |
An integer specifying the voxel block size for parallel
processing, only applicable when |
oasis |
A list of options for the OASIS method (ridge, SE, design
construction, etc.).
See Details and |
stglmnet |
A list of options for the |
prewhiten |
A list of prewhitening options using the fmriAR
package, or |
trial_groups |
Optional vector (character, factor, or integer) with one
condition label per trial, i.e. per column of |
ridge |
Optional fractional ridge penalty: one number, or two numbers
|
The LSS approach fits a separate GLM for each trial, where each model includes:
The trial of interest (from column i of X)
For a one-basis design, all other trials combined into one summed regressor. A K-basis OASIS model uses a K-column summed block.
Common fixed regressors (Z matrix), whose coefficients are not returned
Computation. Each LSS estimate is a linear functional of the data,
. The optimized methods build the
weight matrix from the (small) trial design and
compute all trial betas with one matrix product . Because the
weights lie in the residual space of the confounds, the data matrix is never
residualized or copied, so the cost is a single
BLAS call regardless of the number of confounds.
If Nuisance regressors are provided, the rank-revealed combined span
cbind(Z, Nuisance) is projected from both Y and X before fitting. Without a
separate Nuisance matrix, Z remains explicitly in every trial-wise model.
When using method="oasis", the following options are available in the oasis list
(see also oasis_options for a validated constructor):
design_spec: A list for building trial-wise designs from event onsets using fmrihrf.
Must contain: sframe (sampling frame), cond (list with onsets,
hrf, and optionally span), and optionally others (list of other conditions
to be modeled as nuisances). When provided, X can be NULL and will be constructed automatically.
K: Explicit basis dimension for multi-basis HRF models (e.g., 3 for SPMG3).
A raw multi-basis X also requires ntrials and
trial_basis_map. An unmodified multi-basis
fmridesign::design_matrix() with one event term is recognized
from its metadata; an ordinary raw X is otherwise interpreted as K=1.
ridge_mode: Either "fractional" (default) or "absolute". In absolute mode,
ridge_x and ridge_b are used directly as regularization parameters. In fractional mode,
they represent fractions of the mean design energy for adaptive regularization.
ridge_x: Ridge parameter for trial-specific regressors (default 0.05).
Controls
regularization strength for individual trial estimates.
ridge_b: Ridge parameter for the aggregator regressor (default 0.05).
Controls
regularization strength for the sum of all other trials.
return_se: Logical, whether to return model-based standard errors (default FALSE).
This is available only for unpenalized OASIS without estimated prewhitening.
return_diag: Logical, whether to return design diagnostics (default FALSE).
When TRUE, includes diagnostic information about the design matrix structure.
block_cols: Integer, voxel block size for memory-efficient processing (default 4096).
Larger values use more memory but may be faster for systems with sufficient RAM.
ntrials: Required number of trials when a raw K > 1 design is supplied.
trial_basis_map: Required data frame for a raw K > 1 design,
with one row per X column and fields column, trial, and basis.
design_spec$hrf_grid: Candidate HRFs for grid-based selection
within an event-built design. A top-level oasis$hrf_grid field is
invalid and rejected.
Prewhitening (temporal autocorrelation correction):
Use the top-level prewhiten parameter for all temporal whitening.
This replaces the old oasis$whiten = "ar1" syntax, which is now
deprecated and ignored. Do not put AR options inside the
oasis list; they belong in prewhiten.
When using method = "stglmnet", the backend accepts an additional
nested stglmnet= list for lambda selection, overlap-adaptive
penalties, and optional pooled trial parameterizations. The common pattern is
stglmnet = stglmnet_options(mode = "cv") to select lambda by
cross-validation, or stglmnet = stglmnet_options(mode = "fixed",
lambda = 0.01) for a fixed elastic-net fit. The backend reuses fmrilss
prewhitening and nuisance-projection utilities rather than maintaining a
separate whitening path.
The prewhiten list accepts the following fields
(see also prewhiten_options for a validated constructor):
method: Character, "ar" (default when the list is
non-NULL), "arma", or "none".
"ar" fits a pure autoregressive model; "arma" adds a
moving-average component (requires q > 0).
p: AR order.
An integer, or "auto" (default) to select via AIC/BIC up to
p_max. Use p = 1 for a simple AR(1) model (the most
common choice for fMRI); higher orders are rarely needed but may
help with short TRs or multi-band sequences.
q: Integer MA order for ARMA models (default 0). Only
relevant when method = "arma".
p_max: Integer, maximum AR order when p = "auto"
(default 6).
pooling: How AR coefficients are estimated across voxels.
One of:
"global"(default) A single set of AR coefficients is estimated from the median autocorrelation across all voxels. Fast and usually adequate.
"voxel"Voxel-adaptive noise model. Per-voxel residual
autocorrelations are estimated, voxels are grouped into
voxel_bins bins (default 50) of similar autocorrelation, and an
AR model is refitted per bin; each bin gets its own filtered design,
as in Nilearn's AR(1) GLM. Supported by methods
"r_optimized", "cpp_optimized" and "cpp"; other
methods reject it.
"run"Fit one AR model per run (requires runs).
Useful when noise structure differs between runs.
"parcel"Fit one AR model per parcel (requires
parcels); each parcel is fitted with its own filtered design.
Supported by methods "r_optimized", "cpp_optimized"
and "cpp".
runs: Integer vector of length nrow(Y) giving
run/block labels. Required for pooling = "run" and recommended
whenever data span multiple runs so that whitening respects run
boundaries.
parcels: Integer vector of length ncol(Y) giving
parcel labels. Required for pooling = "parcel".
exact_first: Character, "ar1" (default) or
"none". When "ar1", the first observation of each
segment is scaled by for the exact
likelihood; "none" drops the first observation instead.
compute_residuals: Logical (default TRUE). When TRUE,
OLS residuals (see residual_model) are computed before fitting
the noise model. Set to FALSE only if Y is already residualized.
design: Optional numeric design matrix whose projection
produced those residuals. Supplying it opts in to fmriAR's correction
for downward bias in residual autocovariance. When
compute_residuals = TRUE, it must span the same columns as the
full X/Z/Nuisance design, including the intercept
that fmrilss adds when none is already represented.
acvf_correction: Optional correction matrix or list of
matrices from fmriAR::acvf_bias_matrix(), used instead of
design when reusing a correction across datasets. The two fields
are mutually exclusive.
voxel_bins: Positive integer number of autocorrelation bins
for pooling = "voxel" (default 50).
residual_model: which model's residuals the noise model is
estimated from. "aggregate" (default) uses the confounds plus one
summed trial regressor per trial_groups level (or per basis
function); "full" uses the full trial-wise design, whose many
columns bias the autocorrelation downward in rapid designs;
"corrected" uses the full design with fmriAR's bias correction
(least biased, slower, global/run pooling only). "full" is
implied by design/acvf_correction. See
vignette("prewhitening") for when to use each.
correction_max_lag: Positive integer lag budget used when
design is supplied (default 25). The correction is intended for
high-pass-filtered designs; without high-pass filtering, the required
lag budget can become impractically large. See
fmriAR::fit_noise() for details.
Typical prewhiten recipes:
# Simple AR(1) — good default for most fMRI data
prewhiten = list(method = "ar", p = 1)
# Auto-select AR order (AIC), global pooling
prewhiten = list(method = "ar", p = "auto")
# Per-run AR(1) for multi-run data
prewhiten = list(method = "ar", p = 1, pooling = "run",
runs = blockids)
# Or use the validated constructor:
prewhiten = prewhiten_options(method = "ar", p = 1, pooling = "run",
runs = blockids)
Prewhitening is applied before the LSS analysis to account for temporal autocorrelation in the fMRI time series. Both Y and all design matrices (X, Z, Nuisance) are filtered through the same whitening operator so that OLS on the whitened system is equivalent to GLS on the original data.
The OASIS method provides a mathematically equivalent but computationally optimized version of standard LSS. It reformulates the per-trial GLM fitting as a single matrix operation, eliminating redundant computations. This is particularly beneficial for designs with many trials or when processing large datasets. When K > 1 (multi-basis HRFs), the output will have K*ntrials rows, with basis functions for each trial arranged sequentially.
Normally, a numeric matrix of trial-wise beta estimates: T × V for
a one-basis design or (T K) × V for OASIS with K basis functions. With
OASIS return_diag = TRUE or return_se = TRUE, returns
list(beta, diag?, se?); beta and se have the same row-by-voxel shape.
One-basis diagnostics contain length-T vectors d, alpha, and s;
multi-basis diagnostics contain K × K × T arrays D, C, and E.
Coefficients for common regressors Z are not returned. Multi-basis beta
and SE matrices carry the canonical trial_basis_map attribute. When
estimated prewhitening is active, the actual fitted fmriAR_plan is
available as attr(result, "whiten_plan") (and on result$beta for
structured returns).
Mumford, J. A., Turner, B. O., Ashby, F. G., & Poldrack, R. A. (2012). Deconvolving BOLD activation in event-related designs for multivoxel pattern classification analyses. NeuroImage, 59(3), 2636-2643.
Turner, B. O., Mumford, J. A., Poldrack, R. A., & Ashby, F. G. (2012). Spatiotemporal activity estimation for multivoxel pattern analysis with rapid event-related designs. NeuroImage, 62(3), 1429-1438.
n_timepoints <- 100 n_trials <- 10 n_voxels <- 50 X <- matrix(0, n_timepoints, n_trials) for(i in 1:n_trials) { start <- (i-1) * 8 + 1 if(start + 5 <= n_timepoints) { X[start:(start+5), i] <- 1 } } Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) true_betas <- matrix(rnorm(n_trials * n_voxels, 0, 0.5), n_trials, n_voxels) for(i in 1:n_trials) { Y <- Y + X[, i] %*% matrix(true_betas[i, ], 1, n_voxels) } beta_estimates <- lss(Y, X) Z <- cbind(1, scale(1:n_timepoints)) beta_estimates_with_regressors <- lss(Y, X, Z = Z) Nuisance <- matrix(rnorm(n_timepoints * 6), n_timepoints, 6) beta_estimates_clean <- lss(Y, X, Z = Z, Nuisance = Nuisance) beta_oasis <- lss(Y, X, method = "oasis", oasis = list(ridge_x = 0.1, ridge_b = 0.1, ridge_mode = "fractional")) result_with_se <- lss(Y, X, method = "oasis", oasis = list(return_se = TRUE, ridge_mode = "absolute", ridge_x = 0, ridge_b = 0)) beta_estimates <- result_with_se$beta standard_errors <- result_with_se$se sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1.0) beta_auto <- lss(Y, X = NULL, method = "oasis", oasis = list( design_spec = list( sframe = sframe, cond = list( onsets = c(10, 30, 50, 70), hrf = fmrihrf::HRF_SPMG1, span = 25 ), others = list( list(onsets = c(20, 40, 60, 80)) ) ) )) beta_multibasis <- lss(Y, X = NULL, method = "oasis", oasis = list( design_spec = list( sframe = sframe, cond = list( onsets = c(10, 30, 50, 70), hrf = fmrihrf::HRF_SPMG3, span = 30 ) ), K = 3 ))n_timepoints <- 100 n_trials <- 10 n_voxels <- 50 X <- matrix(0, n_timepoints, n_trials) for(i in 1:n_trials) { start <- (i-1) * 8 + 1 if(start + 5 <= n_timepoints) { X[start:(start+5), i] <- 1 } } Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) true_betas <- matrix(rnorm(n_trials * n_voxels, 0, 0.5), n_trials, n_voxels) for(i in 1:n_trials) { Y <- Y + X[, i] %*% matrix(true_betas[i, ], 1, n_voxels) } beta_estimates <- lss(Y, X) Z <- cbind(1, scale(1:n_timepoints)) beta_estimates_with_regressors <- lss(Y, X, Z = Z) Nuisance <- matrix(rnorm(n_timepoints * 6), n_timepoints, 6) beta_estimates_clean <- lss(Y, X, Z = Z, Nuisance = Nuisance) beta_oasis <- lss(Y, X, method = "oasis", oasis = list(ridge_x = 0.1, ridge_b = 0.1, ridge_mode = "fractional")) result_with_se <- lss(Y, X, method = "oasis", oasis = list(return_se = TRUE, ridge_mode = "absolute", ridge_x = 0, ridge_b = 0)) beta_estimates <- result_with_se$beta standard_errors <- result_with_se$se sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1.0) beta_auto <- lss(Y, X = NULL, method = "oasis", oasis = list( design_spec = list( sframe = sframe, cond = list( onsets = c(10, 30, 50, 70), hrf = fmrihrf::HRF_SPMG1, span = 25 ), others = list( list(onsets = c(20, 40, 60, 80)) ) ) )) beta_multibasis <- lss(Y, X = NULL, method = "oasis", oasis = list( design_spec = list( sframe = sframe, cond = list( onsets = c(10, 30, 50, 70), hrf = fmrihrf::HRF_SPMG3, span = 30 ) ), K = 3 ))
Fast C++ implementation of least squares separate (LSS) beta estimation using vectorized matrix operations. Computes all trial betas in a single pass without loops.
lss_beta_cpp(C_projected, Y_projected)lss_beta_cpp(C_projected, Y_projected)
C_projected |
Projected trial regressors (n x T) from project_confounds_cpp |
Y_projected |
Projected data (n x V) from project_confounds_cpp |
This vectorized implementation computes all LSS betas simultaneously using matrix algebra. It's significantly faster than per-trial loops and automatically benefits from BLAS multithreading. The algorithm handles numerical edge cases by applying finite lower bounds to degenerate denominators.
For best performance on large datasets, ensure your R installation uses optimized BLAS (like OpenBLAS or Intel MKL).
Beta matrix (T x V) with LSS estimates for each trial and voxel
n <- 40; V <- 3; T <- 4 X_confounds <- cbind(1, seq_len(n)) Y_data <- matrix(rnorm(n * V), n, V) C_trials <- matrix(rnorm(n * T), n, T) result <- project_confounds_cpp(X_confounds, Y_data, C_trials) betas <- lss_beta_cpp(result$Q_dmat_ran, result$residual_data)n <- 40; V <- 3; T <- 4 X_confounds <- cbind(1, seq_len(n)) Y_data <- matrix(rnorm(n * V), n, V) C_trials <- matrix(rnorm(n * T), n, T) result <- project_confounds_cpp(X_confounds, Y_data, C_trials) betas <- lss_beta_cpp(result$Q_dmat_ran, result$residual_data)
A wrapper for the optimized C++ LSS implementation
lss_cpp_optimized(Y, bdes)lss_cpp_optimized(Y, bdes)
Y |
the voxel by time data matrix |
bdes |
the block design list created by |
a matrix of beta estimates
set.seed(1) Y <- matrix(rnorm(16), 8, 2) X_trials <- matrix(0, 8, 2) X_trials[2:3, 1] <- 1 X_trials[5:6, 2] <- 1 bdes <- list( dmat_base = matrix(1, 8, 1), dmat_fixed = NULL, dmat_ran = X_trials ) lss_cpp_optimized(Y, bdes)set.seed(1) Y <- matrix(rnorm(16), 8, 2) X_trials <- matrix(0, 8, 2) X_trials[2:3, 1] <- 1 X_trials[5:6, 2] <- 1 bdes <- list( dmat_base = matrix(1, 8, 1), dmat_fixed = NULL, dmat_ran = X_trials ) lss_cpp_optimized(Y, bdes)
Perform Least Squares Separate (LSS) analysis using event_model and baseline_model objects from the fmridesign package. This provides a streamlined interface for complex designs with multi-condition, parametric modulators, and structured nuisance handling.
lss_design( Y, event_model, baseline_model = NULL, method = "oasis", oasis = list(), prewhiten = NULL, blockids = NULL, validate = TRUE, ..., diagnostics = FALSE )lss_design( Y, event_model, baseline_model = NULL, method = "oasis", oasis = list(), prewhiten = NULL, blockids = NULL, validate = TRUE, ..., diagnostics = FALSE )
Y |
Numeric matrix of fMRI data (timepoints × voxels). |
event_model |
An event_model object from |
baseline_model |
Optional baseline_model object from
|
method |
LSS method to use. All methods accepted by |
oasis |
List of OASIS-specific options: ridge regularization
( |
prewhiten |
Optional prewhitening specification as a list (or
|
blockids |
Optional complete exact-integer block/run identifiers, one per scan. If NULL, run intercepts are derived from the sampling frame. |
validate |
Logical. If TRUE (default), performs validation checks on design compatibility, collinearity, and temporal alignment. |
... |
Additional arguments passed to the underlying LSS method. |
diagnostics |
Logical. If TRUE, attach a |
Design Specification:
The event_model should typically use trialwise() for LSS:
emod <- event_model(onset ~ trialwise(basis = "spmg1"),
data = events,
block = ~run,
sampling_frame = sframe)
Non-trial event terms are supported as common fixed regressors, for example a parametric modulator alongside the unique trialwise target term:
emod <- event_model(onset ~ trialwise(basis = "spmg1") + hrf(RT),
data = events,
block = ~run,
sampling_frame = sframe)
Baseline Model:
If provided, baseline_model components are mapped as follows:
drift and block terms → Z parameter (fixed effects)
nuisance term → Nuisance parameter (confounds)
Multi-Run Handling:
Both event_model and baseline_model must use the same sampling_frame.
Event onsets should be run-relative (resetting to 0 each run) as per
fmridesign convention; conversion to global time and run boundaries in the
design are handled automatically. A joint call still uses one OASIS
other-trial aggregate across all runs, so its coefficients are cross-run
pooled. Fit each run separately when run-local LSS coefficients are required.
Prewhitening:
Use the prewhiten parameter (not the oasis list) for temporal
autocorrelation correction. For active multi-run whitening, this adapter
infers the scan-level run segmentation from the sampling frame. An explicit
prewhiten$runs vector is allowed only when it encodes those same
boundaries. The event model's blockids usually has one value per event
and is not the required scan-level vector.
Residual-autocovariance bias correction remains an explicit opt-in:
lss_design() does not silently populate prewhiten$design.
If supplied, it must be the assembled residual-forming design, including
trialwise, fixed event, baseline, nuisance, and intercept columns as
applicable. This preserves fmriAR's requirement that the correction design
be the one that actually produced the residuals.
See lss and prewhiten_options for full details.
Validation:
When validate = TRUE, the function checks:
Temporal alignment: nrow(Y) matches total scans in sampling_frame
Collinearity: emits a warning when the full assembled design has a condition number above 30 and all effective ridge penalties are zero. The condition number is scale-dependent and is not returned.
Compatibility: event_model and baseline_model use same sampling_frame
Normally a trial-by-voxel beta matrix, or a
(trial × basis)-by-voxel matrix for multi-basis HRFs. When OASIS
return_diag = TRUE or return_se = TRUE, returns
list(beta, diag?, se?) with the matrix/list shapes documented in lss().
Multi-basis rows are always trial-major with basis varying within trial.
Adapter metadata are attached to the returned object; multi-basis beta and
SE matrices retain the canonical trial_basis_map. With active estimated
prewhitening, attr(result, "whiten_plan") records the fitted
fmriAR_plan.
lss for the traditional matrix-based interface,
fmridesign::event_model for event model creation,
fmridesign::baseline_model for baseline model creation
library(fmridesign) library(fmrihrf) sframe <- sampling_frame(blocklens = c(150, 150), TR = 2) trials <- data.frame( onset = c(10, 30, 50, 70, 90, 110, 10, 30, 50, 70, 90, 110), run = rep(1:2, each = 6) ) emod <- event_model( onset ~ trialwise(basis = "spmg1"), data = trials, block = ~run, sampling_frame = sframe ) motion <- list( matrix(rnorm(150 * 6), 150, 6), matrix(rnorm(150 * 6), 150, 6) ) bmodel <- baseline_model( basis = "bs", degree = 5, sframe = sframe, nuisance_list = motion ) Y <- matrix(rnorm(300 * 1000), 300, 1000) beta <- lss_design(Y, emod, bmodel, method = "oasis") dim(beta)library(fmridesign) library(fmrihrf) sframe <- sampling_frame(blocklens = c(150, 150), TR = 2) trials <- data.frame( onset = c(10, 30, 50, 70, 90, 110, 10, 30, 50, 70, 90, 110), run = rep(1:2, each = 6) ) emod <- event_model( onset ~ trialwise(basis = "spmg1"), data = trials, block = ~run, sampling_frame = sframe ) motion <- list( matrix(rnorm(150 * 6), 150, 6), matrix(rnorm(150 * 6), 150, 6) ) bmodel <- baseline_model( basis = "bs", degree = 5, sframe = sframe, nuisance_list = motion ) Y <- matrix(rnorm(300 * 1000), 300, 1000) beta <- lss_design(Y, emod, bmodel, method = "oasis") dim(beta)
lss() usageThese functions provide a modern-signature entry point for methods that
historically required a block-design (bdes) object.
No value itself. This topic groups lss_naive_fit() and
lss_optimized_fit().
set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_naive_fit(Y, X)set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_naive_fit(Y, X)
Performs LSS analysis using the naive approach where each trial model is fit
separately. This is the conceptually simplest implementation but less efficient
than the optimized lss function.
lss_naive(Y = NULL, bdes, dset = NULL, trial_groups = NULL, ridge = NULL)lss_naive(Y = NULL, bdes, dset = NULL, trial_groups = NULL, ridge = NULL)
Y |
A numeric matrix where rows are timepoints and columns are voxels/features.
If NULL, the function will attempt to extract data from |
bdes |
A list containing design matrices with components:
|
dset |
Optional dataset object. If provided and Y is NULL, data will be
extracted using |
trial_groups |
Optional vector with one condition label per trial. When
supplied, each trial model uses one summed "other trials" regressor per
group (LSS-N) instead of a single pooled regressor. See |
ridge |
Optional fractional ridge penalty (one or two numbers); see
|
This function implements the LSS approach by fitting a separate GLM for each trial. Following the method described by Mumford et al. (2012), the model for each trial includes:
The regressor for the trial of interest.
A single regressor representing all other trials (the sum of their individual regressors).
All base regressors (e.g., intercept, drift terms).
All fixed effects regressors (if any).
While less efficient than the optimized lss function, this
implementation is conceptually clear and serves as a reference for validation.
A numeric matrix with dimensions (n_events x n_voxels) containing the LSS beta estimates for each trial and voxel.
lss for the optimized implementation
set.seed(1) n_timepoints <- 20 n_trials <- 4 n_voxels <- 3 X <- matrix(0, n_timepoints, n_trials) for (i in seq_len(n_trials)) { onset <- 1 + (i - 1) * 4 X[onset:(onset + 2), i] <- 1 } Z <- cbind(Intercept = 1, Linear = seq_len(n_timepoints)) Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) for (i in seq_len(n_trials)) { Y <- Y + X[, i] %*% matrix(rnorm(n_voxels, sd = 0.2), 1, n_voxels) } bdes <- list( dmat_base = Z, dmat_ran = X, dmat_fixed = NULL, fixed_ind = NULL ) beta_estimates_naive <- lss_naive(Y = Y, bdes = bdes) beta_estimates_fast <- lss(Y = Y, X = X, Z = Z) max(abs(beta_estimates_naive - beta_estimates_fast))set.seed(1) n_timepoints <- 20 n_trials <- 4 n_voxels <- 3 X <- matrix(0, n_timepoints, n_trials) for (i in seq_len(n_trials)) { onset <- 1 + (i - 1) * 4 X[onset:(onset + 2), i] <- 1 } Z <- cbind(Intercept = 1, Linear = seq_len(n_timepoints)) Y <- matrix(rnorm(n_timepoints * n_voxels), n_timepoints, n_voxels) for (i in seq_len(n_trials)) { Y <- Y + X[, i] %*% matrix(rnorm(n_voxels, sd = 0.2), 1, n_voxels) } bdes <- list( dmat_base = Z, dmat_ran = X, dmat_fixed = NULL, fixed_ind = NULL ) beta_estimates_naive <- lss_naive(Y = Y, bdes = bdes) beta_estimates_fast <- lss(Y = Y, X = X, Z = Z) max(abs(beta_estimates_naive - beta_estimates_fast))
Equivalent to calling lss(..., method = "naive").
lss_naive_fit(Y, X, Z = NULL, Nuisance = NULL, prewhiten = NULL)lss_naive_fit(Y, X, Z = NULL, Nuisance = NULL, prewhiten = NULL)
Y |
Numeric matrix (timepoints x voxels). |
X |
Trial design matrix (timepoints x trials). |
Z |
Optional experimental regressors. |
Nuisance |
Optional nuisance regressors to project out. |
prewhiten |
Optional prewhitening options list (see |
A numeric matrix (trials x voxels) of beta estimates.
set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_naive_fit(Y, X)set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_naive_fit(Y, X)
An optimized version of the LSS analysis that avoids creating large intermediate matrices, providing a significant speedup and lower memory usage for the pure R implementation.
lss_optimized(Y = NULL, bdes, dset = NULL, use_cpp = TRUE)lss_optimized(Y = NULL, bdes, dset = NULL, use_cpp = TRUE)
Y |
A numeric matrix where rows are timepoints and columns are voxels/features. |
bdes |
A list containing the design matrices. |
dset |
Optional dataset object. |
use_cpp |
Logical. If TRUE (default), uses the C++ implementation. If FALSE, uses the new optimized R implementation. |
A numeric matrix of LSS beta estimates.
set.seed(1) Y <- matrix(rnorm(16), 8, 2) X_trials <- matrix(0, 8, 2) X_trials[2:3, 1] <- 1 X_trials[5:6, 2] <- 1 bdes <- list( dmat_base = matrix(1, 8, 1), dmat_ran = X_trials, dmat_fixed = NULL, fixed_ind = NULL ) lss_optimized(Y, bdes, use_cpp = FALSE)set.seed(1) Y <- matrix(rnorm(16), 8, 2) X_trials <- matrix(0, 8, 2) X_trials[2:3, 1] <- 1 X_trials[5:6, 2] <- 1 bdes <- list( dmat_base = matrix(1, 8, 1), dmat_ran = X_trials, dmat_fixed = NULL, fixed_ind = NULL ) lss_optimized(Y, bdes, use_cpp = FALSE)
This is a convenience wrapper around lss() that selects one of the optimized
implementations.
lss_optimized_fit( Y, X, Z = NULL, Nuisance = NULL, engine = c("cpp", "r"), block_size = 96, prewhiten = NULL )lss_optimized_fit( Y, X, Z = NULL, Nuisance = NULL, engine = c("cpp", "r"), block_size = 96, prewhiten = NULL )
Y |
Numeric matrix (timepoints x voxels). |
X |
Trial design matrix (timepoints x trials). |
Z |
Optional experimental regressors. |
Nuisance |
Optional nuisance regressors to project out. |
engine |
|
block_size |
Block size used by the C++ optimized path. |
prewhiten |
Optional prewhitening options list (see |
A numeric matrix (trials x voxels) of beta estimates.
set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_optimized_fit(Y, X, engine = "r")set.seed(1) Y <- matrix(rnorm(16), 8, 2) X <- matrix(0, 8, 2) X[2:3, 1] <- 1 X[5:6, 2] <- 1 lss_optimized_fit(Y, X, engine = "r")
Fits the rank-1 GLM of Pedregosa et al. (2015): within each voxel one HRF,
expressed in an HRF basis, is shared by all trials, and each trial has its
own amplitude. With model = "separate" (R1-GLMS, the default and the best
performer in that paper) every trial is fitted in its own least-squares
separate (LSS) model, so the amplitudes are LSS estimates under the
voxel's estimated HRF. With model = "joint" (R1-GLM) all trials enter one
least-squares-all model.
lss_rank1( Y, events, basis, sframe, nuisance_regs = NULL, fixed_regs = NULL, model = c("separate", "joint"), trial_groups = NULL, init = "aggregate", ref_hrf = NULL, prewhiten = NULL, solver = c("als", "lbfgs"), max_iter = 100L, tol = 1e-07 )lss_rank1( Y, events, basis, sframe, nuisance_regs = NULL, fixed_regs = NULL, model = c("separate", "joint"), trial_groups = NULL, init = "aggregate", ref_hrf = NULL, prewhiten = NULL, solver = c("als", "lbfgs"), max_iter = 100L, tol = 1e-07 )
Y |
Numeric matrix of BOLD data (time x voxels). |
events |
Data frame with |
basis |
An |
sframe |
An |
nuisance_regs |
Optional numeric matrix of nuisance regressors. |
fixed_regs |
Optional numeric matrix of fixed regressors (run intercepts are added when not already spanned). |
model |
|
trial_groups |
Optional vector with one condition label per trial
(row of |
init |
|
ref_hrf |
HRF used for the |
prewhiten |
Optional prewhitening options (see |
solver |
|
max_iter |
Maximum number of alternating iterations (for |
tol |
Relative objective change used as the convergence criterion
(for |
For trial with basis-convolved design block (n x K) and
the sum of all blocks , the separate model minimizes, per voxel,
over the HRF coefficients , trial amplitudes and
other-trial amplitudes ; the joint model minimizes
. Fixed regressors (run intercepts are
added when absent) and nuisance regressors are projected from the design
and data first, which gives each trial-wise model its own confound
coefficients, as in lss(). (The original formulation shares one set of
confound coefficients across the trial-wise models.)
The fit uses exact alternating least squares rather than the quasi-Newton
solver of the paper: given , the amplitudes are ordinary LSS (or
LSA) estimates; given the amplitudes, solves a K-dimensional least
squares problem. Each step minimizes its block exactly, so the objective
never increases. All quantities either step needs are K x K Gram blocks of
the residualized design and the product , so after that one
product the separate model costs per voxel and iteration,
independent of the number of scans. The joint model costs
per voxel and iteration and is intended for regions
of interest.
The problem is bilinear: and the amplitudes are determined only up
to a common scale and sign. After fitting, each voxel's HRF is oriented to
correlate positively with ref_hrf and scaled to a unit positive peak, and
the amplitudes absorb that scale, so they are in peak-response units for
unit-amplitude, zero-duration events, like lss_with_hrf(). The returned
hrf is a VoxelHRF object and can be passed to lss_with_hrf() to
estimate trial amplitudes in new data (for example a held-out run) with the
learned HRFs.
The objective is not jointly convex. init = "aggregate" starts each voxel
from the pooled-shape fit of estimate_voxel_hrf() (one common amplitude
for all trials); init = "reference" starts every voxel from the
projection of ref_hrf onto the basis, which is more stable for voxels
whose mean response is near zero. A K x V matrix of starting coefficients
is also accepted.
A list with
Trial amplitudes (trials x voxels), in peak-response units.
For model = "separate", the amplitude of the pooled
other-trials regressor in each trial's model (trials x voxels);
NULL for the joint model.
A VoxelHRF object with the unit-peak HRF coefficients
(K x voxels), the removed amplitude_scale, basis and sframe.
Final residual sum of squares per voxel (summed over
the trial-wise models for "separate").
Per-voxel iteration counts and convergence flags.
Voxels whose estimated HRF has no positive peak of at
least 5% of its largest absolute deflection after orientation (scaled
by that deflection instead, so their
amplitudes are not in peak-response units). Also stored in hrf. The
policy is shared with estimate_voxel_hrf(): such voxels are flagged
and reported in one warning rather than failing the fit.
The fitted model.
When prewhitening is applied the fitted plan is attached as the
whiten_plan attribute.
Pedregosa, F., Eickenberg, M., Ciuciu, P., Thirion, B., & Gramfort, A. (2015). Data-driven HRF estimation for encoding and decoding models. NeuroImage, 104, 209-220.
estimate_voxel_hrf(), lss_with_hrf(), lss_sbhm()
set.seed(1) sframe <- fmrihrf::sampling_frame(blocklens = 200, TR = 1) events <- data.frame(onset = seq(10, 180, by = 9), duration = 0, condition = "A") basis <- fmrihrf::HRF_SPMG3 X <- fmrilss:::.voxhrf_trial_basis(events, basis, sframe)$X h <- c(1, 0.4, -0.2) beta <- matrix(rnorm(nrow(events) * 5, 1, 0.3), nrow(events), 5) Y <- X %*% kronecker(beta, h) + matrix(rnorm(200 * 5, sd = 0.5), 200, 5) fit <- lss_rank1(Y, events, basis, sframe) dim(fit$beta) fit$hrf$coefficients[, 1]set.seed(1) sframe <- fmrihrf::sampling_frame(blocklens = 200, TR = 1) events <- data.frame(onset = seq(10, 180, by = 9), duration = 0, condition = "A") basis <- fmrihrf::HRF_SPMG3 X <- fmrilss:::.voxhrf_trial_basis(events, basis, sframe)$X h <- c(1, 0.4, -0.2) beta <- matrix(rnorm(nrow(events) * 5, 1, 0.3), nrow(events), 5) Y <- X %*% kronecker(beta, h) + matrix(rnorm(200 * 5, sd = 0.5), 200, 5) fit <- lss_rank1(Y, events, basis, sframe) dim(fit$beta) fit$hrf$coefficients[, 1]
Orchestrates the SBHM pipeline: (1) run-safe trial design and OASIS fit in the shared basis, (2) a voxel shape summary from the requested source, (3) hard library matching or explicit blending, and (4) a separate scalar-coefficient refit using the selected voxel shape.
lss_sbhm( Y, sbhm, design_spec, Nuisance = NULL, prewhiten = NULL, prepass = list(), match = list(), oasis = list(), amplitude = list(), return = c("amplitude", "coefficients", "both") )lss_sbhm( Y, sbhm, design_spec, Nuisance = NULL, prewhiten = NULL, prepass = list(), match = list(), oasis = list(), amplitude = list(), return = c("amplitude", "coefficients", "both") )
Y |
Numeric matrix T×V of fMRI time series. |
sbhm |
SBHM object from |
design_spec |
List for design construction:
|
Nuisance |
Optional T×P nuisance regressors. |
prewhiten |
Optional prewhitening options (see |
prepass |
Optional list forwarded to |
match |
Optional list forwarded to
|
oasis |
Optional list forwarded to |
amplitude |
List controlling the scalar amplitude stage. Fields:
|
return |
One of |
Most users should treat the prepass, match, oasis, and amplitude inputs
as optional nested override lists: you can provide only the fields you want
to change. Unspecified nested defaults are preserved.
If you already use fmridesign, prefer lss_sbhm_design() to avoid manually
assembling an OASIS design_spec.
Active prewhitening records the fitted fmriAR_plan in the
whiten_plan attribute on the result and its returned amplitude/coefficient
arrays. diag$prewhitening distinguishes requested and applied whitening,
records the residual model, and reports whether filtering changed Y
exactly. An applied identity filter can leave the response unchanged; inspect
the recorded plan's order and coefficients before interpreting that case.
A list with components:
amplitude ntrials×V matrix (when requested)
coeffs_r r×ntrials×V array of trial-wise coefficients (when requested)
matched_name and matched_idx: named top-scoring library identities
margin named length-V score differences (top1 - top2 cosine); these are
not calibrated confidence measures
alpha_coords r×V matched coordinates per voxel
shape_mode and fallback_low_conf: named vectors recording the shape
policy actually used
prepass_fallback: named logical vector identifying voxels whose requested
shape source fell back to the aggregate prepass
trial_basis_map: complete trial/basis output identity
event_amplitude, event_duration, and event_run: named event-design
metadata
diag list with r, ntrials, and times
Scalar amplitudes are coefficients on the supplied event design using the
selected rank-truncated library shape. They are not automatically peak BOLD
responses. SBHM standard errors are not calibrated and return_se=TRUE
fails explicitly for every amplitude method.
lss_sbhm_design(), sbhm_prepass(), sbhm_match()
library(fmrihrf) set.seed(3) Tlen <- 180; V <- 4 sframe <- sampling_frame(blocklens = Tlen, TR = 1) H <- cbind(exp(-seq(0, 30, length.out = Tlen)/5), exp(-seq(0, 30, length.out = Tlen)/7)) sbhm <- sbhm_build(library_H = H, r = 2, sframe = sframe, normalize = TRUE) onsets <- seq(8, 140, by = 12) design_spec <- list(sframe = sframe, cond = list(onsets = onsets, duration = 0, span = 30)) hrf_B <- sbhm_hrf(sbhm$B, sbhm$tgrid, sbhm$span) rr <- fmrihrf::regressor(onsets = onsets, hrf = hrf_B, duration = 0, span = 30, summate = FALSE) Xr <- fmrihrf::evaluate(rr, grid = sbhm$tgrid, precision = 0.1, method = "conv") alpha_true <- rnorm(ncol(sbhm$B)) Y <- matrix(rnorm(Tlen*V, sd = .6), Tlen, V) Y[,1] <- Y[,1] + Xr %*% alpha_true out <- lss_sbhm(Y, sbhm, design_spec) out2 <- lss_sbhm(Y, sbhm, design_spec, match = list(topK = 2, soft_blend = TRUE), return = "amplitude") names(out)library(fmrihrf) set.seed(3) Tlen <- 180; V <- 4 sframe <- sampling_frame(blocklens = Tlen, TR = 1) H <- cbind(exp(-seq(0, 30, length.out = Tlen)/5), exp(-seq(0, 30, length.out = Tlen)/7)) sbhm <- sbhm_build(library_H = H, r = 2, sframe = sframe, normalize = TRUE) onsets <- seq(8, 140, by = 12) design_spec <- list(sframe = sframe, cond = list(onsets = onsets, duration = 0, span = 30)) hrf_B <- sbhm_hrf(sbhm$B, sbhm$tgrid, sbhm$span) rr <- fmrihrf::regressor(onsets = onsets, hrf = hrf_B, duration = 0, span = 30, summate = FALSE) Xr <- fmrihrf::evaluate(rr, grid = sbhm$tgrid, precision = 0.1, method = "conv") alpha_true <- rnorm(ncol(sbhm$B)) Y <- matrix(rnorm(Tlen*V, sd = .6), Tlen, V) Y[,1] <- Y[,1] + Xr %*% alpha_true out <- lss_sbhm(Y, sbhm, design_spec) out2 <- lss_sbhm(Y, sbhm, design_spec, match = list(topK = 2, soft_blend = TRUE), return = "amplitude") names(out)
Run the SBHM end-to-end pipeline using fmridesign's event_model and
optional baseline_model, mirroring the convenience of lss_design() but
producing SBHM shape coefficients and (optionally) scalar event-design trial
coefficients in the component named amplitude.
lss_sbhm_design( Y, sbhm, event_model, baseline_model = NULL, prewhiten = NULL, prepass = list(), match = list(), oasis = list(), amplitude = list(), return = c("amplitude", "coefficients", "both"), validate = TRUE, ... )lss_sbhm_design( Y, sbhm, event_model, baseline_model = NULL, prewhiten = NULL, prepass = list(), match = list(), oasis = list(), amplitude = list(), return = c("amplitude", "coefficients", "both"), validate = TRUE, ... )
Y |
Numeric matrix T×V of fMRI time series (timepoints × voxels). |
sbhm |
SBHM object as returned by |
event_model |
An |
baseline_model |
Optional |
prewhiten |
Optional prewhitening options (see |
prepass |
Optional list forwarded to |
match |
Optional list forwarded to |
oasis |
Optional SBHM OASIS override list. Supported fields are
|
amplitude |
Amplitude options (see |
return |
One of |
validate |
Logical; when TRUE, performs basic checks (sampling frame
compatibility, temporal alignment) analogous to |
... |
Reserved for future use. |
This function wraps lss_sbhm() by locating the unique trialwise event term,
rebuilding it with the SBHM basis, and retaining run identities and
heterogeneous durations. Non-trial event terms and baseline_model columns
enter the fixed nuisance span. Multi-run regressors are constructed within
runs so HRF tails cannot cross run boundaries.
Same return contract as lss_sbhm().
library(fmridesign) sframe <- fmrihrf::sampling_frame(blocklens = 60, TR = 1) trials <- data.frame(onset = c(5, 20, 35), run = 1) emod <- event_model(onset ~ trialwise(basis = "spmg1"), data = trials, block = ~run, sampling_frame = sframe) times <- fmrihrf::samples(sframe, global = TRUE) H <- cbind(exp(-times / 5), exp(-times / 7)) sbhm <- sbhm_build(library_H = H, r = 2, sframe = sframe, normalize = TRUE, baseline = NULL) Y <- matrix(rnorm(60 * 4), 60, 4) out <- lss_sbhm_design(Y, sbhm, emod)library(fmridesign) sframe <- fmrihrf::sampling_frame(blocklens = 60, TR = 1) trials <- data.frame(onset = c(5, 20, 35), run = 1) emod <- event_model(onset ~ trialwise(basis = "spmg1"), data = trials, block = ~run, sampling_frame = sframe) times <- fmrihrf::samples(sframe, global = TRUE) H <- cbind(exp(-times / 5), exp(-times / 7)) sbhm <- sbhm_build(library_H = H, r = 2, sframe = sframe, normalize = TRUE, baseline = NULL) Y <- matrix(rnorm(60 * 4), 60, 4) out <- lss_sbhm_design(Y, sbhm, emod)
Computes trial-wise beta estimates using voxel-specific HRFs.
lss_with_hrf( Y, events, hrf_estimates, nuisance_regs = NULL, engine = "R", chunk_size = 5000, verbose = TRUE, backing_dir = NULL, sframe = NULL, fixed_regs = NULL )lss_with_hrf( Y, events, hrf_estimates, nuisance_regs = NULL, engine = "R", chunk_size = 5000, verbose = TRUE, backing_dir = NULL, sframe = NULL, fixed_regs = NULL )
Y |
Numeric matrix of BOLD data (time x voxels). |
events |
Data frame with |
hrf_estimates |
A VoxelHRF object returned by |
nuisance_regs |
Optional finite numeric matrix of nuisance regressors. |
engine |
Computational engine: "R" for the pure-R implementation (default), or "C++" to request the compiled backend. The returned metadata records the backend actually used after fallback. |
chunk_size |
Number of voxels to process per batch (C++ engine only). |
verbose |
Logical; emit progress messages. |
backing_dir |
Directory for bigmemory backing files. If NULL, a temporary directory is used (C++ engine only). |
sframe |
Explicit sampling frame. Defaults to the frame stored in
|
fixed_regs |
Optional finite numeric matrix of fixed/common regressors. An intercept is added when absent. |
An object of class LSSBeta for the C++ request, or a numeric
matrix (n_trials x n_vox) for the R engine. Matrix outputs carry
sampling-frame, normalization, coefficient-unit, event-amplitude,
event-duration, requested-engine, realized-engine, and chunk-size metadata.
With a positive-peak-normalized shape, a zero-duration unit-amplitude event
has a peak-response-amplitude coefficient. Otherwise the result is a
coefficient on the supplied duration- and amplitude-coded event design.
The degenerate metadata repeats the per-voxel flags of hrf_estimates:
flagged voxels have no positive-peak shape, so their coefficients are not
in peak-response units.
set.seed(1) Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) times <- fmrihrf::samples(sframe, global = TRUE) rset <- fmrihrf::regressor_set(onsets = events$onset, fac = factor(rep("all events", nrow(events))), hrf = basis, duration = events$duration, span = 30) X <- fmrihrf::evaluate(rset, grid = times, precision = 0.1, method = "conv") coef <- matrix(rnorm(ncol(X) * ncol(Y)), ncol(X), ncol(Y)) Y <- X %*% coef + Y * 0.1 est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) betas <- lss_with_hrf(Y, events, est, verbose = FALSE, engine = "R") dim(betas)set.seed(1) Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) times <- fmrihrf::samples(sframe, global = TRUE) rset <- fmrihrf::regressor_set(onsets = events$onset, fac = factor(rep("all events", nrow(events))), hrf = basis, duration = events$duration, span = 30) X <- fmrihrf::evaluate(rset, grid = times, precision = 0.1, method = "conv") coef <- matrix(rnorm(ncol(X) * ncol(Y)), ncol(X), ncol(Y)) Y <- X %*% coef + Y * 0.1 est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) betas <- lss_with_hrf(Y, events, est, verbose = FALSE, engine = "R") dim(betas)
Simple list-based S3 class returned by lss_with_hrf containing
trial-wise beta estimates. as.matrix() materializes C++-backed estimates
as a dense matrix while preserving voxel names, sampling-frame,
normalization, and unit metadata.
No value itself. This topic documents the object returned by
lss_with_hrf(..., engine = "C++").
betas is the file-backed trial-by-voxel matrix; dimnames preserves trial
and voxel identity; sframe, normalization, units, degenerate, and
event_amplitude and event_duration define timing and coefficient
interpretation; and
engine_requested, engine_used, and chunk_size record execution. Use
as.matrix() for the documented dense extraction path; these metadata are
retained as matrix attributes.
Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) fit <- lss_with_hrf(Y, events, est, engine = "C++", verbose = FALSE) class(fit)Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) fit <- lss_with_hrf(Y, events, est, engine = "C++", verbose = FALSE) class(fit)
Performs expensive matrix computations that don't depend on the response vector, allowing for efficient reuse across multiple voxels.
mixed_precompute(X, Z, K = NULL)mixed_precompute(X, Z, K = NULL)
X |
Fixed effects design matrix (n × p) |
Z |
Random effects design matrix (n × q) |
K |
Kinship/covariance matrix for random effects (q × q) |
Workspace object for use with mixed_solve_optimized
X <- matrix(1, 6, 1) Z <- diag(6) ws <- mixed_precompute(X, Z) names(ws)X <- matrix(1, 6, 1) Z <- diag(6) ws <- mixed_precompute(X, Z) names(ws)
Solves mixed models with random effects using REML or ML estimation. This function provides a unified interface to mixed model estimation, similar to the lss/lsa functions in this package.
mixed_solve( Y, X = NULL, Z = NULL, K = NULL, Nuisance = NULL, method = c("REML", "ML"), bounds = c(1e-09, 1e+09), SE = FALSE, return_Hinv = FALSE ) mixed_solve_cpp( Y, X = NULL, Z = NULL, K = NULL, Nuisance = NULL, method = c("REML", "ML"), bounds = c(1e-09, 1e+09), SE = FALSE, return_Hinv = FALSE )mixed_solve( Y, X = NULL, Z = NULL, K = NULL, Nuisance = NULL, method = c("REML", "ML"), bounds = c(1e-09, 1e+09), SE = FALSE, return_Hinv = FALSE ) mixed_solve_cpp( Y, X = NULL, Z = NULL, K = NULL, Nuisance = NULL, method = c("REML", "ML"), bounds = c(1e-09, 1e+09), SE = FALSE, return_Hinv = FALSE )
Y |
Response vector or matrix. If a matrix, each column is treated as a separate response variable. |
X |
Design matrix for fixed effects. If NULL, defaults to intercept only. |
Z |
Design matrix for random effects. If NULL, defaults to identity matrix. |
K |
Kinship matrix for random effects. If NULL, defaults to identity matrix. |
Nuisance |
An alias for X, provided for consistency with lss/lsa interface. If both X and Nuisance are provided, X takes precedence. |
method |
Character string specifying the estimation method:
|
bounds |
Numeric vector of length 2 specifying bounds for variance component optimization. Defaults to c(1e-9, 1e9). |
SE |
Logical, whether to compute and return standard errors. Defaults to FALSE. |
return_Hinv |
Logical, whether to return the inverse of the H matrix. Defaults to FALSE. |
This function fits the mixed model: Y = Xbeta + Zu + error, where u ~ N(0, VuK) and error ~ N(0, VeI). The variance components Vu and Ve are estimated using REML or ML.
A list containing:
Vu |
Estimated variance component for random effects. |
Ve |
Estimated variance component for residuals. |
beta |
Estimated fixed effects coefficients. |
u |
Estimated random effects coefficients. |
LL |
Log-likelihood of the model. |
beta.SE |
Standard errors of fixed effects coefficients (if SE = TRUE). |
u.SE |
Standard errors of random effects coefficients (if SE = TRUE). |
Hinv |
Inverse of H matrix (if return_Hinv = TRUE). |
set.seed(123) n <- 100 Y <- rnorm(n) Z <- matrix(rnorm(n * 5), n, 5) K <- diag(5) X <- matrix(1, n, 1) result <- mixed_solve(Y, X, Z, K)set.seed(123) n <- 100 Y <- rnorm(n) Z <- matrix(rnorm(n * 5), n, 5) K <- diag(5) X <- matrix(1, n, 1) result <- mixed_solve(Y, X, Z, K)
An optimized implementation of mixed model estimation that precomputes expensive matrix operations and can be reused across multiple voxels for significant performance improvements.
mixed_solve_optimized( X, Z, Y, K = NULL, workspace = NULL, compute_se = FALSE, n_threads = 0 )mixed_solve_optimized( X, Z, Y, K = NULL, workspace = NULL, compute_se = FALSE, n_threads = 0 )
X |
Fixed effects design matrix (n × p) |
Z |
Random effects design matrix (n × q) |
Y |
Response data - can be a vector (single voxel) or matrix (n × V for multiple voxels) |
K |
Kinship/covariance matrix for random effects (q × q). Defaults to identity. |
workspace |
Precomputed workspace (optional, will compute if NULL) |
compute_se |
Whether to compute standard errors (default: FALSE) |
n_threads |
Number of OpenMP threads for multi-voxel (0 = auto) |
List with estimated parameters and variance components
set.seed(1) X <- matrix(1, 6, 1) Z <- diag(6) Y <- matrix(rnorm(12), 6, 2) ws <- mixed_precompute(X, Z) fit <- mixed_solve_optimized(X, Z, Y, workspace = ws) names(fit)set.seed(1) X <- matrix(1, 6, 1) Z <- diag(6) Y <- matrix(rnorm(12), 6, 2) ws <- mixed_precompute(X, Z) fit <- mixed_solve_optimized(X, Z, Y, workspace = ws) names(fit)
Convenience constructor for the oasis= list accepted by lss(method="oasis").
Unknown fields are rejected so misspelled scientific controls cannot be
silently ignored.
oasis_options( design_spec = NULL, K = NULL, ntrials = NULL, trial_basis_map = NULL, ridge_mode = c("fractional", "absolute"), ridge_x = 0.05, ridge_b = 0.05, block_cols = 4096L, return_se = FALSE, return_diag = FALSE, add_intercept = TRUE, hrf_mode = NULL, ... )oasis_options( design_spec = NULL, K = NULL, ntrials = NULL, trial_basis_map = NULL, ridge_mode = c("fractional", "absolute"), ridge_x = 0.05, ridge_b = 0.05, block_cols = 4096L, return_se = FALSE, return_diag = FALSE, add_intercept = TRUE, hrf_mode = NULL, ... )
design_spec |
Optional design spec list used to build |
K |
Optional basis dimension override. |
ntrials |
Number of trials for a raw multi-basis design. |
trial_basis_map |
For a raw multi-basis design, a data frame with one
row per |
ridge_mode |
|
ridge_x, ridge_b
|
Non-negative ridge penalties (defaults 0.05 each). |
block_cols |
Voxel block size for blocked products. |
return_se |
Logical; return standard errors. |
return_diag |
Logical; return diagnostics. |
add_intercept |
Logical; add intercept when |
hrf_mode |
Optional mode (e.g. |
... |
Certified advanced options: |
A list with class "fmrilss_oasis_options".
oasis_options(ridge_mode = "fractional", ridge_x = 0.1, ridge_b = 0.1)oasis_options(ridge_mode = "fractional", ridge_x = 0.1, ridge_b = 0.1)
Creates visualization comparing true vs recovered HRFs
plot_hrf_comparison(results, save_path = NULL)plot_hrf_comparison(results, save_path = NULL)
results |
Output from compare_hrf_recovery |
save_path |
Optional path to save plot |
A ggplot2 plot object. When save_path is supplied, the same plot
is also written to disk.
onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) plot_hrf_comparison(res)onsets <- generate_rapid_design(n_events = 4, total_time = 60, seed = 1) sim <- generate_lwu_data(onsets, total_time = 60, n_voxels = 2, seed = 1) grid <- create_lwu_grid(n_tau = 2, n_sigma = 2, n_rho = 2) res <- compare_hrf_recovery(sim, hrf_grid = grid) plot_hrf_comparison(res)
Convenience constructor for the prewhiten= list accepted by lss().
prewhiten_options( method = c("none", "ar", "arma"), p = "auto", q = 0L, p_max = 6L, pooling = c("global", "voxel", "run", "parcel"), runs = NULL, parcels = NULL, exact_first = c("ar1", "none"), compute_residuals = TRUE, design = NULL, acvf_correction = NULL, correction_max_lag = 25L, voxel_bins = 50L, residual_model = NULL )prewhiten_options( method = c("none", "ar", "arma"), p = "auto", q = 0L, p_max = 6L, pooling = c("global", "voxel", "run", "parcel"), runs = NULL, parcels = NULL, exact_first = c("ar1", "none"), compute_residuals = TRUE, design = NULL, acvf_correction = NULL, correction_max_lag = 25L, voxel_bins = 50L, residual_model = NULL )
method |
|
p |
AR order or |
q |
MA order for ARMA. |
p_max |
Maximum AR order when |
pooling |
|
runs |
Optional complete exact-integer run identifiers. Required for
|
parcels |
Optional complete exact-integer parcel identifiers. Required
for |
exact_first |
|
compute_residuals |
Logical. |
design |
Optional numeric design matrix whose projection produced the residuals used to estimate the noise model. Supplying it opts in to fmriAR's residual-autocovariance bias correction. It must represent the same column space as the design used for residualization. |
acvf_correction |
Optional bias-correction matrix, or list of matrices,
produced by |
correction_max_lag |
Positive integer lag budget used when |
voxel_bins |
Positive integer number of autocorrelation bins used by
|
residual_model |
Which model's OLS residuals the noise model is
estimated from. |
A list with class "fmrilss_prewhiten_options".
prewhiten_options(method = "ar", p = 1, pooling = "run", runs = rep(1:2, each = 50))prewhiten_options(method = "ar", p = 1, pooling = "run", runs = rep(1:2, each = 50))
Computes the orthogonal projection matrix Q = I - X(X'X)^(-1)X' that projects out the space spanned by confound regressors X. This is useful for advanced users who want to cache and reuse projection matrices.
project_confounds(X)project_confounds(X)
X |
Confound design matrix (n x p) where n is number of timepoints and p is number of confound regressors |
This function uses QR decomposition for numerical stability instead of computing the Moore-Penrose pseudoinverse directly. Only the estimated-rank QR basis is used, so redundant confound columns do not over-project. The resulting matrix Q can be applied to data to remove the influence of confound regressors.
Projection matrix Q (n x n) that projects out the column space of X
n <- 100 X_confounds <- cbind(1, 1:n) Y_raw <- matrix(rnorm(n * 3), n, 3) Q <- project_confounds(X_confounds) Y_clean <- Q %*% Y_rawn <- 100 X_confounds <- cbind(1, 1:n) Y_raw <- matrix(rnorm(n * 3), n, 3) Q <- project_confounds(X_confounds) Y_clean <- Q %*% Y_raw
Fast C++ implementation for projecting out confound variables from data and trial design matrices. This uses Cholesky decomposition for numerical stability and avoids creating large projection matrices.
project_confounds_cpp(X_confounds, Y_data, C_trials)project_confounds_cpp(X_confounds, Y_data, C_trials)
X_confounds |
Confound design matrix (n x k) |
Y_data |
Data matrix (n x V) where V is number of voxels |
C_trials |
Trial design matrix (n x T) where T is number of trials |
This function computes residuals Y - X(X'X)^(-1)X'Y and C - X(X'X)^(-1)X'C without explicitly forming the projection matrix Q = I - X(X'X)^(-1)X'. This approach uses ~100x less memory for large n and is numerically more stable.
List with projected data (residual_data) and projected trials (Q_dmat_ran)
n <- 100; V <- 50; T <- 10 X_confounds <- cbind(1, seq_len(n), matrix(rnorm(n * 3), n, 3)) Y_data <- matrix(rnorm(n*V), n, V) C_trials <- matrix(rnorm(n*T), n, T) result <- project_confounds_cpp(X_confounds, Y_data, C_trials)n <- 100; V <- 50; T <- 10 X_confounds <- cbind(1, seq_len(n), matrix(rnorm(n * 3), n, 3)) Y_data <- matrix(rnorm(n*V), n, V) C_trials <- matrix(rnorm(n*T), n, T) result <- project_confounds_cpp(X_confounds, Y_data, C_trials)
Learn a low-rank shared time basis from a parameterized HRF library using
fmrihrf::hrf_library(). The library is evaluated on the TR grid, optionally
baseline-removed and L2-normalized, and decomposed via SVD into
B = U_r (shared basis), singular values S, and library coordinates
A = diag(S) %*% t(V_r).
sbhm_build( library_spec = NULL, library_H = NULL, r = 6, sframe = NULL, tgrid = NULL, span = 32, normalize = TRUE, baseline = c(0, 0.5), shifts = NULL, ref = c("mean", "spmg1") )sbhm_build( library_spec = NULL, library_H = NULL, r = 6, sframe = NULL, tgrid = NULL, span = 32, normalize = TRUE, baseline = c(0, 0.5), shifts = NULL, ref = c("mean", "spmg1") )
library_spec |
Either NULL (when
|
library_H |
Optional precomputed TxK matrix of candidate HRFs, already
aligned to the TR grid |
r |
Positive integer target rank for the shared basis (default 6). Requests above the numerical library rank are clipped with a warning. |
sframe |
Optional |
tgrid |
Optional numeric vector of global times (in seconds). If provided,
takes precedence over |
span |
HRF span in seconds (default 32). Used for reference HRF when needed. |
normalize |
Logical, L2-normalize library columns (default TRUE). |
baseline |
Numeric length-2 vector specifying a time window (in seconds) used for baseline removal (column-wise mean subtraction within this window). Set to NULL to skip. |
shifts |
Optional numeric vector of time shifts (in seconds). When provided,
shifted copies of the library are added by linear interpolation on |
ref |
Reference for coefficient-space shrinkage and orientation.
One of |
A list with components:
B (Txr): shared orthonormal time basis
S (length r): singular values
A (rxK): coordinates of library HRFs in the shared basis
tgrid: the global time grid used (seconds)
span: span used for reference HRF
ref: list with alpha_ref (length r) and name
meta: rank, normalization, candidate identity, full singular values,
numerical rank, and retained total-energy fraction
library(fmrihrf) param_grid <- expand.grid(shape = c(6, 8, 10), rate = c(0.9, 1.0, 1.1)) gamma_fun <- function(shape, rate) fmrihrf::as_hrf( fmrihrf::hrf_gamma, params = list(shape = shape, rate = rate) ) sframe <- fmrihrf::sampling_frame(blocklens = 200, TR = 1) sbhm <- sbhm_build( library_spec = list(fun = gamma_fun, pgrid = param_grid, span = 32), r = 6, sframe = sframe, baseline = c(0, 0.5) ) hrf_B <- sbhm_hrf(sbhm$B, sbhm$tgrid, sbhm$span)library(fmrihrf) param_grid <- expand.grid(shape = c(6, 8, 10), rate = c(0.9, 1.0, 1.1)) gamma_fun <- function(shape, rate) fmrihrf::as_hrf( fmrihrf::hrf_gamma, params = list(shape = shape, rate = rate) ) sframe <- fmrihrf::sampling_frame(blocklens = 200, TR = 1) sbhm <- sbhm_build( library_spec = list(fun = gamma_fun, pgrid = param_grid, span = 32), r = 6, sframe = sframe, baseline = c(0, 0.5) ) hrf_B <- sbhm_hrf(sbhm$B, sbhm$tgrid, sbhm$span)
Convert a shared time basis matrix B (T×r) into an fmrihrf::HRF object
so it can be used directly in OASIS design construction. The HRF returns the
r basis columns evaluated at arbitrary times by piecewise-linear
interpolation on the provided time grid.
sbhm_hrf(B, tgrid, span)sbhm_hrf(B, tgrid, span)
B |
A numeric matrix (T×r) with orthonormal columns (shared basis). |
tgrid |
Numeric vector of length T giving the global times (seconds)
corresponding to the rows of |
span |
Numeric HRF span passed to |
An fmrihrf::HRF object with nbasis = ncol(B).
tgrid <- 0:30 candidates <- cbind(exp(-tgrid / 5), tgrid * exp(-tgrid / 5)) B <- qr.Q(qr(candidates)) hrf_B <- sbhm_hrf(B, tgrid, span = 30) fmrihrf::nbasis(hrf_B)tgrid <- 0:30 candidates <- cbind(exp(-tgrid / 5), tgrid * exp(-tgrid / 5)) B <- qr.Q(qr(candidates)) hrf_B <- sbhm_hrf(B, tgrid, span = 30) fmrihrf::nbasis(hrf_B)
Given per-voxel aggregate coefficients beta_bar in the shared basis B,
and library coordinates A, perform shape-only matching in a whitened,
L2-normalized coefficient space (cosine similarity). Optionally apply a
simple shrinkage of beta_bar towards a reference coordinate before
matching, and an orientation fix.
sbhm_match( beta_bar, S, A, shrink = list(tau = 0, ref = NULL, snr = NULL), topK = 1, whiten = TRUE, sv_floor_rel = 1e-06, whiten_power = 1, orient_ref = TRUE )sbhm_match( beta_bar, S, A, shrink = list(tau = 0, ref = NULL, snr = NULL), topK = 1, whiten = TRUE, sv_floor_rel = 1e-06, whiten_power = 1, orient_ref = TRUE )
beta_bar |
Numeric matrix (r×V): per-voxel coefficients from a prepass GLM in the SBHM basis. Columns correspond to voxels. |
S |
Numeric vector (length r): singular values of the library SVD. |
A |
Numeric matrix (r×K): coordinates of library HRFs in SBHM basis. |
shrink |
List with optional shrinkage options:
|
topK |
Positive integer no larger than the library size; return top-K scores/weights if greater than one (default 1). |
whiten |
Logical, divide coefficients by S before normalization (default TRUE). |
sv_floor_rel |
Relative singular-value floor used when |
whiten_power |
Numeric in |
orient_ref |
Logical, flip beta_bar columns when their dot with |
A list with:
idx length-V integer indices of best-matching library HRF (1..K)
margin named length-V numeric score difference: top1 - top2. It is not
a calibrated confidence measure.
alpha_hat r×V matrix: the selected library coordinates (unwhitened, unnormalized)
scores optional K×V cosine score matrix (returned when topK > 1)
weights optional cosine-softmax top-K weights per voxel (when topK > 1)
Non-finite inputs, zero-norm voxel summaries or library candidates, and malformed matching controls fail explicitly. Named basis axes must identify the same complete set and are aligned before matching.
set.seed(42) r <- 4; K <- 12; V <- 3 A <- matrix(rnorm(r*K), r, K) S <- seq(1, 0.2, length.out = r) alpha2 <- A[,2] beta_bar <- cbind(alpha2 + rnorm(r, sd = 0.1), A[,7] + rnorm(r, sd = 0.1), A[,10] + rnorm(r, sd = 0.1)) m <- sbhm_match(beta_bar, S, A) m$idxset.seed(42) r <- 4; K <- 12; V <- 3 A <- matrix(rnorm(r*K), r, K) S <- seq(1, 0.2, length.out = r) alpha2 <- A[,2] beta_bar <- cbind(alpha2 + rnorm(r, sd = 0.1), A[,7] + rnorm(r, sd = 0.1), A[,10] + rnorm(r, sd = 0.1)) m <- sbhm_match(beta_bar, S, A) m$idx
Fit the trial-aggregated SBHM basis to every voxel after projecting run-specific intercepts, supplied nuisance columns, and complete modeled other-condition spans. The same run-safe design builder is used by the full SBHM pipeline.
sbhm_prepass( Y, sbhm, design_spec, Nuisance = NULL, prewhiten = NULL, ridge = list(mode = "fractional", lambda = 0.01, alpha_ref = NULL), data_fac = NULL )sbhm_prepass( Y, sbhm, design_spec, Nuisance = NULL, prewhiten = NULL, ridge = list(mode = "fractional", lambda = 0.01, alpha_ref = NULL), data_fac = NULL )
Y |
Finite numeric T by V response matrix. |
sbhm |
Object returned by |
design_spec |
Event specification with |
Nuisance |
Optional T by P nuisance matrix. |
prewhiten |
Optional prewhitening options passed to the package
prewhitening layer. Run labels are inferred from |
ridge |
List with |
data_fac |
Optional exact or approximate factorization with |
A list containing named beta_bar (rank by voxel), the residualized
aggregate design A_agg, its Gram matrix G, and design diagnostics and
identity maps. Active whitening is recorded in the whiten_plan attribute.
times <- seq(0, 30, by = 0.5) H <- cbind(stats::dgamma(times, 5, 1), stats::dgamma(times, 7, 1)) basis <- sbhm_build(library_H = H, tgrid = times, span = 30, r = 2) spec <- list(sframe = fmrihrf::sampling_frame(80L, TR = 1), cond = list(onsets = c(5, 20, 35, 50), duration = 0)) set.seed(1) pre <- sbhm_prepass(matrix(rnorm(80 * 3), 80, 3), basis, spec) dim(pre$beta_bar)times <- seq(0, 30, by = 0.5) H <- cbind(stats::dgamma(times, 5, 1), stats::dgamma(times, 7, 1)) basis <- sbhm_build(library_H = H, tgrid = times, span = 30, r = 2) spec <- list(sframe = fmrihrf::sampling_frame(80L, TR = 1), cond = list(onsets = c(5, 20, 35, 50), duration = 0)) set.seed(1) pre <- sbhm_prepass(matrix(rnorm(80 * 3), 80, 3), basis, spec) dim(pre$beta_bar)
Given trial-wise coefficients in the shared basis (rxntrialsxV) and the
voxel-specific matched library coordinates alpha_hat (rxV), compute scalar
amplitudes per trial and voxel via least-squares projection:
a = (alpha' beta) / (alpha' alpha).
sbhm_project(beta_rt, alpha_hat)sbhm_project(beta_rt, alpha_hat)
beta_rt |
3D array of shape r x ntrials x V containing per-trial coefficients in the SBHM basis (as returned by OASIS with K=r, reshaped). |
alpha_hat |
Numeric matrix rxV of matched library coordinates per voxel
(e.g., |
Numeric matrix ntrials x V of scalar amplitudes.
Zero-norm or non-finite coordinates are unidentified and fail explicitly.
When basis or voxel names are present, both inputs must provide the same
complete unique identities; alpha_hat is reordered to beta_rt.
set.seed(1) r <- 2; ntrials <- 3; nvox <- 2 alpha_hat <- matrix(rnorm(r * nvox), r, nvox) beta_rt <- array(rnorm(r * ntrials * nvox), c(r, ntrials, nvox)) amps <- sbhm_project(beta_rt, alpha_hat) dim(amps)set.seed(1) r <- 2; ntrials <- 3; nvox <- 2 alpha_hat <- matrix(rnorm(r * nvox), r, nvox) beta_rt <- array(rnorm(r * ntrials * nvox), c(r, ntrials, nvox)) amps <- sbhm_project(beta_rt, alpha_hat) dim(amps)
Convenience constructor for the stglmnet= list accepted by
lss(method = "stglmnet").
Advanced fields accepted through ... are validated; unknown fields are
rejected so misspelled scientific controls cannot be silently ignored.
stglmnet_options( mode = c("cv", "fixed"), alpha = 0.2, lambda = NULL, overlap_strategy = c("none", "multiplicative", "additive", "hybrid", "threshold"), pool_to_mean = FALSE, pool_strength = 1, pool_mean_penalty = 0, whiten = c("inherit", "auto", "never", "always"), cv_folds = 5L, cv_type.measure = c("auto", "mse", "correlation", "reliability", "composite"), cv_select = c("optimal", "1se"), return_fit = FALSE, ... )stglmnet_options( mode = c("cv", "fixed"), alpha = 0.2, lambda = NULL, overlap_strategy = c("none", "multiplicative", "additive", "hybrid", "threshold"), pool_to_mean = FALSE, pool_strength = 1, pool_mean_penalty = 0, whiten = c("inherit", "auto", "never", "always"), cv_folds = 5L, cv_type.measure = c("auto", "mse", "correlation", "reliability", "composite"), cv_select = c("optimal", "1se"), return_fit = FALSE, ... )
mode |
|
alpha |
Elastic-net mixing parameter passed to |
lambda |
Optional lambda sequence (or scalar in fixed mode), in the
original response coordinates used by |
overlap_strategy |
Trial-overlap penalty mapping. One of |
pool_to_mean |
Logical; reparameterize trial effects into a pooled mean plus orthogonal contrasts. |
pool_strength |
Penalty multiplier applied to pooled contrasts. |
pool_mean_penalty |
Penalty applied to the pooled mean coefficient. |
whiten |
One of |
cv_folds |
Number of folds used when |
cv_type.measure |
Cross-validation objective. |
cv_select |
Lambda selection rule in CV mode. |
return_fit |
Logical; when |
... |
Certified advanced backend fields such as graph-pooling, overlap-strength, fold, and whitening controls. Unknown names are rejected. |
The internal cross-validation scores tune the estimator. They use a shared response scale and full-data nuisance projection; reliability also compares fold estimates with the full-data fit. These scores are not an unbiased held-out assessment of predictive performance.
A list with class "fmrilss_stglmnet_options".
stglmnet_options(mode = "fixed", lambda = 0.05, alpha = 0.5)stglmnet_options(mode = "fixed", lambda = 0.05, alpha = 0.5)
Simple list-based S3 class returned by estimate_voxel_hrf containing
voxel-wise HRF basis coefficients and related metadata.
No value itself. This topic documents the structure returned by
estimate_voxel_hrf().
coefficients contains one normalized HRF-shape column per voxel;
amplitude_scale records the removed positive-peak scale; degenerate
flags voxels whose shape has no positive peak of at least 5% of its largest
absolute deflection (scaled by that deflection instead); basis stores the
HRF basis; conditions records observed labels while condition_pooling
states that all events estimate one pooled shape; sframe preserves physical scan
timing; and normalization plus coefficient_units state the coefficient
scale. Voxel names, when supplied on Y, identify coefficient columns and
amplitude-scale entries.
Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) class(est)Y <- matrix(rnorm(100), 50, 2) events <- data.frame(onset = c(5, 25), duration = 1, condition = "A") basis <- fmrihrf::HRF_SPMG1 sframe <- fmrihrf::sampling_frame(blocklens = nrow(Y), TR = 1) est <- estimate_voxel_hrf(Y, events, basis, sframe = sframe) class(est)