Extensions
Turn any Python package into palette blocks — its own environment, its own sandboxed process, started only when you ask.
What an extension is
Everything in the default palette — MNE, PyTorch, scikit-learn, Hugging Face — runs inside BCILattice's own process, because we bundled it and vetted it together with the app. A extension is how you add a library we did not bundle, without touching that process at all.
Settings › Extensions ~/.bcilattice/extensions/<id>/
[Install] [Start] [Stop] ──spawns──▶ its own venv, its own server, its own port
your library lives here, not inside BCILattice
ML Suite palette
your library ▾ Custom Code (your library) ← the blocks it producedInstall a spec — a package name, a pin, a Git URL — and BCILattice creates a private Python environment for it, starts a small local server inside that environment, and introspects the library's public functions into palette blocks automatically. Nothing you write; the blocks, their ports and their settings come straight from the library's own type hints and defaults.
Bundled provider vs extension
Only one of these gets a process, a Start button, and a Settings page entry.
| Kind | Examples | Environment | Start button |
|---|---|---|---|
| Bundled | BCILattice, MNE, PyTorch, scikit-learn, Hugging Face, XGBoost | the app's own | no — always on |
| Extension | Cedalion, and anything on the curated shelf or a spec you type | its own venv | yes, in Settings › Extensions |
The palette itself does not visually separate them — an extension block sits in the canvas next to a bundled one and wires the same way. The difference only matters in Settings, and only because one of the two has a process to manage.
Cedalion: the free example
Cedalion — the IBS-Lab fNIRS/DOT toolbox — ships as an extension and only an extension: there is no in-process fallback and no bundled copy. Its blocks (Beer-Lambert conversion, motion correction, channel-quality metrics, DQR figures) exist once you install it from Settings › Extensions, and not before. It is free on every plan, because it is the extension mechanism's own demonstration — hiding it behind a paywall would hide the feature it is meant to show off.
Installing one
Where: Settings › Extensions.
Pick a spec
torch==1.2), a Git URL, a private index. What you type is the label everywhere it matters: on the palette, in the consent step, and in a saved graph's requirements.Review the consent step
Install
Start it when you want to run something
Curated shelf vs any spec
Free plans install from the curated shelf below — 24 packages we resolved, tested and licence-checked ourselves. Pro and Enterprise can install any spec: a private index, a GitHub URL, a local path. That boundary is deliberate, not just commercial — the free tier only ever runs code we vetted, so the overwhelming majority of installs never touch a package nobody here has looked at.
Start, stop, restart
Each extension row in Settings shows Start / Stop / Logs / Re-introspect / Uninstall. Stopping a library that has gone into an infinite loop cannot be asked nicely — a stuck call cannot be interrupted — so Stop escalates automatically through progressively firmer signals, and a Force stop option appears while the graceful attempt is still in progress rather than only after it times out. Restart does both steps as one action. An engine survives an app restart in its own right — stopping one that was left running from a previous session works the same way as stopping one from the current session.
One extension = one engine limit
The curated shelf
Every entry below was actually resolved and installed during vetting — nothing here is a package that turns out not to install. Licences are read from package metadata, not assumed, and only permissive licences are curated; a GPL package can still be installed as a custom spec on Pro, but it is your call to make, not ours to recommend.
fNIRS
| Cedalionsource build | Beer-Lambert conversion, motion correction, channel-quality metrics and DQR figures — the IBS-Lab fNIRS/DOT toolbox. | MIT |
EEG / MEG
| Autoreject | Automated epoch rejection and repair, thresholds fitted by cross-validation. | BSD-3 |
| PyPREP | The PREP pipeline: robust referencing, line-noise removal, bad-channel detection. | MIT |
| MNE-ICALabel | Automatic ICA component labelling — brain, muscle, eye, heart, line noise. | BSD-3 |
| MEEGkit | M/EEG denoising: ZapLine, DSS, star, robust detrending, ASR. | BSD-3 |
| MNE-Connectivity | Spectral and effective connectivity: coherence, PLI, wPLI, Granger causality. | BSD-3 |
| MNE-Features | Feature extraction over multivariate time series, sklearn-shaped API. | BSD-3 |
| MNE-BIDS | Read and write BIDS datasets for MEG, EEG and iEEG. | BSD-3 |
| pyXDF | Read XDF files — what a Lab Streaming Layer session records to. | BSD-2 |
| pybv | Write the BrainVision format, readable by most EEG software. | BSD-3 |
| eeglabio | Export to EEGLAB .set files, for collaborators working in MATLAB. | BSD-3 |
BCI decoding
| Braindecode | Deep learning for EEG — ShallowFBCSPNet, Deep4Net, EEGNet and the training utilities around them. | BSD-3 |
| MOABB | Mother of All BCI Benchmarks: standard public datasets and reproducible evaluation protocols. | BSD-3 |
Biosignals
| NeuroKit2 | ECG, EDA, EMG, RSP and PPG processing, with peak detection and HRV built in. | MIT |
| AntroPy | Entropy and complexity measures: sample, permutation, spectral, Higuchi, Petrosian. | BSD-3 |
| Tensorpac | Phase-amplitude coupling, with surrogate methods for testing significance. | BSD-3 |
| YASA | Sleep staging and event detection: spindles, slow waves, REMs. | BSD-3 |
| nolds | Nonlinear dynamics on 1-D series: Lyapunov exponents, DFA, correlation dimension. | MIT |
Dimensionality reduction
| UMAP | Uniform Manifold Approximation and Projection, with a transform for new data. | BSD-3 |
| openTSNE | Parallel t-SNE that can embed new points into an existing map. | BSD-3 |
Acquisition
| BrainFlow | One API for many EEG/EMG/ECG headsets — OpenBCI, Muse, Neurosity, Cyton and others. | MIT |
| pylsl | Lab Streaming Layer: receive and send synchronised real-time streams. | MIT |
Neuroimaging
| NiBabel | Read and write neuroimaging formats: NIfTI, GIFTI, CIFTI, MGH, DICOM stacks. | MIT |
| Nilearn | Statistical learning on neuroimaging volumes: masking, decoding, connectomes, plotting. | BSD-3 |
Already-bundled libraries never appear here twice — mne-nirs and pyriemann, for instance, look like they belong under fNIRS but ship with the app already, so their blocks already live under the MNE palette.
How a library becomes blocks
No one hand-writes an extension's blocks. Installing runs the library's own public functions and classes through an introspector, which reads their type hints, defaults and docstrings and emits the same kind of catalog row the bundled palette is built from — so an extension block gets a real inspector, a tooltip and typed ports the same way any other block does.
Ports vs settings
What ends up wireable versus what ends up a field follows one rule:
| In the library's signature | Becomes |
|---|---|
| An array, a data frame, or one of the library’s own classes | an input port |
| A scalar with a default | a setting |
| A scalar with no default | a required setting |
| A fixed set of choices (an enum) | a dropdown setting |
| A physical quantity (e.g. a frequency) | a unit-suffixed setting, e.g. fmin_hz |
| Anything the introspector cannot classify | an input port — never silently dropped |
The result is a plain, inspectable catalog file — if the heuristic guesses a port wrong for a specific function, that is a text-file fix, not a code change.
Core vs advanced blocks
Introspection is deliberately greedy — it emits a block for every public callable it can classify. Cedalion alone yields 329. Shipping all 329 onto the front page would repeat the mistake the bundled catalog already made, so every generated block starts at advanced and stays out of the way.
An extension may then carry a small hand-written curated.yaml that promotes a handful of targets to core — giving each one a display name, a palette category, defaults and unit hints. Cedalion promotes 18, so a first-run fNIRS palette reads Cedalion SNIRF Reader, Cedalion Beer-Lambert, Cedalion Frequency Filter rather than cedalion.io.read_snirf and 328 siblings.
| Tier | Where it shows | Named by |
|---|---|---|
| core | Front of the palette category, on first run | The curated manifest, by hand |
| advanced | Behind the Advanced toggle, searchable | The library’s own dotted path |
An extension with no curation file is still perfectly usable — all of its blocks simply live at advanced. The file is plain YAML, so you can write your own for a library you installed and give it the front page you want.
Custom Code, per extension
Every installed extension also gets its own Custom Code (your library) block — a Pro feature. Your snippet runs inside that extension's own environment, with the library already imported and ready to use, so it can reach functionality the generated blocks do not cover without leaving the sandbox the rest of the extension runs in.
What is actually isolated
An extension runs code we did not write and did not review, so it is treated as a genuinely separate trust boundary, not just a separate folder:
- It runs as its own operating-system process, in its own Python environment, confined so it cannot spawn child processes and cannot exceed set memory and CPU limits.
- It receives no BCILattice or BCINexus credential — no login token, no licence key, no route back into the app's own local server.
- Only the library's own public functions are callable — a graph cannot reach an arbitrary Python builtin, because it was never introspected in the first place, not because it is filtered after the fact.
- General internet access is allowed by default (many libraries need it — an atlas download, a model checkout) and can be turned off per extension; a route back to BCINexus itself is refused unconditionally.
Stated plainly, not glossed over
Objects never leave the engine
A downstream block that consumes an extension's output does not receive the real object — it receives a small reference token. The extension's own server keeps the actual data and dereferences the token when the next block needs it. Plain numbers and small arrays cross normally; a large array crosses through a temporary file rather than being encoded into a network request. This is what lets two different, even conflicting, versions of the same library run at once without either one touching the other's data.
Free vs Pro vs Enterprise
| Capability | Free | Pro | Enterprise |
|---|---|---|---|
| Curated shelf (24 extensions), including Cedalion | ✓ | ✓ | ✓ |
| Start / stop an engine | ✓ | ✓ | ✓ |
| Simultaneous engines | limited | high | unlimited |
| Any spec — Git URL, local path, private index | — | ✓ | ✓ |
| Custom Code inside an extension | — | ✓ | ✓ |
| Org-wide allowlists and install audit | — | — | ✓ |
Downgrading never deletes an environment already on disk — anything already installed keeps working, only a further install is refused.
Sharing a graph that needs one
A pipeline or workflow that uses extension blocks records which extensions it needs, derived automatically from the blocks actually on the canvas. Opening someone else's graph, or importing one from Community, never installs anything on its own — you are shown what it asks for and choose whether to install and start each one yourself.
A requirement is a request, never an instruction
Publishing a study to BCINexus carries the same list, and it is shown on the study page before download — so a collaborator knows what they will need to install before they commit to downloading anything.
Troubleshooting
| Message | What to do |
|---|---|
| "…not running — start it in Settings › Extensions" | The engine was never started, or was stopped. Start it, then re-run. |
| Install fails partway through | The failing command is shown verbatim in the log — usually a dependency with no prebuilt wheel. Check whether the entry needs a source build and re-try if it timed out. |
| Engine will not respond to Stop | Use Force stop, offered next to the normal Stop button while the graceful attempt is still running. |
| A block from a colleague’s graph is greyed out | That extension is not installed here yet. Install it from the requirement shown on import, or from the curated shelf if it is on it. |
| Version shown differs from what the graph was built on | Reported, not blocked — extensions upgrade independently of the graphs that use them. Re-check results if the new version changed defaults. |
See also Using the ML Suite for where extension blocks fit into a pipeline, and Advanced Blocks for the bundled DL Training family they are commonly wired alongside.