From e9a1b83bc680a0a192c569b5ae3fa5e8e704d93d Mon Sep 17 00:00:00 2001 From: x12sa Date: Wed, 28 Oct 2026 10:40:19 +0100 Subject: [PATCH] feat(csaxs): invert option for BPM and SignalForwarder, enabled for BPM XBox2 The XBox2 BPM amplifier outputs a negative current, so more beam showed as more negative blade values. invert: true in the device config negates the input signals; the BPM position/diagonal ratios are unchanged and the intensity becomes positive. Enabled for bpm_xbox2_slowrb. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017NrH3poJsYBCTVq2Y9fuHZ --- csaxs_bec/device_configs/bl_endstation.yaml | 1 + csaxs_bec/devices/pseudo_devices/bpm.py | 11 ++++++-- .../pseudo_devices/signal_forwarder.py | 15 ++++++++--- tests/tests_devices/test_pseudo_devices.py | 25 +++++++++++++++++-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/csaxs_bec/device_configs/bl_endstation.yaml b/csaxs_bec/device_configs/bl_endstation.yaml index 2c44f656..56cc7ecd 100644 --- a/csaxs_bec/device_configs/bl_endstation.yaml +++ b/csaxs_bec/device_configs/bl_endstation.yaml @@ -1163,6 +1163,7 @@ bpm_xbox2_slowrb: right_top_ref: galilrioesxbox.analog_in.ch1 right_bot_ref: galilrioesxbox.analog_in.ch2 left_bot_ref: galilrioesxbox.analog_in.ch3 + invert: true # amplifier output is negative; positive values = more beam enabled: true readoutPriority: monitored onFailure: retry diff --git a/csaxs_bec/devices/pseudo_devices/bpm.py b/csaxs_bec/devices/pseudo_devices/bpm.py index 74aad13e..4dbd009f 100644 --- a/csaxs_bec/devices/pseudo_devices/bpm.py +++ b/csaxs_bec/devices/pseudo_devices/bpm.py @@ -7,7 +7,12 @@ from ophyd_devices.utils.bec_processed_signal import BECProcessedSignal class BPM(PSIPseudoDeviceBase): - """BPM positioner pseudo device.""" + """BPM positioner pseudo device. + + invert=True negates the four blade signals, e.g. for an amplifier whose + output current is negative. pos_x, pos_y and diagonal are ratios and do + not change; intensity changes sign. + """ # Blade signals, a,b,c,d left_top = Cpt( @@ -76,11 +81,13 @@ class BPM(PSIPseudoDeviceBase): right_top_ref: str, right_bot_ref: str, left_bot_ref: str, + invert: bool = False, device_manager=None, scan_info=None, **kwargs, ): super().__init__(name=name, device_manager=device_manager, scan_info=scan_info, **kwargs) + self._sign = -1 if invert else 1 # Get all blade signal objects from utility method signal_t = self.left_top.get_device_object_from_bec( object_name=left_top_ref, signal_name=self.name, device_manager=device_manager @@ -131,7 +138,7 @@ class BPM(PSIPseudoDeviceBase): ) def _compute_blade_signal(self, signal: Signal) -> float: - return signal.get() + return self._sign * signal.get() def _compute_intensity( self, left_top: Signal, right_top: Signal, right_bot: Signal, left_bot: Signal diff --git a/csaxs_bec/devices/pseudo_devices/signal_forwarder.py b/csaxs_bec/devices/pseudo_devices/signal_forwarder.py index 69ad94ee..48fa0b9c 100644 --- a/csaxs_bec/devices/pseudo_devices/signal_forwarder.py +++ b/csaxs_bec/devices/pseudo_devices/signal_forwarder.py @@ -9,7 +9,7 @@ from ophyd_devices.utils.bec_processed_signal import BECProcessedSignal class SignalForwarder(PSIPseudoDeviceBase): - """Forward one signal unchanged.""" + """Forward one signal unchanged, or negated with invert=True.""" signal = Cpt( BECProcessedSignal, @@ -20,8 +20,17 @@ class SignalForwarder(PSIPseudoDeviceBase): doc="Forwarded signal", ) - def __init__(self, name, signal_ref: str, device_manager=None, scan_info=None, **kwargs): + def __init__( + self, + name, + signal_ref: str, + invert: bool = False, + device_manager=None, + scan_info=None, + **kwargs, + ): super().__init__(name=name, device_manager=device_manager, scan_info=scan_info, **kwargs) + self._sign = -1 if invert else 1 src = self.signal.get_device_object_from_bec( object_name=signal_ref, signal_name=self.name, device_manager=device_manager @@ -30,4 +39,4 @@ class SignalForwarder(PSIPseudoDeviceBase): self.signal.set_compute_method(self._compute_signal, signal=src) def _compute_signal(self, signal: Signal) -> float: - return signal.get() + return self._sign * signal.get() diff --git a/tests/tests_devices/test_pseudo_devices.py b/tests/tests_devices/test_pseudo_devices.py index 336e67cf..373ab3e7 100644 --- a/tests/tests_devices/test_pseudo_devices.py +++ b/tests/tests_devices/test_pseudo_devices.py @@ -141,8 +141,9 @@ def patched_dm_bpm(dm_with_devices): return dm -@pytest.fixture -def bpm(patched_dm_bpm): +@pytest.fixture(params=[False]) +def bpm(request, patched_dm_bpm): + invert = request.param name = "bpm" bpm_config = Device( name=name, @@ -165,6 +166,7 @@ def bpm(patched_dm_bpm): right_top_ref="right_top", right_bot_ref="right_bot", left_bot_ref="left_bot", + invert=invert, device_manager=patched_dm_bpm, ) patched_dm_bpm.devices._add_device(bpm.name, bpm) @@ -239,3 +241,22 @@ def test_bpm_positions(bpm): assert bpm.pos_x.get() == 0 assert bpm.pos_y.get() == 0 assert bpm.diagonal.get() == 1 + + +@pytest.mark.parametrize("bpm", [True], indirect=True) +def test_bpm_invert_negates_blades_keeps_positions(bpm): + """invert=True: negative amplifier currents read as positive blade + signals and intensity; the ratio-based positions are unchanged.""" + devices = bpm.device_manager.devices + devices["left_top"].put(-1) + devices["right_top"].put(-3) + devices["right_bot"].put(-2) + devices["left_bot"].put(-2) + + assert bpm.left_top.get() == 1 + assert bpm.right_top.get() == 3 + assert bpm.intensity.get() == 8 + # same ratios as the non-inverted signals would give + assert bpm.pos_x.get() == 0.25 + assert bpm.pos_y.get() == 0 + assert bpm.diagonal.get() == 0.25