|
| 1 | +"""First-cut JupyterLite setup cell for the xeus-python kernel. |
| 2 | +
|
| 3 | +WIP / CI-PENDING -- this is the xeus counterpart of the Pyodide |
| 4 | +``first_notebook_cell`` in ``conf.py``. It cannot be fully validated locally |
| 5 | +(needs the real ``build_docs`` CI run); the assumptions marked ``CI-VERIFY`` |
| 6 | +below are the ones to confirm/iterate on first. |
| 7 | +
|
| 8 | +Why it is so much smaller than the Pyodide cell |
| 9 | +------------------------------------------------ |
| 10 | +xeus-python is real CPython compiled to WebAssembly (NOT Pyodide), and the |
| 11 | +packages are PRE-INSTALLED at build time from ``jupyterlite_environment.yml``. |
| 12 | +So compared to the Pyodide setup cell this drops: |
| 13 | +
|
| 14 | + * ``piplite.install(...)`` -> MNE is already installed |
| 15 | + * the ``lzma`` / ``multiprocessing`` mocks -> real CPython stdlib is present |
| 16 | + * the ``pyodide.http`` / ``requests`` patches and the ``js`` XHR fetch shims |
| 17 | + -> no Pyodide runtime to patch |
| 18 | +
|
| 19 | +What remains is just: point MNE's dataset loaders at the bundled data, block |
| 20 | +accidental OSF downloads, and (still to port) the pyvista-js 3D shim. |
| 21 | +
|
| 22 | +Data model (CI-VERIFY) |
| 23 | +---------------------- |
| 24 | +With ``XeusAddon.mount_jupyterlite_content=True`` the served JupyterLite content |
| 25 | +is mounted into the kernel filesystem at ``/files``. So the curated MNE data |
| 26 | +must be made available as JupyterLite content (a conf.py change: add the curated |
| 27 | +``mne_data`` tree to ``jupyterlite_contents`` instead of only ``html_extra_path``). |
| 28 | +The cell below resolves the data root by probing the likely mount locations so it |
| 29 | +is robust to which mechanism ends up being used. Because every bundled file is |
| 30 | +then present in the FS, NO runtime fetch is needed -- the loaders just return the |
| 31 | +folder (much simpler than the Pyodide lazy-fetch shims). |
| 32 | +
|
| 33 | +conf.py wiring this expects (next step, not yet applied so the pyodide build |
| 34 | +stays intact on this branch): |
| 35 | +
|
| 36 | + from jupyterlite_xeus_setup_cell import XEUS_FIRST_NOTEBOOK_CELL |
| 37 | + jupyterlite_build_command_options = { |
| 38 | + "XeusAddon.environment_file": "jupyterlite_environment.yml", |
| 39 | + "XeusAddon.mount_jupyterlite_content": True, |
| 40 | + } |
| 41 | + sphinx_gallery_conf["first_notebook_cell"] = XEUS_FIRST_NOTEBOOK_CELL |
| 42 | +""" |
| 43 | + |
| 44 | +# The setup cell source, as a string (mirrors how conf.py stores the Pyodide one). |
| 45 | +XEUS_FIRST_NOTEBOOK_CELL = r"""# 💡 Auto-added setup cell (xeus-python kernel). |
| 46 | +# MNE and its dependencies are pre-installed in this kernel; this cell only |
| 47 | +# points the dataset loaders at the pre-bundled data. |
| 48 | +import os |
| 49 | +from pathlib import Path as _Path |
| 50 | +import mne |
| 51 | +
|
| 52 | +# --- locate the bundled MNE data (mounted from the served jupyterlite content) --- |
| 53 | +# CI-VERIFY: confirm the actual mount path (/files with mount_jupyterlite_content). |
| 54 | +_candidates = ["/files/mne_data", "/drive/mne_data", os.path.expanduser("~/mne_data")] |
| 55 | +mne_data_path = next((p for p in _candidates if os.path.isdir(p)), "/files/mne_data") |
| 56 | +os.makedirs(mne_data_path, exist_ok=True) |
| 57 | +os.environ["MNE_DATA"] = mne_data_path |
| 58 | +
|
| 59 | +# Pre-create a valid empty config so MNE never hits a corrupt read. |
| 60 | +_cfg = mne.get_config_path() |
| 61 | +os.makedirs(os.path.dirname(_cfg), exist_ok=True) |
| 62 | +if not os.path.exists(_cfg): |
| 63 | + with open(_cfg, "w") as _f: |
| 64 | + _f.write("{}") |
| 65 | +mne.set_config("MNE_DATA", mne_data_path) |
| 66 | +for _ds in ["SAMPLE", "TESTING", "SSVEP", "EEGBCI", "KILOWORD", "ERP_CORE", "MTRF"]: |
| 67 | + mne.set_config(f"MNE_DATASETS_{_ds}_PATH", mne_data_path) |
| 68 | +
|
| 69 | +# --- point dataset loaders at the pre-bundled folders (no OSF download) --- |
| 70 | +# Everything is already on the filesystem, so these just return the folder -- |
| 71 | +# no lazy fetching needed (unlike the Pyodide build). |
| 72 | +def _lite_folder(_name): |
| 73 | + def _data_path(*_a, **_kw): |
| 74 | + return _Path(mne_data_path) / _name |
| 75 | + return _data_path |
| 76 | +
|
| 77 | +mne.datasets.sample.data_path = _lite_folder("MNE-sample-data") |
| 78 | +mne.datasets.kiloword.data_path = _lite_folder("MNE-kiloword-data") |
| 79 | +mne.datasets.erp_core.data_path = _lite_folder("MNE-ERP-CORE-data") |
| 80 | +mne.datasets.mtrf.data_path = _lite_folder("mTRF_1.5") |
| 81 | +
|
| 82 | +def _lite_eegbci_load_data(subject, runs, *_a, **_kw): |
| 83 | + _runs = [runs] if isinstance(runs, (int, float)) else list(runs) |
| 84 | + _subs = list(subject) if isinstance(subject, (list, tuple)) else [subject] |
| 85 | + _base = _Path(mne_data_path) / "MNE-eegbci-data" / "files" / "eegmmidb" / "1.0.0" |
| 86 | + return [ |
| 87 | + _base / f"S{int(s):03d}" / f"S{int(s):03d}R{int(r):02d}.edf" |
| 88 | + for s in _subs |
| 89 | + for r in _runs |
| 90 | + ] |
| 91 | +mne.datasets.eegbci.load_data = _lite_eegbci_load_data |
| 92 | +
|
| 93 | +# Block accidental OSF downloads (data is pre-bundled or simply unavailable here). |
| 94 | +import pooch |
| 95 | +_orig_pooch_fetch = pooch.Pooch.fetch |
| 96 | +def _lite_pooch_fetch(self, fname, processor=None, downloader=None): |
| 97 | + if "osf.io" in self.get_url(fname): |
| 98 | + raise RuntimeError( |
| 99 | + f"Cannot download {fname!r} in JupyterLite: open this notebook from " |
| 100 | + "mne.tools where the data is pre-bundled, or run it locally." |
| 101 | + ) |
| 102 | + return _orig_pooch_fetch(self, fname, processor=processor, downloader=downloader) |
| 103 | +pooch.Pooch.fetch = _lite_pooch_fetch |
| 104 | +
|
| 105 | +# TODO(CI-VERIFY): port the pyvista-js SourceEstimate.plot 3D shim from the |
| 106 | +# Pyodide conf.py cell -- the approach (JS-native pyvista-js) is kernel-agnostic |
| 107 | +# per the plan, only the import/interop path needs confirming under xeus. |
| 108 | +""" |
0 commit comments