diff --git a/doc/devices/dae.md b/doc/devices/dae.md index 84011e13..578b217f 100644 --- a/doc/devices/dae.md +++ b/doc/devices/dae.md @@ -191,13 +191,16 @@ Published signals: ### {py:obj}`PeriodSpecIntegralsReducer` This reducer exposes the raw integrals of the configured detector and monitor spectra, as -numpy arrays. By itself, this reducer is not useful in a scan, but is useful for downstream -processing as performed by reflectometry detector-mapping alignment for -example. +numpy arrays. As a convenience, this reducer additionally publishes scalar sums of detector +and monitor intensities, and an 'intensity' (detector sum divided by monitor sum). Published signals: - `reducer.mon_integrals` - `numpy` array of integrated counts on each configured monitor pixel. - `reducer.det_integrals` - `numpy` array of integrated counts on each configured detector pixel. +- `reducer.mon_sum` - scalar of integrated counts on each configured monitor. +- `reducer.det_sum` - scalar of integrated counts on each configured detector pixel. +- `reducer.intensity` - detector sum normalized by monitor sum. +- `reducer.intensity_stddev` - standard deviation of normalized intensity. ### {py:obj}`DSpacingMappingReducer` diff --git a/src/ibex_bluesky_core/devices/simpledae/_reducers.py b/src/ibex_bluesky_core/devices/simpledae/_reducers.py index 82d74fbb..f14625aa 100644 --- a/src/ibex_bluesky_core/devices/simpledae/_reducers.py +++ b/src/ibex_bluesky_core/devices/simpledae/_reducers.py @@ -321,12 +321,19 @@ def additional_readable_signals(self, dae: Dae) -> list[Device]: class PeriodSpecIntegralsReducer(Reducer, StandardReadable): """A DAE Reducer which simultaneously exposes integrals of many spectra in the current period. - Two types of integrals are available: detectors and monitors. Other than defaults, their - behaviour is identical. No normalization is performed in this reducer - exactly how the - detector and monitor integrals are used is defined downstream. + Two types of integrals are available: detectors and monitors. - By itself, the data from this reducer is not suitable for use in a scan - but it provides - raw data which may be useful for further processing as part of callbacks (e.g. LiveDispatchers). + The exposed signals are: + + - det_integrals (1-dimensional array) + - mon_integrals (1-dimensional array) + + For convenience, three additional properties are also exposed: + + - det_sum (scalar) - the sum of detector intensities across every detector. + - mon_sum (scalar) - the sum of monitor intensities across every monitor. + - intensity (scalar) - det_sum / mon_sum + - intensity_stddev (scalar) - det_sum / mon_sum """ def __init__( @@ -354,6 +361,10 @@ def __init__( self.mon_integrals, self._mon_integrals_setter = soft_signal_r_and_setter( Array1D[np.int32], np.ndarray([], dtype=np.int32) ) + self.det_sum, self._det_sum_setter = soft_signal_r_and_setter(int, 0) + self.mon_sum, self._mon_sum_setter = soft_signal_r_and_setter(int, 0) + self.intensity, self._intensity_setter = soft_signal_r_and_setter(float, 0.0) + self.intensity_stddev, self._intensity_stddev_setter = soft_signal_r_and_setter(float, 0.0) super().__init__(name="") @@ -393,6 +404,17 @@ async def reduce_data(self, dae: Dae) -> None: self._det_integrals_setter(det_integrals) self._mon_integrals_setter(mon_integrals) + scalar_det_sum = det_integrals.sum() + scalar_mon_sum = mon_integrals.sum() + self._det_sum_setter(scalar_det_sum) + self._mon_sum_setter(scalar_mon_sum) + + normalized = sc.scalar( + scalar_det_sum, variance=scalar_det_sum + VARIANCE_ADDITION, dtype="float64" + ) / sc.scalar(scalar_mon_sum, variance=scalar_mon_sum + VARIANCE_ADDITION, dtype="float64") + self._intensity_setter(normalized.value) + self._intensity_stddev_setter(math.sqrt(normalized.variance)) + logger.info("reduction complete") def additional_readable_signals(self, dae: Dae) -> list[Device]: @@ -400,6 +422,10 @@ def additional_readable_signals(self, dae: Dae) -> list[Device]: return [ self.mon_integrals, self.det_integrals, + self.det_sum, + self.mon_sum, + self.intensity, + self.intensity_stddev, ] diff --git a/tests/devices/simpledae/test_reducers.py b/tests/devices/simpledae/test_reducers.py index d8cfd177..92f5a091 100644 --- a/tests/devices/simpledae/test_reducers.py +++ b/tests/devices/simpledae/test_reducers.py @@ -1012,6 +1012,14 @@ async def test_period_spec_integrals_reducer( np.testing.assert_equal(await reducer.mon_integrals.get_value(), mon_integrals) np.testing.assert_equal(await reducer.det_integrals.get_value(), det_integrals) + assert await reducer.mon_sum.get_value() == mon_integrals.sum() + assert await reducer.det_sum.get_value() == det_integrals.sum() + + assert ( + pytest.approx(await reducer.intensity.get_value()) + == det_integrals.sum() / mon_integrals.sum() + ) + def test_period_spec_integrals_reducer_publishes_signals(simpledae: SimpleDae): reducer = PeriodSpecIntegralsReducer(detectors=np.array([]), monitors=np.array([]))