From 54d39b7615c69e1b92c34e9ba0f04763e4577ce4 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Fri, 15 Nov 2024 21:59:35 -0600 Subject: [PATCH 1/5] add read_nwb_method --- docs/gallery/general/plot_read_basics.py | 13 ++--- requirements-opt.txt | 3 ++ src/pynwb/__init__.py | 66 ++++++++++++++++++++++++ tests/integration/hdf5/test_io.py | 6 ++- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/docs/gallery/general/plot_read_basics.py b/docs/gallery/general/plot_read_basics.py index d800adbe5..be40ede40 100644 --- a/docs/gallery/general/plot_read_basics.py +++ b/docs/gallery/general/plot_read_basics.py @@ -30,7 +30,6 @@ import matplotlib.pyplot as plt import numpy as np -from pynwb import NWBHDF5IO #################### # We will access NWB data on the `DANDI Archive `_, @@ -103,14 +102,16 @@ # read the data into a :py:class:`~pynwb.file.NWBFile` object. filepath = "sub-P11HMH_ses-20061101_ecephys+image.nwb" -# Open the file in read mode "r", -io = NWBHDF5IO(filepath, mode="r") -nwbfile = io.read() +from pynwb import read_nwb + +nwbfile = read_nwb(filepath) nwbfile ####################################### -# :py:class:`~pynwb.NWBHDF5IO` can also be used as a context manager: +# For more advance use, use the :py:class:`~pynwb.NWBHDF5IO`. Here, we show how it can be used as a context manager +# to read data from an NWB file in a more controlled way: +from pynwb import NWBHDF5IO with NWBHDF5IO(filepath, mode="r") as io2: nwbfile2 = io2.read() @@ -291,4 +292,4 @@ # ----------------------- # It is good practice, especially on Windows, to close any files that you have opened. -io.close() +nwbfile.get_read_io().close() diff --git a/requirements-opt.txt b/requirements-opt.txt index da62bc314..1758f6ee3 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -6,3 +6,6 @@ oaklib==0.5.32; python_version >= "3.9" fsspec==2024.10.0 requests==2.32.3 aiohttp==3.10.10 + +# For read_nwb tests +hdmf-zarr \ No newline at end of file diff --git a/src/pynwb/__init__.py b/src/pynwb/__init__.py index 3a4d95e98..98d95002a 100644 --- a/src/pynwb/__init__.py +++ b/src/pynwb/__init__.py @@ -535,6 +535,72 @@ def read_nwb(**kwargs): return nwbfile +@docval({'name': 'path', 'type': (str, Path), 'doc': 'the path to the nwbfile'}, + is_method=False) +def read_nwb(**kwargs): + """Read an NWB file from a local path or remote URL. + + Provides a simple, high-level interface for reading NWB files in the most + common use cases. Automatically handles both HDF5 and Zarr formats. + For advanced use cases (parallel I/O, custom namespaces), use NWBHDF5IO or NWBZarrIO. + + Parameters + ---------- + path : str or pathlib.Path + Path to the NWB file. Can be either a local filesystem path to an HDF5 (.nwb) + or Zarr (.zarr) file + + Returns + ------- + pynwb.NWBFile + The loaded NWB file object. + + See Also + -------- + pynwb.NWBHDF5IO : Core I/O class for HDF5 files with advanced options. + hdmf_zarr.nwb.NWBZarrIO : Core I/O class for Zarr files with advanced options. + + Notes + ----- + This function uses the following defaults: + * Always opens in read-only mode + * Automatically loads namespaces + * Detects file format based on extension + * Automatically handles local and remote paths + + Advanced features requiring direct use of IO classes include: + * Streaming data from s3 + * Custom namespace extensions + * Parallel I/O with MPI + * Custom build managers + * Write or append modes + * Pre-opened HDF5 file objects or Zarr stores + * Remote file access configuration + + Examples + -------- + Read a local NWB file: + + >>> from pynwb import read_nwb + >>> nwbfile = read_nwb("path/to/file.nwb") + + + """ + + path = popargs('path', kwargs) + backend_is_hdf5 = NWBHDF5IO.can_read(path=path) + if backend_is_hdf5: + return NWBHDF5IO.read_nwb(path=path) + else: + from hdmf_zarr import NWBZarrIO + backend_is_zarr = NWBZarrIO.can_read(path=path) + if backend_is_zarr: + return NWBZarrIO.read_nwb(path=path) + else: + raise ValueError(f"Unsupported backend for file: {path}") + + + from . import io as __io # noqa: F401,E402 from .core import NWBContainer, NWBData # noqa: F401,E402 from .base import TimeSeries, ProcessingModule # noqa: F401,E402 diff --git a/tests/integration/hdf5/test_io.py b/tests/integration/hdf5/test_io.py index 1e6ed0593..545b590de 100644 --- a/tests/integration/hdf5/test_io.py +++ b/tests/integration/hdf5/test_io.py @@ -613,7 +613,6 @@ def test_read_nwb_method_file(self): io.write(self.nwbfile) import h5py - file = h5py.File(self.path, 'r') read_nwbfile = NWBHDF5IO.read_nwb(file=file) @@ -627,4 +626,7 @@ def test_read_nwb_method_s3_path(self): read_nwbfile = NWBHDF5IO.read_nwb(path=s3_test_path) assert read_nwbfile.identifier == "3f77c586-6139-4777-a05d-f603e90b1330" - assert read_nwbfile.subject.subject_id == "1" \ No newline at end of file + assert read_nwbfile.subject.subject_id == "1" + + + From f8ce5587797668947e12da41478c7bb99611da8b Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Fri, 15 Nov 2024 22:08:14 -0600 Subject: [PATCH 2/5] add tests --- tests/integration/io/test_read.py | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/integration/io/test_read.py diff --git a/tests/integration/io/test_read.py b/tests/integration/io/test_read.py new file mode 100644 index 000000000..a70489d4f --- /dev/null +++ b/tests/integration/io/test_read.py @@ -0,0 +1,48 @@ +from pathlib import Path +import tempfile + +from pynwb import read_nwb +from pynwb.testing.mock.file import mock_NWBFile +from pynwb.testing import TestCase + +import unittest +try: + from hdmf_zarr import NWBZarrIO # noqa f401 + HAVE_NWBZarrIO = True +except ImportError: + HAVE_NWBZarrIO = False + + +class TestReadNWBMethod(TestCase): + """ + Test that H5DataIO functions correctly on round trip with the HDF5IO backend + """ + def setUp(self): + self.nwbfile = mock_NWBFile() + + + def test_read_nwb_hdf5(self): + from pynwb import NWBHDF5IO + + with tempfile.TemporaryDirectory() as temp_dir: + path = Path(temp_dir) / "test.nwb" + with NWBHDF5IO(path, 'w') as io: + io.write(self.nwbfile) + + read_nwbfile = read_nwb(path=path) + self.assertContainerEqual(read_nwbfile, self.nwbfile) + read_nwbfile.get_read_io().close() + + @unittest.skipIf(not HAVE_NWBZarrIO, "NWBZarrIO library not available") + def test_read_zarr(self): + # from pynwb import NWBZarrIO + from hdmf_zarr import NWBZarrIO + + with tempfile.TemporaryDirectory() as temp_dir: + path = Path(temp_dir) / "test.nwb" + with NWBZarrIO(path, 'w') as io: + io.write(self.nwbfile) + + read_nwbfile = read_nwb(path=path) + self.assertContainerEqual(read_nwbfile, self.nwbfile) + read_nwbfile.get_read_io().close() \ No newline at end of file From d1064c0d8bbfd8283b754f45cca5ed7214f4bb04 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Fri, 15 Nov 2024 22:14:28 -0600 Subject: [PATCH 3/5] docstring and tutorial editions --- docs/gallery/general/plot_read_basics.py | 5 +++-- src/pynwb/__init__.py | 7 +++---- tests/integration/hdf5/test_io.py | 6 ++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/gallery/general/plot_read_basics.py b/docs/gallery/general/plot_read_basics.py index be40ede40..b13888ee0 100644 --- a/docs/gallery/general/plot_read_basics.py +++ b/docs/gallery/general/plot_read_basics.py @@ -108,8 +108,9 @@ nwbfile ####################################### -# For more advance use, use the :py:class:`~pynwb.NWBHDF5IO`. Here, we show how it can be used as a context manager -# to read data from an NWB file in a more controlled way: +# For more advanced use cases, the :py:class:~pynwb.NWBHDF5IO class provides additional functionality. +# Below, we demonstrate how :py:class:~pynwb.NWBHDF5IO can be used as a context manager +# to read data from an NWB file in a more controlled manner: from pynwb import NWBHDF5IO with NWBHDF5IO(filepath, mode="r") as io2: diff --git a/src/pynwb/__init__.py b/src/pynwb/__init__.py index 98d95002a..e8f2540b0 100644 --- a/src/pynwb/__init__.py +++ b/src/pynwb/__init__.py @@ -540,9 +540,9 @@ def read_nwb(**kwargs): def read_nwb(**kwargs): """Read an NWB file from a local path or remote URL. - Provides a simple, high-level interface for reading NWB files in the most - common use cases. Automatically handles both HDF5 and Zarr formats. - For advanced use cases (parallel I/O, custom namespaces), use NWBHDF5IO or NWBZarrIO. + High-level interface for reading NWB files. Automatically handles both HDF5 + and Zarr formats. For advanced use cases (parallel I/O, custom namespaces), + use NWBHDF5IO or NWBZarrIO. Parameters ---------- @@ -566,7 +566,6 @@ def read_nwb(**kwargs): * Always opens in read-only mode * Automatically loads namespaces * Detects file format based on extension - * Automatically handles local and remote paths Advanced features requiring direct use of IO classes include: * Streaming data from s3 diff --git a/tests/integration/hdf5/test_io.py b/tests/integration/hdf5/test_io.py index 545b590de..f17a7217a 100644 --- a/tests/integration/hdf5/test_io.py +++ b/tests/integration/hdf5/test_io.py @@ -613,6 +613,7 @@ def test_read_nwb_method_file(self): io.write(self.nwbfile) import h5py + file = h5py.File(self.path, 'r') read_nwbfile = NWBHDF5IO.read_nwb(file=file) @@ -626,7 +627,4 @@ def test_read_nwb_method_s3_path(self): read_nwbfile = NWBHDF5IO.read_nwb(path=s3_test_path) assert read_nwbfile.identifier == "3f77c586-6139-4777-a05d-f603e90b1330" - assert read_nwbfile.subject.subject_id == "1" - - - + assert read_nwbfile.subject.subject_id == "1" \ No newline at end of file From 36e4965cee3e231ad0871796c018ed78a47bfbb1 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Fri, 15 Nov 2024 22:16:09 -0600 Subject: [PATCH 4/5] more docstring corrections --- src/pynwb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pynwb/__init__.py b/src/pynwb/__init__.py index e8f2540b0..abcfee671 100644 --- a/src/pynwb/__init__.py +++ b/src/pynwb/__init__.py @@ -538,7 +538,7 @@ def read_nwb(**kwargs): @docval({'name': 'path', 'type': (str, Path), 'doc': 'the path to the nwbfile'}, is_method=False) def read_nwb(**kwargs): - """Read an NWB file from a local path or remote URL. + """Read an NWB file from a local path. High-level interface for reading NWB files. Automatically handles both HDF5 and Zarr formats. For advanced use cases (parallel I/O, custom namespaces), From 46f0d4162a24aa5edadbf7273242f733b7a6e11e Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Fri, 15 Nov 2024 22:18:24 -0600 Subject: [PATCH 5/5] CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6eead1f..137c2a634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements and minor changes * Added `NWBHDF5IO.read_nwb` convenience method to simplify reading an NWB file. @h-mayorquin [#1979](https://github.com/NeurodataWithoutBorders/pynwb/pull/1979) * Removed unused references to region references and builders in preparation for changes in HDMF 4.0. @rly [#1991](https://github.com/NeurodataWithoutBorders/pynwb/pull/1991) +* Added `pynwb.read_nwb` convenience method to simplif yreading an NWBFile written with any backend @h-mayorquin [#1994](https://github.com/NeurodataWithoutBorders/pynwb/pull/1994) ### Documentation and tutorial enhancements - Added documentation example for `SpikeEventSeries`. @stephprince [#1983](https://github.com/NeurodataWithoutBorders/pynwb/pull/1983)