fix(LamNI): accumulate rotation-center shift across calibration iterations
CI for csaxs_bec / test (push) Successful in 1m44s
CI for csaxs_bec / test (pull_request) Successful in 1m47s

lamni_move_to_scan_center's shift_x/shift_y are absolute offsets from the
currently-configured lsamx_center/lsamy_center (unchanged until the operator
confirms at the end of the procedure), not incremental deltas from wherever
the stage currently sits. The extended-sample iteration loop was passing
only the latest click's delta each time, so each new iteration partially
undid the previous one's correction instead of adding to it. Fixed by
accumulating the total shift across iterations and always applying the
running total.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-23 16:21:48 +02:00
co-authored by Claude Sonnet 5
parent 227cbb43ec
commit e8cee257c3
2 changed files with 94 additions and 13 deletions
@@ -185,13 +185,25 @@ def _make_calibration_align(client):
return align, dev_mock
def test_apply_rotation_center_shift_math(bec_client_mock):
def test_compute_shift_to_fzp_math(bec_client_mock):
client = bec_client_mock
align, _dev_mock = _make_calibration_align(client)
target_x, target_y = align._compute_shift_to_fzp(
fzp_x=0.0, fzp_y=0.0, center_x=0.4, center_y=0.5
)
assert target_x == pytest.approx(-0.4)
assert target_y == pytest.approx(0.5)
def test_apply_rotation_center_shift_moves_by_given_absolute_shift(bec_client_mock):
client = bec_client_mock
align, dev_mock = _make_calibration_align(client)
with mock.patch(f"{XRAY_EYE_ALIGN}.dev", dev_mock):
with mock.patch(f"{XRAY_EYE_ALIGN}.time.sleep"):
align._apply_rotation_center_shift(fzp_x=0.0, fzp_y=0.0, center_x=0.4, center_y=0.5)
align._apply_rotation_center_shift(-0.4, 0.5)
assert align.scans.lamni_move_to_scan_center.call_count == 2
for call in align.scans.lamni_move_to_scan_center.call_args_list:
@@ -200,6 +212,44 @@ def test_apply_rotation_center_shift_math(bec_client_mock):
assert call.kwargs["angle"] == 0.0
def test_extended_calibration_accumulates_shift_across_iterations(bec_client_mock):
"""Regression test: lamni_move_to_scan_center's shift_x/shift_y are
absolute offsets from the (not-yet-updated) configured center, so each
iteration in the extended path must pass the running total shift, not
just the latest click's delta -- otherwise iteration N+1 partially
undoes iteration N's correction instead of adding to it."""
client = bec_client_mock
align, dev_mock = _make_calibration_align(client)
# Two clicks of the rotation centre across two iterations, at different
# (px) positions -- deltas of (-0.4, 0.5) then (-0.1, 0.2) mm to FZP (0,0).
dev_mock.omny_xray_gui.xval_x_1.get.side_effect = [400.0, 100.0]
dev_mock.omny_xray_gui.yval_y_1.get.side_effect = [-500.0, -200.0]
with mock.patch(f"{XRAY_EYE_ALIGN}.dev", dev_mock):
with mock.patch(f"{XRAY_EYE_ALIGN}.umv"):
with mock.patch(f"{XRAY_EYE_ALIGN}.time.sleep"):
with mock.patch(f"{XRAY_EYE_ALIGN}.input", side_effect=["n", "y"]):
align.find_rotation_center(sample_type="extended", apply=False)
calls = align.scans.lamni_move_to_scan_center.call_args_list
# 2 calls per iteration (the function is called twice for settling)
assert len(calls) == 4
# iteration 1: cumulative == first delta (fzp=(0,0), center=(0.4,-0.5) mm
# -> target_x = 0-0.4 = -0.4, target_y = -0.5-0 = -0.5)
assert calls[0].kwargs["shift_x"] == pytest.approx(-0.4)
assert calls[0].kwargs["shift_y"] == pytest.approx(-0.5)
assert calls[1].kwargs["shift_x"] == pytest.approx(-0.4)
assert calls[1].kwargs["shift_y"] == pytest.approx(-0.5)
# iteration 2: cumulative == first delta + second delta, NOT just the
# second delta on its own (second click center=(0.1,-0.2) mm ->
# target_x=-0.1, target_y=-0.2; cumulative = (-0.5, -0.7))
assert calls[2].kwargs["shift_x"] == pytest.approx(-0.5)
assert calls[2].kwargs["shift_y"] == pytest.approx(-0.7)
assert calls[3].kwargs["shift_x"] == pytest.approx(-0.5)
assert calls[3].kwargs["shift_y"] == pytest.approx(-0.7)
def test_ensure_at_configured_center_skips_when_already_there(bec_client_mock, capsys):
client = bec_client_mock
align, dev_mock = _make_calibration_align(client)