Skip to content

Commit

Permalink
Merge pull request #220 from scipp/load-function
Browse files Browse the repository at this point in the history
Implement high-level load function
  • Loading branch information
jl-wynen authored Jul 12, 2024
2 parents 2f79d1d + 0d0bff5 commit e8df317
Show file tree
Hide file tree
Showing 9 changed files with 414 additions and 18 deletions.
10 changes: 1 addition & 9 deletions docs/api-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,5 @@
compute_positions
create_field
create_class
```

## Submodules

```{eval-rst}
.. autosummary::
:toctree: ../generated/modules
:template: module-template.rst
:recursive:
load
```
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
}

intersphinx_mapping = {
'h5py': ('https://docs.h5py.org/en/stable/', None),
'python': ('https://docs.python.org/3', None),
'numpy': ('https://numpy.org/doc/stable/', None),
'scipp': ('https://scipp.github.io/', None),
Expand Down
57 changes: 55 additions & 2 deletions docs/user-guide/quick-start-guide.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,67 @@
"filename = data.get_path('PG3_4844_event.nxs')"
]
},
{
"cell_type": "markdown",
"id": "dc5c3080-fcca-4118-8fd2-f22f108b1ee6",
"metadata": {},
"source": [
"## Loading files\n",
"\n",
"Given such a NeXus file, we can load the entire file using [snx.load](../generated/functions/scippnexus.load.rst):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9eac3b09-3b36-464b-821a-4d2617d9fdae",
"metadata": {},
"outputs": [],
"source": [
"import scippnexus as snx\n",
"\n",
"data = snx.load(filename)\n",
"data"
]
},
{
"cell_type": "markdown",
"id": "8fc979f9-92fc-44d3-b77c-14efc9b2de34",
"metadata": {},
"source": [
"[snx.load](../generated/functions/scippnexus.load.rst) supports selecting part of a file to load:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05389cfd-2870-413f-a3a7-5ca791d82281",
"metadata": {},
"outputs": [],
"source": [
"bank102 = snx.load(filename, root='entry/bank102')\n",
"bank102"
]
},
{
"cell_type": "markdown",
"id": "e0161b14-8313-4da6-878d-0ed79a3d23a4",
"metadata": {},
"source": [
"This is a simpler and less powerful version of the interface described below."
]
},
{
"cell_type": "markdown",
"id": "4425ba02-9f53-4783-bdf0-3e966672e3d6",
"metadata": {},
"source": [
"## Opening files\n",
"\n",
"Given such a NeXus file, we first need to open it.\n",
"It is often useful to only load part of a file or inspecting the file structure without loading any data.\n",
"ScippNexus provides an interface that is similar to [h5py](https://docs.h5py.org/en/stable/) for this purpose.\n",
"\n",
"We first need to open the file using [snx.File](../generated/classes/scippnexus.File.rst).\n",
"Wherever possible this should be done using a context manager as follows:"
]
},
Expand Down Expand Up @@ -355,7 +408,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.16"
"version": "3.10.14"
}
},
"nbformat": 4,
Expand Down
1 change: 1 addition & 0 deletions src/scippnexus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
)
from .field import Attrs, Field
from .file import File
from ._load import load
from .nexus_classes import *
from .nxtransformations import compute_positions, zip_pixel_offsets
119 changes: 119 additions & 0 deletions src/scippnexus/_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
import contextlib
import io
from os import PathLike

import h5py as h5
import scipp as sc

from .base import (
DefaultDefinitions,
DefaultDefinitionsType,
Group,
base_definitions,
)
from .file import File
from .typing import Definitions, ScippIndex


def load(
filename: str | PathLike[str] | io.BytesIO | h5.Group | Group,
*,
root: str | None = None,
select: ScippIndex = (),
definitions: Definitions | DefaultDefinitionsType = DefaultDefinitions,
) -> sc.DataGroup | sc.DataArray | sc.Dataset:
"""Load a NeXus file.
This function is a shorthand for opening a file manually.
That is
.. code-block:: python
loaded = snx.load('path/to/nexus_file.nxs')
is equivalent to
.. code-block:: python
with snx.File('path/to/nexus_file.nxs') as f:
loaded = f[()]
The additional arguments of ``load`` are used as:
.. code-block:: python
loaded = snx.load(
'path/to/nexus_file.nxs'
root='entry/instrument',
select={'x': slice(None, 100)},
definitions=my_definitions,
)
which corresponds to
.. code-block:: python
with snx.File('path/to/nexus_file.nxs', definitions=my_definitions) as f:
loaded = f['entry/instrument']['x', :100]
Parameters
----------
filename:
One of:
- A path to a NeXus file.
- A file-like object containing a NeXus file.
- A :class:`h5py.Group`.
- A :class:`scippnexus.Group`.
root:
The root group in the NeXus file to load.
If not provided
- Everything is loaded under the given group if ``filename`` is a group.
- Or the entire file is loaded otherwise.
select:
Selects a subset of the data to load.
Corresponds to the argument passed in brackets when using file objects:
``loaded = group[select]``.
See `Loading groups and datasets
<../../user-guide/quick-start-guide.html#Loading-groups-and-datasets>`_.
Defaults to ``()`` which selects the entire data.
definitions:
NeXus `application definitions <../../user-guide/application-definitions.rst>`_.
Defaults to the ScippNexus base definitions.
Returns
-------
:
The loaded data.
"""
with _open(filename, definitions=definitions) as group:
if root is not None:
group = group[root]
return group[select]


def _open(
filename: str | PathLike[str] | io.BytesIO | h5.Group | Group,
definitions: Definitions | DefaultDefinitionsType = DefaultDefinitions,
):
if isinstance(filename, h5.Group):
return contextlib.nullcontext(
Group(
filename,
definitions=base_definitions()
if definitions is DefaultDefinitions
else definitions,
)
)
if isinstance(filename, Group):
if definitions is not DefaultDefinitions:
raise TypeError(
'Cannot override application definitions. '
'The `definitions` argument must not be used '
'When the file is specified as a scippnexus.Group.'
)
return contextlib.nullcontext(filename)
return File(filename, 'r', definitions=definitions)
8 changes: 8 additions & 0 deletions src/scippnexus/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,11 @@ def base_definitions() -> dict[str, type]:
affecting the original.
"""
return dict(base_definitions_dict)


class DefaultDefinitionsType:
def __repr__(self) -> str:
return "DefaultDefinitions"


DefaultDefinitions = DefaultDefinitionsType()
20 changes: 14 additions & 6 deletions src/scippnexus/file.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
# @author Simon Heybrock
from collections.abc import Mapping
from contextlib import AbstractContextManager

import h5py

from .base import Group, base_definitions

_default_definitions = object()
from .base import (
DefaultDefinitions,
DefaultDefinitionsType,
Group,
base_definitions,
)
from .typing import Definitions


class File(AbstractContextManager, Group):
def __init__(self, *args, definitions: Mapping = _default_definitions, **kwargs):
def __init__(
self,
*args,
definitions: Definitions | DefaultDefinitionsType = DefaultDefinitions,
**kwargs,
):
"""Context manager for NeXus files, similar to h5py.File.
Arguments other than documented are as in :py:class:`h5py.File`.
Expand All @@ -24,7 +32,7 @@ def __init__(self, *args, definitions: Mapping = _default_definitions, **kwargs)
The default is to use the base definitions as defined in the
NeXus standard.
"""
if definitions is _default_definitions:
if definitions is DefaultDefinitions:
definitions = base_definitions()
self._file = h5py.File(*args, **kwargs)
super().__init__(self._file, definitions=definitions)
Expand Down
4 changes: 3 additions & 1 deletion src/scippnexus/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# @author Simon Heybrock
from __future__ import annotations

from collections.abc import Callable
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING, Any, Protocol


Expand Down Expand Up @@ -74,3 +74,5 @@ class ellipsis(Enum):
ScippIndex = (
ellipsis | int | tuple | slice | tuple[str, int | slice] | dict[str, int | slice]
)

Definitions = Mapping[str, type]
Loading

0 comments on commit e8df317

Please sign in to comment.