Skip to content

EEGLAB interoperability

pamica is a drop-in replacement for EEGLAB's AMICA: a fit written to disk loads directly with the same reader EEGLAB uses (loadmodout15.m), with the components in the same order and orientation, so no manual re-sorting, sign-flipping, or reformatting is needed.

Writing EEGLAB-readable output

After a fit, call write_amica_output with a destination directory:

from pamica import AMICA

model = AMICA(n_models=1, n_mix=3)
model.fit(X)                      # X is (n_channels, n_samples)
model.write_amica_output("amicaout")

This writes the raw binary files EEGLAB's AMICA loader reads:

File Contents
gm model probabilities
W unmixing weights (post-sphering)
S sphering matrix
mean data mean
c per-model centers
alpha, mu, sbeta, rho source mixture-density parameters
A mixing matrix in sphered space, one column per component (the reference's layout, for any number of models)
comp_list component ids (for component sharing)
LL log-likelihood per iteration
LLt per-timepoint, per-model log-likelihood, plus the per-timepoint total

For a single model the bytes are identical to the reference Fortran binary's amicaout files, so the directory is interchangeable with a native AMICA run. A directory written with do_approx_sphere=False (a full-rank, genuinely asymmetric S) by a pamica older than issue #336's fix has S transposed; re-run write_amica_output to regenerate it (the default symmetric sphere and a rank-reduced fit were unaffected). loadmodout15.m does not read A; pamica's own load_results does, and refuses an A that does not invert the W beside it. A multi-model directory written by a pamica older than issue #334 stored A in another layout and must be written again to load there (a single-model A was already in the reference layout).

The MLX backend writes the same directory: an Apple-Silicon fit exports to EEGLAB directly, with no torch round trip (epic #278). The export is validated against a torch twin in the test suite to float32 precision (max abs diff < 1e-5; comp_list is the only field compared exactly -- MLX computes in float32, so its files are not bit-identical to a float64 export the way a torch fit's are to Fortran's):

from pamica import AMICA

model = AMICA(n_mix=3, backend="mlx")   # requires the mlx extra (issue #313)
model.fit(X)                            # X is (n_channels, n_samples)
model.write_amica_output("amicaout")

LLt is what loadmodout15.m turns into Lht/Lt and the model-probability odds v; it is written after a fresh fit(), and omitted (with a warning) for a model restored from load(), which carries no E-step stash. Under do_reject, rejected samples are written as exactly 0.0 to match the reference: AMICA's own load_rej reconstructs the rejection mask from those zeros, so they are load-bearing rather than padding.

Like the reference, pamica writes the E-step that produced the last entry of LL, so LLt describes the parameters as they stood one M-step before the W written beside it (issue #157; see amica-differences). Use model.model_loglik(X) if you want the log-likelihood of the written parameters themselves.

Loading in EEGLAB / MATLAB

In MATLAB with the AMICA plugin on the path:

mod = loadmodout15('amicaout');
% mod.W   : unmixing weights (n x n x num_models)
% mod.A   : component scalp maps, columns ordered IC1..ICn by variance
% mod.S   : sphering matrix
% mod.svar: back-projected variance per component

loadmodout15 applies the EEGLAB conventions on load: it orders components by back-projected variance (IC1 has the highest), derives the sensor-space mixing A = pinv(W * S), and normalizes each map to unit norm. Because pamica writes the same format, the components you get in EEGLAB match a native AMICA run.

Variance ordering in Python

To get the EEGLAB display order without a disk round-trip, use variance_order, which ranks sources by the same back-projected variance (IC1 = highest):

order = model.variance_order()           # source indices, highest variance first
A = model.get_sensor_mixing_matrix()[:, order]  # scalp maps in EEGLAB order
W = model.get_unmixing_matrix()[order]   # unmixing rows in EEGLAB order

Pass return_svar=True to also get the per-component variances.

get_sensor_mixing_matrix() gives the maps in input-channel space; get_mixing_matrix() is the sphered-space matrix, which is not a scalp map.

Multi-model note

Every file is written in the reference's layout for any number of models: W with the model axis slowest (issue #159) and A with one column per component (issue #334), so loadmodout15 reads a multi-model directory the same way it reads a native one. The values themselves are another matter: multi-model AMICA is not partition-identifiable, so two runs, native or pamica, do not produce the same models; see the multi-model discussion in Validation & Parity.