fix(lamni): sl6 in xray eye, 30nm FZP distance, OSA collision offset
CI for csaxs_bec / test (push) Failing after 3m2s

- leye_in/leye_out open/restore sl6 in addition to sl1; opening
  increased to 1.0 mm
- lfzp_info and PDF report account for the 30 nm FZP holder placing
  the chip 11 mm further downstream
- commission loptz collision_offset (87.87) and fix the "collide at
  losaz" printout to subtract loptz
- ptycho_lamni config updates from beamtime (eye/flight tube positions,
  loptz limits, OSA in positions)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJWYXx4SS8NgFWog81xjyf
This commit is contained in:
x12saandClaude Opus 5.5 committed 2026-10-14 10:32:57 +02:00
1 parent 04bedba60a
commit 51de36130e
3 files changed
+94 -54

No files matched your search

@@ -2756,10 +2756,13 @@ class LamNI(
except Exception:
focal_distance_str = "N/A"
# FZP focus-to-sample distance: same live z-stage-based calculation
# lfzp_info() already uses -- not a stored value.
# lfzp_info() already uses -- not a stored value. Depends on the
# mounted FZP type (30 nm sits in an extra holder further downstream).
try:
loptz_val = dev.loptz.read()["loptz"]["value"]
fzp_sample_distance_str = f"{-loptz_val + 85.6 + 52:.1f}"
fzp_sample_distance_str = (
f"{self._fzp_sample_distance(loptz_val, fzp_zone_width_nm):.1f}"
)
except Exception:
fzp_sample_distance_str = "N/A"
# Sample-to-detector distance: userParameter on loptx, defaults to
@@ -214,21 +214,65 @@ class LamNIOpticsMixin:
return None
return param.get(var)
@property
def _sl1xs_before_eye(self):
return self.client.get_global_var("lamni_sl1xs_before_eye")
# slits opened by _EYE_SLIT_OPENING (relative) while the xray eye is in
_EYE_SLITS = ("sl1", "sl6")
_EYE_SLIT_OPENING = 1.0 # mm
@_sl1xs_before_eye.setter
def _sl1xs_before_eye(self, val):
self.client.set_global_var("lamni_sl1xs_before_eye", val)
@staticmethod
def _slit_size(name):
# sl1xs/sl1ys are EpicsMotors (user_readback), sl6xs/sl6ys are
# VirtualSlitWidth pseudo motors (readback)
device = dev[name]
if hasattr(device, "user_readback"):
return device.user_readback.get()
return device.readback.get()
@property
def _sl1ys_before_eye(self):
return self.client.get_global_var("lamni_sl1ys_before_eye")
def _slit_before_eye(self, slit):
return (
self.client.get_global_var(f"lamni_{slit}xs_before_eye"),
self.client.get_global_var(f"lamni_{slit}ys_before_eye"),
)
@_sl1ys_before_eye.setter
def _sl1ys_before_eye(self, val):
self.client.set_global_var("lamni_sl1ys_before_eye", val)
def _set_slit_before_eye(self, slit, xs, ys):
self.client.set_global_var(f"lamni_{slit}xs_before_eye", xs)
self.client.set_global_var(f"lamni_{slit}ys_before_eye", ys)
def _open_slits_for_eye(self):
for slit in self._EYE_SLITS:
xs, ys = f"{slit}xs", f"{slit}ys"
if xs not in dev or ys not in dev:
continue
self._set_slit_before_eye(slit, self._slit_size(xs), self._slit_size(ys))
scans.umv(
dev[xs], self._EYE_SLIT_OPENING, dev[ys], self._EYE_SLIT_OPENING, relative=True
)
def _restore_slits_after_eye(self):
for slit in self._EYE_SLITS:
xs, ys = f"{slit}xs", f"{slit}ys"
xs_before, ys_before = self._slit_before_eye(slit)
if xs_before is None or ys_before is None:
continue
if xs not in dev or ys not in dev:
self._set_slit_before_eye(slit, None, None)
continue
# if the slit was manually re-adjusted while the eye was in,
# offer to keep the current size instead of restoring the
# pre-eye value
tol = 0.01 # mm (10 microns)
manually_adjusted = not np.isclose(
self._slit_size(xs), xs_before + self._EYE_SLIT_OPENING, atol=tol
) or not np.isclose(self._slit_size(ys), ys_before + self._EYE_SLIT_OPENING, atol=tol)
if manually_adjusted and self.OMNYTools.yesno(
f"{xs}/{ys} appear to have been adjusted manually while the "
"eye was in. Keep the current slit size instead of "
"restoring the pre-eye value?"
):
print(f"Keeping current {slit} size, not restoring pre-eye value.")
else:
umv(dev[xs], xs_before, dev[ys], ys_before)
self._set_slit_before_eye(slit, None, None)
def leye_out(self):
self.loptics_in()
@@ -240,31 +284,7 @@ class LamNIOpticsMixin:
if self.csaxs is not None:
self.csaxs.fil_trans(1, print_only=False)
sl1xs_before = self._sl1xs_before_eye
sl1ys_before = self._sl1ys_before_eye
if sl1xs_before is not None and sl1ys_before is not None:
# if the slit was manually re-adjusted while the eye was in,
# offer to keep the current size instead of restoring the
# pre-eye value
current_sl1xs = dev.sl1xs.user_readback.get()
current_sl1ys = dev.sl1ys.user_readback.get()
expected_sl1xs = sl1xs_before + 0.3
expected_sl1ys = sl1ys_before + 0.3
tol = 0.01 # mm (10 microns)
manually_adjusted = not np.isclose(
current_sl1xs, expected_sl1xs, atol=tol
) or not np.isclose(current_sl1ys, expected_sl1ys, atol=tol)
if manually_adjusted and self.OMNYTools.yesno(
"sl1xs/sl1ys appear to have been adjusted manually while the "
"eye was in. Keep the current slit size instead of "
"restoring the pre-eye value?"
):
print("Keeping current slit size, not restoring pre-eye value.")
else:
umv(dev.sl1xs, sl1xs_before, dev.sl1ys, sl1ys_before)
self._sl1xs_before_eye = None
self._sl1ys_before_eye = None
self._restore_slits_after_eye()
leyey_out = self._get_user_param_safe("leyey", "out")
umv(dev.leyey, leyey_out)
@@ -318,10 +338,7 @@ class LamNIOpticsMixin:
# both already in position -> do nothing
pass
else:
if "sl1xs" in dev and "sl1ys" in dev:
self._sl1xs_before_eye = dev.sl1xs.user_readback.get()
self._sl1ys_before_eye = dev.sl1ys.user_readback.get()
scans.umv(dev.sl1xs, 0.3, dev.sl1ys, 0.3, relative=True)
self._open_slits_for_eye()
if self.csaxs is not None:
self.csaxs.fil_trans(0.05, print_only=False)
@@ -539,6 +556,17 @@ class LamNIOpticsMixin:
else:
print("OSA is already at the OUT position.")
# the 30 nm FZP sits in an additional holder that puts the chip this
# much further downstream (closer to the sample) than the 60 nm FZP
_FZP_30NM_HOLDER_OFFSET = 11.0 # mm
def _fzp_sample_distance(self, loptz_val, outermost_zone_width_nm):
"""FZP-to-sample distance in mm for the given loptz position and FZP type."""
distance = -loptz_val + 85.6 + 52
if outermost_zone_width_nm == 30:
distance -= self._FZP_30NM_HOLDER_OFFSET
return distance
def lfzp_info(self, mokev_val=-1):
if mokev_val == -1:
# The energy readback device is "ccm_energy" (same as flomni's
@@ -553,8 +581,13 @@ class LamNIOpticsMixin:
)
return
loptz_val = dev.loptz.read()["loptz"]["value"]
distance = -loptz_val + 85.6 + 52
print(f"The sample is in a distance of {distance:.1f} mm from the FZP.")
distance = self._fzp_sample_distance(loptz_val, 60)
distance_30nm = self._fzp_sample_distance(loptz_val, 30)
print(
f"The sample is in a distance of {distance:.1f} mm from a 60 nm FZP and "
f"{distance_30nm:.1f} mm from a 30 nm FZP (extra holder, "
f"{self._FZP_30NM_HOLDER_OFFSET:.0f} mm further downstream)."
)
print(f"At the current energy of {mokev_val:.4f} keV we have following options:\n")
@@ -597,7 +630,7 @@ class LamNIOpticsMixin:
outermost_zonewidth = 30e-9
focal_distance = diameter * outermost_zonewidth / wavelength
beam_size = (
-diameter / (focal_distance * 1000) * (focal_distance * 1000 - distance) * 1e6
-diameter / (focal_distance * 1000) * (focal_distance * 1000 - distance_30nm) * 1e6
)
table.add_row(
f"{diameter*1e6:.2f} microns",
@@ -633,7 +666,7 @@ class LamNIOpticsMixin:
print(f" Current losaz {losaz_val:.1f}")
print(
" The OSA will collide with a normal sample holder at losaz "
f"\033[1m{(collision_offset - losaz_val):.1f}\033[0m"
f"\033[1m{(collision_offset - loptz_val):.1f}\033[0m"
)
print(f" Remaining space (right now): \033[1m{remaining_current:.1f}\033[0m")
+11 -7
View File
@@ -22,9 +22,9 @@ leyex:
in: 13.18
# flight tube / detector table positions used by leye_out() (*_in) and
# leye_in() (*_out)
fttrz_in: 1995
fttrz_in: 1381.433
fttrz_out: 2000
dttrz_in: 13258
dttrz_in: 12614.99
dttrz_out: 13263
deviceTags:
- ptycho_lamni
@@ -111,8 +111,8 @@ loptz:
axis_Id: D
host: mpc2680.psi.ch
limits:
- 0
- 0
- 80
- 89
port: 8081
sign: -1
enabled: true
@@ -120,6 +120,10 @@ loptz:
readOnly: false
readoutPriority: baseline
connectionTimeout: 20
userParameter:
# OSA collides with the sample holder when loptz + losaz reaches this
# value (measured 2026-10-14: loptz 77.8676, losaz 10.0029)
collision_offset: 87.87
deviceTags:
- ptycho_lamni
lsamrot:
@@ -203,7 +207,7 @@ losax:
readoutPriority: baseline
connectionTimeout: 20
userParameter:
in: -1.582
in: -1.66249
deviceTags:
- ptycho_lamni
losay:
@@ -223,7 +227,7 @@ losay:
readoutPriority: baseline
connectionTimeout: 20
userParameter:
in: -0.131
in: -0.236
out: 3.8
deviceTags:
- ptycho_lamni
@@ -244,7 +248,7 @@ losaz:
readoutPriority: baseline
connectionTimeout: 20
userParameter:
in: -1
in: 8.829
out: -3
deviceTags:
- ptycho_lamni