Skip to main content

nimbus-bci

Nimbus Personalizer — Bayesian personalization head for frozen neural EEG embeddings.
Plug any frozen trunk that emits encode(X) → Z, adapt online with a Nimbus head, ship apps on BrainState — without owning or retraining the encoder.

0.8 product shape: the Personalizer surface — cheap online adaptation (~8–10× cheaper than batch refit, ~22–131× cheaper than trunk retraining at matched n), portable profiles, paradigm recipes (MI · P300 · Riemannian), and an online safety layer (nimbus_bci.safety: label budgets, an adaptation governor with burn-in / cooldown / caps, drift surveillance, and snapshot → rollback of the adapted head).

Also ships sklearn-compatible heads for classical feature pipelines (CSP, Riemann, …): Bayesian LDA / QDA / Softmax, plus NimbusSTS (EKF latent state + point-estimated emissions). Those heads power Personalizer and can be used alone.

Docs · Personalizer · API reference · PyPI · Contact

Install

pip install nimbus-bci
# optional: pip install nimbus-bci[softmax]   # JAX Softmax head
# optional: pip install nimbus-bci[riemann]   # pyRiemann pipelines
# optional: pip install nimbus-bci[mne]       # MNE helpers
# optional: pip install nimbus-bci[train]     # torch trunk-arm trainers (LoRA / FT)
# optional: pip install nimbus-bci[viz]       # matplotlib plotting helpers
# optional: pip install nimbus-bci[all]       # everything above

From source: pip install -e ".[all]" after cloning.

Upgrading from 0.5? See Migration notes in CHANGELOG.md (ships in the sdist; also linked from docs.nimbusbci.com).

5-minute quickstart

Run the example (no external data needed):

python examples/quickstart.py

Or follow along:

import numpy as np
from nimbus_bci import Personalizer, wrap

# 1. Wrap any frozen trunk (EEGNet, REVE, ...) — here a random projection.
rng = np.random.default_rng(42)
proj = rng.standard_normal((6, 16))
enc = wrap(lambda X: X @ proj, model_id="toy", embedding_dim=16)

# 2. Fit a Bayesian head on ~30 calibration trials.
#    for_deployment = strict gating + LDA + minimal params (the common case).
#    For research/experimentation: Personalizer.for_research(enc, classes, ...).
#    For classical features (no encoder): Personalizer.for_features(classes).
#    To PIN the accept rate for your trunk (default-gate accept rates span
#    7-78% across trunks), fit + calibrate the gate in one call:
#    adapter.fit_calibrated(X_cal, y_cal, X_val, y_val, target_accept=0.7)
adapter = Personalizer.for_deployment(enc, ["left_hand", "right_hand"],
                                       paradigm="motor_imagery")
adapter.fit(X_cal, y_cal)

# 3. Predict → BrainState with trial rejection.
states = adapter.predict(X_test)         # list[BrainState]
accepted = [s for s in states if not s.rejected]  # confident trials
# s.intent, s.confidence, s.uncertainty, s.rejected, s.rejection_reason

# 4. Adapt online (exact conjugate update — no retraining).
adapter.partial_fit(X_new, y_new)

# 5. Save / load.
adapter.save("profile"); loaded = Personalizer.load("profile", encoder=enc)

The three regimes

# A. Classical features (CSP log-variance, tangent space, hand-crafted) —
#    no encoder, features go straight to the Bayesian head.
p = Personalizer.for_features(["left", "right"]).fit(X_csp, y)

# B. Frozen deep trunk (EEGNet, foundation model) — embeddings → head,
#    strict trial gating out of the box.
p = Personalizer.for_deployment(enc, ["left", "right"]).fit(X_cal, y_cal)
states = p.predict(X_test)          # BrainState per trial, rejected flagged

# C. Riemannian geometry & P300 (OAS / xDAWN tangent space) —
#    weak-trunk / raw epochs; for_riemann defaults to label-free geodesic
#    tracking (tracking_alpha=0.05). Opt out with tracking_alpha=0.0.
p = Personalizer.for_riemann(["left", "right"]).fit(X_cal, y_cal)
# Or for P300 spellers (tracking opt-in; recommend α=0.05 when enabling):
# Personalizer.for_p300(["NonTarget", "Target"], tracking_alpha=0.05)
p.adapt(epoch)                      # label-free reference update (0 labels needed!)

# D. Streaming adaptation loop — predict, optionally partial_fit on new
#    labels; the conjugate head update is exact and cheap (no retraining).
#    Reference: examples/middleware_streaming_adaptation.py
for trial in stream:
    state = p.predict(trial)                # BrainState
    if label_available(state):
        p.partial_fit(trial, label)         # ~1/30-1/131 the wall cost of retraining

Regime A/B/C are the shipped surface — plus an opt-in safety layer (0.8):

# E. Governed adaptation — label budgets, burn-in, cooldown, caps.
from nimbus_bci.safety import LabelBudget, SafetyConfig

p = Personalizer(
    encoder=enc, head="lda", classes=["left_hand", "right_hand"],
    preset="strict",
    safety=SafetyConfig(burn_in=50, cooldown_trials=10,
                        budget=LabelBudget(hard_cap=2_000)),
)
# partial_fit / adapt / adapt_pseudo now preflight the governor and raise
# AdaptationRefusedError (never a silent skip) when a budget or pacing
# control refuses. snapshot_head() / restore_head() give a one-call
# rollback path when your own monitoring says "revert":
snap = p.snapshot_head()          # ~KB-scale head snapshot
p.restore_head(snap)              # byte-equal predictions after restore

The full parameter surface (user=, preset=, head choice) via direct construction — anything that maps inputs to embeddings (n, d) plugs in; Nimbus does not own DL architectures:

adapter = Personalizer(
    encoder=enc, head="lda", user="subject_001",
    classes=["left", "right"], preset="strict",
)

Full walkthrough with copy-paste synthetic data: Quickstart. Feeding real EEG? Read the epochs ingest contract first — epoching, baseline, filtering, sfreq discipline, axis conventions: Preparing epochs.

Optional shortcuts for common external trunks: wrap_eegnet, and from nimbus_bci.middleware.adapters import wrap_braindecode (see encoder contract).

Examples in this repo: quickstart.py, deployment_fit_calibrated.py (pinned accept rates), embeddings_contract.py (precomputed-embedding flow), e2e_mne_physionet_brainstate.py (real-data MI → rejection + ITR), active_calibration_loop.py, middleware_personalizer.py, middleware_streaming_adaptation.py, middleware_geometry_gateway.py, middleware_mi_session.py, middleware_braindecode.py, middleware_reve.py, middleware_recommend_adapt.py.
Bench EEGNet demo (repo-only, needs nimbusbench[dl]): nimbusbench/scripts/middleware_eegnet.py.

When to adapt (thin helper)

Optional mean-shift check before spending labels on partial_fit. You choose the threshold tau.

dec = adapter.recommend_adapt(X_stream, tau=your_tau)  # stay vs stream update
if dec.should_adapt:
    adapter.partial_fit(X_lab, y_lab)

Trial rejection (BrainState.rejected / need_more_data) is a different gate — accept/reject the intent for the app.
Details: BrainState · recipe: examples/middleware_recommend_adapt.py.

Label spend itself can be governed — budgets, burn-in, cooldown, adaptation caps, and snapshot → rollback — via Personalizer(..., safety=SafetyConfig(...)); see the nimbus_bci.safety module and Trust & calibration.

Classical heads (CSP / features)

Same Bayesian heads without an encoder — inputs are already features:

from nimbus_bci import NimbusLDA

clf = NimbusLDA()
clf.fit(X_train, y_train)
proba = clf.predict_proba(X_test)
clf.partial_fit(X_new, y_new)

Also: streaming (StreamingSession), active learning (CalibrationSession), MNE helpers, optional Riemann pipelines. See docs.nimbusbci.com and examples/e2e_mne_csp_nimbus.py.

Head Role
NimbusLDA / NimbusQDA Conjugate Bayesian LDA/QDA
NimbusSoftmax Polya–Gamma VI (optional JAX)
NimbusSTS EKF latent-state / drift (EKF on z; point W/H/b)

Working with precomputed embeddings

If your pipeline already computed embeddings — frozen backbone → per-trial embeddings (N, F) — declare the contract so the Personalizer consumes X directly instead of re-encoding it through the attached trunk:

from nimbus_bci import Personalizer, wrap

adapter = Personalizer(
    encoder=wrap(module),        # attached for model_id / lineage — never called
    x_kind="embeddings",         # every X below is ALREADY the encoder output
    head="softmax",
    classes=["left", "right"],
)
adapter.fit(Z_cal, y_cal)                        # (N, F) — head + transforms fit directly
adapter.partial_fit(Z_stream, y_stream)          # online head updates
adapter.adapt(Z_stream, y_stream, level="head")  # supervised head adaptation

The rule: trunk adaptation needs raw encoder inputs. LoRA / BaLoRA / fine-tuning backpropagate through the trunk, and precomputed embeddings have no gradient path to trunk weights — so adapt(..., level="trunk"), adapt_trunk(...), adapt_async(...), fit_tent(...), trunk MC sampling, and Riemannian reference tracking (update_riemann_reference(...)) raise PersonalizerInputError under x_kind="embeddings". To adapt the trunk, feed raw epochs with the default x_kind="epochs". x_kind="embeddings" with encoder=None is also legal — that is the classical feature-space flow (Personalizer.for_features(...)).

Profiles round-trip the contract: export_profile() records x_kind and the observed embed_dim in UserProfile.meta; from_profile(...) restores them, refuses an x_kind mismatch, and validates the feature dim of the next fit against the recorded embed_dim.

Evidence

Public Personalizer story (heads, cost vs fine-tune, multi-trunk / REVE):
Evidence · Overview.

License

Two-track proprietary license — see the LICENSE.txt shipped with the package (and on PyPI). Non-commercial use is free under the Non-Commercial terms; commercial use requires a paid license. Contact: hello@nimbusbci.com.

Contributing & security

Licensees with source access: follow the repository CONTRIBUTING.md and SECURITY.md. Report vulnerabilities privately to hello@nimbusbci.com — do not open a public issue.

Metadata

Release files for nimbus-bci 0.8.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nimbus-bci 0.8.1
File Size Uploaded Provenance
nimbus_bci-0.8.1.tar.gz 1.4 MB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for nimbus-bci 0.8.1
File
nimbus_bci-0.8.1-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
nimbus_bci-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ x86-64, Linux glibc 2.28+ x86-64 Details
nimbus_bci-0.8.1-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl CPython 3.13 CPython 3.13 macOS 10.13+ x86-64 Details
nimbus_bci-0.8.1-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
nimbus_bci-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64, Linux glibc 2.28+ x86-64 Details
nimbus_bci-0.8.1-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.13+ x86-64 Details
nimbus_bci-0.8.1-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
nimbus_bci-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ x86-64, Linux glibc 2.28+ x86-64 Details
nimbus_bci-0.8.1-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl CPython 3.11 CPython 3.11 macOS 10.9+ x86-64 Details

Total release size: 21.1 MB

Release files / nimbus_bci-0.8.1.tar.gz

Download URL nimbus_bci-0.8.1.tar.gz
Size 1.4 MB
Tags Source
SHA-256 checksum
How to use checksums
eb6b1158e5216cccd83219b663c11f9806814927dcd9d15384e7a4f63eae271b
BLAKE2b-256 checksum
How to use checksums
484106a1834f2bc7541f4007edbd6b5a1759832bc363e9b16c0f2cab28b4b01e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp313-cp313-win_amd64.whl

Download URL nimbus_bci-0.8.1-cp313-cp313-win_amd64.whl
Size 877.0 kB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
fb9094cfa01c0555ac99ae26b4b2d4ae57a5c2cce0f991701acf4819e589a6e5
BLAKE2b-256 checksum
How to use checksums
7d8cff106301ef1f226455516fb21539b9400b0f4618bd948bffaeb3ec2da93e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Size 3.7 MB
Tags CPython 3.13 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
793b3d05640ac46651111a9d9ef1401bf6afa0d4b4afb4ddc288fa0e4060a0b2
BLAKE2b-256 checksum
How to use checksums
732a3cad4fe763ebe9956f6d18162e08c6a7e2707b9e0a79933574f2a794606d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp313-cp313-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.1-cp313-cp313-macosx_11_0_arm64.whl
Size 953.2 kB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
05fe76f35242ef5d642cb1ebfa2e4803600ff94939a0912b591e1863fc56bd7f
BLAKE2b-256 checksum
How to use checksums
3f01f953adcdbd703ee8e86327b16644a5b3515e26e7c12b3ebe855d1b309b65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl

Download URL nimbus_bci-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl
Size 961.9 kB
Tags CPython 3.13 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
f3379b0d8b7c902933c4b3984059e649f8812c72eb8328fdd40821e2c142b09c
BLAKE2b-256 checksum
How to use checksums
03ac837a5db1016b545b259e3b2998877e807cef0ae4e006dfc367bdcda9922f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp312-cp312-win_amd64.whl

Download URL nimbus_bci-0.8.1-cp312-cp312-win_amd64.whl
Size 881.0 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
c66cdcaa453941ead3450f44ec39d404f1dca42322510929042ae7b17ceaad57
BLAKE2b-256 checksum
How to use checksums
713aa7f244c0cae16b07ec9b5c17b2e8289756b8e4146ccf2fde44ed21e47105
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Size 3.8 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
0995018a149ecb34bcceb83ce3774ee24f42d3132b638e2d672d686c2e41b65f
BLAKE2b-256 checksum
How to use checksums
595322f4a26b910f22aacbd1905d959a01021613ee75f8aa19c3c13b9a4f5124
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp312-cp312-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.1-cp312-cp312-macosx_11_0_arm64.whl
Size 957.8 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a4015a99b336dacd41c48007bfffba6a5d183b46092770c6390e5d52beccdaaa
BLAKE2b-256 checksum
How to use checksums
d932be2d94ed4c7f58d8236c07cde9928ece52e66f5d0e6567ee7b5411f183cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl

Download URL nimbus_bci-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl
Size 965.0 kB
Tags CPython 3.12 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
91eb064ee9783042b7374283e5de726e154f1a55795ca8a9e1e567605b18706b
BLAKE2b-256 checksum
How to use checksums
9d6fe881f387639c652412bb54801ebea51227cac3bf72b853e4f1b010dfc2de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp311-cp311-win_amd64.whl

Download URL nimbus_bci-0.8.1-cp311-cp311-win_amd64.whl
Size 901.3 kB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
7c4eb219db2fbb1346e09dadfd6f2fd584f1f41f717b0ab671ca71d717ed207c
BLAKE2b-256 checksum
How to use checksums
8f8c617604d0830950932d020802e240cfd1a83364aecaa627067bddee8bce10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Size 3.8 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
eddcfd5800e4dca5a24e0b2c1c350ced5b24ed48881ef0ba33993a54437fcebd
BLAKE2b-256 checksum
How to use checksums
b77be12d432c9331cfd932d4f7dc7bd39e70882b376472d46ec5acb4ae660f24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp311-cp311-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.1-cp311-cp311-macosx_11_0_arm64.whl
Size 957.1 kB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
850295b0e8f161a43cb214f825d9a6c65b93415de06a938c8d434f5147ff09ac
BLAKE2b-256 checksum
How to use checksums
548340c716129da5c6f252ea4197c8166c5378b6048973582af4abac0e5b60fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / nimbus_bci-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl

Download URL nimbus_bci-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl
Size 983.6 kB
Tags CPython 3.11 macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
bac68ca114f2e25b85895cddbe142e42d206fa871c72628a3d4207b13d53af25
BLAKE2b-256 checksum
How to use checksums
2ab9fced28194eba44f2096ecdd66a73541d54e57fd1e52152704fcf574ea457
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for nimbus-bci 0.8.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page