Foundation model features
NeuRepTrace can use external EEG/MEG foundation encoders as frozen feature extractors through
neureptrace.decoding.foundation. The integration remains dependency-light: NeuRepTrace does not
vendor BENDR, LaBraM, EEGPT, CBraMod, or other model repositories. Instead, it provides wrappers that
standardize how externally supplied TorchScript modules, trusted PyTorch modules, or state-dict
checkpoints are reshaped, frozen, pooled, and probed inside fold-local decoding workflows.
Supported wrapper families
The built-in family names are:
| Family | Decoder alias after registration | Conservative defaults | Typical use |
|---|---|---|---|
generic |
foundation-linear-probe |
pooling="flatten", input_layout="auto" |
Any project-local encoder or exported TorchScript module. |
bendr |
bendr-linear-probe |
pooling="mean_time", input_layout="channels_first" |
BENDR-style backbones consuming batch x channels x time. |
labram |
labram-linear-probe |
pooling="cls", input_layout="channels_first" |
LaBraM-style transformer exports with class-token embeddings. |
eegpt |
eegpt-linear-probe |
pooling="cls", input_layout="channels_first" |
EEGPT-style transformer exports with class-token or hidden-state outputs. |
cbramod |
cbramod-linear-probe |
pooling="mean_time", input_layout="channels_first" |
CBraMod-style frozen time-series backbones. |
These wrappers do not assume a single upstream API. Each model project can export slightly different
objects, so NeuRepTrace exposes explicit selectors for output_key, output_attribute,
output_index, encoder_attr, checkpoint_key, and state_dict_key.
Direct API: TorchScript or already-constructed module
from neureptrace.decoding.foundation import make_foundation_linear_probe
probe = make_foundation_linear_probe(
{
"model_family": "labram", # generic, bendr, labram, eegpt, cbramod
"model_path": "encoder.ts", # TorchScript encoder exported from the model project
"input_shape": [64, 100], # reshape each flattened row to channels x time
"pooling": "cls", # flatten, mean_time, mean_tokens, cls, last
"probe": "logistic", # logistic, linear_svm, ridge, lda
"C": 1.0,
}
)
probe.fit(X_source, y_source)
probabilities = probe.predict_proba(X_target)
You can also pass an instantiated torch.nn.Module directly:
probe = make_foundation_linear_probe(
{
"model_family": "bendr",
"encoder": my_bendr_encoder,
"input_shape": "64x256",
"pooling": "mean_time",
}
)
The encoder is placed in evaluation mode, moved to the requested device, and frozen before feature extraction. Torch is imported lazily, so standard scikit-learn NeuRepTrace workflows are unaffected unless this path is selected.
Loading state-dict checkpoints
For repositories that expose Python constructors rather than TorchScript exports, provide an importable factory and checkpoint metadata:
probe = make_foundation_linear_probe(
{
"model_family": "eegpt",
"load_mode": "state_dict",
"model_factory": "my_eegpt_package.models:build_encoder",
"model_kwargs": {"n_channels": 64, "sfreq": 250},
"model_path": "eegpt_checkpoint.pt",
"state_dict_key": "encoder_state_dict", # optional; common keys are auto-detected
"strip_state_dict_prefix": True, # strips module./model./encoder. when all keys share it
"input_shape": [64, 256],
"pooling": "cls",
}
)
Use load_mode="module" only for trusted Python pickles/checkpoints. The module loader intentionally
requests non-weights-only loading where supported by PyTorch; TorchScript and state-dict loading are
preferred for reproducible decoding runs.
Input and output adaptation
Common input controls:
| Parameter | Meaning |
|---|---|
input_shape |
Reshape each flattened feature row before encoder inference. |
input_layout="channels_first" |
Keep batch x channels x time. |
input_layout="time_first" |
Convert batch x channels x time to batch x time x channels. |
input_layout="add_channel_dim" |
Convert to batch x 1 x channels x time. |
input_layout="add_feature_dim" |
Convert to batch x channels x 1 x time. |
preprocessing="trial_zscore" |
Stateless per-trial z-scoring over all non-batch axes. |
preprocessing="channel_zscore" |
Stateless per-channel z-scoring over the last axis. |
Common output controls:
| Parameter | Meaning |
|---|---|
output_key |
Select a key from dictionary outputs, including dotted or slash paths. |
output_attribute |
Select an attribute from HuggingFace-like output objects. |
output_index |
Select an index from tuple/list outputs after key/attribute selection. |
encoder_attr |
Select a submodule from a loaded checkpoint, for example "encoder" or "backbone.encoder". |
pooling |
Convert encoder outputs to 2-D features: flatten, mean_time, mean_tokens, mean_channels, cls, or last. |
Optional decoder registration
Foundation probes are opt-in. Register them before using the normal decoder factory:
from neureptrace.decoding import make_decoder
from neureptrace.decoding.foundation import register_foundation_linear_probe
register_foundation_linear_probe()
decoder = make_decoder(
"labram-linear-probe",
emission_mode="uncalibrated",
classifier_param={
"model_path": "labram_encoder.ts",
"input_shape": "64x100",
"pooling": "cls",
"probe": "linear_svm",
},
)
The family-specific decoder aliases are convenience wrappers around the same frozen-encoder pipeline.
You can always use foundation-linear-probe with classifier_param={"model_family": "..."} instead.
Precomputed feature tables
If the upstream foundation model is easier to run outside NeuRepTrace, export a row-aligned feature table
and use neureptrace.decoding.precomputed_foundation instead of loading Torch modules at decode time.
This path supports .npy, .npz, .csv, and .tsv tables:
from neureptrace.decoding.precomputed_foundation import (
fit_precomputed_foundation_probe,
load_precomputed_foundation_features,
)
table = load_precomputed_foundation_features(
"labram_features.npz",
source_model="LaBraM",
feature_fit_scope="external_frozen",
)
result = fit_precomputed_foundation_probe(
feature_table=table,
train_row_ids=source_trial_ids,
train_labels=source_labels,
test_row_ids=target_trial_ids,
)
The probe API has no target_labels argument. The feature_fit_scope field records whether the external
feature extractor was declared as external_frozen / source_only, source_plus_unlabeled_target,
target_calibrated, or oracle_target_included, so downstream reports can distinguish Protocol 1,
Protocol 2, Protocol 3, and oracle feature generation.
Protocol interpretation
A frozen external encoder plus a source-label probe is Protocol 1 when the held-out target subject is not used for fitting, model selection, or normalization beyond ordinary fold-local evaluation. If the encoder or an adapter is updated with unlabeled target rows, report it as Protocol 2. If labeled target calibration trials are used for probing, fine-tuning, LoRA/adapters, or model selection, report it as Protocol 3. Using all target labels or choosing checkpoints by target accuracy is an oracle upper bound rather than a benchmark-valid foundation-model result.
The wrappers in this module implement the frozen-feature-extractor part. Fine-tuning, LoRA, masked pretraining, and target-adaptive foundation-model updates should be implemented as separate Protocol 2 or Protocol 3 paths so the target-information boundary remains auditable.