Skip to content

Commit 4639153

Browse files
authored
Merge pull request #274 from scipp/histogram-detectors
Small fixes for loading histogram-mode detectors
2 parents 95a2782 + 1a7930c commit 4639153

File tree

3 files changed

+85
-13
lines changed

3 files changed

+85
-13
lines changed

src/ess/reduce/nexus/workflow.py

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,11 @@ def get_calibrated_detector(
385385
# If the NXdetector in the file is not 1-D, we want to match the order of dims.
386386
# zip_pixel_offsets otherwise yields a vector with dimensions in the order given
387387
# by the x/y/z offsets.
388-
offsets = snx.zip_pixel_offsets(da.coords).transpose(da.dims).copy()
388+
offsets = snx.zip_pixel_offsets(da.coords)
389+
# Get the dims in the order of the detector data array, but filter out dims that
390+
# don't exist in the offsets (e.g. the detector data may have a 'time' dimension).
391+
dims = [dim for dim in da.dims if dim in offsets.dims]
392+
offsets = offsets.transpose(dims).copy()
389393
# We use the unit of the offsets as this is likely what the user expects.
390394
if transform.value.unit is not None and transform.value.unit != '':
391395
transform_value = transform.value.to(unit=offsets.unit)
@@ -399,7 +403,7 @@ def get_calibrated_detector(
399403

400404
def assemble_detector_data(
401405
detector: EmptyDetector[RunType],
402-
event_data: NeXusData[snx.NXdetector, RunType],
406+
neutron_data: NeXusData[snx.NXdetector, RunType],
403407
) -> RawDetector[RunType]:
404408
"""
405409
Assemble a detector data array with event data.
@@ -410,14 +414,15 @@ def assemble_detector_data(
410414
----------
411415
detector:
412416
Calibrated detector data array.
413-
event_data:
414-
Event data array.
417+
neutron_data:
418+
Neutron data array (events or histogram).
415419
"""
416-
grouped = nexus.group_event_data(
417-
event_data=event_data, detector_number=detector.coords['detector_number']
418-
)
420+
if neutron_data.bins is not None:
421+
neutron_data = nexus.group_event_data(
422+
event_data=neutron_data, detector_number=detector.coords['detector_number']
423+
)
419424
return RawDetector[RunType](
420-
_add_variances(grouped)
425+
_add_variances(neutron_data)
421426
.assign_coords(detector.coords)
422427
.assign_masks(detector.masks)
423428
)
@@ -504,6 +509,19 @@ def _drop(
504509
}
505510

506511

512+
class _EmptyField:
513+
"""Empty field that can replace a missing detector_number in NXdetector."""
514+
515+
def __init__(self, sizes: dict[str, int]):
516+
self.attrs = {}
517+
self.sizes = sizes.copy()
518+
self.dims = tuple(sizes.keys())
519+
self.shape = tuple(sizes.values())
520+
521+
def __getitem__(self, key: Any) -> sc.Variable:
522+
return sc.zeros(dims=self.dims, shape=self.shape, unit=None, dtype='int32')
523+
524+
507525
class _StrippedDetector(snx.NXdetector):
508526
"""Detector definition without large geometry or event data for ScippNexus.
509527
@@ -513,8 +531,36 @@ class _StrippedDetector(snx.NXdetector):
513531
def __init__(
514532
self, attrs: dict[str, Any], children: dict[str, snx.Field | snx.Group]
515533
):
516-
children = _drop(children, (snx.NXoff_geometry, snx.NXevent_data))
517-
children['data'] = children['detector_number']
534+
if 'detector_number' in children:
535+
data = children['detector_number']
536+
else:
537+
# We get the 'data' sizes before the NXdata is dropped
538+
if 'data' not in children:
539+
raise KeyError(
540+
"StrippedDetector: Cannot determine shape of the detector. "
541+
"No 'detector_number' was found, and the 'data' entry is missing."
542+
)
543+
if 'value' not in children['data']:
544+
raise KeyError(
545+
"StrippedDetector: Cannot determine shape of the detector. "
546+
"The 'data' entry has no 'value'."
547+
)
548+
# We drop any time-related dimension from the data sizes, as they are not
549+
# relevant for the detector geometry/shape.
550+
data = _EmptyField(
551+
sizes={
552+
dim: size
553+
for dim, size in children['data']['value'].sizes.items()
554+
if dim not in ('time', 'frame_time')
555+
}
556+
)
557+
558+
children = _drop(
559+
children, (snx.NXoff_geometry, snx.NXevent_data, snx.NXdata, snx.NXlog)
560+
)
561+
562+
children['data'] = data
563+
518564
super().__init__(attrs=attrs, children=children)
519565

520566

@@ -528,7 +574,7 @@ def __init__(self, dim: str):
528574
self.shape = (0,)
529575

530576
def __getitem__(self, key: Any) -> sc.Variable:
531-
return sc.empty(dims=self.dims, shape=self.shape, unit=None)
577+
return sc.zeros(dims=self.dims, shape=self.shape, unit=None, dtype='int32')
532578

533579

534580
class _StrippedMonitor(snx.NXmonitor):

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ def tbl_registry() -> Registry:
6363
return make_registry(
6464
'ess/tbl',
6565
files={
66-
"857127_00000112_small.hdf": "md5:6f3059e0e5e111a2a8f1b368f24c6f93",
66+
"857127_00000112_small.hdf": "md5:0db89493b859dbb2f7354c3711ed7fbd",
6767
},
68-
version='1',
68+
version='2',
6969
)
7070

7171

tests/nexus/workflow_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
BackgroundRun,
1414
Beamline,
1515
EmptyBeamRun,
16+
EmptyDetector,
1617
Filename,
1718
FrameMonitor0,
1819
FrameMonitor1,
@@ -585,6 +586,31 @@ def test_load_detector_workflow(loki_tutorial_sample_run_60250: Path) -> None:
585586
assert da.dims == ('detector_number',)
586587

587588

589+
def test_load_histogram_detector_workflow(tbl_commissioning_orca_file: Path) -> None:
590+
wf = LoadDetectorWorkflow(run_types=[SampleRun], monitor_types=[])
591+
wf[Filename[SampleRun]] = tbl_commissioning_orca_file
592+
wf[NeXusName[snx.NXdetector]] = 'orca_detector'
593+
da = wf.compute(RawDetector[SampleRun])
594+
assert 'position' in da.coords
595+
assert da.bins is None
596+
assert 'time' in da.dims
597+
assert da.ndim == 3
598+
599+
600+
def test_load_empty_histogram_detector_workflow(
601+
tbl_commissioning_orca_file: Path,
602+
) -> None:
603+
wf = LoadDetectorWorkflow(run_types=[SampleRun], monitor_types=[])
604+
wf[Filename[SampleRun]] = tbl_commissioning_orca_file
605+
wf[NeXusName[snx.NXdetector]] = 'orca_detector'
606+
da = wf.compute(EmptyDetector[SampleRun])
607+
assert 'position' in da.coords
608+
assert da.bins is None
609+
# The empty detector has no time dimension, only the dimensions of the geometry
610+
assert 'time' not in da.dims
611+
assert da.ndim == 2
612+
613+
588614
@pytest.mark.parametrize('preopen', [True, False])
589615
def test_generic_nexus_workflow(
590616
preopen: bool, loki_tutorial_sample_run_60250: Path

0 commit comments

Comments
 (0)