Skip to content

Commit

Permalink
Merge pull request #106 from scipp/load-histogram-data
Browse files Browse the repository at this point in the history
Load histogram data
  • Loading branch information
jl-wynen authored Sep 26, 2024
2 parents 14c7e86 + 2b94e34 commit c91e01a
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 106 deletions.
4 changes: 2 additions & 2 deletions src/ess/reduce/nexus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from . import types
from ._nexus_loader import (
load_event_data,
load_data,
group_event_data,
load_component,
compute_component_position,
Expand All @@ -22,7 +22,7 @@
__all__ = [
'types',
'group_event_data',
'load_event_data',
'load_data',
'load_component',
'compute_component_position',
'extract_events_or_histogram',
Expand Down
69 changes: 61 additions & 8 deletions src/ess/reduce/nexus/_nexus_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
import scippnexus as snx

from ..logging import get_logger
from .types import AnyRunFilename, NeXusEntryName, NeXusGroup, NeXusLocationSpec
from .types import (
AnyRunFilename,
AnyRunPulseSelection,
NeXusEntryName,
NeXusGroup,
NeXusLocationSpec,
)


class NoNewDefinitionsType: ...
Expand Down Expand Up @@ -98,10 +104,23 @@ def _unique_child_group(

children = group[nx_class]
if len(children) != 1:
raise ValueError(f'Expected exactly one {nx_class} group, got {len(children)}')
raise ValueError(
f"Expected exactly one {nx_class.__name__} group '{group.name}', "
f"got {len(children)}"
)
return next(iter(children.values())) # type: ignore[return-value]


def _contains_nx_class(group: snx.Group, nx_class: type[snx.NXobject]) -> bool:
# See https://github.com/scipp/scippnexus/issues/241
try:
return bool(group[nx_class])
except KeyError:
# This does not happen with the current implementation in ScippNexus.
# The fallback is here to future-proof this function.
return False


def extract_events_or_histogram(dg: sc.DataGroup) -> sc.DataArray:
event_data_arrays = {
key: value
Expand Down Expand Up @@ -150,15 +169,34 @@ def _select_unique_array(
return next(iter(arrays.values()))


def load_event_data(
def _to_snx_selection(
selection: snx.typing.ScippIndex | AnyRunPulseSelection, *, for_events: bool
) -> snx.typing.ScippIndex:
match selection:
case AnyRunPulseSelection(slice(start=None, stop=None)):
return ()
case AnyRunPulseSelection(sel):
if for_events:
return {'event_time_zero': sel}
return {'time': sel}
case _:
return selection


def load_data(
file_path: AnyRunFilename,
selection=(),
selection: snx.typing.ScippIndex | AnyRunPulseSelection = (),
*,
entry_name: NeXusEntryName | None = None,
component_name: str,
definitions: Mapping | NoNewDefinitionsType = NoNewDefinitions,
) -> sc.DataArray:
"""Load NXevent_data of a detector or monitor from a NeXus file.
"""Load data of a detector or monitor from a NeXus file.
Loads either event data from an ``NXevent_data`` group or histogram
data from an ``NXdata`` group depending on which ``group`` contains.
Event data is grouped by ``'event_time_zero'`` as in the NeXus file.
Histogram data is returned as encoded in the file.
Parameters
----------
Expand All @@ -169,6 +207,10 @@ def load_event_data(
- Path to a NeXus file on disk.
- File handle or buffer for reading binary data.
- A ScippNexus group of the root of a NeXus file.
selection:
Select which aprt of the data to load.
By default, load all data.
Supports anything that ScippNexus supports.
component_name:
Name of the NXdetector or NXmonitor containing the NXevent_data to load.
Must be a group in an instrument group in the entry (see below).
Expand All @@ -183,14 +225,25 @@ def load_event_data(
Returns
-------
:
Data array with events grouped by event_time_zero, as in the NeXus file.
Data array with events or a histogram.
"""
with _open_nexus_file(file_path, definitions=definitions) as f:
entry = _unique_child_group(f, snx.NXentry, entry_name)
instrument = _unique_child_group(entry, snx.NXinstrument, None)
component = instrument[component_name]
event_data = _unique_child_group(component, snx.NXevent_data, None)
return event_data[selection]
if _contains_nx_class(component, snx.NXevent_data):
data = _unique_child_group(component, snx.NXevent_data, None)
sel = _to_snx_selection(selection, for_events=True)
elif _contains_nx_class(component, snx.NXdata):
data = _unique_child_group(component, snx.NXdata, None)
sel = _to_snx_selection(selection, for_events=False)
else:
raise ValueError(
f"NeXus group '{component.name}' contains neither "
"NXevent_data nor NXdata."
)

return data[sel]


def group_event_data(
Expand Down
36 changes: 20 additions & 16 deletions src/ess/reduce/nexus/generic_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
import scipp as sc
import scippnexus as snx

from .types import Component, FilePath, NeXusFile, NeXusGroup, NeXusLocationSpec
from .types import (
AnyRunPulseSelection,
Component,
FilePath,
NeXusFile,
NeXusGroup,
NeXusLocationSpec,
)

# 1 TypeVars used to parametrize the generic parts of the workflow

Expand Down Expand Up @@ -105,14 +112,14 @@ class NeXusSource(sciline.Scope[RunType, sc.DataGroup], sc.DataGroup):
"""Raw data from a NeXus source."""


class NeXusDetectorEventData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
"""Data array loaded from a NeXus NXevent_data group within an NXdetector."""
class NeXusDetectorData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
"""Data array loaded from an NXevent_data or NXdata group within an NXdetector."""


class NeXusMonitorEventData(
class NeXusMonitorData(
sciline.ScopeTwoParams[RunType, MonitorType, sc.DataArray], sc.DataArray
):
"""Data array loaded from a NeXus NXevent_data group within an NXmonitor."""
"""Data array loaded from an NXevent_data or NXdata group within an NXmonitor."""


class SourcePosition(sciline.Scope[RunType, sc.Variable], sc.Variable):
Expand Down Expand Up @@ -144,23 +151,20 @@ class CalibratedMonitor(


class DetectorData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
"""Calibrated detector merged with neutron event data."""
"""Calibrated detector merged with neutron event or histogram data."""


class MonitorData(
sciline.ScopeTwoParams[RunType, MonitorType, sc.DataArray], sc.DataArray
):
"""Calibrated monitor merged with neutron event data."""
"""Calibrated monitor merged with neutron event or histogram data."""


class Filename(sciline.Scope[RunType, Path], Path): ...


@dataclass
class PulseSelection(Generic[RunType]):
"""Range of neutron pulses to load from NXevent_data groups."""

value: slice
class PulseSelection(AnyRunPulseSelection, Generic[RunType]):
"""Range of neutron pulses to load from NXevent_data or NXdata groups."""


@dataclass
Expand All @@ -187,14 +191,14 @@ class NeXusMonitorLocationSpec(


@dataclass
class NeXusDetectorEventLocationSpec(
class NeXusDetectorDataLocationSpec(
NeXusLocationSpec[snx.NXevent_data], Generic[RunType]
):
"""NeXus filename and parameters to identify (parts of) detector events to load."""
"""NeXus filename and parameters to identify (parts of) detector data to load."""


@dataclass
class NeXusMonitorEventLocationSpec(
class NeXusMonitorDataLocationSpec(
NeXusLocationSpec[snx.NXevent_data], Generic[RunType, MonitorType]
):
"""NeXus filename and parameters to identify (parts of) monitor events to load."""
"""NeXus filename and parameters to identify (parts of) monitor data to load."""
60 changes: 28 additions & 32 deletions src/ess/reduce/nexus/generic_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def no_detector_position_offset() -> gt.DetectorPositionOffset[RunType]:


def all_pulses() -> PulseSelection[RunType]:
"""Select all neutron pulses in the event data."""
"""Select all neutron pulses in the data."""
return PulseSelection[RunType](slice(None, None))


Expand All @@ -61,15 +61,15 @@ def monitor_by_name(
)


def monitor_events_by_name(
def monitor_data_by_name(
filename: gt.NeXusFileSpec[RunType],
name: gt.NeXusMonitorName[MonitorType],
selection: PulseSelection[RunType],
) -> gt.NeXusMonitorEventLocationSpec[RunType, MonitorType]:
return gt.NeXusMonitorEventLocationSpec[RunType, MonitorType](
) -> gt.NeXusMonitorDataLocationSpec[RunType, MonitorType]:
return gt.NeXusMonitorDataLocationSpec[RunType, MonitorType](
filename=filename.value,
component_name=name,
selection={'event_time_zero': selection.value},
selection=selection,
)


Expand All @@ -81,15 +81,15 @@ def detector_by_name(
)


def detector_events_by_name(
def detector_data_by_name(
filename: gt.NeXusFileSpec[RunType],
name: NeXusDetectorName,
selection: PulseSelection[RunType],
) -> gt.NeXusDetectorEventLocationSpec[RunType]:
return gt.NeXusDetectorEventLocationSpec[RunType](
) -> gt.NeXusDetectorDataLocationSpec[RunType]:
return gt.NeXusDetectorDataLocationSpec[RunType](
filename=filename.value,
component_name=name,
selection={'event_time_zero': selection.value},
selection=selection,
)


Expand Down Expand Up @@ -117,19 +117,17 @@ def load_nexus_monitor(
return gt.NeXusMonitor[RunType, MonitorType](workflow.load_nexus_monitor(location))


def load_nexus_detector_event_data(
location: gt.NeXusDetectorEventLocationSpec[RunType],
) -> gt.NeXusDetectorEventData[RunType]:
return gt.NeXusDetectorEventData[RunType](
workflow.load_nexus_detector_event_data(location)
)
def load_nexus_detector_data(
location: gt.NeXusDetectorDataLocationSpec[RunType],
) -> gt.NeXusDetectorData[RunType]:
return gt.NeXusDetectorData[RunType](workflow.load_nexus_detector_data(location))


def load_nexus_monitor_event_data(
location: gt.NeXusMonitorEventLocationSpec[RunType, MonitorType],
) -> gt.NeXusMonitorEventData[RunType, MonitorType]:
return gt.NeXusMonitorEventData[RunType, MonitorType](
workflow.load_nexus_monitor_event_data(location)
def load_nexus_monitor_data(
location: gt.NeXusMonitorDataLocationSpec[RunType, MonitorType],
) -> gt.NeXusMonitorData[RunType, MonitorType]:
return gt.NeXusMonitorData[RunType, MonitorType](
workflow.load_nexus_monitor_data(location)
)


Expand Down Expand Up @@ -164,11 +162,9 @@ def get_calibrated_detector(

def assemble_detector_data(
detector: gt.CalibratedDetector[RunType],
event_data: gt.NeXusDetectorEventData[RunType],
data: gt.NeXusDetectorData[RunType],
) -> gt.DetectorData[RunType]:
return gt.DetectorData[RunType](
workflow.assemble_detector_data(detector, event_data)
)
return gt.DetectorData[RunType](workflow.assemble_detector_data(detector, data))


def get_calibrated_monitor(
Expand All @@ -183,10 +179,10 @@ def get_calibrated_monitor(

def assemble_monitor_data(
monitor: gt.CalibratedMonitor[RunType, MonitorType],
event_data: gt.NeXusMonitorEventData[RunType, MonitorType],
data: gt.NeXusMonitorData[RunType, MonitorType],
) -> gt.MonitorData[RunType, MonitorType]:
return gt.MonitorData[RunType, MonitorType](
workflow.assemble_monitor_data(monitor, event_data)
workflow.assemble_monitor_data(monitor, data)
)


Expand All @@ -197,8 +193,8 @@ def assemble_monitor_data(
load_nexus_source.__doc__ = workflow.load_nexus_source.__doc__
load_nexus_detector.__doc__ = workflow.load_nexus_detector.__doc__
load_nexus_monitor.__doc__ = workflow.load_nexus_monitor.__doc__
load_nexus_detector_event_data.__doc__ = workflow.load_nexus_detector_event_data.__doc__
load_nexus_monitor_event_data.__doc__ = workflow.load_nexus_monitor_event_data.__doc__
load_nexus_detector_data.__doc__ = workflow.load_nexus_detector_data.__doc__
load_nexus_monitor_data.__doc__ = workflow.load_nexus_monitor_data.__doc__
get_source_position.__doc__ = workflow.get_source_position.__doc__
get_sample_position.__doc__ = workflow.get_sample_position.__doc__
get_calibrated_detector.__doc__ = workflow.get_calibrated_detector.__doc__
Expand All @@ -213,9 +209,9 @@ def assemble_monitor_data(
no_monitor_position_offset,
unique_source_spec,
monitor_by_name,
monitor_events_by_name,
monitor_data_by_name,
load_nexus_monitor,
load_nexus_monitor_event_data,
load_nexus_monitor_data,
load_nexus_source,
get_source_position,
get_calibrated_monitor,
Expand All @@ -227,9 +223,9 @@ def assemble_monitor_data(
unique_source_spec,
unique_sample_spec,
detector_by_name,
detector_events_by_name,
detector_data_by_name,
load_nexus_detector,
load_nexus_detector_event_data,
load_nexus_detector_data,
load_nexus_source,
load_nexus_sample,
get_source_position,
Expand Down
Loading

0 comments on commit c91e01a

Please sign in to comment.