From 2fccdaf35ace0425462a97ccdefccc762b2d588f Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Thu, 20 Aug 2026 09:09:44 -0400 Subject: [PATCH 1/2] Convert ros3 streaming to remfile in User Guide Part I and NWBWidget demo Both notebooks opened DANDI assets with h5py's ros3 driver, which PyPI h5py wheels do not include, so they failed in CI and in the container image verification (and would fail for any pip-installed user). They now stream through remfile per docs/adding-notebooks.md, with remfile==0.1.14 added to their install cells (its dependencies were already pinned). Fixing ros3 unmasked a second blocker in the NWBWidget demo: its local- file section printed a download URL, asked the reader to click it, and then opened ~/Downloads/, which can never run headlessly. That section now downloads a 44 MB asset from the same dandiset programmatically to a temp path and opens that, preserving the point of the demonstration. Both notebooks verified green end to end inside freshly built container images. Co-Authored-By: Claude Fable 5 --- dandi/DANDI User Guide, Part I.ipynb | 5 +++- demos/NWBWidget-demo.ipynb | 34 ++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/dandi/DANDI User Guide, Part I.ipynb b/dandi/DANDI User Guide, Part I.ipynb index dffa6ef..2bb0a8b 100644 --- a/dandi/DANDI User Guide, Part I.ipynb +++ b/dandi/DANDI User Guide, Part I.ipynb @@ -146,6 +146,7 @@ " \"pytz==2025.2\" \\\n", " \"pyyaml==6.0.3\" \\\n", " \"referencing==0.37.0\" \\\n", + " \"remfile==0.1.14\" \\\n", " \"requests==2.32.4\" \\\n", " \"rfc3339-validator==0.1.4\" \\\n", " \"rfc3987==1.3.8\" \\\n", @@ -508,6 +509,8 @@ "metadata": {}, "outputs": [], "source": [ + "import h5py\n", + "import remfile\n", "from dandi.dandiapi import DandiAPIClient\n", "from pynwb import NWBHDF5IO\n", "from nwbwidgets import nwb2widget\n", @@ -516,7 +519,7 @@ " asset = client.get_dandiset(dandiset_id, \"draft\").get_asset_by_path(filepath)\n", " s3_url = asset.get_content_url(follow_redirects=1, strip_query=True)\n", "\n", - "io = NWBHDF5IO(s3_url, mode='r', load_namespaces=True, driver='ros3')\n", + "io = NWBHDF5IO(file=h5py.File(remfile.File(s3_url), \"r\"), load_namespaces=True)\n", "nwb = io.read()\n", "nwb2widget(nwb)" ] diff --git a/demos/NWBWidget-demo.ipynb b/demos/NWBWidget-demo.ipynb index c43f0c7..448ecdb 100644 --- a/demos/NWBWidget-demo.ipynb +++ b/demos/NWBWidget-demo.ipynb @@ -143,6 +143,7 @@ " \"pytz==2025.2\" \\\n", " \"pyyaml==6.0.3\" \\\n", " \"referencing==0.37.0\" \\\n", + " \"remfile==0.1.14\" \\\n", " \"requests==2.32.4\" \\\n", " \"rfc3339-validator==0.1.4\" \\\n", " \"rfc3987==1.3.8\" \\\n", @@ -273,8 +274,11 @@ "metadata": {}, "outputs": [], "source": [ - "# use the \"Read Only S3\" (ros3) driver to stream data directly from DANDI (or any other S3 location)\n", - "io = NWBHDF5IO(s3_path, mode='r', load_namespaces=True, driver='ros3')\n", + "# stream data directly from DANDI (or any other S3 location) with remfile\n", + "import h5py\n", + "import remfile\n", + "\n", + "io = NWBHDF5IO(file=h5py.File(remfile.File(s3_path), \"r\"), load_namespaces=True)\n", "\n", "nwb = io.read()\n", "nwb2widget(nwb)" @@ -287,7 +291,7 @@ "source": [ "## Running NWBWidgets locally\n", "\n", - "You can also download NWB files and run NWB Widgets locally by passing the local path (and omitting the ros3 driver flag). Here we will demonstrate this with a smaller intracellular electrophysiology dataset." + "You can also download NWB files and run NWB Widgets locally by passing the local path directly. Here we will demonstrate this with a smaller intracellular electrophysiology dataset." ] }, { @@ -297,10 +301,23 @@ "metadata": {}, "outputs": [], "source": [ - "# icephys, optogenetics, and behavior, Svoboda Lab (632 MB)\n", - "dandiset_id, filepath = \"000005\", \"sub-anm324650/sub-anm324650_ses-20160422_behavior+icephys+ogen.nwb\"\n", + "# icephys and behavior, Svoboda Lab (44 MB)\n", + "import os\n", + "import tempfile\n", + "\n", + "import requests\n", + "\n", + "dandiset_id, filepath = \"000005\", \"sub-anm236462/sub-anm236462_ses-20140210_behavior+icephys.nwb\"\n", "asset_id = get_asset_id(dandiset_id, filepath)\n", - "print(f\"https://api.dandiarchive.org/api/dandisets/{dandiset_id}/versions/draft/assets/{asset_id}/download/\")" + "url = f\"https://api.dandiarchive.org/api/dandisets/{dandiset_id}/versions/draft/assets/{asset_id}/download/\"\n", + "\n", + "local_path = os.path.join(tempfile.gettempdir(), os.path.basename(filepath))\n", + "with requests.get(url, stream=True) as r:\n", + " r.raise_for_status()\n", + " with open(local_path, \"wb\") as f:\n", + " for chunk in r.iter_content(chunk_size=1 << 20):\n", + " f.write(chunk)\n", + "print(f\"downloaded to {local_path}\")" ] }, { @@ -308,7 +325,7 @@ "id": "39565072", "metadata": {}, "source": [ - "click the link that is output above ^^" + "The file is now on the local filesystem, so we can open it by passing its path to `NWBHDF5IO` directly." ] }, { @@ -318,8 +335,7 @@ "metadata": {}, "outputs": [], "source": [ - "import os.path as op\n", - "io = NWBHDF5IO(op.expanduser(\"~/Downloads/sub-anm324650_ses-20160422_behavior+icephys+ogen.nwb\"), mode='r', load_namespaces=True)\n", + "io = NWBHDF5IO(local_path, mode='r', load_namespaces=True)\n", "\n", "nwb = io.read()\n", "nwb2widget(nwb)" From 4b9aa814ae92efe8ba9bc51d35294d8caac84a1e Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Thu, 20 Aug 2026 09:25:52 -0400 Subject: [PATCH 2/2] Split remfile wrapping, add DiskCache, backfill requirements.in The remfile wrapping now sits on its own lines with a disk cache, per the streaming guidance in docs/adding-notebooks.md. Both notebooks gain a committed requirements file per the new submission contract (a shared requirements.in for demos/, and a per-notebook file for Part I since Part II shares the dandi/ directory), with install cells re-locked from those files via lock_notebook.py. Re-locking surfaced a real conflict: nwbwidgets 0.9.1 only imports on the pre-2024 stack (pynwb<3, hdmf<4, zarr<3; newer zarr removed zarr.core.Array and newer hdmf cannot resolve its bundled ndx-spectrum spec), while a DANDI-server-compatible client needs dandi>=0.76.5, which requires pynwb>=3.1 under Colab's current click pin. The demos notebook, whose subject is NWB Widgets and which does not use the dandi client, keeps the bounded old stack. The User Guide, whose subject is the dandi client, drops nwbwidgets and shows files via pynwb's rich Jupyter repr instead, on the current stack (dandi 0.77, pynwb 4.1). Both notebooks verified green end to end inside freshly built container images. Co-Authored-By: Claude Fable 5 --- dandi/DANDI User Guide, Part I.ipynb | 100 ++++---------- .../DANDI User Guide, Part I.requirements.in | 6 + demos/NWBWidget-demo.ipynb | 124 ++++++++++-------- demos/requirements.in | 7 + 4 files changed, 105 insertions(+), 132 deletions(-) create mode 100644 dandi/DANDI User Guide, Part I.requirements.in create mode 100644 demos/requirements.in diff --git a/dandi/DANDI User Guide, Part I.ipynb b/dandi/DANDI User Guide, Part I.ipynb index 2bb0a8b..df33a2e 100644 --- a/dandi/DANDI User Guide, Part I.ipynb +++ b/dandi/DANDI User Guide, Part I.ipynb @@ -39,110 +39,67 @@ " \"aiosignal==1.4.0\" \\\n", " \"annotated-types==0.7.0\" \\\n", " \"arrow==1.4.0\" \\\n", - " \"asciitree==0.3.3\" \\\n", " \"attrs==26.1.0\" \\\n", - " \"backcall==0.2.0\" \\\n", - " \"bids-validator-deno==2.4.1\" \\\n", - " \"bidsschematools==1.2.2\" \\\n", + " \"bids-validator-deno==3.0.1\" \\\n", + " \"bidsschematools==1.2.7\" \\\n", " \"blessed==1.38.0\" \\\n", - " \"bqplot==0.12.45\" \\\n", " \"certifi==2026.4.22\" \\\n", " \"cffi==2.0.0\" \\\n", " \"charset-normalizer==3.4.7\" \\\n", " \"ci-info==0.4.0\" \\\n", - " \"click==8.1.8\" \\\n", + " \"click==8.3.3\" \\\n", " \"click-didyoumean==0.3.1\" \\\n", - " \"comm==0.2.3\" \\\n", - " \"contourpy==1.3.3\" \\\n", " \"cryptography==43.0.3\" \\\n", - " \"cycler==0.12.1\" \\\n", - " \"dandi==0.74.3\" \\\n", - " \"dandischema==0.12.1\" \\\n", - " \"decorator==4.4.2\" \\\n", - " \"deprecated==1.3.1\" \\\n", + " \"dandi==0.77.0\" \\\n", + " \"dandischema==0.14.0\" \\\n", + " \"deno==2.9.5\" \\\n", " \"dnspython==2.8.0\" \\\n", + " \"donfig==0.8.1.post1\" \\\n", " \"email-validator==2.3.0\" \\\n", " \"etelemetry==0.3.1\" \\\n", " \"fasteners==0.20\" \\\n", - " \"fonttools==4.62.1\" \\\n", " \"fqdn==1.5.1\" \\\n", " \"frozenlist==1.8.0\" \\\n", " \"fscacher==0.4.4\" \\\n", " \"fsspec==2025.3.0\" \\\n", - " \"gast==0.7.0\" \\\n", + " \"google-crc32c==1.8.0\" \\\n", " \"h5py==3.16.0\" \\\n", - " \"hdmf==3.14.6\" \\\n", - " \"hdmf-zarr==0.11.3\" \\\n", + " \"hdmf==6.2.0\" \\\n", " \"humanize==4.15.0\" \\\n", " \"idna==3.13\" \\\n", - " \"imageio==2.37.3\" \\\n", - " \"importlib-metadata==4.13.0\" \\\n", " \"interleave==0.3.0\" \\\n", - " \"ipydatagrid==1.4.0\" \\\n", - " \"ipydatawidgets==4.3.2\" \\\n", - " \"ipyfilechooser==0.6.0\" \\\n", - " \"ipympl==0.10.0\" \\\n", - " \"ipython==7.34.0\" \\\n", - " \"ipython-genutils==0.2.0\" \\\n", - " \"ipyvolume==0.6.3\" \\\n", - " \"ipyvue==1.12.0\" \\\n", - " \"ipyvuetify==1.11.3\" \\\n", - " \"ipywebrtc==0.6.0\" \\\n", - " \"ipywidgets==8.1.8\" \\\n", " \"isodate==0.7.2\" \\\n", " \"isoduration==20.11.0\" \\\n", " \"jaraco-classes==3.4.0\" \\\n", " \"jaraco-context==6.1.2\" \\\n", " \"jaraco-functools==4.4.0\" \\\n", - " \"jedi==0.20.0\" \\\n", " \"jeepney==0.9.0\" \\\n", " \"joblib==1.5.3\" \\\n", " \"jsonpointer==3.1.1\" \\\n", " \"jsonschema==4.26.0\" \\\n", " \"jsonschema-specifications==2025.9.1\" \\\n", - " \"jupyterlab-widgets==3.0.16\" \\\n", " \"keyring==25.7.0\" \\\n", " \"keyrings-alt==5.0.2\" \\\n", - " \"kiwisolver==1.5.0\" \\\n", - " \"lazy-loader==0.5\" \\\n", - " \"matplotlib==3.10.0\" \\\n", - " \"matplotlib-inline==0.2.1\" \\\n", " \"ml-dtypes==0.5.4\" \\\n", " \"more-itertools==10.8.0\" \\\n", " \"multidict==6.7.1\" \\\n", " \"natsort==8.4.0\" \\\n", - " \"ndx-grayscalevolume==0.0.2\" \\\n", - " \"ndx-icephys-meta==0.1.0\" \\\n", - " \"ndx-spectrum==0.2.2\" \\\n", - " \"networkx==3.6.1\" \\\n", - " \"numcodecs==0.15.1\" \\\n", + " \"numcodecs==0.16.5\" \\\n", " \"numpy==2.0.2\" \\\n", - " \"nwbinspector==0.6.3\" \\\n", - " \"nwbwidgets==0.11.3\" \\\n", + " \"nwbinspector==0.7.2\" \\\n", " \"packaging==26.1\" \\\n", " \"pandas==2.2.2\" \\\n", - " \"parso==0.8.6\" \\\n", - " \"pexpect==4.9.0\" \\\n", - " \"pickleshare==0.7.5\" \\\n", - " \"pillow==11.3.0\" \\\n", " \"platformdirs==4.9.6\" \\\n", - " \"plotly==5.13.1\" \\\n", - " \"prompt-toolkit==3.0.52\" \\\n", " \"propcache==0.4.1\" \\\n", - " \"ptyprocess==0.7.0\" \\\n", - " \"py2vega==0.7.0\" \\\n", " \"pycparser==3.0\" \\\n", " \"pycryptodomex==3.23.0\" \\\n", " \"pydantic==2.12.3\" \\\n", " \"pydantic-core==2.41.4\" \\\n", " \"pydantic-settings==2.14.0\" \\\n", - " \"pygments==2.20.0\" \\\n", - " \"pynwb==2.8.3\" \\\n", + " \"pynwb==4.1.0\" \\\n", " \"pyout==0.8.1\" \\\n", - " \"pyparsing==3.3.2\" \\\n", " \"python-dateutil==2.9.0.post0\" \\\n", " \"python-dotenv==1.2.2\" \\\n", - " \"pythreejs==2.4.2\" \\\n", " \"pytz==2025.2\" \\\n", " \"pyyaml==6.0.3\" \\\n", " \"referencing==0.37.0\" \\\n", @@ -152,20 +109,12 @@ " \"rfc3987==1.3.8\" \\\n", " \"rpds-py==0.30.0\" \\\n", " \"ruamel-yaml==0.19.1\" \\\n", - " \"scikit-image==0.25.2\" \\\n", - " \"scipy==1.16.3\" \\\n", " \"secretstorage==3.5.0\" \\\n", " \"semantic-version==2.10.0\" \\\n", - " \"setuptools==75.2.0\" \\\n", " \"six==1.17.0\" \\\n", " \"tenacity==9.1.4\" \\\n", " \"tensorstore==0.1.82\" \\\n", - " \"threadpoolctl==3.6.0\" \\\n", - " \"tifffile==2026.4.11\" \\\n", " \"tqdm==4.67.3\" \\\n", - " \"traitlets==5.7.1\" \\\n", - " \"traittypes==0.2.3\" \\\n", - " \"trimesh==4.12.2\" \\\n", " \"typing-extensions==4.15.0\" \\\n", " \"typing-inspection==0.4.2\" \\\n", " \"tzdata==2026.1\" \\\n", @@ -173,12 +122,9 @@ " \"urllib3==2.5.0\" \\\n", " \"wcwidth==0.6.0\" \\\n", " \"webcolors==25.10.0\" \\\n", - " \"widgetsnbextension==4.0.15\" \\\n", - " \"wrapt==2.1.2\" \\\n", " \"yarl==1.23.0\" \\\n", - " \"zarr==2.18.7\" \\\n", - " \"zarr-checksum==0.4.7\" \\\n", - " \"zipp==3.23.1\"" + " \"zarr==3.1.5\" \\\n", + " \"zarr-checksum==0.4.7\"" ] }, { @@ -457,9 +403,9 @@ "id": "7d980104-839e-44ae-8031-ae3d20517eb7", "metadata": {}, "source": [ - "## Visualization NWB files\n", + "## Inspecting NWB Files\n", "\n", - "NWB Widgets is a library of interactive data visualizations that works automatically with any NWB file. This can be very useful for visually confirming any conversion." + "PyNWB objects render a rich, expandable summary in Jupyter, which is a quick way to visually confirm the contents of a conversion." ] }, { @@ -469,9 +415,7 @@ "metadata": {}, "outputs": [], "source": [ - "from nwbwidgets import nwb2widget\n", - "\n", - "nwb2widget(nwbfile)" + "nwbfile" ] }, { @@ -479,7 +423,7 @@ "id": "1fa27484-8e53-4a6d-a755-a5718806c69f", "metadata": {}, "source": [ - "It can also be used to explore any file shared on DANDI. You can use the DANDI API to access the s3 path of any file and stream it directly into NWB Widgets." + "The same works for any file shared on DANDI. You can use the DANDI API to access the S3 path of any file and stream it directly, inspecting it without downloading the whole file." ] }, { @@ -513,15 +457,17 @@ "import remfile\n", "from dandi.dandiapi import DandiAPIClient\n", "from pynwb import NWBHDF5IO\n", - "from nwbwidgets import nwb2widget\n", "\n", "with DandiAPIClient() as client:\n", " asset = client.get_dandiset(dandiset_id, \"draft\").get_asset_by_path(filepath)\n", " s3_url = asset.get_content_url(follow_redirects=1, strip_query=True)\n", "\n", - "io = NWBHDF5IO(file=h5py.File(remfile.File(s3_url), \"r\"), load_namespaces=True)\n", + "# cache reads on disk so re-running cells doesn't re-fetch the same bytes\n", + "rem_file = remfile.File(s3_url, disk_cache=remfile.DiskCache(\"nwb-cache\"))\n", + "h5py_file = h5py.File(rem_file, \"r\")\n", + "io = NWBHDF5IO(file=h5py_file, load_namespaces=True)\n", "nwb = io.read()\n", - "nwb2widget(nwb)" + "nwb" ] } ], diff --git a/dandi/DANDI User Guide, Part I.requirements.in b/dandi/DANDI User Guide, Part I.requirements.in new file mode 100644 index 0000000..8e20504 --- /dev/null +++ b/dandi/DANDI User Guide, Part I.requirements.in @@ -0,0 +1,6 @@ +dandi>=0.74.0 +h5py +numpy +pynwb +python-dateutil +remfile diff --git a/demos/NWBWidget-demo.ipynb b/demos/NWBWidget-demo.ipynb index 448ecdb..d644c74 100644 --- a/demos/NWBWidget-demo.ipynb +++ b/demos/NWBWidget-demo.ipynb @@ -33,149 +33,160 @@ "# pinned below so the notebook is reproducible regardless of resolver drift.\n", "!pip install -q uv\n", "!uv pip install --system \\\n", - " \"acres==0.5.0\" \\\n", " \"aiohappyeyeballs==2.6.1\" \\\n", " \"aiohttp==3.13.5\" \\\n", " \"aiosignal==1.4.0\" \\\n", - " \"annotated-types==0.7.0\" \\\n", + " \"anyio==4.13.0\" \\\n", + " \"argon2-cffi==25.1.0\" \\\n", + " \"argon2-cffi-bindings==25.1.0\" \\\n", " \"arrow==1.4.0\" \\\n", " \"asciitree==0.3.3\" \\\n", " \"attrs==26.1.0\" \\\n", " \"backcall==0.2.0\" \\\n", - " \"bidsschematools==1.2.2\" \\\n", - " \"blessed==1.38.0\" \\\n", + " \"beautifulsoup4==4.13.5\" \\\n", + " \"bleach==6.3.0\" \\\n", " \"bqplot==0.12.45\" \\\n", + " \"ccfwidget==0.5.3\" \\\n", " \"certifi==2026.4.22\" \\\n", " \"cffi==2.0.0\" \\\n", " \"charset-normalizer==3.4.7\" \\\n", - " \"ci-info==0.4.0\" \\\n", " \"click==8.3.3\" \\\n", - " \"click-didyoumean==0.3.1\" \\\n", - " \"comm==0.2.3\" \\\n", + " \"cloudpickle==3.1.2\" \\\n", + " \"colorcet==3.1.0\" \\\n", " \"contourpy==1.3.3\" \\\n", - " \"cryptography==43.0.3\" \\\n", " \"cycler==0.12.1\" \\\n", - " \"dandi==0.68.1\" \\\n", - " \"dandischema==0.11.1\" \\\n", + " \"dask==2026.3.0\" \\\n", + " \"debugpy==1.8.15\" \\\n", " \"decorator==4.4.2\" \\\n", + " \"defusedxml==0.7.1\" \\\n", " \"deprecated==1.3.1\" \\\n", - " \"dnspython==2.8.0\" \\\n", - " \"email-validator==2.3.0\" \\\n", - " \"etelemetry==0.3.1\" \\\n", + " \"entrypoints==0.4\" \\\n", " \"fasteners==0.20\" \\\n", + " \"fastjsonschema==2.21.2\" \\\n", " \"fonttools==4.62.1\" \\\n", " \"fqdn==1.5.1\" \\\n", " \"frozenlist==1.8.0\" \\\n", - " \"fscacher==0.4.4\" \\\n", " \"fsspec==2025.3.0\" \\\n", - " \"gast==0.7.0\" \\\n", " \"h5py==3.16.0\" \\\n", " \"hdmf==3.14.6\" \\\n", - " \"hdmf-zarr==0.11.3\" \\\n", - " \"humanize==4.15.0\" \\\n", " \"idna==3.13\" \\\n", " \"imageio==2.37.3\" \\\n", - " \"importlib-metadata==4.13.0\" \\\n", - " \"interleave==0.3.0\" \\\n", - " \"ipydatagrid==1.4.0\" \\\n", - " \"ipydatawidgets==4.3.2\" \\\n", - " \"ipyfilechooser==0.6.0\" \\\n", + " \"ipydatawidgets==4.3.5\" \\\n", + " \"ipykernel==6.17.1\" \\\n", " \"ipympl==0.10.0\" \\\n", " \"ipython==7.34.0\" \\\n", " \"ipython-genutils==0.2.0\" \\\n", + " \"ipytree==0.2.2\" \\\n", " \"ipyvolume==0.6.3\" \\\n", - " \"ipyvue==1.12.0\" \\\n", - " \"ipyvuetify==1.11.3\" \\\n", + " \"ipyvue==3.0.0\" \\\n", + " \"ipyvuetify==3.0.0\" \\\n", " \"ipywebrtc==0.6.0\" \\\n", - " \"ipywidgets==8.1.8\" \\\n", - " \"isodate==0.7.2\" \\\n", + " \"ipywidgets==7.7.1\" \\\n", " \"isoduration==20.11.0\" \\\n", - " \"jaraco-classes==3.4.0\" \\\n", - " \"jaraco-context==6.1.2\" \\\n", - " \"jaraco-functools==4.4.0\" \\\n", + " \"itk-core==5.4.7\" \\\n", + " \"itk-filtering==5.4.7\" \\\n", + " \"itk-meshtopolydata==0.11.1\" \\\n", + " \"itk-numerics==5.4.7\" \\\n", + " \"itkwidgets==0.32.4\" \\\n", " \"jedi==0.20.0\" \\\n", - " \"jeepney==0.9.0\" \\\n", - " \"joblib==1.5.3\" \\\n", + " \"jinja2==3.1.6\" \\\n", " \"jsonpointer==3.1.1\" \\\n", " \"jsonschema==4.26.0\" \\\n", " \"jsonschema-specifications==2025.9.1\" \\\n", + " \"jupyter-client==7.4.9\" \\\n", + " \"jupyter-core==5.9.1\" \\\n", + " \"jupyter-events==0.12.1\" \\\n", + " \"jupyter-server==2.14.0\" \\\n", + " \"jupyter-server-terminals==0.5.4\" \\\n", + " \"jupyterlab-pygments==0.3.0\" \\\n", " \"jupyterlab-widgets==3.0.16\" \\\n", - " \"keyring==25.7.0\" \\\n", - " \"keyrings-alt==5.0.2\" \\\n", " \"kiwisolver==1.5.0\" \\\n", + " \"lark==1.3.1\" \\\n", " \"lazy-loader==0.5\" \\\n", + " \"locket==1.0.0\" \\\n", + " \"markupsafe==3.0.3\" \\\n", " \"matplotlib==3.10.0\" \\\n", " \"matplotlib-inline==0.2.1\" \\\n", - " \"ml-dtypes==0.5.4\" \\\n", - " \"more-itertools==10.8.0\" \\\n", + " \"mistune==3.2.0\" \\\n", " \"multidict==6.7.1\" \\\n", - " \"natsort==8.4.0\" \\\n", + " \"nbclassic==1.3.3\" \\\n", + " \"nbclient==0.10.4\" \\\n", + " \"nbconvert==7.17.1\" \\\n", + " \"nbformat==5.10.4\" \\\n", " \"ndx-grayscalevolume==0.0.2\" \\\n", " \"ndx-icephys-meta==0.1.0\" \\\n", " \"ndx-spectrum==0.2.2\" \\\n", + " \"nest-asyncio==1.6.0\" \\\n", " \"networkx==3.6.1\" \\\n", + " \"notebook==6.5.7\" \\\n", + " \"notebook-shim==0.2.4\" \\\n", " \"numcodecs==0.15.1\" \\\n", " \"numpy==2.0.2\" \\\n", - " \"nwbinspector==0.6.3\" \\\n", - " \"nwbwidgets==0.11.3\" \\\n", + " \"nwbwidgets==0.9.1\" \\\n", + " \"overrides==7.7.0\" \\\n", " \"packaging==26.1\" \\\n", " \"pandas==2.2.2\" \\\n", + " \"pandocfilters==1.5.1\" \\\n", " \"parso==0.8.6\" \\\n", + " \"partd==1.4.2\" \\\n", " \"pexpect==4.9.0\" \\\n", " \"pickleshare==0.7.5\" \\\n", " \"pillow==11.3.0\" \\\n", " \"platformdirs==4.9.6\" \\\n", - " \"plotly==5.13.1\" \\\n", + " \"plotly==5.24.1\" \\\n", + " \"prometheus-client==0.25.0\" \\\n", " \"prompt-toolkit==3.0.52\" \\\n", " \"propcache==0.4.1\" \\\n", + " \"psutil==5.9.5\" \\\n", " \"ptyprocess==0.7.0\" \\\n", - " \"py2vega==0.7.0\" \\\n", " \"pycparser==3.0\" \\\n", - " \"pycryptodomex==3.23.0\" \\\n", - " \"pydantic==2.12.3\" \\\n", - " \"pydantic-core==2.41.4\" \\\n", " \"pygments==2.20.0\" \\\n", " \"pynwb==2.8.3\" \\\n", - " \"pyout==0.8.1\" \\\n", " \"pyparsing==3.3.2\" \\\n", " \"python-dateutil==2.9.0.post0\" \\\n", + " \"python-json-logger==4.1.0\" \\\n", " \"pythreejs==2.4.2\" \\\n", " \"pytz==2025.2\" \\\n", " \"pyyaml==6.0.3\" \\\n", + " \"pyzmq==26.2.1\" \\\n", " \"referencing==0.37.0\" \\\n", " \"remfile==0.1.14\" \\\n", " \"requests==2.32.4\" \\\n", " \"rfc3339-validator==0.1.4\" \\\n", - " \"rfc3987==1.3.8\" \\\n", + " \"rfc3986-validator==0.1.1\" \\\n", + " \"rfc3987-syntax==1.1.0\" \\\n", " \"rpds-py==0.30.0\" \\\n", " \"ruamel-yaml==0.19.1\" \\\n", " \"scikit-image==0.25.2\" \\\n", " \"scipy==1.16.3\" \\\n", - " \"secretstorage==3.5.0\" \\\n", - " \"semantic-version==2.10.0\" \\\n", + " \"send2trash==2.1.0\" \\\n", " \"setuptools==75.2.0\" \\\n", " \"six==1.17.0\" \\\n", + " \"soupsieve==2.8.3\" \\\n", " \"tenacity==9.1.4\" \\\n", - " \"tensorstore==0.1.82\" \\\n", - " \"threadpoolctl==3.6.0\" \\\n", + " \"terminado==0.18.1\" \\\n", " \"tifffile==2026.4.11\" \\\n", + " \"tinycss2==1.4.0\" \\\n", + " \"toolz==0.12.1\" \\\n", + " \"tornado==6.5.1\" \\\n", " \"tqdm==4.67.3\" \\\n", " \"traitlets==5.7.1\" \\\n", " \"traittypes==0.2.3\" \\\n", - " \"trimesh==4.12.2\" \\\n", + " \"trimesh==5.0.0\" \\\n", " \"typing-extensions==4.15.0\" \\\n", - " \"typing-inspection==0.4.2\" \\\n", " \"tzdata==2026.1\" \\\n", " \"uri-template==1.3.0\" \\\n", " \"urllib3==2.5.0\" \\\n", " \"wcwidth==0.6.0\" \\\n", " \"webcolors==25.10.0\" \\\n", - " \"widgetsnbextension==4.0.15\" \\\n", + " \"webencodings==0.5.1\" \\\n", + " \"websocket-client==1.9.0\" \\\n", + " \"widgetsnbextension==3.6.10\" \\\n", " \"wrapt==2.1.2\" \\\n", + " \"xarray==2025.12.0\" \\\n", " \"yarl==1.23.0\" \\\n", " \"zarr==2.18.7\" \\\n", - " \"zarr-checksum==0.4.7\" \\\n", - " \"zipp==3.23.1\"" + " \"zstandard==0.25.0\"" ] }, { @@ -278,7 +289,10 @@ "import h5py\n", "import remfile\n", "\n", - "io = NWBHDF5IO(file=h5py.File(remfile.File(s3_path), \"r\"), load_namespaces=True)\n", + "# cache reads on disk so re-running cells doesn't re-fetch the same bytes\n", + "rem_file = remfile.File(s3_path, disk_cache=remfile.DiskCache(\"nwb-cache\"))\n", + "h5py_file = h5py.File(rem_file, \"r\")\n", + "io = NWBHDF5IO(file=h5py_file, load_namespaces=True)\n", "\n", "nwb = io.read()\n", "nwb2widget(nwb)" diff --git a/demos/requirements.in b/demos/requirements.in new file mode 100644 index 0000000..8daf987 --- /dev/null +++ b/demos/requirements.in @@ -0,0 +1,7 @@ +h5py +nwbwidgets +pynwb<3 +remfile +requests +hdmf<4 +zarr<3