API Reference

This section documents the most frequently used modules. All classes operate on numpy arrays or pandas DataFrame inputs and run TensorFlow under the hood.

Encoding models

class braincoder.models.EncodingModel(paradigm=None, data=None, parameters=None, weights=None, omega=None, verbosity=20)

Bases: object

Abstract base class for encoding models.

get_paradigm(paradigm)

Return the cleaned paradigm DataFrame, falling back to stored one.

get_parameter_labels()

Return the ordered list of parameter labels used by the model.

get_stimulus_pdf(data, stimulus_range, parameters=None, weights=None, omega=None, dof=None, normalize=True, include_multidimensional_stimulus_index=False, noise_model=None, temperature=None)

Evaluate posterior over stimuli for each time point given data.

Parameters:
  • data (DataFrame or array (n_timepoints, n_voxels))

  • stimulus_range (array (n_stimuli,) or (n_stimuli, n_dims)) – Grid of candidate stimuli (flat prior over the grid).

  • omega (array, float, optional) – Dense noise covariance and Student-t degrees of freedom, e.g. from ResidualFitter.fit. Without omega, both come from the model (model.omega, model.dof). With omega, dof=None means Gaussian. Ignored when noise_model is given.

  • dof (array, float, optional) – Dense noise covariance and Student-t degrees of freedom, e.g. from ResidualFitter.fit. Without omega, both come from the model (model.omega, model.dof). With omega, dof=None means Gaussian. Ignored when noise_model is given.

  • noise_model (braincoder.noise.LowRankNoise, optional) – Factored noise model (low-rank + diagonal Ω, Gaussian or Student-t). The likelihood is evaluated with the Woodbury identity (no dense V × V matrix). Its temperature is applied unless temperature is passed explicitly.

  • temperature (float, optional) – Likelihood temperature T: posterior ∝ likelihood^(1/T). Default None = 1 (or the noise model’s own temperature). np.inf gives a flat posterior.

predict(paradigm=None, parameters=None, weights=None)

Return pandas predictions for the provided paradigm/parameters.

predict_stimulus_grid(stimulus_range, parameters=None, weights=None)

Predicted response to every stimulus of a decoding grid.

Returns a float64 array (n_stimuli, n_voxels). Uses the same batching as get_stimulus_pdf() (one batch per grid point).

simulate(paradigm=None, parameters=None, weights=None, noise=1.0, dof=None, n_repeats=1)

Generate synthetic data by adding noise to predictions.

class braincoder.models.LinearModelWithBaseline(paradigm=None, data=None, parameters=None, weights=None, omega=None, verbosity=20)

Bases: EncodingModel

Linear encoding model that adds a voxel-specific baseline parameter.

class braincoder.models.LinearModelWithBaselineHRF(paradigm=None, data=None, parameters=None, weights=None, hrf_model=None, verbosity=20, **kwargs)

Bases: LinearModelWithBaseline, HRFEncodingModel

LinearModelWithBaseline variant that automatically applies an HRF convolution.

HRF utilities

class braincoder.hrf.HRFModel(unique_hrfs=False)

Bases: object

Base class that handles HRF generation and convolution with predictions.

class braincoder.hrf.SPMHRFModel(tr, unique_hrfs=False, highres_dt=0.1, time_length=32.0, onset=0.0, delay=6.0, undershoot=16.0, dispersion=1.0, u_dispersion=1.0, ratio=0.167, min_hrf_delay=3.0, max_hrf_delay=7.0, min_dispersion=0.3, max_dispersion=2.0)

Bases: HRFModel

Canonical SPM-style HRF parameterized by delay/dispersion bounds.

braincoder.hrf.spm_hrf(t, a1=6.0, d1=1.0, a2=16.0, d2=1.0, c=0.16666666666666666, highres_dt=0.1)

Compute SPM canonical HRF(s).

Optimization helpers

class braincoder.optimize.WeightFitter(model, parameters, data, paradigm)

Bases: object

Closed-form solver for voxel weights given fixed parameters.

Uses least-squares to compute weights that best map basis predictions to measured data, optionally with L2 regularization via alpha.

fit(alpha=0.0, fit_intercept=False)

Solve for weights using least squares with optional L2 regularization.

fit_intercept=True prepends a column of ones to the basis design matrix and solves for a per-voxel intercept jointly with the weights, in the same closed-form regression. This is the general fix for basis models (e.g. LinearModelWithBaseline, or any model whose _basis_predictions ignores parameters) that declare a baseline/offset without folding it into the fit: without an intercept term, WeightFitter regresses data on the raw basis through the origin, which only recovers the correct slope when the basis already happens to be mean-zero — otherwise the fitted weight is biased by whatever DC level the basis has. Returns (weights, intercept) instead of plain weights when enabled. The intercept column is excluded from the L2 penalty when alpha>0 (standard ridge convention — the offset isn’t shrunk).

Mutually exclusive with the LinearModelWithBaseline fixed-known- baseline path below (that path already subtracts a supplied parameters['baseline'] from the data before the regression; jointly fitting an intercept on top of an already-baseline-removed target is redundant / ill-posed). Pass parameters=None when constructing the fitter if you want fit_intercept to estimate the baseline itself instead of supplying one.

class braincoder.optimize.ParameterFitter(model, data, paradigm, memory_limit=666666666, log_dir=False)

Bases: object

Iterative optimizer that estimates model parameters for each voxel.

Single trainable (n_voxels, n_pars) keras Variable; only the masked positions (meaningful voxels × non-fixed params) receive gradients. Shared parameters are realized by a smaller Variable that’s broadcast back across voxels via a selector matrix.

Parameters:
  • model (EncodingModel)

  • data (pd.DataFrame) – Timeseries, shape (n_timepoints, n_voxels).

  • paradigm (array-like) – Stimulus paradigm; passed through model.get_paradigm.

  • memory_limit (int) – Soft budget for the grid-fit chunk size.

  • log_dir (str | None | False) – TensorBoard log dir. False (default) disables logging.

fit(max_n_iterations=1000, min_n_iterations=100, init_pars=None, confounds=None, optimizer=None, fixed_pars=None, shared_pars=None, noise_model='ssq', store_intermediate_parameters=False, r2_atol=1e-06, lag=100, learning_rate=0.01, progressbar=True, **kwargs)

Estimate per-voxel parameters by iterative optimization.

Parameters:
  • noise_model ({‘ssq’, ‘gaussian’}, default 'ssq') – Loss function. 'ssq' minimizes the unweighted sum-of-squared-residuals (MLE under homoscedastic Gaussian noise). 'gaussian' minimizes a per-voxel Gaussian negative log-likelihood with a free per-voxel σ²ᵥ; the θ-gradient is auto-rescaled by per-voxel noise, which under a shared Adam optimizer gives every voxel comparable per-iteration progress. Same fixed points as 'ssq', but much faster convergence in practice on real fMRI (heteroskedastic noise). When set, self.estimated_sigma2 holds the fitted per-voxel σ² after fit() returns.

  • fixed_pars (list[str] | None) – Parameter names to hold at their init value (fixed_pars) or to tie to a single shared value across voxels (shared_pars).

  • shared_pars (list[str] | None) – Parameter names to hold at their init value (fixed_pars) or to tie to a single shared value across voxels (shared_pars).

  • r2_atol (float, int, int) – Early-stop: stop when R² has improved by less than r2_atol over the last lag steps, after min_n_iterations.

  • lag (float, int, int) – Early-stop: stop when R² has improved by less than r2_atol over the last lag steps, after min_n_iterations.

  • min_n_iterations (float, int, int) – Early-stop: stop when R² has improved by less than r2_atol over the last lag steps, after min_n_iterations.

  • **kwargs – Forwarded to keras.optimizers.Adam when optimizer is None.

Returns:

Per-voxel parameter estimates, indexed by voxel. Voxels that contain any NaN or have zero variance cannot be fitted: they get a warning, and NaN in both the parameters and self.r2.

Return type:

pd.DataFrame

class braincoder.optimize.ResidualFitter(model, data, paradigm=None, parameters=None, weights=None, lambd=0.0)

Bases: object

Fit a parametric noise covariance Ω (and Student-t ν) to residuals.

Ω = ρ ττᵀ + (1 − ρ) diag(τ²) + σ² WWᵀ (optionally mixed with the sample covariance via lambd).

Warning

Ω has a single shared factor. On real single-trial fMRI betas the residuals typically need tens of factors, and this Ω underestimates held-out noise (held-out Mahalanobis distances ~4× too large on 7T data), giving overconfident decoding posteriors. lambd > 0 is unsafe when n_voxels > n_trials (rank-deficient sample covariance). The pseudo-WWᵀ from init_pseudoWWT is uncentred. For calibrated decoding see braincoder.noise.LowRankNoise and the “Noise models for decoding” docs page.

fit(init_rho=0.1, init_tau=None, init_dof=1.0, init_sigma2=0.001, D=None, max_n_iterations=1000, resid_dist='gauss', min_n_iterations=100, method='gauss', residuals=None, normalize_WWT=True, use_wwt=True, learning_rate=0.02, rtol=1e-06, lag=100, init_alpha=0.99, init_beta=0.0, spherical=False, progressbar=True, max_consecutive_failures=50)

use_wwt (default True): include the σ² · WᵀW term in Ω. When False, Ω is built without the tuning-similarity contribution — useful for testing whether WᵀW is doing real spatial-noise modeling or just creating signal-correlation structure that interacts with regularized encoding fits. σ² stays in the trainable set but has zero effect on Ω (gradient → 0; Adam leaves it inert).

Failure handling: a step whose cost or gradients are non-finite, or which raises a numerical error (e.g. a Cholesky failure), is rolled back to the best values so far and the learning rate is reduced by 10%. max_consecutive_failures (default 50) such steps in a row raise RuntimeError. Programming errors (TypeError – including JAX tracer-conversion errors – AttributeError, NameError, …) are re-raised immediately. If no step ever produces a finite cost the fit raises instead of returning an unfitted Ω. The number of rolled-back steps is stored in self.n_failed_steps.

Noise models for decoding

See Noise models for decoding: low-rank Ω and temperature for the workflow.

class braincoder.noise.LowRankNoise(scale, factors, psi, dof=None, temperature=1.0, voxels=None)

Bases: object

Low-rank + diagonal (Gaussian or Student-t) noise model for decoding.

Ω = D (F Fᵀ + Ψ) D, stored in factored form. Construct with fit() (recommended: chooses k and the temperature T by nested grouped cross-validation on the decoding objective) or from_residuals() (covariance only, fixed k).

k

Number of factors.

Type:

int

temperature

Likelihood temperature applied by get_stimulus_pdf (inf = flat posterior).

Type:

float

dof

Student-t degrees of freedom (None = Gaussian).

Type:

float or None

scale

Per-voxel scale s (includes the ML scale factor).

Type:

ndarray (V,)

factors

F, loadings on the correlation scale.

Type:

ndarray (V, k)

psi

Diagonal uniquenesses on the correlation scale.

Type:

ndarray (V,)

voxels

Voxel labels (columns of the training data), used to align test data.

Type:

pandas.Index or None

cv_scores_

Cross-validated decoding score for every (k, T): mean held-out log posterior probability of the true stimulus per trial, minus that of a flat posterior (nats; > 0 = better than uniform). Rows = k, columns = T.

Type:

pandas.DataFrame or None

classmethod fit(model, data, paradigm, parameters=None, stimulus_range=None, groups=None, weights=None, k='cv', temperature='cv', dof='fit', k_grid=(0, 1, 2, 5, 10, 20, 40, 80), temperature_grid=(np.float64(0.25), np.float64(0.29730177875068026), np.float64(0.3535533905932738), np.float64(0.42044820762685725), np.float64(0.5), np.float64(0.5946035575013605), np.float64(0.7071067811865476), np.float64(0.8408964152537145), np.float64(1.0), np.float64(1.189207115002721), np.float64(1.4142135623730951), np.float64(1.681792830507429), np.float64(2.0), np.float64(2.378414230005442), np.float64(2.8284271247461903), np.float64(3.363585661014858), np.float64(4.0), np.float64(4.756828460010884), np.float64(5.656854249492381), np.float64(6.727171322029716), np.float64(8.0), np.float64(9.513656920021768), np.float64(11.313708498984761), np.float64(13.454342644059432), np.float64(16.0), np.float64(19.027313840043536), np.float64(22.627416997969522), np.float64(26.908685288118864), np.float64(32.0), np.float64(38.05462768008707), np.float64(45.254833995939045), np.float64(53.81737057623773), np.float64(64.0), np.float64(76.10925536017415), np.float64(90.50966799187809), np.float64(107.63474115247546), np.float64(128.0), np.float64(152.2185107203483), np.float64(181.01933598375618), np.float64(215.2694823049509), np.float64(256.0), np.float64(304.4370214406966), np.float64(362.03867196751236), np.float64(430.5389646099018), np.float64(512.0), np.float64(608.8740428813932), np.float64(724.0773439350247), np.float64(861.0779292198037), np.float64(1024.0), np.float64(1217.7480857627863), np.float64(1448.1546878700494), np.float64(1722.1558584396073), np.float64(2048.0), np.float64(2435.4961715255727), np.float64(2896.309375740099), np.float64(3444.3117168792146), np.float64(4096.0), np.float64(inf)), inner_parameters=None, psi_floor=0.02, n_folds=5, progressbar=False)

Fit a low-rank noise model; choose k and T by nested CV.

Parameters:
  • model (EncodingModel)

  • data (DataFrame (n_trials, V)) – Training data only (the test data must never be passed here).

  • paradigm (DataFrame or array (n_trials, n_stim_dims)) – True stimulus of every training trial.

  • parameters (DataFrame, optional) – Encoding parameters (default model.parameters).

  • stimulus_range (array (n_stimuli,) or (n_stimuli, n_dims)) – The decoding grid — use the same grid as for get_stimulus_pdf. Required when k or temperature is chosen by CV. True training stimuli are matched to the nearest grid point.

  • groups (array (n_trials,), optional) – Group label per training trial (e.g. run). Inner CV leaves one group out at a time. Strongly recommended: trials within a run share noise. Without it, n_folds contiguous blocks are used.

  • k ('cv' or int)

  • temperature (‘cv’ or float (np.inf = flat posterior))

  • dof ('fit', None or float) – Student-t ν by ML (‘fit’), Gaussian (None) or fixed.

  • k_grid (sequences) – Candidates for CV. k values ≥ the number of inner-training trials or voxels are dropped.

  • temperature_grid (sequences) – Candidates for CV. k values ≥ the number of inner-training trials or voxels are dropped.

  • inner_parameters (None, dict or callable) –

    Encoding parameters used inside inner fold g.

    • None (default): parameters are held fixed. The inner held-out trials were then part of the data the encoding model was fitted on, so their predictions are slightly optimistic and the chosen T is, if anything, too small (posteriors a bit too sharp).

    • dict {group: parameters}: precomputed parameters that did not see group g (e.g. existing leave-one-run-out fits).

    • callable f(train_data, train_paradigm, group) -> parameters: refit the encoding model on the inner-training trials.

    In fold g, Ω is fitted on the residuals of the inner-training trials under these parameters, and the held-out trials are decoded with them.

  • psi_floor (float) – Floor on the diagonal uniquenesses (correlation scale).

Returns:

Fitted on all training trials with the chosen k; carries the chosen temperature, cv_scores_ and cv_info_.

Return type:

LowRankNoise

Notes

The CV objective is the summed held-out log posterior probability of the true stimulus on the grid, log p_T(s* | y), under a flat prior over the grid. This is the decoding objective; it is not the covariance likelihood (which prefers ever larger k on fMRI data while decoding gets worse). For every inner fold and every k, Ω(k) and ν are refitted on the inner-training residuals; all T are evaluated on the same likelihood table at no extra cost.

classmethod from_residuals(residuals, k=40, dof='fit', psi_floor=0.02, temperature=1.0, _svd=None)

Fit Ω (and ν) to residuals for a fixed rank k.

Principal-factor model of the residual correlation (one closed-form step, no iteration): F = first k eigenvectors × √eigenvalues, Ψ = 1 − diag(F Fᵀ), floored at psi_floor; then an overall scale c and ν are fitted by maximum likelihood (Student-t; dof=None for Gaussian, a number to fix ν).

Parameters:
  • residuals (DataFrame or array (n_trials, V))

  • k (int) – Number of factors (capped at min(n_trials, V) − 1).

grid_log_likelihood(data, predictions)

Log-likelihood table (n_trials, n_stimuli).

data: (n_trials, V); predictions: (n_stimuli, V), the model prediction for every candidate stimulus. Computed as d²(t, s) = q(y_t) + q(μ_s) − 2⟨y_t, μ_s⟩ with all terms in factored form — one (n_trials × V) · (V × n_stimuli) product, no dense Ω and no (n_stimuli × n_trials × V) residual tensor. float64 numpy.

log_prob(residuals)

Log density of each row of residuals (numpy, float64).

log_prob_backend(residuals)

Log density of each row, in Keras ops (float32, differentiable).

Same factored computation as log_prob(), usable inside a TensorFlow / JAX / PyTorch graph (e.g. gradient-based stimulus fitting). residuals: tensor (n, V).

mahalanobis(residuals)

Squared Mahalanobis distance rᵀ Ω⁻¹ r of each row (Woodbury).

summary()

Human-readable summary: k, T, ν, and the CV table (if any).

to_dense()

Dense (omega, dof), drop-in for ResidualFitter.fit output.

For code paths that take omega= / dof=. Note the temperature is not part of (omega, dof); pass temperature=noise.temperature to get_stimulus_pdf as well.

braincoder.noise.fit_lowrank_omega(residuals, k=40, dof='fit', psi_floor=0.02)

Functional / legacy path: dense (omega, dof) of a low-rank fit.

Drop-in replacement for the (omega, dof) returned by ResidualFitter.fit (pass them as omega= / dof=). Prefer LowRankNoise for large V (no dense matrix) and for choosing k and the temperature by cross-validation.

Calibration diagnostics

Calibration metrics for grid posteriors (e.g. from get_stimulus_pdf).

All functions take a posterior DataFrame — rows = trials, columns = grid stimuli — and the true stimulus of every trial. Rows are renormalised to sum to one (discrete masses over the grid), so both the trapezoid-normalised 1-D densities and the max-normalised multi-dimensional output of get_stimulus_pdf can be passed directly.

A calibrated posterior gives

  • randomised PIT values ~ Uniform(0, 1) (1-D only; SD ≈ 0.289, slightly less on a coarse discrete grid), U-shaped = overconfident, ∩-shaped = underconfident;

  • randomised HPD levels of the truth ~ Uniform(0, 1), i.e. the q-HDI contains the truth on a fraction q of trials (any dimensionality);

  • a log score that is not below that of a flat posterior.

Randomisation (a uniform draw within the probability mass of the true grid cell) makes the statistics exactly uniform for a calibrated discrete posterior instead of biased by ties and grid coarseness.

braincoder.utils.calibration.calibration_summary(pdf, true_stimulus, grid=None, levels=(0.5, 0.95), rng=0)

One-row summary: log score vs uniform, HDI coverage, PIT SD (1-D), posterior mean r / MAE / posterior SD (1-D).

braincoder.utils.calibration.hdi_coverage(pdf, true_stimulus, levels=(0.5, 0.95), grid=None, rng=0)

Fraction of trials whose q-HDI contains the truth, for each q.

braincoder.utils.calibration.hpd_level(pdf, true_stimulus, grid=None, rng=0)

Randomised level of the smallest HDI that contains the truth.

h = P(p(s) > p(s*)) + U · P(p(s) = p(s*)). The q-HDI covers the truth iff h < q. Works for any dimensionality.

braincoder.utils.calibration.log_score(pdf, true_stimulus, grid=None, relative_to_uniform=True)

Per-trial log posterior mass of the true grid cell.

With relative_to_uniform (default) log(1/n_stimuli) is subtracted, so 0 = as good as a flat posterior and > 0 = informative and calibrated enough to beat it.

braincoder.utils.calibration.marginalize(pdf, dimension, grid=None)

Marginal posterior over one stimulus dimension (int or level name).

braincoder.utils.calibration.pit(pdf, true_stimulus, grid=None, rng=0)

Randomised probability integral transform (1-D posteriors).

F(s* − 1) + U · p(s*), U ~ U(0, 1). For a multi-dimensional posterior, first marginalize() it.

braincoder.utils.calibration.plot_calibration(pdf, true_stimulus, grid=None, rng=0, axes=None, label=None, levels=array([0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]))

Two-panel diagnostic: coverage vs nominal HDI level, and PIT histogram (1-D) or HPD-level histogram (multi-D). Returns the axes.

braincoder.utils.calibration.posterior_grid(pdf, grid=None)

(P, grid): row-normalised masses (n_trials, n_stimuli) and the grid.

grid is taken from the columns (numeric Index or MultiIndex) unless passed explicitly (needed when get_stimulus_pdf was called on a multi-dimensional grid without include_multidimensional_stimulus_index).

braincoder.utils.calibration.true_stimulus_index(true_stimulus, grid)

Index of the nearest grid point for every true stimulus.