changed criteria for judging whether a fit is correct or not

This commit is contained in:
x10sa committed 2026-09-07 10:51:15 +02:00
1 parent 5fbb696eb2
commit 016e9b1394
3 files changed
+244 -68

No files matched your search

+9 -3
View File
@@ -151,7 +151,7 @@ def fit(data, fit_params):
}
def check_peak_fit(x_data, result):
def check_peak_fit(x_data, result, min_fwhm_steps=3):
"""Check whether a fitted peak is reliable
Parameters: X data, results from the fit step
Returns: success or problems"""
@@ -166,13 +166,19 @@ def check_peak_fit(x_data, result):
centre = result["centre"]
fwhm = result["fwhm"]
step_size = np.mean(np.diff(x_data))
fwhm_steps = result["fwhm"] / abs(step_size)
problems = []
if not x_min + 0.1 * scan_range < centre < x_max - 0.1 * scan_range:
problems.append("peak centre is too close to scan edge")
if fwhm < 3 * step:
problems.append("peak is narrower than 3 scan steps")
if fwhm < min_fwhm_steps * step:
problems.append(
f"Peak FWHM spans only {fwhm_steps:.2f} scan steps "
f"(minimum {min_fwhm_steps})"
)
if fwhm > 0.7 * scan_range:
problems.append("peak is too broad for scan range")
+1 -1
View File
@@ -76,7 +76,7 @@ def plot_fitted_data(data, fit_result, window_name="Fitting", plot_widget=None,
f"FWHM = {fit_result['fwhm']:.5f}\n"
f"Height = {fit_result['height']:.4g}\n"
f"Model = {fit_result['model']}\n"
f"Chi sq = {fit_result['chi_sq']:.3g}\n"
f"Centre_StdErr = {fit_result['centre_stderr']:.3g}\n"
f"SNR = {fit_result['snr']:.3g}"
)
+234 -64
View File
@@ -117,87 +117,161 @@ def go_to_peak(
gomax: bool = False,
negative: bool = False,
gap: bool = False,
min_fwhm_steps: float = 3,
window_name: str = "Fitting",
plot_widget=None,
):
"""
Go to the peak of a signal by scanning a motor within a specified range and
identifying the optimal position based on signal peak data.
Scan a motor and move either to the fitted peak centre or the
highest measured data point.
Parameters:
motor_device: The motor device to be scanned.
signal_device: The signal device to monitor during the scan.
start (float): The starting position of the scan. Ignored if `relative` is True.
stop (float): The ending position of the scan. Ignored if `relative` is True.
steps (int): The number of steps to divide the scan range into.
relative (bool, optional): If True, interpret `start` and `stop` as relative to
the current motor position. Defaults to RELATIVE_MODE constant.
plot (bool, optional): If True, plot the scan data and the fitted results.
Defaults to True.
settle (float, optional): The time in seconds to wait after each step for the
signal to stabilize. Defaults to DEFAULT_SETTLE_TIME constant.
confirm (bool, optional): If True, ask for user confirmation before starting
the scan. Defaults to True.
If gomax=False:
Fit the scan, validate the fit using check_peak_fit(),
and move to the fitted centre.
Raises:
Exception: Raises exceptions potentially raised by dependent functions or
operations such as plotting, fitting, or motor movement.
If gomax=True:
Skip peak-fit validation and move directly to the highest
measured signal value. Information about whether the maximum
occurred at a scan boundary is returned.
Returns:
fit parameters
Parameters
----------
motor_device
Motor device to scan.
signal_device
Signal device to monitor.
start : float
Scan start position.
stop : float
Scan end position.
steps : int
Number of scan steps.
relative : bool
If True, start and stop are relative to current motor position.
plot : bool
Plot scan data/results.
settle : float
Settling time at each scan point.
confirm : bool
Ask for confirmation before starting scan.
gomax : bool
If True, move to highest measured point rather than fitted centre.
negative : bool
Fit a negative-going peak.
gap : bool
Apply minimum-gap protection when using fitted peak position.
min_fwhm_steps : float
Minimum allowed fitted FWHM in scan steps. Only used when
gomax=False.
window_name : str
BEC GUI window name.
plot_widget
Optional explicit plot widget.
Returns
-------
dict
Result information.
For a successful fitted peak:
success
problems
centre
fwhm
height
chi_sq
centre_stderr
snr
For gomax=True:
success
problems
x_max
max_index
at_lower_boundary
at_upper_boundary
For a rejected fit:
success=False
problems=list of fit rejection reasons
"""
# ------------------------------------------------------------
# Get motor and signal names
# ------------------------------------------------------------
mres = get_value(motor_device, True)
motor_name = mres["root"]
sres = get_value(signal_device, True)
signal_name = sres["root"]
print(f"Signal name is {signal_name}")
# if plot:
# if plot_widget is not None:
# plot_live_data(plot_widget, motor_name, signal_name, title=f"{motor_name} scan")
# else:
# plot_live_data_bec(motor_name, signal_name, window_name=window_name)
# ------------------------------------------------------------
# Set up live plotting
# ------------------------------------------------------------
if plot:
plot_live_data(
motor_name,
motor_name,
signal_name,
window_name=window_name,
plot_widget=plot_widget,
title=f"{motor_name} scan")
plot_widget=plot_widget,
title=f"{motor_name} scan",
)
# ------------------------------------------------------------
# Validate scan range and get starting position
# ------------------------------------------------------------
# Validate and calculate step size
step_size = calculate_step_size(start, stop, steps)
# Confirm the scan range
starting_position = mres["value"]
print(f"Starting motor position = {starting_position}")
if confirm:
if relative:
scan_start = starting_position + start
scan_end = starting_position + stop
print(
f"\nScanning from {scan_start: .6g} to {scan_end: .6g} in "
f"{steps} steps of size {step_size}"
f"\nScanning from {scan_start:.6g} to {scan_end:.6g} "
f"in {steps} steps of size {step_size}"
)
print(f"Relative mode = {relative}")
else:
print(
f"\nScanning from {start: .5g} to {stop: .5g} in {steps} steps of size {step_size}"
f"\nScanning from {start:.5g} to {stop:.5g} "
f"in {steps} steps of size {step_size}"
)
print(f"Relative mode = {relative}")
input("Press Enter to continue...")
# Perform the scan
# ------------------------------------------------------------
# Perform scan
# ------------------------------------------------------------
scan_result = scans.line_scan(
motor_device, start, stop, steps=steps, relative=relative, settling_time=settle
motor_device,
start,
stop,
steps=steps,
relative=relative,
settling_time=settle,
)
motor_data = scan_result.scan.live_data[mres["live_root"]][mres["name"]].val
# # motor_data = scan_result.scan.live_data[motor_name][motor_name].val
# sres = get_value(signal_name, True)
signal_data = scan_result.scan.live_data[sres["root"]][sres["name"]].val
motor_data = (
scan_result.scan.live_data[mres["live_root"]][mres["name"]].val
)
signal_data = (
scan_result.scan.live_data[sres["root"]][sres["name"]].val
)
scan_number = "Current"
data = {
@@ -209,16 +283,87 @@ def go_to_peak(
"scan_number": scan_number,
}
# Define and fit model to scan data
# ============================================================
# GOMAX MODE
# ============================================================
#
# Do not fit or apply peak-shape validation.
# Simply use the highest measured point.
#
if gomax:
if negative:
max_index = int(np.argmin(data["y_data"]))
else:
max_index = int(np.argmax(data["y_data"]))
x_max = float(data["x_data"][max_index])
at_lower_boundary = max_index == 0
at_upper_boundary = max_index == len(data["x_data"]) - 1
print(f"Maximum measured signal at {x_max:.6g}")
if at_lower_boundary:
print("Maximum is at the lower scan boundary")
if at_upper_boundary:
print("Maximum is at the upper scan boundary")
# Plot raw scan rather than trying to assess a fitted peak
if plot:
plot_scan_data(
data,
window_name=window_name,
plot_widget=plot_widget,
title=f"{motor_name} scan - maximum at {x_max:.6g}",
)
# Move to measured maximum
move_to_scan_position(
data["motor_device"],
data["motor_name"],
x_max,
data,
)
return {
"success": True,
"problems": [],
"x_max": x_max,
"max_index": max_index,
"at_lower_boundary": at_lower_boundary,
"at_upper_boundary": at_upper_boundary,
}
# ============================================================
# FITTED PEAK MODE
# ============================================================
fit_params = create_fit_parameters(
deriv=False, negative=False, model=FitDefaults.MODEL, baseline=FitDefaults.BASELINE
deriv=False,
negative=negative,
model=FitDefaults.MODEL,
baseline=FitDefaults.BASELINE,
)
fit_result = fit(data, fit_params)
check = check_peak_fit(data["x_data"], fit_result)
# ------------------------------------------------------------
# Validate fitted peak
# ------------------------------------------------------------
check = check_peak_fit(
data["x_data"],
fit_result,
min_fwhm_steps=min_fwhm_steps,
)
if not check["success"]:
print("Peak fit rejected:")
for problem in check["problems"]:
print(f" - {problem}")
@@ -229,42 +374,67 @@ def go_to_peak(
plot_widget=plot_widget,
title=f"{motor_name} scan - FIT REJECTED",
)
print(f"Returning motor to start position, {starting_position:.5g}")
print(
f"Returning motor to start position, "
f"{starting_position:.5g}"
)
restore_position(
motor_device,
starting_position,
)
return None
return {
"success": False,
"problems": check["problems"],
}
# ------------------------------------------------------------
# Plot accepted fit
# ------------------------------------------------------------
# Plot the fitted data if plot = True
if plot:
plot_fitted_data(data, fit_result, window_name=window_name, plot_widget=plot_widget)
plot_fitted_data(
data,
fit_result,
window_name=window_name,
plot_widget=plot_widget,
)
# ------------------------------------------------------------
# Move to fitted peak centre
# ------------------------------------------------------------
centre = fit_result["centre"]
if gap and centre < 4.5:
print(
f"Fitted gap position {centre:.5g} mm is below "
"minimum gap of 4.5 mm"
)
umv(dev.id_gap, 4.5)
# If gomax is set then move to the maximum value, rather than the fit centre
if gomax:
value = fit_result["x_max"]
print(f"Max position is at {value}")
move_to_scan_position(data["motor_device"], data["motor_name"], fit_result["x_max"], data)
else:
if gap:
if fit_result["centre"] < 4.5:
umv(dev.id_gap, 4.5)
else:
move_to_scan_position(
data["motor_device"], data["motor_name"], fit_result["centre"], data
)
else:
# Safely move the motor to the peak position
move_to_scan_position(data["motor_device"], data["motor_name"], fit_result["centre"], data)
move_to_scan_position(
data["motor_device"],
data["motor_name"],
centre,
data,
)
# ------------------------------------------------------------
# Return successful fit information
# ------------------------------------------------------------
return {
"centre": fit_result["centre"],
"success": True,
"problems": [],
"centre": centre,
"fwhm": float(fit_result["fwhm"]),
"height": float(fit_result["height"]),
"chi_sq": float(fit_result["chi_sq"]),
"centre_stderr": float(fit_result["centre_stderr"]),
"snr": float(fit_result["snr"]),
}