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:
objectAbstract 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. Withoutomega, both come from the model (model.omega,model.dof). Withomega,dof=Nonemeans Gaussian. Ignored whennoise_modelis given.dof (array, float, optional) – Dense noise covariance and Student-t degrees of freedom, e.g. from
ResidualFitter.fit. Withoutomega, both come from the model (model.omega,model.dof). Withomega,dof=Nonemeans Gaussian. Ignored whennoise_modelis 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
temperatureis applied unlesstemperatureis passed explicitly.temperature (float, optional) – Likelihood temperature T: posterior ∝ likelihood^(1/T). Default
None= 1 (or the noise model’s own temperature).np.infgives 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 asget_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:
EncodingModelLinear 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,HRFEncodingModelLinearModelWithBaseline variant that automatically applies an HRF convolution.
HRF utilities¶
- class braincoder.hrf.HRFModel(unique_hrfs=False)¶
Bases:
objectBase 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:
HRFModelCanonical 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:
objectClosed-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=Trueprepends 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_predictionsignoresparameters) that declare a baseline/offset without folding it into the fit: without an intercept term,WeightFitterregresses 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 plainweightswhen enabled. The intercept column is excluded from the L2 penalty whenalpha>0(standard ridge convention — the offset isn’t shrunk).Mutually exclusive with the
LinearModelWithBaselinefixed-known- baseline path below (that path already subtracts a suppliedparameters['baseline']from the data before the regression; jointly fitting an intercept on top of an already-baseline-removed target is redundant / ill-posed). Passparameters=Nonewhen constructing the fitter if you wantfit_interceptto estimate the baseline itself instead of supplying one.
- class braincoder.optimize.ParameterFitter(model, data, paradigm, memory_limit=666666666, log_dir=False)¶
Bases:
objectIterative 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_sigma2holds the fitted per-voxel σ² afterfit()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_atolover the lastlagsteps, aftermin_n_iterations.lag (float, int, int) – Early-stop: stop when R² has improved by less than
r2_atolover the lastlagsteps, aftermin_n_iterations.min_n_iterations (float, int, int) – Early-stop: stop when R² has improved by less than
r2_atolover the lastlagsteps, aftermin_n_iterations.**kwargs – Forwarded to
keras.optimizers.Adamwhenoptimizer 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:
objectFit 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 > 0is unsafe when n_voxels > n_trials (rank-deficient sample covariance). The pseudo-WWᵀ frominit_pseudoWWTis uncentred. For calibrated decoding seebraincoder.noise.LowRankNoiseand 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ᵀWterm in Ω. When False, Ω is built without the tuning-similarity contribution — useful for testing whetherWᵀWis 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 raiseRuntimeError. 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 inself.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:
objectLow-rank + diagonal (Gaussian or Student-t) noise model for decoding.
Ω = D (F Fᵀ + Ψ) D, stored in factored form. Construct with
fit()(recommended: chooseskand the temperatureTby nested grouped cross-validation on the decoding objective) orfrom_residuals()(covariance only, fixedk).- 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,)
- 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
kandTby 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 whenkortemperatureis 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_foldscontiguous 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.
kvalues ≥ the number of inner-training trials or voxels are dropped.temperature_grid (sequences) – Candidates for CV.
kvalues ≥ 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):parametersare 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 chosenTis, if anything, too small (posteriors a bit too sharp).dict{group: parameters}: precomputed parameters that did not see groupg(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 chosentemperature,cv_scores_andcv_info_.- Return type:
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 largerkon fMRI data while decoding gets worse). For every inner fold and everyk, Ω(k) and ν are refitted on the inner-training residuals; allTare 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=Nonefor 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 forResidualFitter.fitoutput.For code paths that take
omega=/dof=. Note the temperature is not part of (omega, dof); passtemperature=noise.temperaturetoget_stimulus_pdfas 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 byResidualFitter.fit(pass them asomega=/dof=). PreferLowRankNoisefor large V (no dense matrix) and for choosingkand 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.
gridis taken from the columns (numeric Index or MultiIndex) unless passed explicitly (needed whenget_stimulus_pdfwas called on a multi-dimensional grid withoutinclude_multidimensional_stimulus_index).
- braincoder.utils.calibration.true_stimulus_index(true_stimulus, grid)¶
Index of the nearest grid point for every true stimulus.