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.6 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, and paradigm recipes (MI · P300 · Riemannian).
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. A governed session runtime (drift gates, attribution, label budgets, quarantine) with subconscious error-potential self-supervision is in research and will ship separately.
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.
Optional shortcuts for common external trunks: wrap_eegnet, and
from nimbus_bci.middleware.adapters import wrap_braindecode
(see encoder contract).
Examples in this repo: 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.
A governed adaptation session — drift gates, label budgets, quarantine, and rollback — is under research and will ship as a separate release once its evidence matures.
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.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| nimbus_bci-0.7.0.tar.gz | 1.3 MB | Without provenance | Details |
Built distributions (wheels)
Total release size: 20.4 MB
Release files / nimbus_bci-0.7.0.tar.gz
| Download URL | nimbus_bci-0.7.0.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4b87e6af74a81f31ff9d15455f630bca97d14b79108c45586eef40e9e970af22
|
|
BLAKE2b-256 checksum How to use checksums |
0c013f9e2d02a485e54b9e09c908f2d5ff5a5106d294181fd03ed6338de47f53
|
| 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.7.0-cp313-cp313-win_amd64.whl
| Download URL | nimbus_bci-0.7.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 825.4 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
a6c5f322aa2b6c78ec2c73cf579161d702673674ac1f078b0f0816ba70597dc7
|
|
BLAKE2b-256 checksum How to use checksums |
4f71a1fe58f439bc0a897668fac6bec5711454b6221518660ba0638d1bd24159
|
| 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.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | nimbus_bci-0.7.0-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 |
2393965a935946eba8a5fa8567ff6ca24d63f2b66858942abb5c13b247f2e716
|
|
BLAKE2b-256 checksum How to use checksums |
8aa1c3f2547e2a85ed55f630999da082820c7e3029d9860c86666f93e6676d33
|
| 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.7.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | nimbus_bci-0.7.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 901.9 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e4606a40146d1344d4c8ee8c479774c0e6e1a531b0e1093654f1b3fff957c6f0
|
|
BLAKE2b-256 checksum How to use checksums |
2e9294bf612466e7dbc272f1de6ab2266a710386c0c5c85b56fef9e119fd8fbf
|
| 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.7.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | nimbus_bci-0.7.0-cp313-cp313-macosx_10_13_x86_64.whl |
|---|---|
| Size | 910.6 kB |
| Tags | CPython 3.13 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
9ec6ecd3d2ac5450ff534c3c846686989692d505f8c80f5a1d07d3f640fab0e0
|
|
BLAKE2b-256 checksum How to use checksums |
12a3aae91e0b0092383d8fb27323b9efc98a16a38c7a4e9953bad6bc7be26ca7
|
| 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.7.0-cp312-cp312-win_amd64.whl
| Download URL | nimbus_bci-0.7.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 829.5 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9f8de9c235a4ef989a95587e9cfc197e7958b6c545e7ce3efe356a8ab77988b5
|
|
BLAKE2b-256 checksum How to use checksums |
63c827e2d17a26913a0bddb9294f17a2175424eb0295966acfeac437d8efec15
|
| 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.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | nimbus_bci-0.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
b706ed62009ef68aa666423685331a6164fb458a2fc5e95e8c657726b30ccde0
|
|
BLAKE2b-256 checksum How to use checksums |
5f8ae47a9c206ab1b46cdd21dacb13392a50af088b9670824e09ea0c98b64c1d
|
| 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.7.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | nimbus_bci-0.7.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 906.5 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
05187363be6e5e0e6bc83d09280c6db7fb4a81d7595ad35e6238bceb6bd04b6a
|
|
BLAKE2b-256 checksum How to use checksums |
3462148b9e4e943f7a617651ef85d800ddcd4d82343dee931d5cd8988b07a3a4
|
| 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.7.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | nimbus_bci-0.7.0-cp312-cp312-macosx_10_13_x86_64.whl |
|---|---|
| Size | 913.7 kB |
| Tags | CPython 3.12 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
aeb80ccf6ac8fc46f285eb93377c5c0fb6f6776743428d01f33773124026e60a
|
|
BLAKE2b-256 checksum How to use checksums |
085aa6c65c935270615831c5199a0d097cfb2d37219a6c31cc0a63cf78ca2fff
|
| 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.7.0-cp311-cp311-win_amd64.whl
| Download URL | nimbus_bci-0.7.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 849.7 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
82bf058476d4f0a1e4506497e381b8a9a0fd18be05e9350eaf8a0cea3bc180c9
|
|
BLAKE2b-256 checksum How to use checksums |
a7afe92b8c06205d3963822f928203284dd8845c00d7a508c3656ad9c290210e
|
| 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.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | nimbus_bci-0.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
1a1c89bbf50af4b0c535346363ede644c81b1ff78d0c0eb1c3e5433b16d5afea
|
|
BLAKE2b-256 checksum How to use checksums |
b0a3062165bc533d80a132d7412b261419584d6f4cb011f8b2be8b9844f3b2ad
|
| 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.7.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | nimbus_bci-0.7.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 905.8 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
199bd6f11ffbd3a2b7704a21c7757069b0922a219587ecd26eb33b8ce08cbe7f
|
|
BLAKE2b-256 checksum How to use checksums |
b26bce2d331c6c9ebbaa0783215b56f1dec4cf38b1caa35457cea1a352ee2072
|
| 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.7.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | nimbus_bci-0.7.0-cp311-cp311-macosx_10_9_x86_64.whl |
|---|---|
| Size | 932.2 kB |
| Tags | CPython 3.11 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
84fbe8323a9a4d6e46d9b573ca4b83d4020bdb0fad0c3ccac452aed2733148b8
|
|
BLAKE2b-256 checksum How to use checksums |
a5fee4d6c93e254f4bc98080e2a8569081a0d69b2231b7fe3c6308629de3ed31
|
| 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.7.0
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.