ENH: Add JupyterLite CI/CD infrastructure#13925
Conversation
1e40137 to
f5bbad2
Compare
|
Hi @teonbrooks, Status Update: This PR is now ready for your review! As the first step in my GSoC roadmap, this PR successfully introduces the core JupyterLite infrastructure to the MNE-Python documentation. Here is what has been achieved:
I also looked deeply into the To gracefully handle this and prevent user confusion, I've written a custom For smaller datasets that are CORS-friendly, it automatically falls back to Pyodide's native I think this is the best architectural approach for handling the massive tutorials. I'm ready to mark this PR as complete so we can move down the GSoC checklist and start tackling |
|
Quick Follow-up: It turned out to be a race condition during the Sphinx |
10c4d98 to
c41059a
Compare
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
f0095cf to
9e6e288
Compare
…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 <[email protected]>
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
This reverts commit 60fcc7f.
This reverts commit 825740e.
|
Notes on where this is at. Data. Comes from The setup cell used to fetch ~403 MB at the start of every notebook. Now it takes 18 19 files have the launch button hidden — 31 files still show a badge but will fail on missing data. Seven datasets are
Each is either "bundle the dataset" or "add to the exclude list". 3D. |
Also note which pages can't run in the browser and why, so it's not a mystery when the JupyterLite badge is missing.
Copies one raw, one forward and the FreeSurfer surfaces out of the somato dataset CI already has on disk. Also fixes the 3D shim, which fetched surfaces from MNE-sample-data even when subjects_dir pointed elsewhere.
It built the 3D renderer before the time-course figure, so the whole call died in WASM and the notebook lost both. Draw the glass brain and the dipole markers with pyvista-js instead, and plot the time courses first.
compute_mne_inverse_volume needs the ~178 MB volume inverse and volume source estimates aren't rendered in the browser; mixed_source_space_inverse needs aseg.mgz plus the 3D src.plot(); 20_dipole_fit needs the BEM solution and nilearn downloads an MNI template at runtime, which CORS blocks.
Patch _get_renderer instead of reimplementing the 3D functions, so MNE still does its own geometry and coordinate-frame work and only the drawing is replaced. Gets plot_alignment and plot_bem working for the surfaces we serve. The interactive Brain/plot_field widget layer is still out of reach.
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:
jupyterlite-sphinxinto the Sphinx-Gallery pipeline, automatically generating "Try in JupyterLite" buttons for tutorials and examples.conf.pyto automatically handle Pyodide-specific browser quirks:mneandpyodide-httpnatively viamicropip.pyodide_http.patch_all()) so MNE'spoochdownloader can successfully fetch datasets from the browser.mne.viz.utils.plt_showto correctly render MNE's Matplotlib figures inline within the WebAssembly environment.Additional information
mainbranch version mismatches since JupyterLite currently pulls the stable MNE release.