ENH: JupyterLite integration for the documentation by natinew77-creator · Pull Request #13925 · mne-tools/mne-python · GitHub
Skip to content

ENH: JupyterLite integration for the documentation - #13925

Draft
natinew77-creator wants to merge 181 commits into
mne-tools:mainfrom
natinew77-creator:jupyterlite-gh-actions
Draft

natinew77-creator wants to merge 181 commits into
mne-tools:mainfrom
natinew77-creator:jupyterlite-gh-actions

Conversation

@natinew77-creator

@natinew77-creator natinew77-creator commented May 27, 2026

Copy link
Copy Markdown
Contributor

Tracking Issue: #13929

What does this implement/fix?
This PR integrates JupyterLite into the MNE-Python documentation build, allowing users to run tutorials interactively directly in their browser without a local Python environment.

Key technical implementations:

  • Integrates jupyterlite-sphinx into the Sphinx-Gallery pipeline, automatically generating "Try in JupyterLite" buttons for tutorials and examples.
  • Injects a hidden setup cell into the generated notebooks via conf.py to automatically handle Pyodide-specific browser quirks:
    • Installs mne and pyodide-http natively via micropip.
    • Patches Pyodide networking (pyodide_http.patch_all()) so MNE's pooch downloader can successfully fetch datasets from the browser.
    • Monkey-patches mne.viz.utils.plt_show to correctly render MNE's Matplotlib figures inline within the WebAssembly environment.

Additional information

  • This represents the completion of the first major GSoC milestone.
  • CircleCI will now automatically build the JupyterLite assets and provide a live preview link in the CI checks.
  • Note on limitations: During testing, I identified two architectural edge cases for future discussion: browser RAM limitations when tutorials attempt to download massive (>1GB) datasets, and occasional PyPI vs. main branch version mismatches since JupyterLite currently pulls the stable MNE release.
A28CDACE-D867-491E-8B11-013DD1635D4A 16D95C3F-B372-47D1-AFE3-D5E10395F775 0F9F0048-D585-4B8C-BE90-1E4D34BF1EEF

@natinew77-creator

natinew77-creator commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@natinew77-creator

Copy link
Copy Markdown
Contributor Author

Quick Follow-up:
I also tracked down and fixed the bug that prevented the notebooks from loading correctly in the JupyterLite UI.

It turned out to be a race condition during the Sphinx build-finished event—jupyterlite_sphinx was executing before sphinx_gallery had finished generating the example notebooks. I've reordered the extensions in conf.py so they execute in the correct sequence, and all the notebooks are now successfully populating!

Integrates jupyterlite-sphinx into the MNE-Python doc build so every
sphinx-gallery example gets a 'Try in Browser' button backed by a
Pyodide/WebAssembly kernel.

- doc/conf.py: configure jupyterlite_sphinx; build a local MNE dev
  wheel with relaxed Pyodide constraints; copy required MNE sample-data
  subset into JupyterLite's virtual filesystem; inject a setup cell that
  installs MNE via micropip (keep_going=True bypasses version conflicts),
  mocks missing stdlib modules (lzma, multiprocessing), patches pooch to
  block large OSF downloads, and sets MNE_DATA paths
- .circleci/config.yml: ensure MNE sample data is on disk before the
  doc build so conf.py can copy it into jupyterlite_contents/
- .github/workflows/jupyterlite.yml: standalone GH Actions workflow on
  the jupyterlite-gh-actions branch that builds and uploads the site
- pyproject.toml: add jupyterlite-pyodide-kernel and jupyterlite-sphinx
  to the [doc] extras
- .gitignore: exclude jupyterlite_contents build artifacts
- mne/parallel.py: return False early in _running_in_joblib_context()
  on emscripten; joblib parallel backends are unavailable in the browser
- mne/utils/config.py: catch Exception (not just ValueError) when
  loading the MNE config JSON; Pyodide's json parser raises SyntaxError
  on a corrupt or absent config file
Tutorials and examples that call interactive Qt backends (raw.plot(),
epochs.plot(), ica.plot_sources(), etc.) or depend on large datasets not
bundled in JupyterLite will hang or error in Pyodide. Wrap them with
sys.platform guards so they are skipped when running in the browser.

Interactive Qt plots (skip on emscripten):
- tutorials/intro/10_overview.py: raw.plot(), stc.plot()
- tutorials/intro/15_inplace.py: original_raw.plot(), rereferenced_raw.plot()
- tutorials/intro/20_events_from_raw.py: raw.copy().pick().plot(), raw.plot()
- tutorials/intro/40_sensor_locations.py: mne.viz.plot_alignment()
- tutorials/evoked/40_whitened.py: raw.plot(), epochs.plot()
- examples/preprocessing/muscle_ica.py: all ica.plot_* calls

Large datasets unavailable in the browser (raise RuntimeError on emscripten):
- tutorials/io/60_ctf_bst_auditory.py: BST auditory dataset (~2.9 GB)
- tutorials/io/70_reading_eyetracking_data.py: EyeLink misc dataset
- examples/visualization/eyetracking_plot_heatmap.py: EyeLink dataset
natinew77-creator and others added 13 commits June 26, 2026 09:35
…erLite

- doc/conf.py: Fix lzma mock to use real stdlib lzma when available in
  Pyodide instead of LZMAFile=object which broke joblib's compressor
  registration
- 10_overview.py: Guard ica.plot_properties() which opens an interactive
  Qt window
- 15_inplace.py: Guard set_eeg_reference block which fails under
  Python 3.13 in Pyodide
- 20_events_from_raw.py: Guard STIM channel plot and all EEGLAB sections
  that require the unavailable testing dataset
- 40_sensor_locations.py: Guard ssvep dataset loading and sphere plot
  that require the unavailable ssvep dataset
- 50_configure_mne.py: Guard KIT test data loading whose test files are
  stripped from the Pyodide wheel
- 70_report.py: Skip Report.save() file-writing in browser, guard
  nibabel-dependent add_bem, 3D methods (add_trans/add_stc/add_forward/
  add_inverse_operator), missing ECG/events files, pandas-dependent
  make_metadata, and the HDF5 round-trip section

All intro tutorials (10, 15, 20, 30, 40, 50, 70) now run cleanly in
JupyterLite/Pyodide without errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Overrides jupyterlite-sphinx's jupyterlite-core cap so the docs build picks up
pyodide-kernel 0.8.2, whose matplotlib clears MNE's minimum. Revert if the
notebooks break.
Same content as the PR 2 branch so the two do not conflict when it merges.
Drops the pyproject patching, which Pyodide 314 makes unnecessary.
Same content as the browser-runtime branch so the two do not conflict when it
merges.
Keeps the section identical to the split PR so the two do not conflict.
@natinew77-creator

Copy link
Copy Markdown
Contributor Author

Updated breakdown of the split, since the pieces divided a bit differently than I first described.

The renderer and the setup cell are separate because the setup cell appends the
renderer, so that one goes first. The last PR depends on all four, so it lands
last rather than in any order.

Resolved doc/conf.py: took upstream's import block from mne-tools#14140, which needs
tomllib and UTC, and restored the dynamic min_py read that goes with it.
Nothing in the repo produces files matching it.
Keeps the shared files identical so the branches do not conflict when the
split PRs merge.
@natinew77-creator

Copy link
Copy Markdown
Contributor Author

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.

3 participants