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: 6 additions & 3 deletions doc/devices/dae.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,16 @@ Published signals:
### {py:obj}`PeriodSpecIntegralsReducer<ibex_bluesky_core.devices.simpledae.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<ibex_bluesky_core.devices.simpledae.DSpacingMappingReducer>`

Expand Down
36 changes: 31 additions & 5 deletions src/ibex_bluesky_core/devices/simpledae/_reducers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down Expand Up @@ -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="")

Expand Down Expand Up @@ -393,13 +404,28 @@ 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]:
"""Publish interesting signals derived or used by this reducer."""
return [
self.mon_integrals,
self.det_integrals,
self.det_sum,
self.mon_sum,
self.intensity,
self.intensity_stddev,
]


Expand Down
8 changes: 8 additions & 0 deletions tests/devices/simpledae/test_reducers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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([]))
Expand Down