fix(lamni): correct X-ray eye alignment DAP forwarding, offset reference point, and pixel calibration
CI for csaxs_bec / test (push) Failing after 1m36s
CI for csaxs_bec / test (pull_request) Failing after 1m23s

- Enable DAP fit parameter forwarding (self.gui.set_dap_params_forwarding),
  never called before -- without it the dap_params_update signal was never
  connected, so no fit data could ever reach fit_params_x/y regardless of
  how long anything waited. Disabled again once the fit is read (or on
  abort, via the existing KeyboardInterrupt cleanup in lamni.py).
- Replace the fixed 5s sleep before reading the DAP fit with
  _wait_for_dap_fit(), which polls fit_params_x/y for the real converged
  shape (up to 30s) instead of guessing a duration.
- Fix write_output()'s offset reference: was using one of the 8 angle
  points as its own zero-reference (first via an off-by-one at k=1, then
  at k=2), which forces that point's offset to exactly 0 by construction
  and biases the fit. Now references alignment_values[0] (the FZP centre,
  the only angle-independent point in the procedure), matching both
  FlOMNI's own reference point and the original SPEC macro
  (l_xrayeyealign.mac) exactly, including the asymmetric x/y sign
  convention.
- pixel_calibration is now a property reading the cam_xeye device's
  pixel_calibration user parameter, matching FlOMNI's camera-sourced
  calibration, with the old hardcoded constant kept only as a fallback
  (PIXEL_CALIBRATION_DEFAULT) -- by design, to match FlOMNI's own pattern.
- lamni_optics_mixin.py: lfzp_in() and loptics_out() now print before/after
  re-establishing interferometer feedback, previously silent.
- device_configs: add the missing userParameter.pixel_calibration to
  cam_xeye in ptycho_lamni.yaml and simulated_omny/simulated_lamni.yaml
  (present on the FlOMNI side already; this is what the pixel_calibration
  property above actually reads at runtime).
This commit is contained in:
x01dc
2026-07-12 18:02:10 +02:00
parent 48f1ebb95f
commit b76593e4ff
12 changed files with 224 additions and 135 deletions
@@ -208,4 +208,4 @@ class DataDrivenLamNI(LamNI):
shapes = [data.shape for data in self.tomo_data.values()]
if len(set(shapes)) > 1:
raise ValueError(f"Tomo data file has entries of inconsistent lengths: {shapes}.")
raise ValueError(f"Tomo data file has entries of inconsistent lengths: {shapes}.")
@@ -250,6 +250,10 @@ class LamNI(LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools):
aligner.gui.hide_crosshair()
except Exception as gui_exc: # pylint: disable=broad-except
logger.warning(f"Failed to hide XRayEye alignment crosshair: {gui_exc}")
try:
aligner.gui.set_dap_params_forwarding(False)
except Exception as gui_exc: # pylint: disable=broad-except
logger.warning(f"Failed to disable XRayEye DAP parameter forwarding: {gui_exc}")
raise exc
# ── Reset manual shifts if needed ─────────────────────────────
@@ -1219,4 +1223,4 @@ class LamNI(LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools):
print(f"{angle},{voltage1},{voltage2}")
time.sleep(.3)
print("Finished")
print("Finished")
@@ -209,11 +209,29 @@ class LamNIAlignmentMixin:
tomo_fit_xray_eye = np.zeros((2, 3))
params_x = _dev.omny_xray_gui.fit_params_x.get()
params_y = _dev.omny_xray_gui.fit_params_y.get()
for label, params in (("x", params_x), ("y", params_y)):
if (
not isinstance(params, dict)
or not {"SineModel_0_amplitude", "SineModel_0_shift", "LinearModel_1_intercept"}
<= params.keys()
):
raise RuntimeError(
f"No converged DAP sinusoidal fit available for the {label} axis "
f"(omny_xray_gui.fit_params_{label} did not return the expected "
"named parameters -- got "
f"{type(params).__name__ if not isinstance(params, dict) else 'a dict missing keys'}). "
"This means the fit hasn't converged / DAP forwarding hasn't produced "
"a result yet, not that the correction data is missing. Wait for the "
"fit to converge in the XRayEye GUI's Fit tab, or use "
"read_xray_eye_correction() to reload from the archived text files "
"instead."
)
tomo_fit_xray_eye[0][0] = params_x["SineModel_0_amplitude"]
tomo_fit_xray_eye[0][1] = params_x["SineModel_0_shift"]
tomo_fit_xray_eye[0][2] = params_x["LinearModel_1_intercept"]
params_y = _dev.omny_xray_gui.fit_params_y.get()
tomo_fit_xray_eye[1][0] = params_y["SineModel_0_amplitude"]
tomo_fit_xray_eye[1][1] = params_y["SineModel_0_shift"]
tomo_fit_xray_eye[1][2] = params_y["LinearModel_1_intercept"]
@@ -348,4 +366,4 @@ class LamNIAlignmentMixin:
shift_y = corr_pos_y[-1]
print(f"Additional correction {label}: x={shift_x}, y={shift_y}")
return (shift_x, shift_y)
return (shift_x, shift_y)
@@ -242,7 +242,10 @@ class LamNIOpticsMixin:
self._lfzp_in()
if "rtx" in dev and dev.rtx.enabled:
print("Re-establishing interferometer feedback...")
_t0 = time.time()
dev.rtx.controller.feedback_enable_with_reset()
print(f"Interferometer feedback re-established ({time.time() - _t0:.1f} s).")
def loptics_in(self):
"""Move in the LamNI optics (FZP + OSA)."""
@@ -261,7 +264,10 @@ class LamNIOpticsMixin:
if "rtx" in dev and dev.rtx.enabled:
time.sleep(1)
print("Re-establishing interferometer feedback...")
_t0 = time.time()
dev.rtx.controller.feedback_enable_with_reset()
print(f"Interferometer feedback re-established ({time.time() - _t0:.1f} s).")
def lcs_in(self):
pass
@@ -0,0 +1,25 @@
num_elements = 8
corr_pos_x[0] = 0.5
corr_pos_y[0] = -0.3
corr_angle[0] = 0.0
corr_pos_x[1] = 0.8
corr_pos_y[1] = 0.1
corr_angle[1] = 45.0
corr_pos_x[2] = 0.6
corr_pos_y[2] = 0.9
corr_angle[2] = 90.0
corr_pos_x[3] = -0.2
corr_pos_y[3] = 1.1
corr_angle[3] = 135.0
corr_pos_x[4] = -0.7
corr_pos_y[4] = 0.4
corr_angle[4] = 180.0
corr_pos_x[5] = -0.9
corr_pos_y[5] = -0.5
corr_angle[5] = 225.0
corr_pos_x[6] = -0.3
corr_pos_y[6] = -1.0
corr_angle[6] = 270.0
corr_pos_x[7] = 0.4
corr_pos_y[7] = -0.8
corr_angle[7] = 315.0
@@ -1,37 +0,0 @@
1. Things to test
Progress persistence
Start tomo_scan(), let a few projections run, restart the BEC client mid-scan, confirm client.get_global_var("tomo_progress") still shows heartbeat/tomo_start_time from before the restart
After ~10 projections, confirm CLI output shows a real ETA (not "N/A") — the threshold is projection > 9
Confirm the GUI progress display (lamnigui_show_progress()) shows a real start time / ETA, not "N/A"
Force a gap between projections (pause, or simulate an interlock) and confirm the "Detected a X gap... excluding Y from ETA" message appears and accumulated_idle_time grows
Call lamni.tomo_progress_reset(), confirm it clears and prints confirmation
Correction persistence
lamni.read_additional_correction(<file>), restart the client, confirm lamni.corr_pos_x/corr_pos_y/corr_angle (+ _2) still hold the loaded values
lamni.reset_correction() / reset_correction_2() — confirm they clear to [] and that clears persist across a restart too
Confirm lamni.compute_additional_correction(angle) still returns sensible values after a restart
X-ray eye alignment (full run)
Run lamni.xrayeye_alignment_start() end-to-end
Crosshair appears at the FZP centre after step 0, stays put through all angle steps, disappears at completion
Ctrl-C mid-alignment — confirm the crosshair still disappears (abort path)
Confirm dev.fsh physically opens/closes correctly (not a leftover omnyfsh reference anywhere)
Open the resulting xrayeye_alignmentvalues_<timestamp>.h5: check alignment_values (9,2), alignment_images has real frames, roi_pixel_data has one row per submit, alignment_fit is (3,8)
Confirm the old plain-text file is still written unchanged alongside it
keep_shutter_open=True path still prompts and closes correctly
Watch specifically for whether click positions land where expected (the un-verified /2 pixel-calibration question)
Burst mode
Set lamni.frames_per_trigger = N (N>1), run one projection, confirm the detector actually acquires N frames per point
Confirm tomo_parameters() shows and lets you edit it
Confirm 0, negative, non-int, or bool raise ValueError
Confirm leaving it unset still behaves as 1 (no behavior change for existing scans)
Regression
extra_tomo.py: exercise MagLamNI.rotate_slowly() — confirm no more AttributeError on self.align
One full short tomo_scan() end-to-end, to confirm none of the above broke the main scan path
@@ -43,8 +43,13 @@ class XrayEyeAlign:
directions.
"""
# Pixel calibration: multiply pixel coordinate by this to get mm
PIXEL_CALIBRATION = 0.2 / 218 # mm/pixel (0.2 mm FOV, 218 px)
# Pixel calibration in mm/pixel (multiply pixel values to get mm).
# The live value is read from the cam_xeye device's ``pixel_calibration``
# user parameter (see the ``pixel_calibration`` property, matching the
# FlOMNI side); this constant is only the fallback used when that
# device/parameter is unavailable.
PIXEL_CALIBRATION_DEFAULT = 0.2 / 218 # mm/pixel (0.2 mm FOV, 218 px)
PIXEL_CALIBRATION_USER_PARAM = "pixel_calibration"
def __init__(self, client, lamni: LamNI) -> None:
self.client = client
@@ -64,10 +69,14 @@ class XrayEyeAlign:
# fresh alignment run should do -- see the start of align() below.
# alignment_values[k] = [x_mm, y_mm]
# k=0 : FZP centre
# k=1 : sample at 0° (reference; shift_xy computed from k=0 vs k=1)
# k=2 : sample at 45°
# k=1 : sample at 0°, pre-shift (used only to compute shift_xy,
# the base shift to scan centre -- NOT one of the 8
# angle-fit points used in write_output())
# k=2 : sample at 0°, post-shift/at scan centre (first real
# angle-fit point; reference for all offsets below)
# k=3 : sample at 45°
# ...
# k=8 : sample at 315°
# k=9 : sample at 315°
self.alignment_values: dict[int, list[float]] = {}
self.alignment_images = []
@@ -80,6 +89,23 @@ class XrayEyeAlign:
"""Return the live XRayEye RPC handle stored by LamniGuiTools."""
return self.lamni.xeyegui
@property
def pixel_calibration(self) -> float:
"""Pixel calibration in mm/pixel.
Reads the ``pixel_calibration`` user parameter from the cam_xeye
device; falls back to ``PIXEL_CALIBRATION_DEFAULT`` if the device or
parameter is unavailable. Matches the FlOMNI side, which moved from a
hardcoded constant to this camera-sourced value.
"""
try:
mm_per_pixel = dev.cam_xeye.user_parameter.get(self.PIXEL_CALIBRATION_USER_PARAM)
except Exception:
mm_per_pixel = None
if mm_per_pixel is None:
return self.PIXEL_CALIBRATION_DEFAULT
return float(mm_per_pixel)
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
@@ -118,7 +144,7 @@ class XrayEyeAlign:
dev.cam_xeye.live_mode_enabled.put(True)
self.gui.on_live_view_enabled(True)
dev.fsh.fshopen()
time.sleep(0.5)
time.sleep(1)
# store the image: the relevant frame for any submit that follows,
# since the shutter is closed again (unless keep_shutter_open) by
# the time the user actually clicks submit
@@ -150,6 +176,37 @@ class XrayEyeAlign:
# Main alignment procedure
# ------------------------------------------------------------------
def _wait_for_dap_fit(self, timeout: float = 30.0, poll_interval: float = 1.0) -> bool:
"""Poll ``omny_xray_gui.fit_params_x``/``fit_params_y`` until both report
a converged DAP fit, or *timeout* seconds have passed.
Returns True if a converged fit was seen, False on timeout. Either way,
the caller (read_xray_eye_correction_from_gui) does its own check and
raises a clear error if the fit still isn't ready -- this just avoids
giving up after an arbitrary fixed sleep when the DAP service is simply
running a bit slower than usual.
"""
required_keys = {"SineModel_0_amplitude", "SineModel_0_shift", "LinearModel_1_intercept"}
t0 = time.time()
while time.time() - t0 < timeout:
params_x = dev.omny_xray_gui.fit_params_x.get()
params_y = dev.omny_xray_gui.fit_params_y.get()
if (
isinstance(params_x, dict)
and required_keys <= params_x.keys()
and isinstance(params_y, dict)
and required_keys <= params_y.keys()
):
print(f"DAP fit converged after {time.time() - t0:.1f} s.")
return True
time.sleep(poll_interval)
print(
f"DAP fit did not report converged parameters within {timeout:.0f} s -- "
"proceeding anyway; the next step will raise a clear error if it's "
"still not ready."
)
return False
def align(self, keep_shutter_open: bool = False):
"""Run the full LamNI X-ray eye alignment.
@@ -177,6 +234,7 @@ class XrayEyeAlign:
then load fit parameters into the global variable store.
"""
self.lamni.lamnigui_show_xeyealign()
self.gui.set_dap_params_forwarding(True)
self.send_message("Getting things ready. Please wait...")
# Actual start of a fresh alignment run -- see the comment in
@@ -209,14 +267,8 @@ class XrayEyeAlign:
k = 0
while True:
if dev.omny_xray_gui.submit.get() == 1:
val_x = (
getattr(dev.omny_xray_gui, f"xval_x_{k}").get()
* self.PIXEL_CALIBRATION
)
val_y = (
getattr(dev.omny_xray_gui, f"yval_y_{k}").get()
* self.PIXEL_CALIBRATION
)
val_x = getattr(dev.omny_xray_gui, f"xval_x_{k}").get() * self.pixel_calibration
val_y = getattr(dev.omny_xray_gui, f"yval_y_{k}").get() * self.pixel_calibration
self.alignment_values[k] = [val_x, val_y]
print(
f"Clicked position {k}: "
@@ -382,8 +434,8 @@ class XrayEyeAlign:
# ------------------------------------------------------------------
self.write_output()
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
fovx = self._xray_fov_xy[0] * self.pixel_calibration * 1000 / 2
fovy = self._xray_fov_xy[1] * self.pixel_calibration * 1000 / 2
print(
f"Largest FOV from X-ray eye alignment:\n"
f" fovx = {fovx:.0f} um, fovy = {fovy:.0f} um"
@@ -394,14 +446,20 @@ class XrayEyeAlign:
)
self.client.set_global_var("tomo_fov_offset", self.shift_xy)
# Switch GUI to fit tab and wait for DAP to finish fitting
# Switch GUI to fit tab and wait for DAP to finish fitting.
# A fixed sleep isn't reliable here -- how long the DAP service
# takes to fit and forward a result varies -- so poll the actual
# signal state instead of guessing a duration.
self.lamni.lamnigui_show_xeyealign_fittab()
print("Waiting 5 s for DAP sinusoidal fit to converge...")
time.sleep(5)
print("Waiting for DAP sinusoidal fit to converge...")
self._wait_for_dap_fit(timeout=30)
# Read fit parameters from the GUI into the global variable store
print("Loading new alignment parameters from X-ray eye GUI fit.")
self.lamni.read_xray_eye_correction_from_gui()
try:
self.lamni.read_xray_eye_correction_from_gui()
finally:
self.gui.set_dap_params_forwarding(False)
if keep_shutter_open:
answer = input("Close the shutter now? [Y/n]: ").strip().lower()
@@ -446,10 +504,19 @@ class XrayEyeAlign:
def write_output(self):
"""Compute x/y offsets for each angle and push them to the GUI fit tab.
Offsets are the displacement of the sample from its position at 0 deg
(k=1 is the reference):
x_offset[i] = (x_at_0deg - x_at_angle_i) * 1000 [um]
y_offset[i] = (y_at_angle_i - y_at_0deg) * 1000 [um]
Offsets are the displacement of the sample at each angle from the FZP
centre (k=0) -- matching FlOMNI's own reference point
(self.alignment_values[0] - self.alignment_values[k]). The FZP centre
is the only point in the whole procedure that's independent of
rotation angle, so it's the only valid zero-reference: using one of
the 8 angle points as the reference instead (as earlier versions of
this method did, first via an off-by-one at k=1, then via k=2) forces
that one point's own offset to be exactly zero by construction -- not
because the sample happens to be centred there, but because
subtracting a value from itself is always zero -- which biases the
whole sinusoidal fit rather than measuring it.
x_offset[i] = (x_at_FZP - x_at_angle_i) * 1000 [um]
y_offset[i] = (y_at_angle_i - y_at_FZP) * 1000 [um]
The array passed to submit_fit_array has shape (3, 8):
row 0: angles [0, 45, ..., 315]
@@ -467,14 +534,10 @@ class XrayEyeAlign:
os.makedirs(os.path.dirname(file), exist_ok=True)
with open(file, "w") as f:
f.write("angle\thorizontal\tvertical\n")
for k in range(1, 9):
angle_deg = LAMNI_ALIGNMENT_ANGLES[k - 1]
x_off = (
self.alignment_values[1][0] - self.alignment_values[k][0]
) * 1000
y_off = (
self.alignment_values[k][1] - self.alignment_values[1][1]
) * 1000
for k in range(2, 10):
angle_deg = LAMNI_ALIGNMENT_ANGLES[k - 2]
x_off = (self.alignment_values[0][0] - self.alignment_values[k][0]) * 1000
y_off = (self.alignment_values[k][1] - self.alignment_values[0][1]) * 1000
f.write(f"{angle_deg}\t{x_off:.4f}\t{y_off:.4f}\n")
print(
f" Angle {angle_deg:3d} deg: "
@@ -484,14 +547,14 @@ class XrayEyeAlign:
angles = np.array(LAMNI_ALIGNMENT_ANGLES, dtype=float)
x_offsets = np.array(
[
(self.alignment_values[1][0] - self.alignment_values[k][0]) * 1000
for k in range(1, 9)
(self.alignment_values[0][0] - self.alignment_values[k][0]) * 1000
for k in range(2, 10)
]
)
y_offsets = np.array(
[
(self.alignment_values[k][1] - self.alignment_values[1][1]) * 1000
for k in range(1, 9)
(self.alignment_values[k][1] - self.alignment_values[0][1]) * 1000
for k in range(2, 10)
]
)
data = np.array([angles, x_offsets, y_offsets])
@@ -3761,7 +3761,12 @@ class Flomni(
csaxs_bec_basepath.parent / "bec_ipython_client" / "plugins" / "flomni" / logo_file_rel
).resolve()
print(logo_file)
bec.messaging.scilog.new().add_attachment(logo_file, width=200).add_text(
scilog = getattr(bec.messaging, "scilog", None)
if scilog is None or not getattr(scilog, "_enabled", False):
logger.warning("SciLog is not enabled; skipping PDF report entry.")
return
scilog.new().add_attachment(logo_file, width=200).add_text(
content.replace("\n", "<br>")
).add_tags("tomoscan").send()
@@ -10,6 +10,7 @@ import termios
import threading
import time
import tty
import re
from pathlib import Path
import epics
@@ -323,11 +324,13 @@ class PtychoReconstructor:
with open(json_file, "w") as f:
json.dump(json_content, f, indent=4)
class TomoIDManager:
"""Registers a tomography measurement in the OMNY sample database
and returns its assigned tomo ID.
Falls back to tomo ID 0 for non-production accounts (e.g. test
accounts like "gac-x01dc") which the server rejects.
Usage:
id_manager = TomoIDManager()
tomo_id = id_manager.register(
@@ -341,11 +344,14 @@ class TomoIDManager:
)
"""
# OMNY_URL = "https://omny.web.psi.ch/samples/newmeasurement.php"
OMNY_URL = "https://v1p0zyg2w9n2k9c1.myfritz.net/samples/newmeasurement.php"
OMNY_USER = ""
OMNY_PASSWORD = ""
TMP_FILE = "~/currsamplesnr.txt"
FALLBACK_TOMO_ID = 0
@staticmethod
def _is_valid_eaccount(eaccount: str) -> bool:
"""True for real e-accounts (e.g. "e12345"), False for test accounts."""
return bool(re.fullmatch(r"e\d{5}", eaccount.strip()))
def register(
self,
@@ -359,18 +365,16 @@ class TomoIDManager:
) -> int:
"""Register a new measurement and return the assigned tomo ID.
Args:
sample_name (str): Name of the sample.
date (str): Date string (e.g. "2024-03-08").
eaccount (str): E-account identifier.
scan_number (int): First scan number of the measurement.
setup (str): Setup name (e.g. "lamni").
additional_info (str): Any additional sample information.
user (str): User name.
Returns:
int: The tomo ID assigned by the OMNY database.
Returns FALLBACK_TOMO_ID (0) if the account is not a real e-account
or if the server cannot be reached / returns an unusable response.
"""
if not self._is_valid_eaccount(eaccount):
logger.warning(
f"Account '{eaccount}' is not a valid e-account; "
f"skipping OMNY registration, using tomo ID {self.FALLBACK_TOMO_ID}."
)
return self.FALLBACK_TOMO_ID
url = (
f"{self.OMNY_URL}"
f"?sample={sample_name}"
@@ -381,25 +385,20 @@ class TomoIDManager:
f"&additional={additional_info}"
f"&user={user}"
)
# subprocess.run(
# f"wget --user={self.OMNY_USER} --password={self.OMNY_PASSWORD}"
# f" -q -O {self.TMP_FILE} '{url}'",
# shell=True,
# )
# print(url)
tmp_file = os.path.expanduser(self.TMP_FILE)
result = subprocess.run(f"wget -q -O {tmp_file} '{url}'", shell=True)
if result.returncode != 0:
raise OMNYToolsError(
f"wget failed (exit code {result.returncode}) fetching tomo ID from {self.OMNY_URL}"
)
try:
result = subprocess.run(f"wget -q -O {tmp_file} '{url}'", shell=True, timeout=30)
if result.returncode != 0:
raise OMNYToolsError(
f"wget failed (exit code {result.returncode}) fetching tomo ID from {self.OMNY_URL}"
)
with open(tmp_file) as f:
content = f.read().strip()
return int(content)
except FileNotFoundError as exc:
raise OMNYToolsError(f"wget did not produce output file {tmp_file}") from exc
except ValueError as exc:
raise OMNYToolsError(
f"Unexpected response from tomo ID server, got: {content!r}"
) from exc
except (subprocess.TimeoutExpired, FileNotFoundError, ValueError, OMNYToolsError) as exc:
logger.warning(
f"Could not obtain tomo ID from OMNY database ({exc}); "
f"falling back to tomo ID {self.FALLBACK_TOMO_ID}."
)
return self.FALLBACK_TOMO_ID
+4 -23
View File
@@ -298,20 +298,10 @@ cam_xeye:
onFailure: buffer
readOnly: false
readoutPriority: async
userParameter:
pixel_calibration: 0.0009174311926605505 # mm/pixel (= 0.2 / 218)
# ############################################################
# ########## OMNY / flOMNI / LamNI fast shutter ##############
# ############################################################
omnyfsh:
description: omnyfsh connects to fast shutter at X12 if device fsh exists
deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastShutter
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: baseline
############################################################
############################################################
#################### GUI Signals ###########################
############################################################
omny_xray_gui:
@@ -321,13 +311,4 @@ omny_xray_gui:
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
# calculated_signal:
# description: Calculated signal from alignment for fit
# deviceClass: ophyd_devices.ComputedSignal
# deviceConfig:
# compute_method: "def just_rand():\n return 42"
# enabled: true
# readOnly: false
# readoutPriority: baseline
readoutPriority: on_request
@@ -16,6 +16,18 @@
# omny_panda, omny_xray_gui.
############################################################
############################################################
#################### GUI Signals ###########################
############################################################
omny_xray_gui:
description: Gui signals
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayAlignGUI
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request
############################################################
#################### flOMNI Galil motors ###################
############################################################
@@ -136,7 +136,7 @@ lsamrot:
sign: 1
sim_stppermm: 50154.32099
sim_encpermm: 36000
sim_velocity: 12
sim_velocity: 48 #4x than hw
sim_initial_position: 0
deviceTags:
- lamni
@@ -328,7 +328,8 @@ cam_xeye:
onFailure: buffer
readOnly: false
readoutPriority: async
userParameter:
pixel_calibration: 0.0009174311926605505 # mm/pixel (= 0.2 / 218)
ddg1:
description: Simulated main delay generator for triggering
deviceClass: csaxs_bec.devices.sim.simulated_beamline_devices.SimulatedDDG1
@@ -347,3 +348,15 @@ fsh:
onFailure: raise
enabled: true
readoutPriority: monitored
############################################################
#################### GUI Signals ###########################
############################################################
omny_xray_gui:
description: Gui signals
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayAlignGUI
deviceConfig: {}
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: on_request