Skip to content

Commit

Permalink
Merge pull request #77 from scipp/nexus-fixes
Browse files Browse the repository at this point in the history
Fix problems in new NeXus workflows
SimonHeybrock authored Aug 26, 2024

Verified

This commit was signed with the committer’s verified signature.
sourabhxyz Sourabh
2 parents ba87bfd + 776e303 commit 232d7db
Showing 7 changed files with 183 additions and 145 deletions.
48 changes: 24 additions & 24 deletions src/ess/reduce/nexus/_nexus_loader.py
Original file line number Diff line number Diff line change
@@ -13,16 +13,16 @@

from ..logging import get_logger
from .types import (
Filename,
NeXusDetector,
AnyNeXusMonitorName,
AnyRunAnyNeXusMonitor,
AnyRunFilename,
AnyRunNeXusDetector,
AnyRunNeXusSample,
AnyRunNeXusSource,
NeXusDetectorName,
NeXusEntryName,
NeXusGroup,
NeXusLocationSpec,
NeXusMonitor,
NeXusMonitorName,
NeXusSample,
NeXusSource,
NeXusSourceName,
RawDetectorData,
RawMonitorData,
@@ -36,13 +36,13 @@ class NoNewDefinitionsType: ...


def load_detector(
file_path: Filename,
file_path: AnyRunFilename,
selection=(),
*,
detector_name: NeXusDetectorName,
entry_name: NeXusEntryName | None = None,
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
) -> NeXusDetector:
) -> AnyRunNeXusDetector:
"""Load a single detector (bank) from a NeXus file.
The detector positions are computed automatically from NeXus transformations,
@@ -74,7 +74,7 @@ def load_detector(
A data group containing the detector events or histogram
and any auxiliary data stored in the same NeXus group.
"""
return NeXusDetector(
return AnyRunNeXusDetector(
load_component(
NeXusLocationSpec(
filename=file_path,
@@ -89,13 +89,13 @@ def load_detector(


def load_monitor(
file_path: Filename,
file_path: AnyRunFilename,
selection=(),
*,
monitor_name: NeXusMonitorName,
monitor_name: AnyNeXusMonitorName,
entry_name: NeXusEntryName | None = None,
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
) -> NeXusMonitor:
) -> AnyRunAnyNeXusMonitor:
"""Load a single monitor from a NeXus file.
The monitor position is computed automatically from NeXus transformations,
@@ -127,7 +127,7 @@ def load_monitor(
A data group containing the monitor events or histogram
and any auxiliary data stored in the same NeXus group.
"""
return NeXusMonitor(
return AnyRunAnyNeXusMonitor(
load_component(
NeXusLocationSpec(
filename=file_path,
@@ -142,12 +142,12 @@ def load_monitor(


def load_source(
file_path: Filename,
file_path: AnyRunFilename,
*,
source_name: NeXusSourceName | None = None,
entry_name: NeXusEntryName | None = None,
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
) -> NeXusSource:
) -> AnyRunNeXusSource:
"""Load a source from a NeXus file.
The source position is computed automatically from NeXus transformations,
@@ -181,7 +181,7 @@ def load_source(
A data group containing all data stored in
the source NeXus group.
"""
return NeXusSource(
return AnyRunNeXusSource(
load_component(
NeXusLocationSpec(
filename=file_path, component_name=source_name, entry_name=entry_name
@@ -193,10 +193,10 @@ def load_source(


def load_sample(
file_path: Filename,
file_path: AnyRunFilename,
entry_name: NeXusEntryName | None = None,
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
) -> NeXusSample:
) -> AnyRunNeXusSample:
"""Load a sample from a NeXus file.
The sample is located based on its NeXus class.
@@ -226,7 +226,7 @@ def load_sample(
A data group containing all data stored in
the sample NeXus group.
"""
return NeXusSample(
return AnyRunNeXusSample(
load_component(
NeXusLocationSpec(filename=file_path, entry_name=entry_name),
nx_class=snx.NXsample,
@@ -274,7 +274,7 @@ def load_component(


def _open_nexus_file(
file_path: Filename,
file_path: AnyRunFilename,
definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
) -> ContextManager:
if isinstance(file_path, getattr(NeXusGroup, '__supertype__', type(None))):
@@ -310,7 +310,7 @@ def _unique_child_group(
return next(iter(children.values())) # type: ignore[return-value]


def extract_detector_data(detector: NeXusDetector) -> RawDetectorData:
def extract_detector_data(detector: AnyRunNeXusDetector) -> RawDetectorData:
"""Get and return the events or histogram from a detector loaded from NeXus.
This function looks for a data array in the detector group and returns that.
@@ -339,7 +339,7 @@ def extract_detector_data(detector: NeXusDetector) -> RawDetectorData:
return RawDetectorData(_extract_events_or_histogram(detector))


def extract_monitor_data(monitor: NeXusMonitor) -> RawMonitorData:
def extract_monitor_data(monitor: AnyRunAnyNeXusMonitor) -> RawMonitorData:
"""Get and return the events or histogram from a monitor loaded from NeXus.
This function looks for a data array in the monitor group and returns that.
@@ -413,7 +413,7 @@ def _select_unique_array(


def load_event_data(
file_path: Filename,
file_path: AnyRunFilename,
selection=(),
*,
entry_name: NeXusEntryName | None = None,
@@ -629,7 +629,7 @@ def _parse_monitor(group: snx.Group) -> NeXusMonitorInfo:
)


def read_nexus_file_info(file_path: Filename) -> NeXusFileInfo:
def read_nexus_file_info(file_path: AnyRunFilename) -> NeXusFileInfo:
"""Opens and inspects a NeXus file, returning a summary of its contents."""
with _open_nexus_file(file_path) as f:
entry = _unique_child_group(f, snx.NXentry, None)
7 changes: 4 additions & 3 deletions src/ess/reduce/nexus/generic_types.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Domain types for use with Sciline, parametrized by run- and monitor-type."""

from dataclasses import dataclass
from pathlib import Path
from typing import Generic, NewType, TypeVar

import sciline
import scipp as sc
import scippnexus as snx

from .types import FilePath, NeXusFile, NeXusGroup, NeXusLocationSpec
from .types import Component, FilePath, NeXusFile, NeXusGroup, NeXusLocationSpec

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

@@ -148,11 +149,11 @@ class MonitorData(
"""Calibrated monitor merged with neutron event data."""


Component = TypeVar('Component', bound=snx.NXobject)
class Filename(sciline.Scope[RunType, Path], Path): ...


@dataclass
class Filename(Generic[RunType]):
class NeXusFileSpec(Generic[RunType]):
value: FilePath | NeXusFile | NeXusGroup


26 changes: 17 additions & 9 deletions src/ess/reduce/nexus/generic_workflow.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,10 @@
from .types import DetectorBankSizes, GravityVector, NeXusDetectorName, PulseSelection


def file_path_to_file_spec(filename: gt.Filename[RunType]) -> gt.NeXusFileSpec[RunType]:
return gt.NeXusFileSpec[RunType](filename)


def no_monitor_position_offset() -> gt.MonitorPositionOffset[RunType, MonitorType]:
return gt.MonitorPositionOffset[RunType, MonitorType](workflow.no_offset)

@@ -29,34 +33,38 @@ def no_detector_position_offset() -> gt.DetectorPositionOffset[RunType]:


def unique_sample_spec(
filename: gt.Filename[RunType],
filename: gt.NeXusFileSpec[RunType],
) -> gt.NeXusComponentLocationSpec[snx.NXsample, RunType]:
return gt.NeXusComponentLocationSpec[snx.NXsample, RunType](filename=filename)
return gt.NeXusComponentLocationSpec[snx.NXsample, RunType](filename=filename.value)


def unique_source_spec(
filename: gt.Filename[RunType],
filename: gt.NeXusFileSpec[RunType],
) -> gt.NeXusComponentLocationSpec[snx.NXsource, RunType]:
return gt.NeXusComponentLocationSpec[snx.NXsource, RunType](filename=filename)
return gt.NeXusComponentLocationSpec[snx.NXsource, RunType](filename=filename.value)


def monitor_by_name(
filename: gt.Filename[RunType],
filename: gt.NeXusFileSpec[RunType],
name: gt.NeXusMonitorName[MonitorType],
selection: PulseSelection,
) -> gt.NeXusMonitorLocationSpec[RunType, MonitorType]:
return gt.NeXusMonitorLocationSpec[RunType, MonitorType](
filename=filename, component_name=name, selection={'event_time_zero': selection}
filename=filename.value,
component_name=name,
selection={'event_time_zero': selection},
)


def detector_by_name(
filename: gt.Filename[RunType],
filename: gt.NeXusFileSpec[RunType],
name: NeXusDetectorName,
selection: PulseSelection,
) -> gt.NeXusComponentLocationSpec[snx.NXdetector, RunType]:
return gt.NeXusComponentLocationSpec[snx.NXdetector, RunType](
filename=filename, component_name=name, selection={'event_time_zero': selection}
filename=filename.value,
component_name=name,
selection={'event_time_zero': selection},
)


@@ -174,7 +182,7 @@ def assemble_monitor_data(
assemble_monitor_data.__doc__ = workflow.assemble_monitor_data.__doc__


_common_providers = (workflow.gravity_vector_neg_y,)
_common_providers = (workflow.gravity_vector_neg_y, file_path_to_file_spec)

_monitor_providers = (
no_monitor_position_offset,
34 changes: 17 additions & 17 deletions src/ess/reduce/nexus/types.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
"""Name of a detector (bank) in a NeXus file."""
NeXusEntryName = NewType('NeXusEntryName', str)
"""Name of an entry in a NeXus file."""
NeXusMonitorName = NewType('NeXusMonitorName', str)
AnyNeXusMonitorName = NewType('AnyNeXusMonitorName', str)
"""Name of a monitor in a NeXus file."""
NeXusSourceName = NewType('NeXusSourceName', str)
"""Name of a source in a NeXus file."""
@@ -35,47 +35,47 @@
RawMonitorData = NewType('RawMonitorData', sc.DataArray)
"""Data extracted from a RawMonitor."""

NeXusDetector = NewType('NeXusDetector', sc.DataGroup)
AnyRunNeXusDetector = NewType('AnyRunNeXusDetector', sc.DataGroup)
"""Full raw data from a NeXus detector."""
NeXusMonitor = NewType('NeXusMonitor', sc.DataGroup)
AnyRunAnyNeXusMonitor = NewType('AnyRunAnyNeXusMonitor', sc.DataGroup)
"""Full raw data from a NeXus monitor."""
NeXusSample = NewType('NeXusSample', sc.DataGroup)
AnyRunNeXusSample = NewType('AnyRunNeXusSample', sc.DataGroup)
"""Raw data from a NeXus sample."""
NeXusSource = NewType('NeXusSource', sc.DataGroup)
AnyRunNeXusSource = NewType('AnyRunNeXusSource', sc.DataGroup)
"""Raw data from a NeXus source."""
NeXusDetectorEventData = NewType('NeXusDetectorEventData', sc.DataArray)
AnyRunNeXusDetectorEventData = NewType('AnyRunNeXusDetectorEventData', sc.DataArray)
"""Data array loaded from a NeXus NXevent_data group within an NXdetector."""
NeXusMonitorEventData = NewType('NeXusMonitorEventData', sc.DataArray)
AnyRunAnyNeXusMonitorEventData = NewType('AnyRunAnyNeXusMonitorEventData', sc.DataArray)
"""Data array loaded from a NeXus NXevent_data group within an NXmonitor."""

SourcePosition = NewType('SourcePosition', sc.Variable)
AnyRunSourcePosition = NewType('AnyRunSourcePosition', sc.Variable)
"""Position of the neutron source."""

SamplePosition = NewType('SamplePosition', sc.Variable)
AnyRunSamplePosition = NewType('AnyRunSamplePosition', sc.Variable)
"""Position of the sample."""

DetectorPositionOffset = NewType('DetectorPositionOffset', sc.Variable)
AnyRunDetectorPositionOffset = NewType('AnyRunDetectorPositionOffset', sc.Variable)
"""Offset for the detector position, added to base position."""

MonitorPositionOffset = NewType('MonitorPositionOffset', sc.Variable)
AnyRunAnyMonitorPositionOffset = NewType('AnyRunAnyMonitorPositionOffset', sc.Variable)
"""Offset for the monitor position, added to base position."""


DetectorBankSizes = NewType("DetectorBankSizes", dict[str, dict[str, int | Any]])

CalibratedDetector = NewType('CalibratedDetector', sc.DataArray)
CalibratedMonitor = NewType('CalibratedMonitor', sc.DataArray)
AnyRunCalibratedDetector = NewType('AnyRunCalibratedDetector', sc.DataArray)
AnyRunAnyCalibratedMonitor = NewType('AnyRunAnyCalibratedMonitor', sc.DataArray)

DetectorData = NewType('DetectorData', sc.DataArray)
MonitorData = NewType('MonitorData', sc.DataArray)
AnyRunDetectorData = NewType('AnyRunDetectorData', sc.DataArray)
AnyRunAnyMonitorData = NewType('AnyRunAnyMonitorData', sc.DataArray)

PulseSelection = NewType('PulseSelection', slice)

GravityVector = NewType('GravityVector', sc.Variable)

Component = TypeVar('Component', bound=snx.NXobject)

Filename = FilePath | NeXusFile | NeXusGroup
AnyRunFilename = FilePath | NeXusFile | NeXusGroup


@dataclass
@@ -84,7 +84,7 @@ class NeXusLocationSpec(Generic[Component]):
NeXus filename and optional parameters to identify (parts of) a component to load.
"""

filename: Filename
filename: AnyRunFilename
entry_name: NeXusEntryName | None = None
component_name: str | None = None
selection: snx.typing.ScippIndex = ()
134 changes: 71 additions & 63 deletions src/ess/reduce/nexus/workflow.py
Original file line number Diff line number Diff line change
@@ -13,27 +13,27 @@

from . import _nexus_loader as nexus
from .types import (
CalibratedDetector,
CalibratedMonitor,
AnyNeXusMonitorName,
AnyRunAnyCalibratedMonitor,
AnyRunAnyMonitorData,
AnyRunAnyMonitorPositionOffset,
AnyRunAnyNeXusMonitor,
AnyRunAnyNeXusMonitorEventData,
AnyRunCalibratedDetector,
AnyRunDetectorData,
AnyRunDetectorPositionOffset,
AnyRunFilename,
AnyRunNeXusDetector,
AnyRunNeXusDetectorEventData,
AnyRunNeXusSample,
AnyRunNeXusSource,
AnyRunSamplePosition,
AnyRunSourcePosition,
DetectorBankSizes,
DetectorData,
DetectorPositionOffset,
Filename,
GravityVector,
MonitorData,
MonitorPositionOffset,
NeXusDetector,
NeXusDetectorEventData,
NeXusDetectorName,
NeXusLocationSpec,
NeXusMonitor,
NeXusMonitorEventData,
NeXusMonitorName,
NeXusSample,
NeXusSource,
PulseSelection,
SamplePosition,
SourcePosition,
)

origin = sc.vector([0, 0, 0], unit="m")
@@ -49,7 +49,7 @@ def gravity_vector_neg_y() -> GravityVector:
return GravityVector(sc.vector(value=[0, -1, 0]) * g)


def unique_sample_spec(filename: Filename) -> NeXusLocationSpec[snx.NXsample]:
def unique_sample_spec(filename: AnyRunFilename) -> NeXusLocationSpec[snx.NXsample]:
"""
Create a location spec for a unique sample group in a NeXus file.
@@ -61,7 +61,7 @@ def unique_sample_spec(filename: Filename) -> NeXusLocationSpec[snx.NXsample]:
return NeXusLocationSpec[snx.NXsample](filename=filename)


def unique_source_spec(filename: Filename) -> NeXusLocationSpec[snx.NXsource]:
def unique_source_spec(filename: AnyRunFilename) -> NeXusLocationSpec[snx.NXsource]:
"""
Create a location spec for a unique source group in a NeXus file.
@@ -74,7 +74,7 @@ def unique_source_spec(filename: Filename) -> NeXusLocationSpec[snx.NXsource]:


def monitor_by_name(
filename: Filename, name: NeXusMonitorName, selection: PulseSelection
filename: AnyRunFilename, name: AnyNeXusMonitorName, selection: PulseSelection
) -> NeXusLocationSpec[snx.NXmonitor]:
"""
Create a location spec for a monitor group in a NeXus file.
@@ -94,7 +94,7 @@ def monitor_by_name(


def detector_by_name(
filename: Filename, name: NeXusDetectorName, selection: PulseSelection
filename: AnyRunFilename, name: NeXusDetectorName, selection: PulseSelection
) -> NeXusLocationSpec[snx.NXdetector]:
"""
Create a location spec for a detector group in a NeXus file.
@@ -113,7 +113,7 @@ def detector_by_name(
)


def load_nexus_sample(location: NeXusLocationSpec[snx.NXsample]) -> NeXusSample:
def load_nexus_sample(location: NeXusLocationSpec[snx.NXsample]) -> AnyRunNeXusSample:
"""
Load a NeXus sample group from a file.
@@ -131,10 +131,10 @@ def load_nexus_sample(location: NeXusLocationSpec[snx.NXsample]) -> NeXusSample:
dg = nexus.load_component(location, nx_class=snx.NXsample)
except ValueError:
dg = sc.DataGroup()
return NeXusSample(dg)
return AnyRunNeXusSample(dg)


def load_nexus_source(location: NeXusLocationSpec[snx.NXsource]) -> NeXusSource:
def load_nexus_source(location: NeXusLocationSpec[snx.NXsource]) -> AnyRunNeXusSource:
"""
Load a NeXus source group from a file.
@@ -143,10 +143,12 @@ def load_nexus_source(location: NeXusLocationSpec[snx.NXsource]) -> NeXusSource:
location:
Location spec for the source group.
"""
return NeXusSource(nexus.load_component(location, nx_class=snx.NXsource))
return AnyRunNeXusSource(nexus.load_component(location, nx_class=snx.NXsource))


def load_nexus_detector(location: NeXusLocationSpec[snx.NXdetector]) -> NeXusDetector:
def load_nexus_detector(
location: NeXusLocationSpec[snx.NXdetector],
) -> AnyRunNeXusDetector:
"""
Load detector from NeXus, but with event data replaced by placeholders.
@@ -180,12 +182,14 @@ def load_nexus_detector(location: NeXusLocationSpec[snx.NXdetector]) -> NeXusDet
# The selection is only used for selecting a range of event data.
location = replace(location, selection=())

return NeXusDetector(
return AnyRunNeXusDetector(
nexus.load_component(location, nx_class=snx.NXdetector, definitions=definitions)
)


def load_nexus_monitor(location: NeXusLocationSpec[snx.NXmonitor]) -> NeXusMonitor:
def load_nexus_monitor(
location: NeXusLocationSpec[snx.NXmonitor],
) -> AnyRunAnyNeXusMonitor:
"""
Load monitor from NeXus, but with event data replaced by placeholders.
@@ -216,14 +220,14 @@ def load_nexus_monitor(location: NeXusLocationSpec[snx.NXmonitor]) -> NeXusMonit
"""
definitions = snx.base_definitions()
definitions["NXmonitor"] = _StrippedMonitor
return NeXusMonitor(
return AnyRunAnyNeXusMonitor(
nexus.load_component(location, nx_class=snx.NXmonitor, definitions=definitions)
)


def load_nexus_detector_event_data(
location: NeXusLocationSpec[snx.NXdetector],
) -> NeXusDetectorEventData:
) -> AnyRunNeXusDetectorEventData:
"""
Load event data from a NeXus detector group.
@@ -232,7 +236,7 @@ def load_nexus_detector_event_data(
location:
Location spec for the detector group.
"""
return NeXusDetectorEventData(
return AnyRunNeXusDetectorEventData(
nexus.load_event_data(
file_path=location.filename,
entry_name=location.entry_name,
@@ -244,7 +248,7 @@ def load_nexus_detector_event_data(

def load_nexus_monitor_event_data(
location: NeXusLocationSpec[snx.NXmonitor],
) -> NeXusMonitorEventData:
) -> AnyRunAnyNeXusMonitorEventData:
"""
Load event data from a NeXus monitor group.
@@ -253,7 +257,7 @@ def load_nexus_monitor_event_data(
location:
Location spec for the monitor group.
"""
return NeXusMonitorEventData(
return AnyRunAnyNeXusMonitorEventData(
nexus.load_event_data(
file_path=location.filename,
entry_name=location.entry_name,
@@ -263,7 +267,7 @@ def load_nexus_monitor_event_data(
)


def get_source_position(source: NeXusSource) -> SourcePosition:
def get_source_position(source: AnyRunNeXusSource) -> AnyRunSourcePosition:
"""
Extract the source position from a NeXus source group.
@@ -272,10 +276,10 @@ def get_source_position(source: NeXusSource) -> SourcePosition:
source:
NeXus source group.
"""
return SourcePosition(source["position"])
return AnyRunSourcePosition(source["position"])


def get_sample_position(sample: NeXusSample) -> SamplePosition:
def get_sample_position(sample: AnyRunNeXusSample) -> AnyRunSamplePosition:
"""
Extract the sample position from a NeXus sample group.
@@ -286,18 +290,18 @@ def get_sample_position(sample: NeXusSample) -> SamplePosition:
sample:
NeXus sample group.
"""
return SamplePosition(sample.get("position", origin))
return AnyRunSamplePosition(sample.get("position", origin))


def get_calibrated_detector(
detector: NeXusDetector,
detector: AnyRunNeXusDetector,
*,
offset: DetectorPositionOffset,
source_position: SourcePosition,
sample_position: SamplePosition,
offset: AnyRunDetectorPositionOffset,
source_position: AnyRunSourcePosition,
sample_position: AnyRunSamplePosition,
gravity: GravityVector,
bank_sizes: DetectorBankSizes,
) -> CalibratedDetector:
) -> AnyRunCalibratedDetector:
"""
Extract the data array corresponding to a detector's signal field.
@@ -322,14 +326,15 @@ def get_calibrated_detector(
Dictionary of detector bank sizes.
"""
da = nexus.extract_detector_data(detector)
if (sizes := (bank_sizes or {}).get(detector['nexus_component_name'])) is not None:
if (
sizes := (bank_sizes or {}).get(detector.get('nexus_component_name'))
) is not None:
da = da.fold(dim="detector_number", sizes=sizes)
# Note: We apply offset as early as possible, i.e., right in this function
# the detector array from the raw loader NeXus group, to prevent a source of bugs.
position = detector['position']
return CalibratedDetector(
return AnyRunCalibratedDetector(
da.assign_coords(
position=position + offset,
position=da.coords['position'] + offset,
source_position=source_position,
sample_position=sample_position,
gravity=gravity,
@@ -338,8 +343,8 @@ def get_calibrated_detector(


def assemble_detector_data(
detector: CalibratedDetector, event_data: NeXusDetectorEventData
) -> DetectorData:
detector: AnyRunCalibratedDetector, event_data: AnyRunNeXusDetectorEventData
) -> AnyRunDetectorData:
"""
Assemble a detector data array with event data and source- and sample-position.
@@ -355,18 +360,18 @@ def assemble_detector_data(
grouped = nexus.group_event_data(
event_data=event_data, detector_number=detector.coords['detector_number']
)
return DetectorData(
return AnyRunDetectorData(
_add_variances(grouped)
.assign_coords(detector.coords)
.assign_masks(detector.masks)
)


def get_calibrated_monitor(
monitor: NeXusMonitor,
offset: MonitorPositionOffset,
source_position: SourcePosition,
) -> CalibratedMonitor:
monitor: AnyRunAnyNeXusMonitor,
offset: AnyRunAnyMonitorPositionOffset,
source_position: AnyRunSourcePosition,
) -> AnyRunAnyCalibratedMonitor:
"""
Extract the data array corresponding to a monitor's signal field.
@@ -382,7 +387,7 @@ def get_calibrated_monitor(
source_position:
Position of the neutron source.
"""
return CalibratedMonitor(
return AnyRunAnyCalibratedMonitor(
nexus.extract_monitor_data(monitor).assign_coords(
position=monitor['position'] + offset,
source_position=source_position,
@@ -391,8 +396,9 @@ def get_calibrated_monitor(


def assemble_monitor_data(
monitor: CalibratedMonitor, event_data: NeXusMonitorEventData
) -> MonitorData:
monitor: AnyRunAnyCalibratedMonitor,
event_data: AnyRunAnyNeXusMonitorEventData,
) -> AnyRunAnyMonitorData:
"""
Assemble a monitor data array with event data.
@@ -406,7 +412,7 @@ def assemble_monitor_data(
Event data array.
"""
da = event_data.assign_coords(monitor.coords).assign_masks(monitor.masks)
return MonitorData(_add_variances(da))
return AnyRunAnyMonitorData(_add_variances(da))


def _drop(
@@ -484,7 +490,7 @@ def LoadMonitorWorkflow() -> sciline.Pipeline:
)
)
wf[PulseSelection] = PulseSelection(())
wf[MonitorPositionOffset] = MonitorPositionOffset(no_offset)
wf[AnyRunAnyMonitorPositionOffset] = AnyRunAnyMonitorPositionOffset(no_offset)
return wf


@@ -507,11 +513,11 @@ def LoadDetectorWorkflow() -> sciline.Pipeline:
)
wf[PulseSelection] = PulseSelection(())
wf[DetectorBankSizes] = DetectorBankSizes({})
wf[DetectorPositionOffset] = DetectorPositionOffset(no_offset)
wf[AnyRunDetectorPositionOffset] = AnyRunDetectorPositionOffset(no_offset)
return wf


def LoadNeXusWorkflow(filename: Filename) -> sciline.Pipeline:
def LoadNeXusWorkflow(filename: AnyRunFilename) -> sciline.Pipeline:
"""
Workflow for loading detector and monitor data from a NeXus file.
@@ -528,9 +534,9 @@ def LoadNeXusWorkflow(filename: Filename) -> sciline.Pipeline:
import pandas as pd

wf = sciline.Pipeline()
wf[DetectorData] = LoadDetectorWorkflow()
wf[MonitorData] = LoadMonitorWorkflow()
wf[Filename] = filename
wf[AnyRunDetectorData] = LoadDetectorWorkflow()
wf[AnyRunAnyMonitorData] = LoadMonitorWorkflow()
wf[AnyRunFilename] = filename
wf.insert(nexus.read_nexus_file_info)
wf[nexus.NeXusFileInfo] = info = wf.compute(nexus.NeXusFileInfo)
# Note: There is a good reason against auto-mapping here:
@@ -541,7 +547,9 @@ def LoadNeXusWorkflow(filename: Filename) -> sciline.Pipeline:
dets = [name for name, det in info.detectors.items() if det.n_pixel is not None]
det_df = pd.DataFrame({NeXusDetectorName: dets}, index=dets).rename_axis('detector')
mons = list(info.monitors)
mon_df = pd.DataFrame({NeXusMonitorName: mons}, index=mons).rename_axis('monitor')
mon_df = pd.DataFrame({AnyNeXusMonitorName: mons}, index=mons).rename_axis(
'monitor'
)
return wf.map(det_df).map(mon_df)


16 changes: 8 additions & 8 deletions tests/nexus/nexus_loader_test.py
Original file line number Diff line number Diff line change
@@ -364,7 +364,7 @@ def test_load_monitor(nexus_file, expected_monitor, entry_name, selection):
monitor = nexus.load_monitor(
nexus_file,
**({'selection': selection} if selection is not None else {}),
monitor_name=nexus.types.NeXusMonitorName('monitor'),
monitor_name=nexus.types.AnyNeXusMonitorName('monitor'),
entry_name=entry_name,
)
expected = expected_monitor[selection] if selection else expected_monitor
@@ -382,7 +382,7 @@ def test_load_source(nexus_file, expected_source, entry_name, source_name):
# NeXus details that we don't need to test as long as the positions are ok:
del source['depends_on']
del source['transformations']
sc.testing.assert_identical(source, nexus.types.NeXusSource(expected_source))
sc.testing.assert_identical(source, nexus.types.AnyRunNeXusSource(expected_source))


@pytest.mark.parametrize(
@@ -414,7 +414,7 @@ def new(*args, **kwargs):
@pytest.mark.parametrize('entry_name', [None, nexus.types.NeXusEntryName('entry-001')])
def test_load_sample(nexus_file, expected_sample, entry_name):
sample = nexus.load_sample(nexus_file, entry_name=entry_name)
sc.testing.assert_identical(sample, nexus.types.NeXusSample(expected_sample))
sc.testing.assert_identical(sample, nexus.types.AnyRunNeXusSample(expected_sample))


def test_extract_detector_data():
@@ -425,7 +425,7 @@ def test_extract_detector_data():
' _': sc.linspace('xx', 2, 3, 10),
}
)
data = nexus.extract_detector_data(nexus.types.NeXusDetector(detector))
data = nexus.extract_detector_data(nexus.types.AnyRunNeXusDetector(detector))
sc.testing.assert_identical(data, nexus.types.RawDetectorData(detector['jdl2ab']))


@@ -437,7 +437,7 @@ def test_extract_monitor_data():
' _': sc.linspace('xx', 2, 3, 10),
}
)
data = nexus.extract_monitor_data(nexus.types.NeXusMonitor(monitor))
data = nexus.extract_monitor_data(nexus.types.AnyRunAnyNeXusMonitor(monitor))
sc.testing.assert_identical(data, nexus.types.RawMonitorData(monitor['(eed)']))


@@ -453,7 +453,7 @@ def test_extract_detector_data_requires_unique_dense_data():
with pytest.raises(
ValueError, match="Cannot uniquely identify the data to extract"
):
nexus.extract_detector_data(nexus.types.NeXusDetector(detector))
nexus.extract_detector_data(nexus.types.AnyRunNeXusDetector(detector))


def test_extract_detector_data_requires_unique_event_data():
@@ -468,7 +468,7 @@ def test_extract_detector_data_requires_unique_event_data():
with pytest.raises(
ValueError, match="Cannot uniquely identify the data to extract"
):
nexus.extract_detector_data(nexus.types.NeXusDetector(detector))
nexus.extract_detector_data(nexus.types.AnyRunNeXusDetector(detector))


def test_extract_detector_data_favors_event_data_over_histogram_data():
@@ -480,5 +480,5 @@ def test_extract_detector_data_favors_event_data_over_histogram_data():
' _': sc.linspace('xx', 2, 3, 10),
}
)
data = nexus.extract_detector_data(nexus.types.NeXusDetector(detector))
data = nexus.extract_detector_data(nexus.types.AnyRunNeXusDetector(detector))
sc.testing.assert_identical(data, nexus.types.RawDetectorData(detector['lob']))
63 changes: 42 additions & 21 deletions tests/nexus/workflow_test.py
Original file line number Diff line number Diff line change
@@ -7,13 +7,13 @@


@pytest.fixture(params=[{}, {'aux': 1}])
def group_with_no_position(request) -> workflow.NeXusSample:
return workflow.NeXusSample(sc.DataGroup(request.param))
def group_with_no_position(request) -> workflow.AnyRunNeXusSample:
return workflow.AnyRunNeXusSample(sc.DataGroup(request.param))


def test_sample_position_returns_position_of_group() -> None:
position = sc.vector([1.0, 2.0, 3.0], unit='m')
sample_group = workflow.NeXusSample(sc.DataGroup(position=position))
sample_group = workflow.AnyRunNeXusSample(sc.DataGroup(position=position))
assert_identical(workflow.get_sample_position(sample_group), position)


@@ -27,7 +27,7 @@ def test_get_sample_position_returns_origin_if_position_not_found(

def test_get_source_position_returns_position_of_group() -> None:
position = sc.vector([1.0, 2.0, 3.0], unit='m')
source_group = workflow.NeXusSource(sc.DataGroup(position=position))
source_group = workflow.AnyRunNeXusSource(sc.DataGroup(position=position))
assert_identical(workflow.get_source_position(source_group), position)


@@ -39,17 +39,23 @@ def test_get_source_position_raises_exception_if_position_not_found(


@pytest.fixture()
def nexus_detector() -> workflow.NeXusDetector:
def nexus_detector() -> workflow.AnyRunNeXusDetector:
detector_number = sc.arange('detector_number', 6, unit=None)
x = sc.linspace('detector_number', 0, 1, num=6, unit='m')
position = sc.spatial.as_vectors(x, sc.zeros_like(x), sc.zeros_like(x))
data = sc.DataArray(
sc.empty_like(detector_number),
coords={
'detector_number': detector_number,
'position': position,
},
)
return workflow.NeXusDetector(
return workflow.AnyRunNeXusDetector(
sc.DataGroup(
data=data,
# Note that this position (the overall detector position) will be ignored,
# only the pixel positions (typically defined relative to this in an
# actual NeXus file) will be used.
position=sc.vector([1.0, 2.0, 3.0], unit='m'),
nexus_component_name='detector1',
)
@@ -61,7 +67,7 @@ def source_position() -> sc.Variable:
return sc.vector([0.0, 0.0, -10.0], unit='m')


def test_get_calibrated_detector_extracts_data_field_from_nexus_monitor(
def test_get_calibrated_detector_extracts_data_field_from_nexus_detector(
nexus_detector,
source_position,
) -> None:
@@ -74,9 +80,7 @@ def test_get_calibrated_detector_extracts_data_field_from_nexus_monitor(
bank_sizes={},
)
assert_identical(
detector.drop_coords(
('position', 'sample_position', 'source_position', 'gravity')
),
detector.drop_coords(('sample_position', 'source_position', 'gravity')),
nexus_detector['data'],
)

@@ -98,6 +102,21 @@ def test_get_calibrated_detector_folds_detector_number_if_mapping_given(
assert detector.sizes == sizes


def test_get_calibrated_detector_works_if_nexus_component_name_is_missing(
nexus_detector, source_position
):
del nexus_detector['nexus_component_name']
detector = workflow.get_calibrated_detector(
nexus_detector,
offset=workflow.no_offset,
source_position=source_position,
sample_position=workflow.origin,
gravity=workflow.gravity_vector_neg_y(),
bank_sizes={},
)
assert detector.sizes == nexus_detector['data'].sizes


def test_get_calibrated_detector_adds_offset_to_position(
nexus_detector,
source_position,
@@ -111,7 +130,9 @@ def test_get_calibrated_detector_adds_offset_to_position(
gravity=workflow.gravity_vector_neg_y(),
bank_sizes={},
)
assert_identical(detector.coords['position'], sc.vector([1.1, 2.2, 3.3], unit='m'))
position = nexus_detector['data'].coords['position'] + offset
assert detector.coords['position'].sizes == {'detector_number': 6}
assert_identical(detector.coords['position'], position)


def test_get_calibrated_detector_forwards_coords(
@@ -147,9 +168,9 @@ def test_get_calibrated_detector_forwards_masks(


@pytest.fixture()
def calibrated_detector() -> workflow.CalibratedDetector:
def calibrated_detector() -> workflow.AnyRunCalibratedDetector:
detector_number = sc.arange('detector_number', 6, unit=None)
return workflow.CalibratedDetector(
return workflow.AnyRunCalibratedDetector(
sc.DataArray(
sc.empty_like(detector_number),
coords={
@@ -161,13 +182,13 @@ def calibrated_detector() -> workflow.CalibratedDetector:


@pytest.fixture()
def detector_event_data() -> workflow.NeXusDetectorEventData:
def detector_event_data() -> workflow.AnyRunNeXusDetectorEventData:
content = sc.DataArray(
sc.ones(dims=['event'], shape=[17], unit='counts'),
coords={'event_id': sc.arange('event', 17, unit=None) % sc.index(6)},
)
weights = sc.bins(data=content, dim='event')
return workflow.NeXusDetectorEventData(
return workflow.AnyRunNeXusDetectorEventData(
sc.DataArray(
weights,
coords={
@@ -217,9 +238,9 @@ def test_assemble_detector_preserves_masks(calibrated_detector, detector_event_d


@pytest.fixture()
def nexus_monitor() -> workflow.NeXusMonitor:
def nexus_monitor() -> workflow.AnyRunAnyNeXusMonitor:
data = sc.DataArray(sc.scalar(1.2), coords={'something': sc.scalar(13)})
return workflow.NeXusMonitor(
return workflow.AnyRunAnyNeXusMonitor(
sc.DataGroup(data=data, position=sc.vector([1.0, 2.0, 3.0], unit='m'))
)

@@ -250,8 +271,8 @@ def test_get_calibrated_monitor_subtracts_offset_from_position(


@pytest.fixture()
def calibrated_monitor() -> workflow.CalibratedMonitor:
return workflow.CalibratedMonitor(
def calibrated_monitor() -> workflow.AnyRunAnyCalibratedMonitor:
return workflow.AnyRunAnyCalibratedMonitor(
sc.DataArray(
sc.scalar(0),
coords={'position': sc.vector([1.0, 2.0, 3.0], unit='m')},
@@ -260,10 +281,10 @@ def calibrated_monitor() -> workflow.CalibratedMonitor:


@pytest.fixture()
def monitor_event_data() -> workflow.NeXusMonitorEventData:
def monitor_event_data() -> workflow.AnyRunAnyNeXusMonitorEventData:
content = sc.DataArray(sc.ones(dims=['event'], shape=[17], unit='counts'))
weights = sc.bins(data=content, dim='event')
return workflow.NeXusMonitorEventData(
return workflow.AnyRunAnyNeXusMonitorEventData(
sc.DataArray(
weights,
coords={

0 comments on commit 232d7db

Please sign in to comment.