Skip to content

Commit 21b99ac

Browse files
committed
Reconstruct wavelength as bin edges
1 parent 9654b95 commit 21b99ac

File tree

3 files changed

+65
-30
lines changed

3 files changed

+65
-30
lines changed

src/ess/powder/correction.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import enum
66
from typing import TypeVar
7+
from uuid import uuid4
78

89
import sciline
910
import scipp as sc
@@ -58,7 +59,9 @@ def normalize_by_monitor_histogram(
5859
)
5960
lut = sc.lookup(norm, dim="wavelength")
6061
if detector.bins is None:
61-
result = detector / lut[detector.coords['wavelength']]
62+
result = (
63+
detector / lut[sc.midpoints(detector.coords['wavelength'], dim='dspacing')]
64+
)
6265
else:
6366
result = detector.bins / lut
6467
return ScaledCountsDspacing[RunType](result)
@@ -104,19 +107,30 @@ def normalize_by_monitor_integrated(
104107

105108
# Clip `monitor` to the range of `detector`, where the bins at the boundary
106109
# may extend past the detector range (how label-based indexing works).
107-
if dim not in detector.coords:
110+
if detector.bins is not None and dim in detector.bins.coords:
108111
det_coord = detector.bins.coords.get(dim)
112+
lo = det_coord.nanmin()
113+
hi = det_coord.nanmax()
109114
else:
110-
# In this case, we have the wavelength at the bin centers. There is thus some
111-
# imprecision in the definition of the wavelength limits, since the detector
112-
# bins strictly speaking extend beyond these limits.
113-
counts = detector if detector.bins is None else detector.bins.size()
114-
flat_counts = counts.flatten(to='dummy')
115-
with_counts = flat_counts[flat_counts.data > sc.scalar(0.0, unit=counts.unit)]
116-
det_coord = with_counts.coords[dim]
117-
118-
lo = det_coord.nanmin()
119-
hi = det_coord.nanmax()
115+
# Mask zero count bins, which are an artifact from the rectangular 2-D binning.
116+
# The wavelength of those bins must be excluded when determining the integration
117+
# range.
118+
counts = (
119+
detector.copy(deep=False) if detector.bins is None else detector.bins.size()
120+
)
121+
counts.masks[uuid4().hex] = counts.data == sc.scalar(0.0, unit=counts.unit)
122+
det_coord = detector.coords[dim]
123+
edge_dims = {
124+
dim: size == det_coord.sizes[dim] + 1 for dim, size in counts.sizes.items()
125+
}
126+
if len(edge_dims) != 1:
127+
raise sc.CoordError(
128+
f"Cannot determine edges of coordinate '{dim}' in detector data."
129+
)
130+
edge_dim = next(iter(edge_dims))
131+
lo = counts.assign(det_coord[edge_dim, :-1]).nanmin().data
132+
hi = counts.assign(det_coord[edge_dim, 1:]).nanmax().data
133+
120134
if monitor.coords[dim].min() > lo or monitor.coords[dim].max() < hi:
121135
raise ValueError(
122136
"Cannot normalize by monitor: The wavelength range of the monitor is "

src/ess/powder/grouping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
def _reconstruct_wavelength(
2222
dspacing_bins: DspacingBins, two_theta_bins: TwoThetaBins
2323
) -> sc.Variable:
24-
dspacing = sc.midpoints(dspacing_bins)
24+
dspacing = dspacing_bins
2525
two_theta = sc.midpoints(two_theta_bins)
2626
return (2 * dspacing * sc.sin(two_theta / 2)).to(unit='angstrom')
2727

tests/powder/correction_test.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -376,8 +376,6 @@ def test_normalize_by_monitor_integrated_expected_results():
376376
sc.arange('wavelength', 1, 4, unit='counts'),
377377
coords={'wavelength': sc.arange('wavelength', 3.0, unit='Å')},
378378
).bin(wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å'))
379-
# "midpoints" at bounds to ensure we include that range.
380-
detector.coords['wavelength'] = detector.coords['wavelength'][::2]
381379
monitor = sc.DataArray(
382380
sc.array(dims=['wavelength'], values=[4.0, 5.0, 6.0], unit='counts'),
383381
coords={
@@ -391,19 +389,32 @@ def test_normalize_by_monitor_integrated_expected_results():
391389
monitor=WavelengthMonitor[SampleRun, CaveMonitor](monitor),
392390
uncertainty_broadcast_mode=UncertaintyBroadcastMode.fail,
393391
)
392+
# Last event is at 2, so the monitor bin with value 6.0 is not used.
394393
expected = ScaledCountsDspacing[SampleRun](
395-
detector / sc.scalar(4 * 0.5 + 5 * 1.5 + 6 * 1, unit='counts * Å')
394+
detector / sc.scalar(4 * 0.5 + 5 * 1.5, unit='counts * Å')
396395
)
397396
sc.testing.assert_identical(normalized, expected)
398397

399398

400-
def test_normalize_by_monitor_integrated_ignores_monitor_values_out_of_range():
399+
@pytest.mark.parametrize('event_coord', [True, False])
400+
def test_normalize_by_monitor_integrated_ignores_monitor_values_out_of_range(
401+
event_coord: bool,
402+
):
401403
detector = sc.DataArray(
402-
sc.arange('wavelength', 3, unit='counts'),
403-
coords={'wavelength': sc.arange('wavelength', 3.0, unit='Å')},
404-
).bin(wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å'))
405-
# "midpoints" at bounds to ensure we include that range.
406-
detector.coords['wavelength'] = detector.coords['wavelength'][::2]
404+
sc.arange('wavelength', 4, unit='counts'),
405+
coords={'wavelength': sc.arange('wavelength', 4.0, unit='Å')},
406+
)
407+
if event_coord:
408+
# Make sure event at 3 is included
409+
detector = detector.bin(
410+
wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3.1], unit='Å')
411+
)
412+
del detector.coords['wavelength']
413+
else:
414+
detector = detector.bin(
415+
wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å')
416+
)
417+
del detector.bins.coords['wavelength']
407418
monitor = sc.DataArray(
408419
sc.array(dims=['wavelength'], values=[4.0, 10.0], unit='counts'),
409420
coords={
@@ -421,13 +432,25 @@ def test_normalize_by_monitor_integrated_ignores_monitor_values_out_of_range():
421432
sc.testing.assert_identical(normalized, expected)
422433

423434

424-
def test_normalize_by_monitor_integrated_uses_monitor_values_at_boundary():
435+
@pytest.mark.parametrize('event_coord', [True, False])
436+
def test_normalize_by_monitor_integrated_uses_monitor_values_at_boundary(
437+
event_coord: bool,
438+
):
425439
detector = sc.DataArray(
426-
sc.arange('wavelength', 3, unit='counts'),
427-
coords={'wavelength': sc.arange('wavelength', 3.0, unit='Å')},
428-
).bin(wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å'))
429-
# "midpoints" at bounds to ensure we include that range.
430-
detector.coords['wavelength'] = detector.coords['wavelength'][::2]
440+
sc.arange('wavelength', 4, unit='counts'),
441+
coords={'wavelength': sc.arange('wavelength', 4.0, unit='Å')},
442+
)
443+
if event_coord:
444+
# Make sure event at 3 is included
445+
detector = detector.bin(
446+
wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3.1], unit='Å')
447+
)
448+
del detector.coords['wavelength']
449+
else:
450+
detector = detector.bin(
451+
wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å')
452+
)
453+
del detector.bins.coords['wavelength']
431454
monitor = sc.DataArray(
432455
sc.array(dims=['wavelength'], values=[4.0, 10.0], unit='counts'),
433456
coords={
@@ -450,8 +473,6 @@ def test_normalize_by_monitor_integrated_raises_if_monitor_range_too_narrow():
450473
sc.arange('wavelength', 3, unit='counts'),
451474
coords={'wavelength': sc.arange('wavelength', 3.0, unit='Å')},
452475
).bin(wavelength=sc.array(dims=['wavelength'], values=[0.0, 2, 3], unit='Å'))
453-
# "midpoints" at bounds to ensure we include that range.
454-
detector.coords['wavelength'] = detector.coords['wavelength'][::2]
455476
monitor = sc.DataArray(
456477
sc.array(dims=['wavelength'], values=[4.0, 10.0], unit='counts'),
457478
coords={

0 commit comments

Comments
 (0)