updated plotting of scans and added the option to fit features
CI for pxii_bec / test (push) Successful in 37s
CI for pxii_bec / test (push) Successful in 37s
This commit is contained in:
@@ -376,13 +376,28 @@ def plot_live_data_bec(
|
||||
wf.y_label = signal_name
|
||||
wf.plot(device_x=motor_name, device_y=signal_name)
|
||||
|
||||
def plot_live_data(
|
||||
wf,
|
||||
motor_name,
|
||||
signal_name,
|
||||
title="Live scan",
|
||||
):
|
||||
wf.clear_all()
|
||||
wf.title = title
|
||||
wf.x_label = motor_name
|
||||
wf.y_label = signal_name
|
||||
|
||||
wf.plot(
|
||||
device_x=motor_name,
|
||||
device_y=signal_name,
|
||||
)
|
||||
|
||||
|
||||
def plot_fitted_data_bec(
|
||||
data,
|
||||
fit_result,
|
||||
window_name="Fitting",
|
||||
plot_widget=None
|
||||
):
|
||||
"""
|
||||
Plot fitted data and display fitting parameters in the specified window.
|
||||
@@ -402,7 +417,10 @@ def plot_fitted_data_bec(
|
||||
as 'centre', 'fwhm', 'height', and the fitted model stored under
|
||||
'lmfit_result', with its 'best_fit' attribute representing the fitted data.
|
||||
"""
|
||||
wf, text_box = select_bec_window(window_name)
|
||||
if plot_widget is not None:
|
||||
wf = plot_widget
|
||||
else:
|
||||
wf, text_box = select_bec_window(window_name)
|
||||
|
||||
fit_text = (
|
||||
f"Fit parameters: Centre = {fit_result['centre']:.5g}, "
|
||||
@@ -412,7 +430,8 @@ def plot_fitted_data_bec(
|
||||
f"Chi sq = {fit_result['chi_sq']:.3g}\n"
|
||||
f"SNR = {fit_result['snr']:.3g}"
|
||||
)
|
||||
text_box.set_plain_text(fit_text)
|
||||
if plot_widget is None:
|
||||
text_box.set_plain_text(fit_text)
|
||||
|
||||
wf.clear_all()
|
||||
wf.title = f"Scan: {data['scan_number']}"
|
||||
@@ -423,74 +442,75 @@ def plot_fitted_data_bec(
|
||||
# wf.Fit.set(symbol_size = 0)
|
||||
wf.get_curve('Fit').set(symbol_size=0)
|
||||
|
||||
def plot_feature_bec(
|
||||
data,
|
||||
result,
|
||||
window_name="Feature",
|
||||
):
|
||||
"""
|
||||
Plot feature data and indicate the detected edges and centre.
|
||||
"""
|
||||
# def plot_feature_bec(
|
||||
# data,
|
||||
# result,
|
||||
# window_name="Feature",
|
||||
# ):
|
||||
# """
|
||||
# Plot feature data and indicate the detected edges and centre.
|
||||
# """
|
||||
|
||||
wf, text_box = select_bec_window(window_name)
|
||||
# wf, text_box = select_bec_window(window_name)
|
||||
|
||||
feature_text = (
|
||||
f"Feature centre = {result['centre']:.5g}\n"
|
||||
f"Left edge = {result['left_edge']:.5g}\n"
|
||||
f"Right edge = {result['right_edge']:.5g}\n"
|
||||
f"Width = {result['width']:.5g}\n"
|
||||
f"Threshold = {result['threshold']:.4g}"
|
||||
)
|
||||
# feature_text = (
|
||||
# f"Feature centre = {result['centre']:.5g}\n"
|
||||
# f"Left edge = {result['left_edge']:.5g}\n"
|
||||
# f"Right edge = {result['right_edge']:.5g}\n"
|
||||
# f"Width = {result['width']:.5g}\n"
|
||||
# f"Threshold = {result['threshold']:.4g}"
|
||||
# )
|
||||
|
||||
text_box.set_plain_text(feature_text)
|
||||
# text_box.set_plain_text(feature_text)
|
||||
|
||||
wf.clear_all()
|
||||
wf.title = f"Scan: {data['scan_number']}"
|
||||
wf.x_label = data["motor_name"]
|
||||
wf.y_label = data["signal_name"]
|
||||
# wf.clear_all()
|
||||
# # wf.title = f"Scan: {data['scan_number']}"
|
||||
# wf.title = "What now??"
|
||||
# wf.x_label = data["motor_name"]
|
||||
# wf.y_label = data["signal_name"]
|
||||
|
||||
x = data["x_data"]
|
||||
y = data["y_data"]
|
||||
# x = data["x_data"]
|
||||
# y = data["y_data"]
|
||||
|
||||
wf.plot(
|
||||
x=x,
|
||||
y=y,
|
||||
label="Data",
|
||||
)
|
||||
# wf.plot(
|
||||
# x=x,
|
||||
# y=y,
|
||||
# label="Data",
|
||||
# )
|
||||
|
||||
ymin = np.min(y)
|
||||
ymax = np.max(y)
|
||||
# ymin = np.min(y)
|
||||
# ymax = np.max(y)
|
||||
|
||||
# Horizontal threshold
|
||||
wf.plot(
|
||||
x=[np.min(x), np.max(x)],
|
||||
y=[result["threshold"], result["threshold"]],
|
||||
label="Threshold",
|
||||
)
|
||||
# # Horizontal threshold
|
||||
# wf.plot(
|
||||
# x=[np.min(x), np.max(x)],
|
||||
# y=[result["threshold"], result["threshold"]],
|
||||
# label="Threshold",
|
||||
# )
|
||||
|
||||
# Left edge
|
||||
wf.plot(
|
||||
x=[result["left_edge"], result["left_edge"]],
|
||||
y=[ymin, ymax],
|
||||
label="Left edge",
|
||||
)
|
||||
# # Left edge
|
||||
# wf.plot(
|
||||
# x=[result["left_edge"], result["left_edge"]],
|
||||
# y=[ymin, ymax],
|
||||
# label="Left edge",
|
||||
# )
|
||||
|
||||
# Right edge
|
||||
wf.plot(
|
||||
x=[result["right_edge"], result["right_edge"]],
|
||||
y=[ymin, ymax],
|
||||
label="Right edge",
|
||||
)
|
||||
# # Right edge
|
||||
# wf.plot(
|
||||
# x=[result["right_edge"], result["right_edge"]],
|
||||
# y=[ymin, ymax],
|
||||
# label="Right edge",
|
||||
# )
|
||||
|
||||
# Centre
|
||||
wf.plot(
|
||||
x=[result["centre"], result["centre"]],
|
||||
y=[ymin, ymax],
|
||||
label="Centre",
|
||||
)
|
||||
# # Centre
|
||||
# wf.plot(
|
||||
# x=[result["centre"], result["centre"]],
|
||||
# y=[ymin, ymax],
|
||||
# label="Centre",
|
||||
# )
|
||||
|
||||
# Remove symbols from guide lines
|
||||
wf.get_curve("Threshold").set(symbol_size=0)
|
||||
wf.get_curve("Left edge").set(symbol_size=0)
|
||||
wf.get_curve("Right edge").set(symbol_size=0)
|
||||
wf.get_curve("Centre").set(symbol_size=0)
|
||||
# # Remove symbols from guide lines
|
||||
# wf.get_curve("Threshold").set(symbol_size=0)
|
||||
# wf.get_curve("Left edge").set(symbol_size=0)
|
||||
# wf.get_curve("Right edge").set(symbol_size=0)
|
||||
# wf.get_curve("Centre").set(symbol_size=0)
|
||||
+114
-35
@@ -108,6 +108,7 @@ def go_to_peak(
|
||||
negative: bool = False,
|
||||
gap: bool = False,
|
||||
window_name: str = "Fitting",
|
||||
plot_widget = None,
|
||||
):
|
||||
"""
|
||||
Go to the peak of a signal by scanning a motor within a specified range and
|
||||
@@ -143,7 +144,15 @@ def go_to_peak(
|
||||
print(f"Signal name is {signal_name}")
|
||||
# wf.plot(x_name=motor_name, y_name=signal_name)
|
||||
if plot:
|
||||
plot_live_data_bec(motor_name, signal_name, window_name=window_name)
|
||||
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)
|
||||
|
||||
# Validate and calculate step size
|
||||
step_size = calculate_step_size(start, stop, steps)
|
||||
@@ -204,7 +213,7 @@ def go_to_peak(
|
||||
|
||||
# Plot the fitted data if plot = True
|
||||
if plot:
|
||||
plot_fitted_data_bec(data, fit_result, window_name=window_name)
|
||||
plot_fitted_data_bec(data, fit_result, window_name=window_name, plot_widget = plot_widget)
|
||||
|
||||
# If gomax is set then move to the maximum value, rather than the fit centre
|
||||
if gomax:
|
||||
@@ -368,22 +377,18 @@ def find_feature(
|
||||
stop: float,
|
||||
steps: int,
|
||||
feature: str = "peak",
|
||||
offset=None,
|
||||
fraction: float = 0.5,
|
||||
relative: bool = FitDefaults.RELATIVE_MODE,
|
||||
plot: bool = True,
|
||||
settle: float = FitDefaults.SETTLE_TIME,
|
||||
confirm: bool = True,
|
||||
window_name: str = "Fitting",
|
||||
plot_widget=None,
|
||||
):
|
||||
"""
|
||||
Scan a motor, determine the centre of a high- or low-signal region,
|
||||
and move the motor to that position.
|
||||
|
||||
feature="peak":
|
||||
Centre of high-signal region, e.g. collimator.
|
||||
|
||||
feature="dip":
|
||||
Centre of low-signal region, e.g. beamstop.
|
||||
"""
|
||||
|
||||
# Get motor and signal names
|
||||
@@ -395,14 +400,6 @@ def find_feature(
|
||||
|
||||
print(f"Signal name is {signal_name}")
|
||||
|
||||
# Start live BEC plot
|
||||
if plot:
|
||||
plot_live_data_bec(
|
||||
motor_name,
|
||||
signal_name,
|
||||
window_name=window_name,
|
||||
)
|
||||
|
||||
# Calculate step size
|
||||
step_size = calculate_step_size(start, stop, steps)
|
||||
|
||||
@@ -427,6 +424,24 @@ def find_feature(
|
||||
|
||||
input("Press Enter to continue...")
|
||||
|
||||
# -------------------------------------------------
|
||||
# Set up live plot BEFORE starting the scan
|
||||
# -------------------------------------------------
|
||||
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,
|
||||
)
|
||||
|
||||
# Perform scan
|
||||
scan_result = scans.line_scan(
|
||||
motor_device,
|
||||
@@ -456,25 +471,30 @@ def find_feature(
|
||||
}
|
||||
|
||||
# Find feature centre
|
||||
result = find_feature_centre(
|
||||
data["x_data"],
|
||||
data["y_data"],
|
||||
feature=feature,
|
||||
fraction=fraction,
|
||||
)
|
||||
|
||||
print(f"\nLeft edge = {result['left_edge']:.4f}")
|
||||
print(f"Right edge = {result['right_edge']:.4f}")
|
||||
print(f"Width = {result['width']:.4f}")
|
||||
print(f"Centre = {result['centre']:.4f}")
|
||||
|
||||
# Add feature information to BEC plot
|
||||
if plot:
|
||||
plot_feature_bec(
|
||||
data,
|
||||
result,
|
||||
window_name=window_name,
|
||||
if feature in ("peak", "dip"):
|
||||
result = find_feature_centre(
|
||||
data["x_data"],
|
||||
data["y_data"],
|
||||
feature=feature,
|
||||
fraction=fraction,
|
||||
)
|
||||
elif feature == "edge_offset":
|
||||
result = find_edge_offset(
|
||||
data["x_data"],
|
||||
data["y_data"],
|
||||
feature=feature,
|
||||
offset=offset,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unknown feature type {feature}")
|
||||
if feature in ("peak", "dip"):
|
||||
print(f"\nLeft edge = {result['left_edge']:.4f}")
|
||||
print(f"Right edge = {result['right_edge']:.4f}")
|
||||
print(f"Width = {result['width']:.4f}")
|
||||
print(f"Centre = {result['centre']:.4f}")
|
||||
elif feature == "edge_offset":
|
||||
print(f"Edge = {result['edge']:.4f}")
|
||||
print(f"Centre = {result['centre']:.4f}")
|
||||
|
||||
# Move safely to centre
|
||||
move_to_position(
|
||||
@@ -484,10 +504,69 @@ def find_feature(
|
||||
data,
|
||||
)
|
||||
|
||||
if plot and plot_widget is not None:
|
||||
# Replace the live subscription with static completed scan data
|
||||
plot_widget.clear_all()
|
||||
|
||||
plot_widget.plot(
|
||||
x=data["x_data"],
|
||||
y=data["y_data"],
|
||||
label="Scan data",
|
||||
)
|
||||
# Mark centre
|
||||
|
||||
plot_widget.plot(
|
||||
x=[result["centre"], result["centre"]],
|
||||
y=[
|
||||
np.min(data["y_data"]),
|
||||
np.max(data["y_data"]),
|
||||
],
|
||||
label="Centre",
|
||||
)
|
||||
|
||||
|
||||
plot_widget.title = f"{motor_name} scan"
|
||||
plot_widget.x_label = motor_name
|
||||
plot_widget.y_label = signal_name
|
||||
|
||||
return result
|
||||
|
||||
import numpy as np
|
||||
def find_edge_offset(x, y, fraction=0.5, feature="edge_offset", offset=0.0):
|
||||
"""
|
||||
Find a single signal edge and calculate a target position
|
||||
at a known offset from that edge.
|
||||
"""
|
||||
|
||||
x = np.asarray(x)
|
||||
y = np.asarray(y)
|
||||
|
||||
y_min = np.min(y)
|
||||
y_max = np.max(y)
|
||||
|
||||
threshold = y_min + fraction * (y_max - y_min)
|
||||
|
||||
# Find threshold crossings
|
||||
crossings = np.where(np.diff(np.sign(y - threshold)) != 0)[0]
|
||||
|
||||
if len(crossings) == 0:
|
||||
raise RuntimeError("No edge found in scan")
|
||||
|
||||
# If we expect only one useful edge, use the strongest/first one
|
||||
i = crossings[0]
|
||||
|
||||
# Linear interpolation for more accurate edge position
|
||||
x1, x2 = x[i], x[i + 1]
|
||||
y1, y2 = y[i], y[i + 1]
|
||||
|
||||
edge = x1 + (threshold - y1) * (x2 - x1) / (y2 - y1)
|
||||
|
||||
centre = edge + offset
|
||||
|
||||
return {
|
||||
"edge": edge,
|
||||
"centre": centre,
|
||||
"threshold": threshold,
|
||||
}
|
||||
|
||||
def find_feature_centre(x, y, feature="peak", fraction=0.5):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user