Skip to content

ENH: add a xeus-python kernel for the JupyterLite docs#14093

Draft
natinew77-creator wants to merge 11 commits into
mne-tools:mainfrom
natinew77-creator:jupyterlite-xeus
Draft

ENH: add a xeus-python kernel for the JupyterLite docs#14093
natinew77-creator wants to merge 11 commits into
mne-tools:mainfrom
natinew77-creator:jupyterlite-xeus

Conversation

@natinew77-creator

@natinew77-creator natinew77-creator commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Draft. Exploring xeus-python as an alternative to the Pyodide kernel for the JupyterLite docs, since comparing the two is part of the GSoC evaluation (see #13925).

Scaffolding only so far:

  • doc/jupyterlite_environment.yml — the xeus kernel environment. The full MNE stack resolves for emscripten-wasm32 (numpy, scipy, matplotlib, pandas, scikit-learn, h5py, nibabel, ...).
  • doc/jupyterlite_xeus_setup_cell.py — first cut of the setup cell. It is much smaller than the Pyodide one because packages are pre-installed, so piplite and the lzma/multiprocessing/requests patches are not needed.
  • Not wired into conf.py yet, so the Pyodide build is untouched.

Still to do: conf.py wiring, serve the data as JupyterLite content so it mounts, port the pyvista-js 3D shim, then iterate on CI.

The setup cell probed /files/mne_data but never fetched anything, so
every data-dependent notebook failed on the first read and the rest of
the cells cascaded into NameErrors. The curated data is already served
at the docs root via html_extra_path, so fetch it the same way the
pyodide build does: a synchronous XMLHttpRequest into /tmp/mne_data,
small files eagerly and the heavy ones lazily via the reader shims, so
each notebook only downloads what it touches.

The sync request may set responseType='arraybuffer' because the kernel
runs in a web worker (the spec only forbids that on a Window), which
means this needs neither cross-origin isolation nor a service worker --
static hosts such as the CircleCI artifact server provide neither.

Also port the pyvista-js SourceEstimate.plot shim, which turned out to
have no kernel-specific code, and add pyvista-js to the environment
since the built kernel had no pyvista or vtk at all. Drop the
threadpoolctl patch (Pyodide-only API) but keep the generic WASM ones.
@natinew77-creator natinew77-creator changed the title ENH: explore xeus-python kernel for the JupyterLite docs ENH: add a xeus-python kernel for the JupyterLite docs Jul 23, 2026
The xeus kernel's WASM heap is capped at 2 GiB by the emscripten build
(bin/xpython.js requests WebAssembly.Memory with maximum=32768 pages),
and the 3D shim runs at the end of a notebook that is still holding the
raw, epochs and source estimates.

Build the meshes with int32 face indices and float32 points, which are
the types vtk.js uses anyway, and drop the KDTree query results and the
full-resolution surface arrays as soon as they are dead.

This is a small win (~14 MB retained, ~28 MB transient for the sample
surfaces) and is NOT on its own enough to keep 10_overview under the
cap -- that notebook still runs out of memory in the 3D cell.
SourceEstimate.plot defaults to hemi='lh' and 10_overview asks for
hemi='rh', but the shim always built both hemispheres, so it drew a
hemisphere the tutorial never asked for.

That is expensive as well as wrong. pyvista-js serialises every mesh to
JSON through .tolist(), which turns ~6.8 MB of surface arrays into
~55 MB of Python floats plus ~17 MB of text, and the browser then holds
a UTF-16 copy of that text. Skipping the unwanted hemisphere removes
about half of the scene.

Also centre the surface when only one hemisphere is drawn, instead of
applying the +/-60 mm split offset.
Experiment to check whether the 2 GiB cap is what kills 10_overview in
ica.plot_properties. Revert once we know.
The wasm declares max=32768 pages on its env.memory import, so handing it
a 4 GiB memory is a LinkError and the kernel never starts. Only an
emscripten-forge rebuild can raise it.
@natinew77-creator

Copy link
Copy Markdown
Contributor Author

Notes on where this is at.

Same as #13925 but with the xeus-python kernel. Packages are pre-installed at build
time from doc/jupyterlite_environment.yml, so no runtime piplite install and
none of the lzma/multiprocessing mocks. CircleCI needs micromamba for the
emscripten env.

Working. import mne runs in the browser (Python 3.13 XPython). Data works the
same way as #13925 — the setup cell fetches over HTTP into /tmp/mne_data with a
synchronous XMLHttpRequest; responseType='arraybuffer' is allowed because the
kernel runs in a web worker, so no COOP/COEP or service worker needed. Verified by
reading the 66 MB filtered raw and rendering the plots.

Blocked. 10_overview dies with Aborted(OOM) in ica.plot_properties.
emscripten-forge builds xeus-python with a 2 GB WASM heap; Pyodide ships 4 GB,
which is why the same notebook passes on #13925.

Can't be raised from here — patching the built xpython.js just stops the kernel
starting, since the wasm declares max=32768 pages on its own env.memory import.
Committed and reverted (0070e08, 4bced00) for the record. Needs an
emscripten-forge rebuild with -sMAXIMUM_MEMORY=4GB.

3D. pyvista-js shim carried over from #13925 unchanged, plus pyvista-js in the
environment. Untested — the notebook OOMs first. Found while porting: the shim
ignored the hemi argument and always drew both hemispheres. Fixed here in
bbc00ab; same bug is still in #13925.

Kernel env is 87 MB / 82 packages, fetched on kernel start.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant