Changelog
Source:NEWS.md
fmrireg 0.2.0
Datasets are now fmri_frame objects
fmrireghas moved from the removed legacy dataset API offmridataset(matrix_dataset(),fmri_mem_dataset(),fmri_dataset(),latent_dataset(),get_data_matrix(),get_mask(),data_chunks(),read_fmri_config(), …) to its canonical frame API. Every function that takes adataset(fmri_lm(),fmri_rlm(),fmri_model(),create_fmri_model(),estimate_betas(),estimate_hrf(),fmri_latent_lm(),glm_ols(),glm_lss(),extract_nuisance_timeseries(),build_model(), and the engine plugin API) now requires afmridataset::fmri_frame(or a lazyfmri_viewof one). The old dataset classes are no longer accepted, andfmridataset (>= 0.10.0.9000)is required.-
Four thin constructors build a frame from the inputs
fmriregusers typically hold; each stores the run structure asrun_id/TRobservation columns, an optional logicalcensorcolumn, and the event table as a keyedfmridataset::event_table():-
matrix_frame(datamat, TR, run_length, event_table, censor)for a time-by-feature matrix (anindex_space; replacesmatrix_dataset()). -
neurovec_frame(scans, mask, TR, ...)for in-memoryNeuroVecruns (avolume_spaceon the mask; replacesfmri_mem_dataset()). -
nifti_frame(scans, mask, TR, ...)for NIfTI files, lazily read throughfmridataset::nifti_array_source()(replacesfmri_dataset()). -
latent_frame(x, TR, run_length, ...)for afmristore::LatentNeuroVec: component scores become the assay and the loadings a synthesis-onlybasis_space(replaceslatent_dataset()). For fMRIPrep derivatives usefmridataset::read_bids_bold()directly.
-
Reading a frame back uses the
fmridatasetAPI instead of list fields:fmridataset::as_sampling_frame(x)replacesx$sampling_frame,fmridataset::temporal_schema(x)exposes run lengths, TR, and censoring,fmridataset::collect_assay(x)replacesget_data_matrix(), the event table isfmridataset::event_data(x$tables$events), and the spatial reference is recovered fromfmridataset::space(x). Censoring forar_options = list(censor = "auto")is read from the frame’scensorcolumn.estimate_betas()has a singlefmri_framemethod. Frames on avolume_spacereturnNeuroVecbetas as the volumetric method did; frames on anindex_spaceorbasis_spacereturn coefficient matrices as the matrix and latent methods did. The experimentalprewhitenargument of the latent method, which never ran, has been dropped.dataset_spec()/instantiate()/realize_dataset()name the new constructors: file-backed bindings produce"nifti_frame"specs and inline matrices produce"matrix_frame"specs; the constructor allowlist ismatrix_frame,nifti_frame,neurovec_frame, andlatent_frame.simulate_fmri_matrix()$time_seriesis now anfmri_frame.Engine capability
forbid_by_cluster_dataset_classesis matched against the dataset’s class or its feature-space class ("basis_space"marks latent frames).read_fmri_config()is gone with the legacy API; nothing infmriregused it outside commented-out tests, so it was not ported.Fixed the
unused variable 'P'compiler warning inols_t_cpp()(src/ols_t.cpp).Internally, chunkwise and runwise fitting iterate over lazy frame views (
x[rows, ],x[, cols]+collect_assay()) instead ofdata_chunks()/exec_strategy(), with the same chunk partition, so fitted numbers are unchanged.
HRF Estimation
-
estimate_hrf()is now a vectorized, condition-level smooth FIR estimator. It constructs an explicit event-aligned spline basis, removes baseline and fixed nuisance designs once, fits all voxels with one penalized multiresponse solve, and can choose a shared smoothing strength by scale-normalized GCV. The newfmri_hrf_estimateresult preserves curve and voxel labels and provides standard errors, confidence intervals,tidy(),predict(),coef(), andas.matrix()methods. This replaces a voxel-by-voxel GAM path that treated convolved design values as if they were post-stimulus time and failed before prediction.
Statistical Corrections
Explicit volume weights.
weights_spec(values = )now enables weighting without requiring a construction method, and joint IID OLS applies the same weights to its shared design and every response chunk. Zero-weight volumes are excluded from residual degrees of freedom. Reference backends and joint AR/robust combinations that cannot apply requested volume weights now fail instead of fitting an unweighted model.-
AR degrees of freedom.
fmri_lm()no longer deflates the residual degrees of freedom when an AR structure is used. The previous adjustment multipliedn - pby1 / (1 + 2 * sum(1 - k/n)), which is the variance inflation one would obtain if every autocorrelation equalled 1. It never depended on the fitted AR coefficients — atn = 200,p = 12, AR(1) it returned 62.9 whether the true autocorrelation was 0.05 or 0.9 — and it was applied on top of prewhitening, double-counting a correction already made.This changes reported AR p-values. They become less conservative; degrees of freedom roughly triple for AR(1). The previous behaviour cost statistical power rather than inflating false positives, so results that were significant before remain significant, but p-values and any power-sensitive analyses will differ from earlier versions.
calculate_effective_df()gains a workingmethod = "satterthwaite"(previously a verbatim duplicate ofmethod = "simple") that computestr(RV)^2 / tr(RVRV)from a supplied design and post-whitening covariance, reducing exactly ton - pwhen the errors are uncorrelated. Residual correlation surviving the filter is what legitimately costs degrees of freedom; AR order by itself does not.
Bug Fixes
The
fmridatasetrequirement is now(>= 0.11.0.9000). The pre-frame API was removed upstream without a version change, so both sides of the break reported0.10.0.9000and the previous constraint could not tell them apart: a stale install resolved cleanly and then failed at lazy loading withobject 'as.matrix_dataset' is not exported by 'namespace:fmridataset'. fmridataset bumped to0.11.0.9000for this purpose (bbuchsbaum/fmridataset#92), so the mismatch is now caught at dependency resolution with a message that names the real problem.The test suite has been ported to the frame API. The frame migration and a parallel test-coverage branch were developed against the same base and merged independently, so the merged tree combined frame-only package code with ~50 test files still building legacy
matrix_dataset(),fmri_mem_dataset(), andlatent_dataset()fixtures.R CMD checkfailed on every platform with 106 test failures that neither branch saw on its own. Fixtures now usematrix_frame(),neurovec_frame(), andlatent_frame(); no assertion was relaxed to accommodate the port.chunkwise_lm()dispatches onfmri_framefrom outside the package.chunkwise_lm.fmri_frame()was never registered as an S3 method, so dispatch from outside the namespace failed with “no applicable method” even though internal calls resolved..rrr_extract_response_matrix()validates its input again. A non-frame argument fell through tocollect_assay()and failed insidefmridatasetwith a message naming neither the argument nor the caller.extract_censor_from_dataset()no longer errors on a non-frame dataset. It now reports “no censoring” instead, which matters because censoring is consulted on every fit under the default change below.A censor column carried by a dataset is now used by default.
fmri_lm()previously consulted it only whenar_options = list(censor = "auto")was passed as well, so amatrix_frame(censor = )(and, before it,fmri_dataset(censor = )) was silently discarded: fits on a censored frame were bit-identical to fits on an uncensored one, with no warning. An unsetcensornow resolves against the dataset, which is what"auto"asked for explicitly; passcensor = "none"to ignore a censor column deliberately. This changes results for anyone who set censoring on a dataset and did not opt in, since those fits were not censored at all. An explicitcensorvector inar_optionsstill overrides the dataset.fmri_lm()now warns when censoring cannot affect the fit. Censoring feeds AR estimation and whitening only and never drops volumes from the regression, so under the default iid noise model it does nothing at all. That was previously silent, and indistinguishable from censoring having been applied.Shared AR estimation now pools voxel residual autocovariances by default instead of fitting the cross-voxel mean residual series. The former targets a typical voxel covariance; the latter suppresses voxel-specific noise and targets a different, often more autocorrelated coherent component. The old behavior remains available explicitly as
noise_spec(..., shared_estimator = "mean_series"). Fixed-order fits now use fmriAR’s pooled AR autocovariance path as well; the previous ARMA(p, 0) shortcut accidentally collapsed both choices to the mean series.shared_estimator = "mean_series"now retains the matching OLS design-bias correction. OLS projection is linear across response columns, so averaging residuals after projection is exactly the same as projecting the mean series.The OLS residual-bias correction now uses a separate covariance-tail lag budget rather than stopping at the fitted AR order. Projection mixes tail autocovariances into lags 0:p, so a p-lag correction left avoidable bias.
Global AR estimation over separately fitted runs now supplies the exact block-diagonal residual-forming design. Row-binding the per-run designs incorrectly described a single coefficient vector shared across runs.
Design-corrected AR coefficients are estimated once from initial OLS residuals and held fixed for the GLS solve. Later GLS residuals have a different residual-forming operator and no longer reuse the OLS
design=correction. The low-rank engine now follows this contract too, and the low-rank and RRR whitening paths reset at run boundaries.GDS exports now release unreachable HDF5 handles before atomic finalization, allowing Windows to move and immediately reopen the completed file.
write_results(strategy = "by_stat")no longer scatters contrast maps onto the wrong voxels..compute_statistical_volumes()calledas.logical()on the mask, which dropsdimfor plain arrays (the type.fmri_dataset_mask_space()always supplies), sowhich(..., arr.ind = TRUE)returned linear indices instead of 3D coordinates.Oversized residual-bootstrap blocks now remain one contiguous temporal block instead of silently becoming interleaved odd/even samples.
Mixed-model solver failures now warn and return
NAcoefficients rather than plausible zeros; malformed response dimensions fail before solver dispatch.robust_psinow has an effect. It was previously unreachable:robustdefaulted toFALSErather thanNULL, sorobust_options$typewas always claimed beforerobust_psiwas consulted.robust’s default is nowNULL, which also makes “unspecified” distinguishable from “explicitly off”.An explicit
robust = FALSEis no longer silently overridden byrobust_options = list(type = "huber"); the combination is now an error.Supplying
cfgalongside other configuration arguments is now an error instead of silently discarding them. Previouslycor_struct = "ar2"with acfgnaming"iid"ran IID without warning.AR shorthands (
cor_struct,cor_iter,cor_global,ar1_exact_first,ar_p,ar_voxelwise) that disagree with the correspondingar_optionsentry now error rather than being silently dropped. Shorthands that agree are still accepted.enginenow warns about the execution arguments it ignores (strategy,nchunks,use_fast_path,progress,parallel_voxels,parallel_chunks), which were previously accepted and silently discarded.compute_sandwich_variance()computed its meat matrix asX' diag(e^4) Xinstead ofX' diag(e^2) X, giving standard errors about 3.5x too large. Both sandwich helpers are now checked againstsandwich::vcovHC().
Performance
- Voxelwise AR fitting no longer recomputes the run-level design projection for every voxel.
.fast_preproject()performs ann x nsolve and was being called once per voxel on a design that does not vary by voxel; hoisting it gives roughly a 1.7x speedup on the voxelwise AR path (4.95s to 2.98s for 2000 voxels at 300 timepoints).
Documentation
-
ar_voxelwisewas documented as overridingar_options$voxelwise. It never did — the options list took precedence — and disagreement is now an error. -
use_fast_pathwas documented as defaulting toFALSEin the runwise and chunkwise fitters; it defaults toTRUE. - Corrected claims that sandwich variance estimation is applied automatically. It is not used by any
fmri_lm()fitting path; the robust path reports a model-based weighted-least-squares variance. The accompanying degrees-of- freedom formula was also wrong and has been rewritten.
Changes
-
fmriARis now declared with a minimum version (>= 0.3.3) and listed inRemotes:; it previously had neither.
New Features
Reexported
feature()(from fmridesign) andfeature_regressor()(from fmrihrf) so mixed event formulas such asonset ~ hrf(condition) + feature(rms, dt = 0.1)work afterlibrary(fmrireg). Feature terms are sampled series, not trials; helpers that assumehrfspectrial structure skip or unwrap them instead of erroring (fitted_hrf(),shortnames(),design_plot(),preflight(), pair-contrast resolution).write_results()now exports statistical maps as NIfTI volumes viaformat = "nifti"(orformat = c("h5", "nifti")), reusing the same BIDS entity and filename machinery as the HDF5 backend. This removes the need to hand-roll loops overcoef_image()+neuroim2::write_vol().Added
coef_images(), a plural companion tocoef_image()that returns a named list ofNeuroVols for every coefficient of a giventype("estimates","contrasts", or"F") andstatistic.
Bug Fixes
-
write_results()now labels F-contrast maps asfstat/fpvalinstead of reusing thetstat/pvallabels. F statistics follow an F distribution (not Student’s t), so mixing them under atstatlabel produced mislabeled maps and, understrategy = "by_stat", could collide t- and F-contrast outputs in a single file. t- and F-contrasts are now written to separate, correctly labeled files.
fmrireg 0.1.2
Changes
- Moved PDF report generation (
report.fmri_lm) to the standalonefmrireportpackage. Usefmrireport::report()for report rendering.
Bug Fixes
- Fixed low-rank/sketch residual degrees of freedom to use original timepoints (
T - p) instead of sketch rows. - Replaced placeholder
fit_contrasts.fmri_lm()outputs with computed standard errors, test statistics, and p-values. - Consolidated duplicate meta-analysis pathways to shared implementations and removed inconsistent behavior across runwise/chunkwise pooling.
- Fixed F-contrast meta pooling to combine evidence via p-values rather than averaging F statistics.
- Fixed thread-safety issue in C++ meta kernels by deferring Paule-Mandel non-convergence warnings until after OpenMP regions.
- Added bounds validation and safer inverse fallback in sketch kernels to prevent unsafe indexing and hard failures on ill-conditioned data.
- Improved AR pipeline consistency for whitened covariance/fitted/residual handling and unified AR effective-df calculations.
- Added regression coverage for
fit_contrasts.fmri_lm()to prevent placeholder-stat regressions.
fmrireg 0.1.1
Bug Fixes
- Fixed
glm_lss()tests that incorrectly expected specific Cholesky decomposition errors. - Fixed convolution test with non-strictly-increasing onsets within blocks.
- Fixed testthat API usage (
expect_lt/expect_gtno longer use deprecatedinfoargument). - Fixed
latent_datasetAPI usage: now usesget_latent_scores()instead of deprecatedget_data(). - Changed
glm_lss()use_cppparameter default fromTRUEtoFALSE(C++ implementation retired; fmrilss package now used). - Added
fmrireg.suppress_deprecationoption check to all deprecated functions for cleaner test output. - Added
tests/testthat/setup.Rto suppress expected deprecation warnings during testing. - Suppressed expected kmeans convergence warnings in landmark SRHT tests.
fmrireg 0.1.0
Breaking Changes
-
Design Matrix Column Naming: The naming scheme for columns in design matrices generated by
event_model()has been completely revised for consistency and clarity.- All column names now strictly follow the format:
term_tag+_+condition_tag+ [_b##basis suffix]. -
term_tag: Automatically generated from variable names (e.g.,var1_var2) or user-providedid=inhrf(), sanitized (dots become underscores), and made unique with#suffix if needed (e.g.,cond,cond#1). -
condition_tag: Represents factor levels (e.g.,Factor.Level), continuous basis columns (e.g.,poly_RT_01,z_RT), or interactions joined by_(e.g.,Factor.Level_poly_RT_01). -
_b##: Optional suffix added only when the HRF has multiple basis functions (e.g.,_b01,_b02). - The previous
styleargument ("compact","qualified","uid") indesign_matrix()is removed. Only the single canonical format is produced. - Scripts or analyses that relied on matching previous column name formats (e.g., using
Var[Level],Var:Level,:basis[]) will need to be updated to use the newterm_tag_Condition.Tag_b##format.
- All column names now strictly follow the format:
Major Changes
-
Regressor System Refactoring:
- Introduced a new internal S3 class
Regfor representing regressors. - The main
regressor()function now usesReginternally but maintains backward compatibility (returns classc("regressor", "Reg", "list")). - Deprecated
single_trial_regressor()andnull_regressor()in favour of usingregressor()directly. - Unified regressor evaluation under the
evaluate.RegS3 method, supporting different calculation methods (“fft”, “conv”, “loop”, “Rconv”). - Evaluation methods now consistently use the refactored
evaluate.HRFfor HRF sampling. - Refactored C++ evaluation code into a single wrapper (
evaluate_regressor_cpp). - Removed redundant internal helper functions (
fastevalreg,fastevalreg2,conform_len,dots). - Implemented
autoplot.Reg(ggplot2) andprint.Reg(cli) methods, deprecating olderplot.regressorandprint.regressor. - Improved input validation and recycling for
regressor()arguments usingvctrs. - Added optional sparse matrix output to
evaluate.Reg. - Added memoization for HRF sampling within evaluation.
- Introduced a new internal S3 class