diff --git a/src/silx/io/blissdatah5.py b/src/silx/io/blissdatah5.py new file mode 100644 index 0000000000..bed29ac0c4 --- /dev/null +++ b/src/silx/io/blissdatah5.py @@ -0,0 +1,91 @@ +import logging +from typing import Generator, Literal + +import numpy + +from . import commonh5 + +from blissdata.h5api import abstract as abc +from blissdata.h5api.redis_hdf5 import File + +_logger = logging.getLogger(__name__) + + +class BlissDataH5(commonh5.File): + def __init__( + self, + name: str, + mode: Literal["r"] | None = None, + attrs: dict | None = None, + ) -> None: + if mode not in ("r", None): + raise ValueError(f"Unsupported mode: {mode}") + + if attrs is None: + attrs = {} + + self.__file = File(name) + + super().__init__(name, mode, attrs={**self.__file.attrs, **attrs}) + + for child in _children(self.__file): + self.add_node(child) + + _logger.warning( + "blissdata support is a preview feature: This may change or be removed without notice." + ) + + def close(self) -> None: + super().close() + self.__file.close() + self.__file = None + + +class BlissDataGroup(commonh5.LazyLoadableGroup): + def __init__( + self, + name: str, + group: abc.Group, + parent: BlissDataH5 | "BlissDataGroup" | None = None, + attrs: dict | None = None, + ) -> None: + super().__init__(name, parent, attrs) + self.__group = group + + def _create_child(self) -> None: + for child in _children(self.__group): + self.add_node(child) + + +class BlissDataDataset(commonh5.Dataset): + + @property + def shape(self) -> tuple[int, ...]: + return self._get_data().shape + + @property + def size(self) -> int: + return self._get_data().size + + def __len__(self) -> int: + return len(self._get_data()) + + def __getitem__(self, item): + if isinstance(item, tuple) and len(item): + return self._get_data()[()][item] + return self._get_data()[item] + + @property + def value(self) -> numpy.ndarray: + return self._get_data()[()] + + +def _children(group: abc.Group) -> Generator[BlissDataDataset | BlissDataGroup]: + for name in group.keys(): + item = group[name] + if isinstance(item, abc.Group): + yield BlissDataGroup(name, item, parent=group, attrs=item.attrs) + elif isinstance(item, abc.Dataset): + yield BlissDataDataset(name, item, parent=group, attrs=item.attrs) + else: + _logger.warning(f"Cannot map child {name}: Ignored") diff --git a/src/silx/io/meson.build b/src/silx/io/meson.build index e3deb3e28d..beb9f16d55 100644 --- a/src/silx/io/meson.build +++ b/src/silx/io/meson.build @@ -4,6 +4,7 @@ subdir('specfile') py.install_sources([ '__init__.py', '_sliceh5.py', + 'blissdatah5.py', 'commonh5.py', 'configdict.py', 'convert.py', diff --git a/src/silx/io/url.py b/src/silx/io/url.py index 67b9e2be46..636fed7692 100644 --- a/src/silx/io/url.py +++ b/src/silx/io/url.py @@ -134,7 +134,7 @@ class DataUrl: be false. """ - _SCHEMES = ("fabio", "silx", "http", "https") + _SCHEMES = ("fabio", "silx", "http", "https", "blissdata") def __init__( self, diff --git a/src/silx/io/utils.py b/src/silx/io/utils.py index 98800f06da..a5e0dc7d1e 100644 --- a/src/silx/io/utils.py +++ b/src/silx/io/utils.py @@ -699,6 +699,14 @@ def open(filename): # pylint:disable=redefined-builtin h5_file = _open_local_file(url.file_path()) elif url.scheme() in ("http", "https"): return _open_url_with_h5pyd(filename) + elif url.scheme() == "blissdata": + try: + from .blissdatah5 import BlissDataH5 + except ImportError: + raise IOError( + f"blissdata support is not available, cannot open: {filename}" + ) + h5_file = BlissDataH5(url.file_path()) else: raise OSError(f"Unsupported URL scheme {url.scheme}: {filename}")