Skip to content

Commit 5391cd2

Browse files
authored
Merge pull request #267 from scipp/public-nexus-functions
Extend public interface of nexus loader
2 parents c0fd1ef + 303d322 commit 5391cd2

File tree

2 files changed

+120
-14
lines changed

2 files changed

+120
-14
lines changed

src/ess/reduce/nexus/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
load_all_components,
2121
load_component,
2222
load_data,
23+
open_component_group,
24+
open_nexus_file,
2325
)
2426
from .workflow import GenericNeXusWorkflow
2527

@@ -31,5 +33,7 @@
3133
'load_all_components',
3234
'load_component',
3335
'load_data',
36+
'open_component_group',
37+
'open_nexus_file',
3438
'types',
3539
]

src/ess/reduce/nexus/_nexus_loader.py

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,52 @@ def load_component(
4646
location: NeXusLocationSpec,
4747
*,
4848
nx_class: type[snx.NXobject],
49+
parent_class: type[snx.NXobject] | None = None,
4950
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
5051
) -> sc.DataGroup:
51-
"""Load a single component of a given class from NeXus."""
52+
"""Load a single component of a given class from NeXus.
53+
54+
Parameters
55+
----------
56+
location:
57+
Specifies (part of) the location of the component to load.
58+
nx_class:
59+
NeXus class of the component to load.
60+
parent_class:
61+
NeXus class of the parent of the component to load.
62+
If ``None``, is deduced from ``nx_class`` if possible.
63+
definitions:
64+
Application definitions to use for the file.
65+
66+
Returns
67+
-------
68+
:
69+
The loaded component as a data group.
70+
"""
5271
selection = location.selection
53-
group_name = location.component_name
54-
with _open_component_parent(
55-
location, nx_class=nx_class, definitions=definitions
56-
) as parent:
57-
component = _unique_child_group(parent, nx_class, group_name)
58-
loaded = cast(sc.DataGroup, component[selection])
59-
loaded['nexus_component_name'] = component.name.rsplit('/', 1)[-1]
72+
with open_component_group(
73+
location,
74+
nx_class=nx_class,
75+
parent_class=parent_class,
76+
definitions=definitions,
77+
) as group:
78+
loaded = cast(sc.DataGroup, group[selection])
79+
loaded['nexus_component_name'] = group.name.rsplit('/', 1)[-1]
6080
return loaded
6181

6282

6383
def load_all_components(
6484
location: NeXusAllLocationSpec,
6585
*,
6686
nx_class: type[snx.NXobject],
87+
parent_class: type[snx.NXobject] | None = None,
6788
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
6889
) -> sc.DataGroup:
6990
"""Load all components of a given class from NeXus."""
7091
with _open_component_parent(
71-
location, nx_class=nx_class, definitions=definitions
92+
location,
93+
parent_class=_deduce_component_parent_class(nx_class, parent_class),
94+
definitions=definitions,
7295
) as parent:
7396
components = sc.DataGroup()
7497
for name, component in parent[nx_class].items():
@@ -119,6 +142,29 @@ def open_nexus_file(
119142
*,
120143
locking: bool | str | None | NoLockingIfNeededType = NoLockingIfNeeded,
121144
) -> AbstractContextManager[snx.Group]:
145+
"""Open a NeXus file.
146+
147+
Parameters
148+
----------
149+
file_path:
150+
Path of the file to open or a NeXus file or group handle.
151+
definitions:
152+
If set, application definitions to use for the file.
153+
If ``file_path`` is a NeXus file or group, this must be unset or match
154+
the existing definitions.
155+
locking:
156+
This is an advanced feature to work around a limitation of the DMSC file system.
157+
It may be removed in the future.
158+
159+
This flag can disable or force locking the HDF5 file.
160+
By default, the file is locked if possible but may remain unlocked
161+
if it is on a read-only filesystem.
162+
163+
Returns
164+
-------
165+
:
166+
A context manager for the opened file.
167+
"""
122168
if isinstance(file_path, getattr(NeXusGroup, '__supertype__', type(None))):
123169
if (
124170
definitions is not NoNewDefinitions
@@ -203,21 +249,77 @@ def _open_nexus_file_from_path(
203249

204250

205251
@contextmanager
206-
def _open_component_parent(
252+
def open_component_group(
207253
location: NeXusLocationSpec,
208254
*,
209255
nx_class: type[snx.NXobject],
256+
parent_class: type[snx.NXobject] | None = None,
257+
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
258+
) -> Generator[snx.Group, None, None]:
259+
"""Open the HDF5 group of a NeXus component.
260+
261+
Parameters
262+
----------
263+
location:
264+
Specifies (part of) the location of the component to load.
265+
nx_class:
266+
NeXus class of the component to load.
267+
parent_class:
268+
NeXus class of the parent of the component to load.
269+
If ``None``, is deduced from ``nx_class`` if possible.
270+
definitions:
271+
Application definitions to use for the file.
272+
273+
Returns
274+
-------
275+
:
276+
A context manager for the group of the specified component.
277+
"""
278+
group_name = location.component_name
279+
with _open_component_parent(
280+
location,
281+
parent_class=_deduce_component_parent_class(nx_class, parent_class),
282+
definitions=definitions,
283+
) as parent:
284+
yield _unique_child_group(parent, nx_class, group_name)
285+
286+
287+
@contextmanager
288+
def _open_component_parent(
289+
location: NeXusLocationSpec,
290+
*,
291+
parent_class: type[snx.NXobject],
210292
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
211293
) -> Generator[snx.Group, None, None]:
212294
"""Locate the parent group of a NeXus component."""
213295
file_path = location.filename
214296
entry_name = location.entry_name
215297
with open_nexus_file(file_path, definitions=definitions) as f:
216298
entry = _unique_child_group(f, snx.NXentry, entry_name)
217-
if nx_class is snx.NXsample:
218-
yield entry
219-
else:
220-
yield _unique_child_group(entry, snx.NXinstrument, None)
299+
match parent_class:
300+
case snx.NXentry:
301+
yield entry
302+
case snx.NXinstrument:
303+
yield _unique_child_group(entry, snx.NXinstrument, None)
304+
case _:
305+
raise NotImplementedError(
306+
f"No support for loading a NeXus component from a {parent_class}."
307+
)
308+
309+
310+
def _deduce_component_parent_class(
311+
nx_class: type[snx.NXobject], parent_class: type[snx.NXobject] | None
312+
) -> type[snx.NXobject]:
313+
if parent_class is not None:
314+
return parent_class
315+
316+
match nx_class:
317+
case snx.NXsample:
318+
return snx.NXentry
319+
case _:
320+
# Most components are in the instrument,
321+
# callers need to override this for specialized components stored elsewhere.
322+
return snx.NXinstrument
221323

222324

223325
def _unique_child_group(

0 commit comments

Comments
 (0)