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 import DecisionConfig
from nimbus_bci.safety import LabelBudget, SafetyConfig

p = Personalizer(
    encoder=enc, head="lda", classes=["left_hand", "right_hand"],
    decision=DecisionConfig(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"],
    decision=DecisionConfig(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, trust_dashboard.py (live safety dashboard — governor, drift, CUSUM revert), wallet_profile_roundtrip.py (the <30 KB neural-profile pass demo), lsl_live_loop.py (live lab ingest, --simulate without hardware), 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. Preregistered headline numbers: leaderboard.

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.2

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.2
File Size Uploaded Provenance
nimbus_bci-0.8.2.tar.gz 1.4 MB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for nimbus-bci 0.8.2
File
nimbus_bci-0.8.2-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
nimbus_bci-0.8.2-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.2-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.2-cp313-cp313-macosx_10_13_x86_64.whl CPython 3.13 CPython 3.13 macOS 10.13+ x86-64 Details
nimbus_bci-0.8.2-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
nimbus_bci-0.8.2-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.2-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.2-cp312-cp312-macosx_10_13_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.13+ x86-64 Details
nimbus_bci-0.8.2-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
nimbus_bci-0.8.2-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.28+ x86-64, Linux glibc 2.17+ x86-64 Details
nimbus_bci-0.8.2-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
nimbus_bci-0.8.2-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.2.tar.gz

Download URL nimbus_bci-0.8.2.tar.gz
Size 1.4 MB
Tags Source
SHA-256 checksum
How to use checksums
f47ede2c1caedef1d2708f7ae5711c5fd9d8358d82f83ee38efd0a00bd865781
BLAKE2b-256 checksum
How to use checksums
fe40cd07e4bf68ef8e8aee66fab294e7b6600479cd95977c0fe3fb69d07a8737
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.2-cp313-cp313-win_amd64.whl

Download URL nimbus_bci-0.8.2-cp313-cp313-win_amd64.whl
Size 877.1 kB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
138e221fb99564577bc5ec4c0033866dce78d2afef797c35dd09ca163b9a09ab
BLAKE2b-256 checksum
How to use checksums
f0fa62ddb82691a48717188c30fcd3f111b9854c13fbfb667f676d469a9ac6f0
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.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.2-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
b4d47fbab37431692633841e834f2912af5e4d0cd2ca6888ca6792cb2466b7d9
BLAKE2b-256 checksum
How to use checksums
91fcd720aaa5d8a4f20ee0c5a71e65d2c4d9908d75c6e8dbf864898be55a9a38
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.2-cp313-cp313-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.2-cp313-cp313-macosx_11_0_arm64.whl
Size 953.4 kB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
61cf8031f0d72e118b58b298a3233aa31969552419da6b5dbd7d5cd4c98cc198
BLAKE2b-256 checksum
How to use checksums
0f35c631f3fc5c115d772da49841abb9bb46482afc6fca73567bc7f09b58549d
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.2-cp313-cp313-macosx_10_13_x86_64.whl

Download URL nimbus_bci-0.8.2-cp313-cp313-macosx_10_13_x86_64.whl
Size 962.1 kB
Tags CPython 3.13 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
93974b922fb3039f07e6dd6d4805187983f617aa836e0ecd1e4454ac88cfea62
BLAKE2b-256 checksum
How to use checksums
0bed81b896b0574fb2b35b50bad7d26cae2877eab36a47a9128ac6725ffb2ee3
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.2-cp312-cp312-win_amd64.whl

Download URL nimbus_bci-0.8.2-cp312-cp312-win_amd64.whl
Size 881.2 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
36d659c4550b8386480e17de9f78fba44a76a5b7bc84a2547eb1af0779b7faf0
BLAKE2b-256 checksum
How to use checksums
f9af46c863eda0f3eb271c96e0a78013878d189f7882484e91364c4dc81408f1
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.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.2-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
8c3973da0df52747f93c3b7682f65aaaa2a5f3046a15e2bdb7fe4af735328936
BLAKE2b-256 checksum
How to use checksums
c13b3a616d3fdc5d2d271f5d0836dc4732c95749d46c57eefee8805340f26ef3
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.2-cp312-cp312-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.2-cp312-cp312-macosx_11_0_arm64.whl
Size 958.0 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d44dd95746df741e1c496f6d8bf70a2da8e1383057ee5f08ad76007cfe263bf7
BLAKE2b-256 checksum
How to use checksums
33b8f3f7e8bf4ef37b1091ae74d434aa2e76995535a952749691bc6cef3790b2
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.2-cp312-cp312-macosx_10_13_x86_64.whl

Download URL nimbus_bci-0.8.2-cp312-cp312-macosx_10_13_x86_64.whl
Size 965.2 kB
Tags CPython 3.12 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
01321bf907402658c8b7dca66f3be88091d207f30df20ccea922568f8b91676e
BLAKE2b-256 checksum
How to use checksums
758f436a809e79f84646591d6e22d1edc9e1ee24883e7f3155500836aa78fb8f
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.2-cp311-cp311-win_amd64.whl

Download URL nimbus_bci-0.8.2-cp311-cp311-win_amd64.whl
Size 901.4 kB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
5d7a145d090ea1bb939907a713884038830881f6c1d60bd34daeae5d755ca888
BLAKE2b-256 checksum
How to use checksums
23efbeebf2a3c76dc0bd5b2e3e2f05de564435bb56f55745f9a200dd10cb2d6a
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.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl

Download URL nimbus_bci-0.8.2-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
3a754a6f473c1179542305832dfce598e4471b7c6a06638d584ed1ccbff14861
BLAKE2b-256 checksum
How to use checksums
d5eff11626f91cc537d4daf92fb005c67e71288d37998afb17c03da316e96408
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.2-cp311-cp311-macosx_11_0_arm64.whl

Download URL nimbus_bci-0.8.2-cp311-cp311-macosx_11_0_arm64.whl
Size 957.3 kB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
be03195d372e913a27cacbbf010b83a22e10a6bf6ba37ae9f642db22c51ddf03
BLAKE2b-256 checksum
How to use checksums
49b6b2e1cf74bb2d9faf7e5e7089efcaf257e4f60f152c963b45b642bae02bf5
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.2-cp311-cp311-macosx_10_9_x86_64.whl

Download URL nimbus_bci-0.8.2-cp311-cp311-macosx_10_9_x86_64.whl
Size 983.7 kB
Tags CPython 3.11 macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
be3cc5ce6e3e33b2a4f7687b095254ebc984074d2590089925f573c50d6a78c1
BLAKE2b-256 checksum
How to use checksums
4cebd5a69fc8bb416cdbb02a72db7063f15073ec6107cbcf6fa10351dd3062b2
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.2

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