fix(xrayeye): make DAP sine fit converge by fixing start values and signed amplitude
CI for csaxs_bec / test (push) Failing after 2m13s

The xray eye alignment fit (A*sin(f*x+shift)+intercept, 5 points) relied on
lmfit's SineModel.guess() for the start values. Its FFT-based shift guess is
arbitrary for 5 points and often left the fit stuck at the shift bound (2*pi)
far from the data, so the plotted fit missed the submitted points and
wrong parameters were loaded into flomni.

Override all five parameters so the DAP server skips guess(): signed
amplitude (start 30), shift start 0 within +-pi, intercept start 0,
frequency and slope still fixed. Shared by the x and y waveforms.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017szgTwuHG65YhjoBGiK2Gj
This commit is contained in:
x12saandClaude Sonnet 5 committed 2026-09-20 11:03:06 +02:00
1 parent d01af690a9
commit bd85d5e673
1 file changed
+22 -8
@@ -34,6 +34,26 @@ from qtpy.QtWidgets import (
logger = bec_logger.logger
CAMERA = ("cam_xeye", "image")
# Start values / bounds for the DAP fit A*sin(f*x + shift) + intercept of the
# alignment offsets vs. angle (5 points, 0..180 deg, f fixed to 1 deg in rad).
# All five parameters are given so the DAP server does not use lmfit's
# SineModel.guess(): its FFT-based shift guess is arbitrary for 5 points and
# regularly left the fit stuck at the shift bound (2*pi) far from the data.
# A signed amplitude (SineModel defaults to A >= 0) plus a fixed start
# (A=30, shift=0, intercept=0) converged to the global least-squares optimum
# in 1500/1500 random test datasets; A >= 0 failed in a few percent.
SINE_FIT_DAP_PARAMETERS = [
{
"amplitude": {"value": 30, "vary": True, "min": -1e6, "max": 1e6},
"frequency": {"value": 0.0174533, "vary": False, "min": 0.01, "max": 0.02},
"shift": {"value": 0, "vary": True, "min": -math.pi, "max": math.pi},
},
{
"slope": {"value": 0, "vary": False, "min": 0.0, "max": 0.02},
"intercept": {"value": 0, "vary": True, "min": -1e6, "max": 1e6},
},
]
class TargetCrosshair:
"""
@@ -615,10 +635,7 @@ class OMNY_XRayEye(BECWidget, QWidget):
y=[1],
label="fit-x",
dap=["SineModel", "LinearModel"],
dap_parameters=[
{"frequency": {"value": 0.0174533, "vary": False, "min": 0.01, "max": 0.02}},
{"slope": {"value": 0, "vary": False, "min": 0.0, "max": 0.02}},
],
dap_parameters=SINE_FIT_DAP_PARAMETERS,
dap_oversample=5,
)
self.waveform_y.plot(
@@ -626,10 +643,7 @@ class OMNY_XRayEye(BECWidget, QWidget):
y=[2],
label="fit-y",
dap=["SineModel", "LinearModel"],
dap_parameters=[
{"frequency": {"value": 0.0174533, "vary": False, "min": 0.01, "max": 0.02}},
{"slope": {"value": 0, "vary": False, "min": 0.0, "max": 0.02}},
],
dap_parameters=SINE_FIT_DAP_PARAMETERS,
dap_oversample=5,
)
self.fit_x = self.waveform_x.curves[0]