diff --git a/csaxs_bec/device_configs/bl_detectors.yaml b/csaxs_bec/device_configs/bl_detectors.yaml index 74cbc7b4..be88dd87 100644 --- a/csaxs_bec/device_configs/bl_detectors.yaml +++ b/csaxs_bec/device_configs/bl_detectors.yaml @@ -1,23 +1,16 @@ -eiger2_si_500k: - description: Eiger 2 Si 500K detector - deviceClass: csaxs_bec.devices.jungfraujoch.eiger2_si_500k.Eiger2Si500K - deviceConfig: - detector_distance: 1000 - beam_center: [200, 200] - onFailure: raise - enabled: True - readoutPriority: async - softwareTrigger: False - deviceTags: - - bl_detectors - - eiger +# Eiger detectors (JungfrauJoch). Only one may be enabled at a time -- the JFJ broker +# serves a single selected/initialised detector, so switching the active one here also +# requires selecting/initialising the matching detector on the JFJ broker itself. +# JungfrauJoch-side config (detector selection/init, pixel mask, azimuthal integration +# settings) is done in the Web UI at http://sls-jfjoch-001.psi.ch:8080. +# The Eiger 2 Si 500K detector server itself is reachable at 129.129.122.159. +# eiger2_si_500k: -# eiger_1_5: -# description: Eiger 1.5M in-vacuum detector -# deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M +# description: Eiger 2 Si 500K detector +# deviceClass: csaxs_bec.devices.jungfraujoch.eiger2_si_500k.Eiger2Si500K # deviceConfig: -# detector_distance: 2200 -# beam_center: [870, 1203] +# detector_distance: 1000 +# beam_center: [200, 200] # onFailure: raise # enabled: True # readoutPriority: async @@ -26,6 +19,20 @@ eiger2_si_500k: # - bl_detectors # - eiger +eiger_1_5: + description: Eiger 1.5M in-vacuum detector + deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M + deviceConfig: + detector_distance: 2200 + beam_center: [870, 1203] + onFailure: raise + enabled: True + readoutPriority: async + softwareTrigger: False + deviceTags: + - bl_detectors + - eiger + # eiger_9: # description: Eiger 9M detector # deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py index 6e41a007..c597ac10 100644 --- a/csaxs_bec/devices/jungfraujoch/eiger.py +++ b/csaxs_bec/devices/jungfraujoch/eiger.py @@ -226,11 +226,18 @@ class Eiger(PSIDeviceBase): message (dict): The message received from the preview stream. """ if message.get("type", "") == "image": - data = message.get("data", {}).get("default", None) - if data is None: + channels = message.get("data", {}) + # The data dict's key varies by detector: "default" for eiger_1_5/eiger_9, + # but a threshold name (e.g. "threshold_1") for the multi-threshold-capable + # Eiger2 Si 500K. Prefer "default" when present, else take whichever single + # channel the broker actually sent. + frame = channels.get("default") + if frame is None: + frame = next(iter(channels.values()), None) + if frame is None: logger.error(f"Received image message on device {self.name} without data.") return - self.preview_image.put(data) + self.preview_image.put(frame) # pylint: disable=missing-function-docstring @property diff --git a/tests/tests_devices/test_eiger.py b/tests/tests_devices/test_eiger.py index 5e036de5..979494c2 100644 --- a/tests/tests_devices/test_eiger.py +++ b/tests/tests_devices/test_eiger.py @@ -542,6 +542,29 @@ def test_eiger_preview_callback(eiger_1_5m): mock_preview_put.assert_not_called() +def test_eiger_preview_callback_falls_back_to_non_default_channel(eiger2_si_500k): + """The Eiger2 Si 500K's multi-threshold preview data has no "default" key (it's + named after the threshold, e.g. "threshold_1" -- confirmed against a live capture + of the JFJ preview stream); the callback must still forward that single channel. + """ + eiger = eiger2_si_500k + frame = np.array([[1, 2], [3, 4]]) + msg = {"type": "image", "data": {"threshold_1": frame}} + with mock.patch.object(eiger.preview_image, "put") as mock_preview_put: + eiger._preview_callback(msg) + mock_preview_put.assert_called_once_with(frame) + + +def test_eiger_preview_callback_logs_when_data_is_truly_empty(eiger_1_5m): + """No channel at all (empty or missing data dict) must still be treated as missing, + not silently pass through some other unrelated dict key.""" + eiger = eiger_1_5m + for msg in [{"type": "image", "data": {}}, {"type": "image"}]: + with mock.patch.object(eiger.preview_image, "put") as mock_preview_put: + eiger._preview_callback(msg) + mock_preview_put.assert_not_called() + + def test_eiger_acquisition_matches_the_gated_window(eiger_1_5m): """The detector must acquire for exp_time - readout, not for the whole period.