updated daily setup and helper file mxcomm_utils
This commit is contained in:
1 parent
5f0d1fddeb
commit
d928c4deee
2 files changed
+2014
-705
No files matched your search
+1721
-705
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
Reference in new issue
Block a user