Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/ess/livedata/workflows/detector_view/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def detector_image(
Returns
-------
:
2D detector image.
2D detector image, published as float32.
"""
spectral_dim = histogram.dims[-1]
if histogram_slice is not None:
Expand All @@ -273,7 +273,12 @@ def detector_image(

if use_weighting:
image = image / weights
return DetectorImage[AccumulationMode](image)
# da00 is uncompressed, so float32 halves the wire size of every image (and what
# the dashboard buffers per frame). Only the published image is cast: the
# accumulation above must stay float64 because float32 stops incrementing at
# 2**24, which a long-running cumulative image can reach. Casting the result
# instead rounds it by at most one part in 2**24 and nothing accumulates on top.
return DetectorImage[AccumulationMode](image.to(dtype='float32', copy=False))


def counts_total(
Expand Down
6 changes: 4 additions & 2 deletions tests/config/tbl_specs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ def test_multiblade_spectrum_sums_strips():
assert spectrum.sizes['blade'] == MULTIBLADE_SIZES['blade']
assert spectrum.sizes['wire'] == MULTIBLADE_SIZES['wire']
# Summing strips redistributes but does not lose counts.
assert sc.isclose(spectrum.sum().data, result['cumulative'].sum().data).value
# 'cumulative' is published as float32; compare values, not scipp variables.
assert spectrum.sum().value == pytest.approx(result['cumulative'].sum().value)


def test_he3_spectrum_keeps_every_pixel():
Expand All @@ -195,7 +196,8 @@ def test_he3_spectrum_keeps_every_pixel():
assert spectrum.dims == ('tube', 'pixel', 'time_of_arrival')
assert spectrum.sizes['tube'] == HE3_SIZES['dim_0']
assert spectrum.sizes['pixel'] == HE3_SIZES['dim_1']
assert sc.isclose(spectrum.sum().data, result['cumulative'].sum().data).value
# 'cumulative' is published as float32; compare values, not scipp variables.
assert spectrum.sum().value == pytest.approx(result['cumulative'].sum().value)


def test_he3_spectrum_does_not_alias_the_accumulator_buffer():
Expand Down
6 changes: 6 additions & 0 deletions tests/workflows/detector_view/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ def test_full_workflow_accumulate_and_finalize(self):
assert result['cumulative'].dims == ('y', 'x')
assert result['cumulative'].sizes == {'y': 4, 'x': 4}

# Images go on the wire as float32; the count scalars are not images and
# keep the accumulator's dtype.
assert result['cumulative'].dtype == 'float32'
assert result['current'].dtype == 'float32'
assert result['counts_total_cumulative'].dtype == 'float64'

def test_cumulative_accumulates_current_resets(self):
"""Test that cumulative accumulates and current resets after finalize."""
# Use factory to create workflow (same code path as production)
Expand Down
55 changes: 54 additions & 1 deletion tests/workflows/detector_view/providers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@ def test_detector_image_sums_over_spectral_dim(self):
assert result.dims == ('y', 'x')
assert result.sizes == {'y': 4, 'x': 4}
# Each pixel should have sum of 10 spectral bins
expected = sc.full(dims=['y', 'x'], shape=[4, 4], value=10.0, unit='counts')
expected = sc.full(
dims=['y', 'x'], shape=[4, 4], value=10.0, unit='counts', dtype='float32'
)
assert sc.allclose(result.data, expected)

def test_detector_image_with_histogram_slice(self):
Expand Down Expand Up @@ -329,6 +331,57 @@ def test_detector_image_with_histogram_slice(self):
# Should only sum ~5 bins (0-50000 ns from 0-100000 ns range)
assert result.dims == ('y', 'x')

@pytest.mark.parametrize("use_weighting", [False, True])
def test_detector_image_is_published_as_float32(self, use_weighting):
"""The image is cast on the way out to halve the da00 payload."""
data = sc.DataArray(
sc.ones(
dims=['y', 'x', 'event_time_offset'],
shape=[4, 4, 10],
unit='counts',
dtype='float64',
)
)
weights = PixelWeights(sc.full(dims=['y', 'x'], shape=[4, 4], value=2.0))

result = detector_image(
histogram=AccumulatedHistogram[Cumulative](data),
histogram_slice=None,
weights=weights,
use_weighting=UsePixelWeighting(use_weighting),
)

assert result.dtype == 'float32'
expected = 5.0 if use_weighting else 10.0
assert sc.allclose(
result.data,
sc.full(dims=['y', 'x'], shape=[4, 4], value=expected, unit='counts').to(
dtype='float32'
),
)

def test_detector_image_cast_leaves_accumulated_histogram_untouched(self):
"""Accumulation must stay float64: float32 stops counting at 2**24."""
data = sc.DataArray(
sc.full(
dims=['y', 'x', 'event_time_offset'],
shape=[1, 1, 1],
value=2.0**24,
unit='counts',
)
)
histogram = AccumulatedHistogram[Cumulative](data)

detector_image(
histogram=histogram,
histogram_slice=None,
weights=PixelWeights(sc.ones(dims=['y', 'x'], shape=[1, 1])),
use_weighting=UsePixelWeighting(False),
)

assert histogram.dtype == 'float64'
assert (histogram + sc.scalar(1.0, unit='counts')).sum().value == 2.0**24 + 1


class TestCountProviders:
"""Tests for count provider functions."""
Expand Down
4 changes: 3 additions & 1 deletion tests/workflows/detector_view/spectrum_view_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""Integration tests for the unified spectrum-view output."""

import pydantic
import pytest
import scipp as sc
from ess.reduce.nexus.types import RawDetector, SampleRun

Expand Down Expand Up @@ -76,7 +77,8 @@ def test_spectrum_view_sums_over_declared_dim(self):
assert spectrum.dims == ('x', 'time_of_arrival')
cumulative = result['cumulative']
# Cumulative is summed over time_of_arrival => total counts match total events.
assert sc.isclose(spectrum.sum().data, cumulative.sum().data).value
# It is published as float32, so compare values rather than scipp variables.
assert spectrum.sum().value == pytest.approx(cumulative.sum().value)

def test_spectrum_view_rebin_factor_applied(self):
spec = SpectrumViewSpec(
Expand Down
Loading