updated daily setup and helper file mxcomm_utils

This commit is contained in:
x10sa authored and perl_d committed 2026-10-20 16:07:54 +02:00
1 parent 5f0d1fddeb
commit d928c4deee
2 files changed
+2014 -705

No files matched your search

+1721 -705
View File
File diff suppressed because it is too large. Load diff
+293
View File
@@ -0,0 +1,293 @@
"""General reusable utilities for MX beamline commissioning and operation."""
import epics
import time
import numpy as np
import matplotlib.pyplot as plt
from pxii_bec.scripts import beamline_context as bl
# =============================================================================
# Camera utilities
# =============================================================================
def read_scintXY(zoom=800, image=False):
"""Get the X and Y beam positions from the sample camera at
a sepcified zoom. Default zoom is 800"""
# Target position on sample camera
x_target = BeamMove.scint_target_x
y_target = BeamMove.scint_target_y
results = {}
# Ensure shutter and detector cover are closed before changing states
dev.bcu_shutter.put(0)
if bl.d["det_cov"].pos != "close":
bl.d["det_cov"].move("close")
try:
# set transmission to 10%
current_transm = get_value(dev.transm)
if abs(current_transm - 0.1) > 0.01:
dev.transm.put(0.1)
# set camera zoom
if get_value(dev.scam_zoom) != zoom:
umv(dev.scam_zoom, zoom)
# move to beam visualisation
if not bl.planner.is_state(BeamlineState.BEAM_VISUALISATION):
bl.planner.move_to(BeamlineState.BEAM_VISUALISATION)
# open shutter
dev.bcu_shutter.put(1)
# set autoexposure and check there is a reading in the image
time.sleep(0.5)
auto_exposure(cam="samcam", max_iter=25)
time.sleep(0.2)
max_signal = get_value(dev.samcam_max)
results["max_signal"] = max_signal
if max_signal < BeamMove.min_scint_signal:
return {
"ok": False,
"message": (
f"No beam detected on the scintillator "
f"(max signal = {max_signal:.0f})"
),
"max_signal": max_signal,
}
x_px = dev.samcam_x.read()["samcam_x"]["value"]
y_px = dev.samcam_y.read()["samcam_y"]["value"]
results["X_pixels"] = x_px
results["Y_pixels"] = y_px
results["ok"] = True
results["message"] = (
f"Beam detected at X={x_px:.1f}, Y ={y_px:.1f} px"
)
# Calculate difference from target position
diff_x = x_target - x_px
diff_y = y_target - y_px
results["X_diff_px"] = diff_x
results["Y diff_px"] = diff_y
# Convert to pixels
diff_x_mic = calc_scam_microns(diff_x, zoom)
diff_y_mic = calc_scam_microns(diff_y, zoom)
results["X_diff_microns"] = diff_x_mic
results["Y_diff_microns"] = diff_y_mic
# Measure beamsize
x_fwhm, y_fwhm = measure_beamsize()
results["X_FWHM"] = x_fwhm
results["Y_FWHM"] = y_fwhm
print(f"Beam position: X = {results['X_pixels']:.1f} px, "
f"Y = {results['Y_pixels']:.1f} px\n"
f"Distance from crosshair: X = {results['X_diff_microns']:.1f} microns, "
f"Y = {results['Y_diff_microns']:.1f} microns\n",
f"X size (fwhm) = {results["X_FWHM"]:.1f} microns, "
f"Y size (fwhm) = {results["Y_FWHM"]:.1f} microns")
if image:
imagedata = get_cam_image(Cameras.sample)
results["image"] = imagedata
return results
finally:
# print("done")
dev.bcu_shutter.put(0)
def get_cam_image(prefix):
"""Return the current RGB image from an EPICS areaDetector camera."""
width = int(epics.caget(f"{prefix}:image1:ArraySize1_RBV"))
height = int(epics.caget(f"{prefix}:image1:ArraySize2_RBV"))
data = epics.caget(f"{prefix}:image1:ArrayData")
if data is None:
raise RuntimeError(f"Could not read camera image from {prefix}")
expected_size = width * height * 3
if data.size < expected_size:
raise RuntimeError(
f"Camera returned {data.size} bytes; "
f"expected {expected_size}"
)
return np.asarray(
data[:expected_size],
dtype=np.uint8,
).reshape(height, width, 3)
def save_cam_image(
image,
filename,
*,
crosshair=None,
beam_position=None,
crop=None,
title=None,
):
"""Save a camera image, optionally with crosshair and beam position."""
fig, ax = plt.subplots(figsize=(4, 4))
ax.imshow(image)
# Target crosshair
if crosshair is not None:
x, y = crosshair
ax.axvline(x, linewidth=1)
ax.axhline(y, linewidth=1)
# Measured beam position
if beam_position is not None:
x, y = beam_position
ax.plot(
x,
y,
marker="+",
markersize=12,
markeredgewidth=2,
)
# Crop around crosshair
if crop is not None:
if crosshair is None:
raise ValueError(
"crosshair must be supplied when using crop"
)
x, y = crosshair
ax.set_xlim(x - crop, x + crop)
# Image coordinates have Y increasing downwards
ax.set_ylim(y + crop, y - crop)
if title:
ax.set_title(title)
ax.axis("off")
fig.tight_layout()
fig.savefig(filename, dpi=150, bbox_inches="tight")
plt.close(fig)
return filename
def create_cam_report(
image,
pdf_filename,
*,
crosshair=None,
beam_position=None,
crop=None,
title="Camera snapshot",
):
"""Create a one-page PDF containing an annotated camera image."""
fig, ax = plt.subplots(figsize=(6, 6))
ax.imshow(image)
if crosshair is not None:
x, y = crosshair
ax.axvline(x, linewidth=1)
ax.axhline(y, linewidth=1)
if beam_position is not None:
x, y = beam_position
ax.plot(
x,
y,
marker="+",
markersize=12,
markeredgewidth=2,
)
if crop is not None:
if crosshair is None:
raise ValueError(
"crosshair must be supplied when using crop"
)
x, y = crosshair
ax.set_xlim(x - crop, x + crop)
ax.set_ylim(y + crop, y - crop)
ax.set_title(title)
ax.axis("off")
fig.tight_layout()
fig.savefig(
pdf_filename,
format="pdf",
bbox_inches="tight",
)
plt.close(fig)
return pdf_filename
# =============================================================================
# Daily-setup PDF reports
# =============================================================================
def create_empty_daily_report(filename):
"""Create the placeholder Daily Setup report."""
fig, ax = plt.subplots(figsize=(8, 6))
ax.axis("off")
ax.text(
0.5,
0.60,
"Daily Setup",
ha="center",
va="center",
fontsize=20,
weight="bold",
)
ax.text(
0.5,
0.48,
"Setup in progress.",
ha="center",
va="center",
fontsize=12,
)
ax.text(
0.5,
0.42,
"The report will appear here when complete.",
ha="center",
va="center",
fontsize=10,
)
fig.savefig(
filename,
format="pdf",
bbox_inches="tight",
)
plt.close(fig)
return filename