From ff6f348e9dd6aafd8540437bdf610775ae2ffddc Mon Sep 17 00:00:00 2001 From: Simon Heybrock Date: Mon, 3 Feb 2025 08:40:46 +0100 Subject: [PATCH 1/3] Add support for selecting a TOA range in detector plots --- .../handlers/detector_data_handler.py | 29 +++++++++++++++ src/beamlime/services/dashboard.py | 37 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/beamlime/handlers/detector_data_handler.py b/src/beamlime/handlers/detector_data_handler.py index 3fe98ef1..5ce6d8c4 100644 --- a/src/beamlime/handlers/detector_data_handler.py +++ b/src/beamlime/handlers/detector_data_handler.py @@ -19,6 +19,7 @@ from ..core.handler import ( Accumulator, Config, + ConfigValueAccessor, Handler, HandlerFactory, PeriodicAccumulatingHandler, @@ -129,15 +130,43 @@ def make_handler(self, key: MessageKey) -> Handler[DetectorEvents, sc.DataArray] ) +def _convert_toa_range( + value: dict[str, Any] | None, +) -> tuple[sc.Variable, sc.Variable] | None: + if value is None: + return None + return ( + sc.scalar(value['low'], unit=value['unit']).to(unit='ns'), + sc.scalar(value['high'], unit=value['unit']).to(unit='ns'), + ) + + class DetectorCounts(Accumulator[sc.DataArray, sc.DataArray]): """Accumulator for detector counts, based on a rolling detector view.""" def __init__(self, config: Config, detector_view: raw.RollingDetectorView): self._config = config self._det = detector_view + self._toa_range = ConfigValueAccessor( + config, 'toa_range', default=None, convert=_convert_toa_range + ) + self._current_toa_range = None + + def set_toa_range(self, toa_range: tuple[sc.Variable, sc.Variable] | None) -> None: + if toa_range != self._current_toa_range: + self._current_toa_range = toa_range + self.clear() + + def apply_toa_range(self, data: sc.DataArray) -> sc.DataArray: + if self._current_toa_range is None: + return data + low, high = self._current_toa_range + return data.bins.assign_coords(toa=data.bins.data).bins['toa', low:high] def add(self, timestamp: int, data: sc.DataArray) -> None: _ = timestamp + self.set_toa_range(self._toa_range()) + data = self.apply_toa_range(data) self._det.add_events(data) def get(self) -> sc.DataArray: diff --git a/src/beamlime/services/dashboard.py b/src/beamlime/services/dashboard.py index 49068f45..5d820797 100644 --- a/src/beamlime/services/dashboard.py +++ b/src/beamlime/services/dashboard.py @@ -157,6 +157,23 @@ def _setup_layout(self) -> None: value=[45, 55], marks={i: str(i) for i in range(0, 101, 20)}, ), + html.Label('Time-of-arrival range (us)'), + dcc.Checklist( + id='toa-checkbox', + options=[ + {'label': 'Filter by time-of-arrival [μs]', 'value': 'enabled'} + ], + value=[], + style={'margin': '10px 0'}, + ), + dcc.RangeSlider( + id='toa-range', + min=0, + max=71_000, + step=100, + value=[0, 71_000], + marks={i: str(i) for i in range(0, 71_001, 10_000)}, + ), html.Button('Clear', id='clear-button', n_clicks=0), ] self._app.layout = html.Div( @@ -204,6 +221,17 @@ def _setup_callbacks(self) -> None: Input('roi-y', 'value'), )(self.update_roi) + self._app.callback( + Output('toa-range', 'disabled'), + Input('toa-checkbox', 'value'), + )(lambda value: len(value) == 0) + + self._app.callback( + Output('toa-range', 'value'), + Input('toa-range', 'value'), + Input('toa-checkbox', 'value'), + )(self.update_toa_range) + def update_roi(self, roi_x, roi_y): if roi_x is not None: self._config_service.update_config( @@ -236,6 +264,15 @@ def update_roi(self, roi_x, roi_y): return roi_x, roi_y + def update_toa_range(self, toa_range, toa_enabled): + if len(toa_enabled) == 0: + self._config_service.update_config('toa_range', None) + else: + self._config_service.update_config( + 'toa_range', {'low': toa_range[0], 'high': toa_range[1], 'unit': 'us'} + ) + return toa_range + @staticmethod def create_monitor_plot(key: str, data: sc.DataArray) -> go.Figure: fig = go.Figure() From 09d209113944cf20a2fc23bcbfb0e45079dc18fe Mon Sep 17 00:00:00 2001 From: Simon Heybrock Date: Mon, 3 Feb 2025 08:47:46 +0100 Subject: [PATCH 2/3] Simplify logic --- .../handlers/detector_data_handler.py | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/beamlime/handlers/detector_data_handler.py b/src/beamlime/handlers/detector_data_handler.py index 5ce6d8c4..d73d0a12 100644 --- a/src/beamlime/handlers/detector_data_handler.py +++ b/src/beamlime/handlers/detector_data_handler.py @@ -130,17 +130,6 @@ def make_handler(self, key: MessageKey) -> Handler[DetectorEvents, sc.DataArray] ) -def _convert_toa_range( - value: dict[str, Any] | None, -) -> tuple[sc.Variable, sc.Variable] | None: - if value is None: - return None - return ( - sc.scalar(value['low'], unit=value['unit']).to(unit='ns'), - sc.scalar(value['high'], unit=value['unit']).to(unit='ns'), - ) - - class DetectorCounts(Accumulator[sc.DataArray, sc.DataArray]): """Accumulator for detector counts, based on a rolling detector view.""" @@ -148,24 +137,27 @@ def __init__(self, config: Config, detector_view: raw.RollingDetectorView): self._config = config self._det = detector_view self._toa_range = ConfigValueAccessor( - config, 'toa_range', default=None, convert=_convert_toa_range + config, 'toa_range', default=None, convert=self._convert_toa_range ) self._current_toa_range = None - def set_toa_range(self, toa_range: tuple[sc.Variable, sc.Variable] | None) -> None: - if toa_range != self._current_toa_range: - self._current_toa_range = toa_range - self.clear() + def _convert_toa_range(self, value: dict[str, Any] | None) -> None: + self.clear() + if value is None: + return None + return ( + sc.scalar(value['low'], unit=value['unit']).to(unit='ns'), + sc.scalar(value['high'], unit=value['unit']).to(unit='ns'), + ) def apply_toa_range(self, data: sc.DataArray) -> sc.DataArray: - if self._current_toa_range is None: + if (toa_range := self._toa_range()) is None: return data - low, high = self._current_toa_range + low, high = toa_range return data.bins.assign_coords(toa=data.bins.data).bins['toa', low:high] def add(self, timestamp: int, data: sc.DataArray) -> None: _ = timestamp - self.set_toa_range(self._toa_range()) data = self.apply_toa_range(data) self._det.add_events(data) From d1d4d6c3ad008f349ec40703c2db0ac4e2d9a576 Mon Sep 17 00:00:00 2001 From: Simon Heybrock Date: Fri, 7 Feb 2025 06:16:51 +0100 Subject: [PATCH 3/3] Add comment and docstring --- src/beamlime/handlers/detector_data_handler.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/beamlime/handlers/detector_data_handler.py b/src/beamlime/handlers/detector_data_handler.py index d73d0a12..9dca37d8 100644 --- a/src/beamlime/handlers/detector_data_handler.py +++ b/src/beamlime/handlers/detector_data_handler.py @@ -154,9 +154,23 @@ def apply_toa_range(self, data: sc.DataArray) -> sc.DataArray: if (toa_range := self._toa_range()) is None: return data low, high = toa_range + # GroupIntoPixels stores time-of-arrival as the data variable of the bins to + # avoid allocating weights that are all ones. For filtering we need to turn this + # into a coordinate, since scipp does not support filtering on data variables. return data.bins.assign_coords(toa=data.bins.data).bins['toa', low:high] def add(self, timestamp: int, data: sc.DataArray) -> None: + """ + Add data to the accumulator. + + Parameters + ---------- + timestamp: + Timestamp of the data. + data: + Data to be added. It is assumed that this is ev44 data that was passed + through :py:class:`GroupIntoPixels`. + """ _ = timestamp data = self.apply_toa_range(data) self._det.add_events(data)