Skip to content

Commit

Permalink
Merge pull request #13 from scipp/grow-events-script
Browse files Browse the repository at this point in the history
feat: add grow nexus script
  • Loading branch information
jokasimr authored Mar 14, 2024
2 parents c1b1598 + 4341528 commit a17457f
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 18 deletions.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ dependencies = [

dynamic = ["version"]

[project.scripts]
grow-nexus = "ess.reduce.scripts.grow_nexus:main"

[project.urls]
"Bug Tracker" = "https://github.com/scipp/essreduce/issues"
"Documentation" = "https://scipp.github.io/essreduce"
Expand Down
1 change: 1 addition & 0 deletions requirements/basetest.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
# Do not make an environment from this file, use test.txt instead!

pytest
numpy
8 changes: 5 additions & 3 deletions requirements/basetest.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SHA1:0eaa389e1fdb3a1917c0f987514bd561be5718ee
# SHA1:43a90d54540ba9fdbb2c21a785a9d5d7483af53b
#
# This file is autogenerated by pip-compile-multi
# To update, run:
Expand All @@ -9,11 +9,13 @@ exceptiongroup==1.2.0
# via pytest
iniconfig==2.0.0
# via pytest
packaging==23.2
numpy==1.26.4
# via -r basetest.in
packaging==24.0
# via pytest
pluggy==1.4.0
# via pytest
pytest==8.0.2
pytest==8.1.1
# via -r basetest.in
tomli==2.0.1
# via pytest
4 changes: 2 additions & 2 deletions requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ gitpython==3.1.42
# via -r ci.in
idna==3.6
# via requests
packaging==23.2
packaging==24.0
# via
# -r ci.in
# pyproject-api
Expand All @@ -48,7 +48,7 @@ tomli==2.0.1
# via
# pyproject-api
# tox
tox==4.13.0
tox==4.14.1
# via -r ci.in
urllib3==2.2.1
# via requests
Expand Down
10 changes: 5 additions & 5 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ isoduration==20.11.0
# via jsonschema
jinja2-ansible-filters==1.3.2
# via copier
json5==0.9.20
json5==0.9.22
# via jupyterlab-server
jsonpointer==2.4
# via jsonschema
Expand All @@ -61,7 +61,7 @@ jsonschema[format-nongpl]==4.21.1
# nbformat
jupyter-events==0.9.0
# via jupyter-server
jupyter-lsp==2.2.3
jupyter-lsp==2.2.4
# via jupyterlab
jupyter-server==2.13.0
# via
Expand All @@ -71,7 +71,7 @@ jupyter-server==2.13.0
# notebook-shim
jupyter-server-terminals==0.5.2
# via jupyter-server
jupyterlab==4.1.3
jupyterlab==4.1.4
# via -r dev.in
jupyterlab-server==2.25.3
# via jupyterlab
Expand All @@ -83,7 +83,7 @@ pathspec==0.12.1
# via copier
pip-compile-multi==2.6.3
# via -r dev.in
pip-tools==7.4.0
pip-tools==7.4.1
# via pip-compile-multi
plumbum==1.8.2
# via copier
Expand Down Expand Up @@ -121,7 +121,7 @@ terminado==0.18.0
# jupyter-server-terminals
toposort==1.10
# via pip-compile-multi
types-python-dateutil==2.8.19.20240106
types-python-dateutil==2.8.19.20240311
# via arrow
uri-template==1.3.0
# via jsonschema
Expand Down
2 changes: 1 addition & 1 deletion requirements/docs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ nbsphinx==0.9.3
# via -r docs.in
nest-asyncio==1.6.0
# via ipykernel
packaging==23.2
packaging==24.0
# via
# ipykernel
# nbconvert
Expand Down
2 changes: 1 addition & 1 deletion requirements/mypy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# pip-compile-multi
#
-r test.txt
mypy==1.8.0
mypy==1.9.0
# via -r mypy.in
mypy-extensions==1.0.0
# via mypy
Expand Down
5 changes: 0 additions & 5 deletions requirements/nightly.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
-r basetest.txt
h5py==3.10.0
# via scippnexus
numpy==1.26.4
# via
# h5py
# scipp
# scipy
python-dateutil==2.9.0.post0
# via scippnexus
scipp==24.2.0
Expand Down
2 changes: 1 addition & 1 deletion requirements/wheels.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
build==1.1.1
# via -r wheels.in
packaging==23.2
packaging==24.0
# via build
pyproject-hooks==1.0.0
# via build
Expand Down
115 changes: 115 additions & 0 deletions src/ess/reduce/scripts/grow_nexus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import argparse
import shutil
from typing import Optional

import h5py


def _scale_group(event_data: h5py.Group, scale: int):
if not all(
required_field in event_data
for required_field in ('event_index', 'event_time_offset', 'event_id')
):
return
event_index = (event_data['event_index'][:] * scale).astype('uint')
event_data['event_index'][:] = event_index

size = event_data['event_id'].size
event_data['event_id'].resize(event_index[-1], axis=0)
event_data['event_time_offset'].resize(event_index[-1], axis=0)

for s in range(1, scale):
event_data['event_id'][s * size : (s + 1) * size] = event_data['event_id'][
:size
]
event_data['event_time_offset'][s * size : (s + 1) * size] = event_data[
'event_time_offset'
][:size]


def _grow_nexus_file_impl(file: h5py.File, detector_scale: int, monitor_scale: int):
for group in file.values():
if group.attrs.get('NX_class', '') == 'NXentry':
entry = group
break
for group in entry.values():
if group.attrs.get('NX_class', '') == 'NXinstrument':
instrument = group
break
for group in instrument.values():
if (nx_class := group.attrs.get('NX_class', '')) in (
'NXdetector',
'NXmonitor',
):
for subgroup in group.values():
if subgroup.attrs.get('NX_class', '') == 'NXevent_data':
_scale_group(
subgroup,
scale=detector_scale
if nx_class == 'NXdetector'
else monitor_scale,
)


def grow_nexus_file(
*, filename: str, detector_scale: int, monitor_scale: Optional[int]
):
with h5py.File(filename, 'a') as f:
_grow_nexus_file_impl(
f,
detector_scale,
monitor_scale if monitor_scale is not None else detector_scale,
)


def integer_greater_than_one(x):
x = int(x)
if x < 1:
raise argparse.ArgumentTypeError('Must be larger than or equal to 1')
return x


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
"--file",
type=str,
help=(
'Input file name. The events in the input file will be '
'repeated `scale` times and stored in the output file.'
),
required=True,
)
parser.add_argument(
"-o",
"--output",
type=str,
help='Output file name where the resulting nexus file will be written.',
required=True,
)
parser.add_argument(
"-s",
"--detector-scale",
type=integer_greater_than_one,
help=('Scale factor to multiply the number of detector events by.'),
required=True,
)
parser.add_argument(
"-m",
"--monitor-scale",
type=integer_greater_than_one,
default=None,
help=(
'Scale factor to multiply the number of monitor events by. '
'If not given, the detector scale will be used'
),
)
args = parser.parse_args()
if args.file != args.output:
shutil.copy2(args.file, args.output)
grow_nexus_file(
filename=args.output,
detector_scale=args.detector_scale,
monitor_scale=args.monitor_scale,
)
84 changes: 84 additions & 0 deletions tests/scripts/test_grow_nexus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import tempfile

import h5py
import numpy as np
import pytest

from ess.reduce.scripts.grow_nexus import grow_nexus_file


@pytest.fixture
def nexus_file():
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, 'test.nxs')
with h5py.File(path, 'a') as hf:
entry = hf.create_group('entry')
entry.attrs['NX_class'] = 'NXentry'

instrument = entry.create_group('instrument')
instrument.attrs['NX_class'] = 'NXinstrument'

for group, nxclass in (
('detector', 'NXdetector'),
('monitor', 'NXmonitor'),
):
detector = instrument.create_group(group)
detector.attrs['NX_class'] = nxclass

event_data = detector.create_group('event_data')
event_data.attrs['NX_class'] = 'NXevent_data'

event_data.create_dataset(
'event_index',
data=np.array([2, 4, 6]),
maxshape=(None,),
chunks=True,
)
event_data.create_dataset(
'event_time_zero',
data=np.array([0, 1, 2]),
maxshape=(None,),
chunks=True,
)
event_data.create_dataset(
'event_id',
data=np.array([0, 1, 2, 0, 1, 2]),
maxshape=(None,),
chunks=True,
)
event_data.create_dataset(
'event_time_offset',
data=np.array([1, 2, 1, 2, 1, 2]),
maxshape=(None,),
chunks=True,
)

yield path


@pytest.mark.parametrize('monitor_scale', (1, 2, None))
@pytest.mark.parametrize('detector_scale', (1, 2))
def test_grow_nexus(nexus_file, detector_scale, monitor_scale):
grow_nexus_file(
filename=nexus_file, detector_scale=detector_scale, monitor_scale=monitor_scale
)

monitor_scale = monitor_scale if monitor_scale is not None else detector_scale

with h5py.File(nexus_file, 'r') as f:
for detector, scale in zip(
('detector', 'monitor'), (detector_scale, monitor_scale)
):
np.testing.assert_equal(
[scale * i for i in [2, 4, 6]],
f[f'entry/instrument/{detector}/event_data/event_index'][()],
)
np.testing.assert_equal(
scale * [0, 1, 2, 0, 1, 2],
f[f'entry/instrument/{detector}/event_data/event_id'][()],
)
np.testing.assert_equal(
scale * [1, 2, 1, 2, 1, 2],
f[f'entry/instrument/{detector}/event_data/event_time_offset'][()],
)

0 comments on commit a17457f

Please sign in to comment.