diff --git a/src/beamlime/handlers/detector_data_handler.py b/src/beamlime/handlers/detector_data_handler.py index 3fe98ef1..9dca37d8 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, @@ -135,9 +136,43 @@ class DetectorCounts(Accumulator[sc.DataArray, sc.DataArray]): 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=self._convert_toa_range + ) + self._current_toa_range = None + + 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 (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) 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()