braindecode.models.MAPA#

class braindecode.models.MAPA(n_outputs=None, n_chans=None, chs_info=None, n_times=None, input_window_seconds=None, sfreq=None, *, contact_labels=None, regions=None, d_model=384, mlp_ratio=4, region_embed=True, space_rope=True, deep_sup=True, pooling='mean', normalization='window', activation=<class 'torch.nn.modules.activation.GELU'>)[source]#

MAPA from Tang, Spalding and Cogan (2026) [Tang2026].

Attention/Transformer Foundation Model Channel

Added in version 1.8.2.

Architecture Overview

MAPA is a masked-autoencoder foundation model for intracranial EEG whose only knowledge of an electrode is anatomical: the atlas region of each contact and its position along the implanted array. Coordinates, montage size and channel order never reach the model, so one pretrained encoder reads subjects it has never seen [Tang2026]. It works in four stages:

  1. Turn each channel into slow, mid and fast magnitude spectrograms on a shared 32 Hz frame clock, robust z-scored and clipped (frontend).

  2. Embed each (contact, band, time) token with its band’s linear layer and a per-band vector (stem).

  3. Add the embedding of each contact’s DKT region, then apply twelve pre-norm transformer blocks whose attention spans contacts and time jointly but never crosses from one array to another (encoder).

  4. Pool the normed outputs of blocks 3, 6, 9 and 12 and classify them (final_layer).

Encoding is threefold: temporal (the 32 Hz frame clock, one rotary axis shared by tokens on the same slot), spatial (the clinical contact number along the array, the other rotary axis, plus the additive region embedding; attention is block-diagonal over arrays and uses no coordinates) and spectral (the three STFT bands at 2-14, 16-56 and 64-160 Hz).

Additional Mechanisms

Electrode metadata. The array and the contact number are read off the clinical label ("LA7" is contact 7 of array LA), from contact_labels or else the chs_info names, verbatim with gaps kept; regions must be exact names of MAPA_DKT_REGIONS.

One model, many subjects. The constructor’s montage is only the default. To read another recording, pass forward() the sensor_indices that sensor_indices() builds from its labels and regions; all samples of a batch share them. Only pooling="flatten" is tied to one montage and one window length.

Sampling rate and window length. Resample to 2048 Hz. A window yields 1 + n_times // 64 frames, truncated to a multiple of 8, so it needs at least 448 samples. With normalization="session" the input is the spectrogram itself (sfreq=32, n_times in frames): the 20 retained bins, robust z-scored per contact and bin over the whole recording.

Important

Pre-trained Weights Available

The released mapa_vits384 encoder (the default configuration, 21,335,424 parameters) is hosted on the Hugging Face Hub. The head is randomly initialized, so fine-tune or linear-probe before use:

from braindecode.models import MAPA

model = MAPA.from_pretrained(
    "braindecode/mapa-pretrained",
    n_outputs=2,
    chs_info=raw.info["chs"],  # clinical labels, e.g. "LA7"
    regions=regions,
)

Note

Differences from the reference implementation:

  • Attention runs on arrays padded to a common size, with the padding masked, rather than on one ragged sequence; the outputs are the same.

  • normalization="window" fits the robust z-score on each window rather than on the whole recording. Only normalization="session" reproduces the reference inputs.

  • The pooled features are the normed four-tap concatenation, whereas the paper’s frozen evaluation reads block 12 before that norm.

  • The pretraining objective and decoder, the anatomical localization and the artifact detectors (“Guard 1” and “Guard 2”) are out of scope; the input clipping (“Guard 3”) is kept.

Parameters:
  • n_outputs (int) – Number of outputs of the model. This is the number of classes in the case of classification.

  • n_chans (int) – Number of EEG channels.

  • chs_info (list of dict) – Information about each individual EEG channel. This should be filled with info["chs"]. Refer to mne.Info for more details.

  • n_times (int) – Number of time samples of the input window.

  • input_window_seconds (float) – Length of the input window in seconds.

  • sfreq (float) – Sampling frequency of the EEG recordings.

  • contact_labels (list[str] | None) – Clinical label of each channel, such as "LA7". Defaults to the chs_info channel names, then to a single array numbered from 1.

  • regions (list[str | int | None] | None) – DKT region of each channel, as an exact name from MAPA_DKT_REGIONS or its integer slot; None selects the reserved unassigned slot. Defaults to unassigned everywhere.

  • d_model (int) – Token embedding dimension, a multiple of the head dimension 64. Default 384, the released mapa_vits384.

  • mlp_ratio (int) – Hidden dimension of the feed-forward blocks, as a multiple of d_model.

  • region_embed (bool) – Whether the region embedding is used. False is the paper’s no_region ablation.

  • space_rope (bool) – Whether the rotary encoding carries the contact number. False is the paper’s no_relpos ablation.

  • deep_sup (bool) – Whether the encoder returns the four normed deep-supervision taps, concatenated to width 4 * d_model, or one terminal LayerNorm.

  • pooling (str) – "mean" averages the tokens, so the head fits any montage and window length; "flatten" keeps every token, so the head fits one only.

  • normalization (str) – "window" robust z-scores the spectrograms of each raw window; "session" takes the spectrogram itself, normalized over the whole recording upstream; "none" passes the raw STFT magnitude.

  • activation (type[Module]) – Activation layer class of the feed-forward blocks.

Raises:

ValueError – If some input signal-related parameters are not specified: and can not be inferred.

Notes

If some input signal-related parameters are not specified, there will be an attempt to infer them from the other parameters.

Examples

>>> import torch
>>> from braindecode.models import MAPA
>>> model = MAPA(
...     n_outputs=2,
...     n_chans=3,
...     n_times=2048,
...     sfreq=2048,
...     contact_labels=["LA1", "LA2", "LB4"],
...     regions=["ctx-lh-insula", "ctx-lh-insula", "Left-Hippocampus"],
... )
>>> model(torch.randn(4, 3, 2048)).shape
torch.Size([4, 2])

The same model reads another subject, given that subject’s electrodes:

>>> other = MAPA.sensor_indices(["RC1", "RC2", "RC3", "RD7"])
>>> model(torch.randn(4, 4, 4096), other).shape
torch.Size([4, 2])

References

[Tang2026] (1,2)

Tang, B., Spalding, Z. & Cogan, G. B. (2026). Pretraining for sample-efficient neural interfaces. https://arxiv.org/abs/2609.13507

Hugging Face Hub integration

When the optional huggingface_hub package is installed, all models automatically gain the ability to be pushed to and loaded from the Hugging Face Hub. Install with:

pip install braindecode[hub]

Pushing a model to the Hub:

from braindecode.models import MAPA

# Train your model
model = MAPA(n_chans=22, n_outputs=4, n_times=1000)
# ... training code ...

# Push to the Hub
model.push_to_hub(
    repo_id="username/my-mapa-model",
    commit_message="Initial model upload",
)

Loading a model from the Hub:

from braindecode.models import MAPA

# Load pretrained model
model = MAPA.from_pretrained("username/my-mapa-model")

# Load with a different number of outputs (head is rebuilt automatically)
model = MAPA.from_pretrained("username/my-mapa-model", n_outputs=4)

Extracting features and replacing the head:

import torch

x = torch.randn(1, model.n_chans, model.n_times)
# Extract encoder features (consistent dict across all models)
out = model(x, return_features=True)
features = out["features"]

# Replace the classification head
model.reset_head(n_outputs=10)

Saving and restoring full configuration:

import json

config = model.get_config()            # all __init__ params
with open("config.json", "w") as f:
    json.dump(config, f)

model2 = MAPA.from_config(config)    # reconstruct (no weights)

All model parameters (both EEG-specific and model-specific such as dropout rates, activation functions, number of filters) are automatically saved to the Hub and restored when loading.

See Loading and Adapting Pretrained Foundation Models for a complete tutorial.

Methods

forward(x, sensor_indices=None, return_features=False)[source]#

Encode an iEEG batch into class logits or pooled features.

Parameters:
  • x (Tensor) – Raw signal of shape (batch, n_chans, n_times), or with normalization="session" the normalized spectrogram of shape (batch, n_chans, 20, n_frames).

  • sensor_indices (Tensor | None) – (n_chans, 3) electrode metadata of the recording this batch comes from, one row of (array, contact number, region slot) per channel, as sensor_indices() builds it. Every sample of the batch shares it. Defaults to the montage resolved at construction, which only fits the construction-time channel count.

  • return_features (bool) – Whether to return the pooled token embedding instead of the logits.

Returns:

Class logits of shape (batch, n_outputs), or with return_features=True a dict whose "features" entry is the pooled embedding of shape (batch, final_layer.in_features) and whose "cls_token" entry is None.

Return type:

torch.Tensor or dict

reset_head(n_outputs)[source]#

Replace the linear classification head for a new n_outputs.

The new head keeps the old one’s device and dtype, and the value is validated and recorded through the mixin’s _set_n_outputs().

Parameters:

n_outputs (int) – New number of output classes.

Return type:

None

Examples

>>> from braindecode.models import BENDR
>>> model = BENDR(n_chans=22, n_times=1000, n_outputs=4)
>>> model.reset_head(10)
>>> model.n_outputs
10

Added in version 1.4.

static sensor_indices(contact_labels, regions=None)[source]#

Assemble one recording’s electrode metadata for forward().

Parameters:
  • contact_labels (list[str]) – Clinical label of each channel, such as "LA7", from which the array and the contact number are read.

  • regions (list[str | int | None] | None) – DKT region of each channel, either an exact name from MAPA_DKT_REGIONS or its integer slot, with None selecting the reserved unassigned slot. Defaults to unassigned everywhere.

Returns:

(n_chans, 3) long tensor whose columns are the array, the contact number along it, and the region slot.

Return type:

Tensor

Examples

>>> from braindecode.models import MAPA
>>> MAPA.sensor_indices(["LA1", "LA3", "LB2"]).tolist()
[[0, 1, 74], [0, 3, 74], [1, 2, 74]]