diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py
index 82b4a96..9da179b 100644
--- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py
+++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py
@@ -9,12 +9,13 @@ import numpy as np
from bec_lib import bec_logger
from bec_lib.alarm_handler import AlarmBase
from bec_lib.pdf_writer import PDFWriter
+from bec_lib.scan_repeat import scan_repeat
from typeguard import typechecked
from csaxs_bec.bec_ipython_client.plugins.cSAXS import cSAXSBeamlineChecks
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_optics_mixin import FlomniOpticsMixin
-from csaxs_bec.bec_ipython_client.plugins.flomni.x_ray_eye_align import XrayEyeAlign
from csaxs_bec.bec_ipython_client.plugins.flomni.gui_tools import flomniGuiTools
+from csaxs_bec.bec_ipython_client.plugins.flomni.x_ray_eye_align import XrayEyeAlign
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
OMNYTools,
PtychoReconstructor,
@@ -786,9 +787,7 @@ class FlomniSampleTransferMixin:
dev.ftransy.controller.socket_put_confirmed("confirm=1")
else:
print("Stopping.")
- raise FlomniError(
- "User abort sample transfer."
- )
+ raise FlomniError("User abort sample transfer.")
def ftransfer_gripper_is_open(self) -> bool:
status = bool(float(dev.ftransy.controller.socket_put_and_receive("MG @OUT[9]").strip()))
@@ -811,7 +810,7 @@ class FlomniSampleTransferMixin:
def ftransfer_gripper_move(self, position: int):
self.check_position_is_valid(position)
- #this is not used for sample stage position!
+ # this is not used for sample stage position!
self._ftransfer_shiftx = -0.15
self._ftransfer_shiftz = -0.5
@@ -1237,6 +1236,7 @@ class _ProgressProxy:
"tomo_type": 0,
"tomo_start_time": None,
"estimated_remaining_time": None,
+ "estimated_finish_time": None,
"heartbeat": None,
}
@@ -1314,12 +1314,13 @@ class Flomni(
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_webpage_generator import (
FlomniWebpageGenerator,
)
+
self._webpage_gen = FlomniWebpageGenerator(
bec_client=client,
output_dir="~/data/raw/webpage/",
- #upload_url="http://s1090968537.online.de/upload.php", # optional
+ # upload_url="http://s1090968537.online.de/upload.php", # optional
upload_url="https://v1p0zyg2w9n2k9c1.myfritz.net/upload.php",
- local_port=8080
+ local_port=8080,
)
self._webpage_gen.start()
@@ -1578,6 +1579,22 @@ class Flomni(
def tomo_stitch_overlap(self, val: float):
self.client.set_global_var("tomo_stitch_overlap", val)
+ @property
+ def tomo_angle_range(self):
+ """Total angular sweep in degrees for tomo_type 1 (equally spaced
+ sub-tomograms), inclusive of the upper bound. Either 180 (default,
+ original behaviour) or 360."""
+ val = self.client.get_global_var("tomo_angle_range")
+ if val is None:
+ return 180
+ return val
+
+ @tomo_angle_range.setter
+ def tomo_angle_range(self, val: float):
+ if val not in (180, 360):
+ raise ValueError("tomo_angle_range must be 180 or 360 degrees.")
+ self.client.set_global_var("tomo_angle_range", val)
+
@property
def golden_projections_at_0_deg_for_damage_estimation(self):
val = self.client.get_global_var("golden_projections_at_0_deg_for_damage_estimation")
@@ -1600,6 +1617,39 @@ class Flomni(
def golden_ratio_bunch_size(self, val: float):
self.client.set_global_var("golden_ratio_bunch_size", val)
+ @property
+ def frames_per_trigger(self):
+ """Number of burst frames acquired per point/projection. Used both
+ by scans.flomni_fermat_scan (via tomo_scan_projection) and by
+ tomo_acquire_at_angle (scans.acquire)."""
+ val = self.client.get_global_var("frames_per_trigger")
+ if val is None:
+ return 1
+ return val
+
+ @frames_per_trigger.setter
+ def frames_per_trigger(self, val: int):
+ if isinstance(val, bool) or not isinstance(val, int):
+ raise ValueError("frames_per_trigger must be a positive integer.")
+ if val <= 0:
+ raise ValueError("frames_per_trigger must be a positive integer.")
+ self.client.set_global_var("frames_per_trigger", val)
+
+ @property
+ def single_point_instead_of_fermat_scan(self):
+ """If True, tomo_scan acquires a single point (or burst) at each
+ angle via scans.acquire instead of running scans.flomni_fermat_scan.
+ Applies to all tomo_types, since it only changes how a given angle
+ is acquired, not which angles are visited."""
+ val = self.client.get_global_var("single_point_instead_of_fermat_scan")
+ if val is None:
+ return False
+ return val
+
+ @single_point_instead_of_fermat_scan.setter
+ def single_point_instead_of_fermat_scan(self, val: bool):
+ self.client.set_global_var("single_point_instead_of_fermat_scan", val)
+
@property
def sample_name(self):
return self.sample_get_name(0)
@@ -1645,7 +1695,7 @@ class Flomni(
end_scan_number = bec.queue.next_scan_number
for scan_nr in range(start_scan_number, end_scan_number):
- #self._write_tomo_scan_number(scan_nr, angle, 0)
+ # self._write_tomo_scan_number(scan_nr, angle, 0)
alignment_scan_numbers.append(scan_nr)
umv(dev.fsamroy, 0)
@@ -1655,7 +1705,7 @@ class Flomni(
# summary of alignment scan numbers
scan_list_str = ", ".join(str(s) for s in alignment_scan_numbers)
- #print(f"\nAlignment scan numbers ({len(alignment_scan_numbers)} total): {scan_list_str}")
+ # print(f"\nAlignment scan numbers ({len(alignment_scan_numbers)} total): {scan_list_str}")
# BEC scilog entry (no logo)
scilog_content = (
@@ -1665,7 +1715,9 @@ class Flomni(
f"Alignment scan numbers: {scan_list_str}\n"
)
print(scilog_content)
- bec.messaging.scilog.new().add_text(scilog_content.replace("\n", "
")).add_tags("alignmentscan").send()
+ bec.messaging.scilog.new().add_text(scilog_content.replace("\n", "
")).add_tags(
+ "alignmentscan"
+ ).send()
def write_alignment_scan_numbers(self, first_scan):
import os
@@ -1691,8 +1743,6 @@ class Flomni(
f.write(" ".join(map(str, x_vals)) + "\n")
f.write(" ".join(map(str, zeros)) + "\n")
-
-
def sub_tomo_scan(self, subtomo_number, start_angle=None):
"""
Performs a sub tomogram scan.
@@ -1729,14 +1779,14 @@ class Flomni(
start = start_angle + _tomo_shift_angles
if subtomo_number % 2: # odd = forward
- max_allowed_angle = 180.05 + self.tomo_angle_stepsize
- proposed_end = start + 180
+ max_allowed_angle = self.tomo_angle_range + 0.05 + self.tomo_angle_stepsize
+ proposed_end = start + self.tomo_angle_range
angle_end = min(proposed_end, max_allowed_angle)
span = angle_end - start
else: # even = reverse
min_allowed_angle = 0
- proposed_end = start - 180
+ proposed_end = start - self.tomo_angle_range
angle_end = max(proposed_end, min_allowed_angle)
span = start - angle_end
@@ -1747,8 +1797,8 @@ class Flomni(
if subtomo_number % 2: # odd subtomos → forward direction
# clamp end angle to max allowed
- max_allowed_angle = 180.05 + self.tomo_angle_stepsize
- proposed_end = start + 180
+ max_allowed_angle = self.tomo_angle_range + 0.05 + self.tomo_angle_stepsize
+ proposed_end = start + self.tomo_angle_range
angle_end = min(proposed_end, max_allowed_angle)
angles = np.linspace(start, angle_end, num=N, endpoint=True)
@@ -1756,7 +1806,7 @@ class Flomni(
else: # even subtomos → reverse direction
# go FROM start_angle down toward 0
min_allowed_angle = 0
- proposed_end = start - 180
+ proposed_end = start - self.tomo_angle_range
angle_end = max(proposed_end, min_allowed_angle)
angles = np.linspace(start, angle_end, num=N, endpoint=True)
@@ -1778,58 +1828,48 @@ class Flomni(
if subtomo_number % 2: # odd = forward direction
self._subtomo_offset = round(sa / step)
else: # even = reverse direction
- self._subtomo_offset = round((180 - sa) / step)
+ self._subtomo_offset = round((self.tomo_angle_range - sa) / step)
# progress index must always increase
self.progress["subtomo_projection"] = self._subtomo_offset + i
# ------------------------------------------------------------
# existing progress fields
- self.progress["subtomo_total_projections"] = int(180 / self.tomo_angle_stepsize)
+ self.progress["subtomo_total_projections"] = int(
+ self.tomo_angle_range / self.tomo_angle_stepsize
+ )
self.progress["projection"] = (subtomo_number - 1) * self.progress[
"subtomo_total_projections"
] + self.progress["subtomo_projection"]
- self.progress["total_projections"] = 180 / self.tomo_angle_stepsize * 8
+ self.progress["total_projections"] = (
+ self.tomo_angle_range / self.tomo_angle_stepsize
+ ) * 8
self.progress["angle"] = angle
# finally do the scan at this angle
self._tomo_scan_at_angle(angle, subtomo_number)
+ @scan_repeat(max_repeats=10, default=True)
def _tomo_scan_at_angle(self, angle, subtomo_number):
- successful = False
- error_caught = False
- if 0 <= angle < 180.05:
+
+ if 0 <= angle < self.tomo_angle_range + 0.05:
self.progress["heartbeat"] = datetime.datetime.now().isoformat()
print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}")
self._print_progress()
- while not successful:
- # self.bl_chk._bl_chk_start()
- if not self.special_angles:
- self._current_special_angles = []
- if self._current_special_angles:
- next_special_angle = self._current_special_angles[0]
- if np.isclose(angle, next_special_angle, atol=0.5):
- self._current_special_angles.pop(0)
- num_repeats = self.special_angle_repeats
- else:
- num_repeats = 1
- try:
- start_scan_number = bec.queue.next_scan_number
- for i in range(num_repeats):
- self._at_each_angle(angle)
- error_caught = False
- except AlarmBase as exc:
- if exc.alarm_type == "TimeoutError":
- bec.queue.request_queue_reset()
- time.sleep(2)
- error_caught = True
- else:
- raise exc
- # if self.bl_chk._bl_chk_stop() and not error_caught:
- successful = True
- # else:
- # self.bl_chk._bl_chk_wait_until_recovered()
+ if not self.special_angles:
+ self._current_special_angles = []
+ if self._current_special_angles:
+ next_special_angle = self._current_special_angles[0]
+ if np.isclose(angle, next_special_angle, atol=0.5):
+ self._current_special_angles.pop(0)
+ num_repeats = self.special_angle_repeats
+ else:
+ num_repeats = 1
+ start_scan_number = bec.queue.next_scan_number
+ for i in range(num_repeats):
+ self._at_each_angle(angle)
+
end_scan_number = bec.queue.next_scan_number
for scan_nr in range(start_scan_number, end_scan_number):
self._write_tomo_scan_number(scan_nr, angle, subtomo_number)
@@ -1851,6 +1891,10 @@ class Flomni(
bec = builtins.__dict__.get("bec")
scans = builtins.__dict__.get("scans")
+
+ bec.builtin_actors.scan_interlock.trigger_setting = "restart_scan"
+ bec.builtin_actors.scan_interlock.enabled = True
+
self._current_special_angles = self.special_angles.copy()
# a new tomo scan was started
if (
@@ -1859,7 +1903,7 @@ class Flomni(
or (self.tomo_type == 3 and projection_number == None)
):
- #pylint: disable=undefined-variable
+ # pylint: disable=undefined-variable
if bec.active_account != "":
self.tomo_id = self.add_sample_database(
self.sample_name,
@@ -1875,6 +1919,11 @@ class Flomni(
self.tomo_id = 0
self.write_pdf_report()
self.progress["tomo_start_time"] = datetime.datetime.now().isoformat()
+ # reset stale estimates from any previous scan, otherwise the GUI
+ # would keep showing a leftover ETA from before this scan has
+ # accumulated enough projections to compute a fresh one
+ self.progress["estimated_remaining_time"] = None
+ self.progress["estimated_finish_time"] = None
with scans.dataset_id_on_hold:
if self.tomo_type == 1:
@@ -2000,15 +2049,18 @@ class Flomni(
projection = self.progress["projection"]
total = self.progress["total_projections"]
if start_str is not None and total > 0 and projection > 9:
- elapsed = (
- datetime.datetime.now() - datetime.datetime.fromisoformat(start_str)
- ).total_seconds()
+ now = datetime.datetime.now()
+ elapsed = (now - datetime.datetime.fromisoformat(start_str)).total_seconds()
rate = projection / elapsed # projections per second
remaining_s = (total - projection) / rate
self.progress["estimated_remaining_time"] = remaining_s
eta_str = self._format_duration(remaining_s)
+ finish_dt = now + datetime.timedelta(seconds=remaining_s)
+ self.progress["estimated_finish_time"] = finish_dt.isoformat()
+ finish_str = finish_dt.strftime("%Y-%m-%d %H:%M:%S")
else:
eta_str = "N/A"
+ finish_str = "N/A"
# ----------------------------------------------------------------------
print("\x1b[95mProgress report:")
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
@@ -2017,7 +2069,8 @@ class Flomni(
print(f"Angle: ........................... {self.progress['angle']}")
print(f"Current subtomo: ................. {self.progress['subtomo']}")
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}")
- print(f"Estimated remaining time: ........ {eta_str}\x1b[0m")
+ print(f"Estimated remaining time: ........ {eta_str}")
+ print(f"Estimated finish time: ........... {finish_str}\x1b[0m")
self._flomnigui_update_progress()
def add_sample_database(
@@ -2040,6 +2093,10 @@ class Flomni(
flomni_at_each_angle(self, angle)
return
+ if self.single_point_instead_of_fermat_scan:
+ self.tomo_acquire_at_angle(angle)
+ return
+
self.tomo_scan_projection(angle)
def _golden(self, ii, howmany_sorted, maxangle, reverse=False):
@@ -2123,6 +2180,7 @@ class Flomni(
offsets[1]
- self.compute_additional_correction_y(angle)
- self.compute_additional_correction_y_2(angle)
+ + self.manual_shift_y
)
sum_offset_z = offsets[2]
@@ -2155,6 +2213,7 @@ class Flomni(
zshift=sum_offset_z,
angle=angle,
exp_time=self.tomo_countingtime,
+ frames_per_trigger=self.frames_per_trigger,
)
if self.corridor_size > 0:
@@ -2164,6 +2223,67 @@ class Flomni(
self.tomo_reconstruct()
+ def tomo_acquire_at_angle(self, angle: float, frames_per_trigger: int | None = None):
+ """
+ Move fsamroy to `angle`, then move rtx/rty/rtz to the alignment-corrected
+ scan center (same alignment-offset logic as tomo_scan_projection, but
+ without stitching), and acquire a single frame or a burst via
+ scans.acquire instead of running a fermat scan.
+
+ This mirrors the positioning sequence used internally by
+ flomni_fermat_scan (rotation, then rtx/rty/rtz with laser-tracker
+ on/check/move-to-region), but executes it as plain blocking
+ client-side calls, since this runs in the BEC client, not on the
+ scan server.
+
+ Args:
+ angle (float): rotation angle [deg] to move fsamroy to.
+ frames_per_trigger (int, optional): number of burst frames for
+ this acquisition. Defaults to self.frames_per_trigger.
+ """
+ scans = builtins.__dict__.get("scans")
+
+ # --- rotation ---
+ fsamroy_current_setpoint = dev.fsamroy.user_setpoint.get()
+ if angle != fsamroy_current_setpoint:
+ umv(dev.fsamroy, angle)
+ else:
+ print("No rotation required")
+
+ # --- alignment offset (same as tomo_scan_projection, no stitching) ---
+ offsets = self.get_alignment_offset(angle)
+ sum_offset_x = offsets[0]
+ sum_offset_y = (
+ offsets[1]
+ - self.compute_additional_correction_y(angle)
+ - self.compute_additional_correction_y_2(angle)
+ + self.manual_shift_y
+ )
+ sum_offset_z = offsets[2]
+
+ # --- positioning + laser tracker, mirroring
+ # flomni_fermat_scan._prepare_setup_part2 ---
+ dev.rtx.controller.laser_tracker_on()
+ umv(dev.rtx, sum_offset_x, dev.rty, sum_offset_y, dev.rtz, sum_offset_z)
+ tracker_signal = dev.rtx.controller.laser_tracker_check_signalstrength()
+ # checks that the fsamx coarse stage is at a position that leaves
+ # sufficient piezo range on the fine (rtx) stage
+ dev.rtx.controller.move_samx_to_scan_region(sum_offset_x)
+
+ if tracker_signal == "low":
+ logger.warning(
+ "Signal strength of the laser tracker is low. Realignment recommended!"
+ )
+ elif tracker_signal == "toolow":
+ raise FlomniError(
+ "Signal strength of the laser tracker is too low for scanning. Realignment required!"
+ )
+
+ # --- acquire ---
+ n_frames = (
+ frames_per_trigger if frames_per_trigger is not None else self.frames_per_trigger
+ )
+ scans.acquire(exp_time=self.tomo_countingtime, frames_per_trigger=n_frames)
def tomo_parameters(self):
"""print and update the tomo parameters"""
@@ -2174,12 +2294,19 @@ class Flomni(
print(f"Stitching number x,y = {self.stitch_x}, {self.stitch_y}")
print(f"Stitching overlap = {self.tomo_stitch_overlap}")
print(f"Reconstruction queue name = {self.ptycho_reconstruct_foldername}")
- print(f" _manual_shift_y = {self.manual_shift_y}")
+ print(f" _manual_shift_y = {self.manual_shift_y}")
+ print(f"Frames per trigger (burst) = {self.frames_per_trigger}")
+ print(f"Single point instead of fermat = {self.single_point_instead_of_fermat_scan}")
print("")
if self.tomo_type == 1:
print("\x1b[1mTomo type 1:\x1b[0m 8 equally spaced sub-tomograms")
- print(f"Total number of projections: {180/self.tomo_angle_stepsize*8}")
+ print(f"Angular range = {self.tomo_angle_range} degrees")
+ print(f"Total number of projections: {(self.tomo_angle_range/self.tomo_angle_stepsize)*8}")
print(f"Angular step within sub-tomogram: {self.tomo_angle_stepsize} degrees")
+ print(
+ "Angular step of the final (combined) tomogram:"
+ f" {self.tomo_angle_range/((self.tomo_angle_range/self.tomo_angle_stepsize)*8)} degrees"
+ )
if self.tomo_type == 2:
print("\x1b[1mTomo type 2:\x1b[0m Golden ratio tomography")
print(f"Sorted in bunches of: {self.golden_ratio_bunch_size}")
@@ -2214,11 +2341,38 @@ class Flomni(
self.tomo_shellstep = self._get_val(" um", self.tomo_shellstep, float)
self.fovx = self._get_val(" um", self.fovx, float)
self.fovy = self._get_val(" um", self.fovy, float)
- self.stitch_x = self._get_val("", self.stitch_x, int)
- self.stitch_y = self._get_val("", self.stitch_y, int)
+ if self.single_point_instead_of_fermat_scan:
+ print(
+ "Stitching is disabled while single point instead of fermat scan is"
+ " active; stitch X/Y forced to 0."
+ )
+ self.stitch_x = 0
+ self.stitch_y = 0
+ else:
+ self.stitch_x = self._get_val("", self.stitch_x, int)
+ self.stitch_y = self._get_val("", self.stitch_y, int)
self.ptycho_reconstruct_foldername = self._get_val(
"Reconstruction queue ", self.ptycho_reconstruct_foldername, str
)
+ self.frames_per_trigger = self._get_val(
+ "Frames per trigger (burst)", self.frames_per_trigger, int
+ )
+ self.single_point_instead_of_fermat_scan = bool(
+ self._get_val(
+ "Single point instead of fermat scan (acquire at angle) 1/0?",
+ int(self.single_point_instead_of_fermat_scan),
+ int,
+ )
+ )
+ if self.single_point_instead_of_fermat_scan and (
+ self.stitch_x != 0 or self.stitch_y != 0
+ ):
+ print(
+ "Stitching is not supported with single point instead of fermat scan;"
+ " stitch X/Y forced to 0."
+ )
+ self.stitch_x = 0
+ self.stitch_y = 0
print("Tomography type:")
print(" 1: 8 equally spaced sub-tomograms")
@@ -2227,12 +2381,22 @@ class Flomni(
self.tomo_type = self._get_val("Tomography type", self.tomo_type, int)
if self.tomo_type == 1:
+ self.tomo_angle_range = self._get_val(
+ "Angular range (180 or 360)", self.tomo_angle_range, int
+ )
tomo_numberofprojections = self._get_val(
- "Total number of projections", 180 / self.tomo_angle_stepsize * 8, int
+ "Total number of projections",
+ (self.tomo_angle_range / self.tomo_angle_stepsize) * 8,
+ int,
+ )
+ self.tomo_angle_stepsize = (self.tomo_angle_range / tomo_numberofprojections) * 8
+ print(
+ f"The angular step within a sub-tomogram will be {self.tomo_angle_stepsize} degrees"
+ )
+ print(
+ "The angular step of the final (combined) tomogram will be"
+ f" {self.tomo_angle_range / tomo_numberofprojections} degrees"
)
- print(f"The angular step will be {180/tomo_numberofprojections}")
- self.tomo_angle_stepsize = 180 / tomo_numberofprojections * 8
- print(f"The angular step in a subtomogram it will be {self.tomo_angle_stepsize}")
if self.tomo_type == 2:
self.golden_ratio_bunch_size = self._get_val(
@@ -2313,11 +2477,11 @@ class Flomni(
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
f"{'e-account:':<{padding}}{str(account):>{padding}}\n",
- f"{'Number of projections:':<{padding}}{int(180 / self.tomo_angle_stepsize * 8):>{padding}}\n",
+ f"{'Number of projections:':<{padding}}{int((self.tomo_angle_range / self.tomo_angle_stepsize) * 8):>{padding}}\n",
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
- f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(180 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n",
+ f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int((self.tomo_angle_range / self.tomo_angle_stepsize) * 8) + 10:>{padding}}\n",
f"{'Current photon energy:':<{padding}}To be implemented\n",
- #f"{'Current photon energy:':<{padding}}{dev.mokev.read()['mokev']['value']:>{padding}.4f}\n",
+ # f"{'Current photon energy:':<{padding}}{dev.mokev.read()['mokev']['value']:>{padding}.4f}\n",
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
f"{'FOV:':<{padding}}{fovxy:>{padding}}\n",
@@ -2326,7 +2490,9 @@ class Flomni(
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
]
content = "".join(content)
- user_target = os.path.expanduser(f"~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
+ user_target = os.path.expanduser(
+ f"~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf"
+ )
with PDFWriter(user_target) as file:
file.write(header)
file.write(content)
@@ -2342,7 +2508,6 @@ class Flomni(
# self.client.tomo_progress.send_tomo_progress_message("~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf").send()
import csaxs_bec
-
# Ensure this is a Path object, not a string
csaxs_bec_basepath = Path(csaxs_bec.__file__)
@@ -2350,14 +2515,12 @@ class Flomni(
# Build the absolute path correctly
logo_file = (
- csaxs_bec_basepath.parent
- / "bec_ipython_client"
- / "plugins"
- / "flomni"
- / logo_file_rel
+ 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(content.replace("\n", "
")).add_tags("tomoscan").send()
+ bec.messaging.scilog.new().add_attachment(logo_file, width=200).add_text(
+ content.replace("\n", "
")
+ ).add_tags("tomoscan").send()
if __name__ == "__main__":
diff --git a/csaxs_bec/device_configs/bl_detectors.yaml b/csaxs_bec/device_configs/bl_detectors.yaml
index 3df5103..80a159e 100644
--- a/csaxs_bec/device_configs/bl_detectors.yaml
+++ b/csaxs_bec/device_configs/bl_detectors.yaml
@@ -13,8 +13,8 @@ eiger_9:
description: Eiger 9M detector
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
deviceConfig:
- detector_distance: 100
- beam_center: [0, 0]
+ detector_distance: 2200
+ beam_center: [870, 1203]
onFailure: raise
enabled: True
readoutPriority: async
diff --git a/csaxs_bec/device_configs/bl_endstation.yaml b/csaxs_bec/device_configs/bl_endstation.yaml
index d9e6534..09e644a 100644
--- a/csaxs_bec/device_configs/bl_endstation.yaml
+++ b/csaxs_bec/device_configs/bl_endstation.yaml
@@ -1089,47 +1089,47 @@ bim_xbox3_slowrb:
-# ####################
-# ### Beamstop diode control for flight tube
-# ### This requires galilrioft device. On top of that the gain control device is built as well as a slow voltage readback.
-# ####################
+####################
+### Beamstop diode control for flight tube
+### This requires galilrioft device. On top of that the gain control device is built as well as a slow voltage readback.
+####################
-# galilrioesft:
-# description: Galil RIO for remote gain switching and slow reading FlightTube
-# deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
-# deviceConfig:
-# host: galilrioesft.psi.ch
-# enabled: true
-# onFailure: retry
-# readOnly: false
-# readoutPriority: baseline
-# connectionTimeout: 20
+galilrioesft:
+ description: Galil RIO for remote gain switching and slow reading FlightTube
+ deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
+ deviceConfig:
+ host: galilrioesft.psi.ch
+ enabled: true
+ onFailure: retry
+ readOnly: false
+ readoutPriority: baseline
+ connectionTimeout: 20
-# gain_beamstop_diode:
-# description: Gain control for beamstop flightube
-# deviceClass: csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl
-# deviceConfig:
-# gain_lsb: galilrioesft.digital_out.ch0 # Pin 10 -> Galil ch0
-# gain_mid: galilrioesft.digital_out.ch1 # Pin 11 -> Galil ch1
-# gain_msb: galilrioesft.digital_out.ch2 # Pin 12 -> Galil ch2
-# coupling: galilrioesft.digital_out.ch3 # Pin 13 -> Galil ch3
-# speed_mode: galilrioesft.digital_out.ch4 # Pin 14 -> Galil ch4
-# enabled: true
-# readoutPriority: baseline
-# onFailure: retry
-# needs:
-# - galilrioesft
+gain_beamstop_diode:
+ description: Gain control for beamstop flightube
+ deviceClass: csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl
+ deviceConfig:
+ gain_lsb: galilrioesft.digital_out.ch0 # Pin 10 -> Galil ch0
+ gain_mid: galilrioesft.digital_out.ch1 # Pin 11 -> Galil ch1
+ gain_msb: galilrioesft.digital_out.ch2 # Pin 12 -> Galil ch2
+ coupling: galilrioesft.digital_out.ch3 # Pin 13 -> Galil ch3
+ speed_mode: galilrioesft.digital_out.ch4 # Pin 14 -> Galil ch4
+ enabled: true
+ readoutPriority: baseline
+ onFailure: retry
+ needs:
+ - galilrioesft
-# beamstop_intensity:
-# description: Beamstop intensity from Galil analog input ch6
-# deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
-# deviceConfig:
-# signal: galilrioesft.analog_in.ch0
-# enabled: true
-# readoutPriority: baseline
-# onFailure: retry
-# needs:
-# - galilrioesft
+beamstop_intensity:
+ description: Beamstop intensity from Galil analog input ch6
+ deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
+ deviceConfig:
+ signal: galilrioesft.analog_in.ch0
+ enabled: true
+ readoutPriority: monitored
+ onFailure: retry
+ needs:
+ - galilrioesft
diff --git a/csaxs_bec/device_configs/bl_optics_hutch.yaml b/csaxs_bec/device_configs/bl_optics_hutch.yaml
index 5d0edce..14bdaf3 100644
--- a/csaxs_bec/device_configs/bl_optics_hutch.yaml
+++ b/csaxs_bec/device_configs/bl_optics_hutch.yaml
@@ -264,6 +264,7 @@ scinx:
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
init_position: -23
bl_smar_stage: 2
+ in_position: -12.5
poly:
description: polarizer holder in OPbox
@@ -328,25 +329,25 @@ polrot:
# readOnly: false
# softwareTrigger: false
-# ####################
-# ### BPM and polarization diag XBox1 (optics hutch)
-# ### This requires galilrioop device. On top of that the gain control devices and slow readback devices are built.
-# ### dev.galilrioop.read() shows the analog inputs
-# ### another example ...analog_in.ch0.get()
-# ### dev.galilrioop.read_configuration() shows the digital channels
-# ### example for direct access dev.galilrioesxbox.digital_out.ch1.put(0)
-# ####################
+####################
+### BPM and polarization diag XBox1 (optics hutch)
+### This requires galilrioop device. On top of that the gain control devices and slow readback devices are built.
+### dev.galilrioop.read() shows the analog inputs
+### another example ...analog_in.ch0.get()
+### dev.galilrioop.read_configuration() shows the digital channels
+### example for direct access dev.galilrioesxbox.digital_out.ch1.put(0)
+####################
-# galilrioop:
-# description: Galil RIO for remote gain switching and slow reading XBox OP
-# deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
-# deviceConfig:
-# host: galilrioop.psi.ch
-# enabled: true
-# onFailure: retry
-# readOnly: false
-# readoutPriority: monitored
-# connectionTimeout: 20
+galilrioop:
+ description: Galil RIO for remote gain switching and slow reading XBox OP
+ deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
+ deviceConfig:
+ host: galilrioop.psi.ch
+ enabled: true
+ onFailure: retry
+ readOnly: false
+ readoutPriority: monitored
+ connectionTimeout: 20
# gain_bpm_xbox1:
# description: Gain control for BPM XBox1 (OP hutch)
@@ -377,42 +378,42 @@ polrot:
# needs:
# - galilrioop
-# gain_diodes_xbox1:
-# description: Gain control for diodes (horizontal and vertical) XBox1
-# deviceClass: csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl
-# deviceConfig:
-# gain_lsb: galilrioop.digital_out.ch6 # Pin 10 -> Galil ch0
-# gain_mid: galilrioop.digital_out.ch7 # Pin 11 -> Galil ch1
-# gain_msb: galilrioop.digital_out.ch8 # Pin 12 -> Galil ch2
-# coupling: galilrioop.digital_out.ch9 # Pin 13 -> Galil ch3
-# speed_mode: galilrioop.digital_out.ch10 # Pin 14 -> Galil ch4
-# enabled: true
-# readoutPriority: baseline
-# onFailure: retry
-# needs:
-# - galilrioop
+gain_diodes_xbox1:
+ description: Gain control for diodes (horizontal and vertical) XBox1
+ deviceClass: csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl
+ deviceConfig:
+ gain_lsb: galilrioop.digital_out.ch6 # Pin 10 -> Galil ch0
+ gain_mid: galilrioop.digital_out.ch7 # Pin 11 -> Galil ch1
+ gain_msb: galilrioop.digital_out.ch8 # Pin 12 -> Galil ch2
+ coupling: galilrioop.digital_out.ch9 # Pin 13 -> Galil ch3
+ speed_mode: galilrioop.digital_out.ch10 # Pin 14 -> Galil ch4
+ enabled: true
+ readoutPriority: baseline
+ onFailure: retry
+ needs:
+ - galilrioop
-# diode_horizontal_xbox1_slowrb:
-# description: Slow readback diode horizontal XBox OP (polarization diagnostics)
-# deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
-# deviceConfig:
-# signal: galilrioop.analog_in.ch6
-# enabled: true
-# readoutPriority: baseline
-# onFailure: retry
-# needs:
-# - galilrioop
+diode_horizontal_xbox1_slowrb:
+ description: Slow readback diode horizontal XBox OP (polarization diagnostics)
+ deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
+ deviceConfig:
+ signal: galilrioop.analog_in.ch6
+ enabled: true
+ readoutPriority: monitored
+ onFailure: retry
+ needs:
+ - galilrioop
-# diode_vertical_xbox1_slowrb:
-# description: Slow readback diode vertical XBox OP (polarization diagnostics)
-# deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
-# deviceConfig:
-# signal: galilrioop.analog_in.ch7
-# enabled: true
-# readoutPriority: baseline
-# onFailure: retry
-# needs:
-# - galilrioop
+diode_vertical_xbox1_slowrb:
+ description: Slow readback diode vertical XBox OP (polarization diagnostics)
+ deviceClass: csaxs_bec.devices.pseudo_devices.signal_forwarder.SignalForwarder
+ deviceConfig:
+ signal: galilrioop.analog_in.ch7
+ enabled: true
+ readoutPriority: monitored
+ onFailure: retry
+ needs:
+ - galilrioop
sl3xi:
description: "slit 2 (optics) x ring"
@@ -525,3 +526,339 @@ sl3ys:
deviceTags:
- cSAXS
- optics
+
+kbvbendu:
+ description: "KB Vertical Focusing Mirror, bender upstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:BNDU"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvbendd:
+ description: "KB Vertical Focusing Mirror, bender downstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:BNDD"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtrx:
+ description: "KB Vertical Focusing Mirror, translation X"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRX"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtry:
+ description: "KB Vertical Focusing Mirror, translation Y"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRY"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvyaw:
+ description: "KB Vertical Focusing Mirror, yaw"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:YAW"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvroll:
+ description: "KB Vertical Focusing Mirror, roll"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:ROLL"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvpitch:
+ description: "KB Vertical Focusing Mirror, pitch"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:PITCH"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtrxu:
+ description: "KB Vertical Focusing Mirror, translation X upstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRXU"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtrxd:
+ description: "KB Vertical Focusing Mirror, translation X downstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRXD"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtryur:
+ description: "KB Vertical Focusing Mirror, translation Y upstream ring"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRYUR"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtryw:
+ description: "KB Vertical Focusing Mirror, translation Y wall"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRYW"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbvtrydr:
+ description: "KB Vertical Focusing Mirror, translation Y downstream ring"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-VFM:TRYDR"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhbendu:
+ description: "KB Horizontal Focusing Mirror, bender upstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:BNDU"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhbendd:
+ description: "KB Horizontal Focusing Mirror, bender downstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:BNDD"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtrx:
+ description: "KB Horizontal Focusing Mirror, translation X"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRX"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtry:
+ description: "KB Horizontal Focusing Mirror, translation Y"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRY"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhyaw:
+ description: "KB Horizontal Focusing Mirror, yaw"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:YAW"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhroll:
+ description: "KB Horizontal Focusing Mirror, roll"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:ROLL"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhpitch:
+ description: "KB Horizontal Focusing Mirror, pitch"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:PITCH"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtrxu:
+ description: "KB Horizontal Focusing Mirror, translation X upstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRXU"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtrxd:
+ description: "KB Horizontal Focusing Mirror, translation X downstream"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRXD"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtryuw:
+ description: "KB Horizontal Focusing Mirror, translation Y upstream wall"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRYUW"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtryr:
+ description: "KB Horizontal Focusing Mirror, translation Y ring"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRYR"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
+
+kbhtrydw:
+ description: "KB Horizontal Focusing Mirror, translation Y downstream wall"
+ deviceClass: ophyd_devices.EpicsMotorEC
+ deviceConfig:
+ prefix: "X12SA-OP-HFM:TRYDW"
+ onFailure: raise
+ enabled: true
+ readoutPriority: baseline
+ readOnly: false
+ softwareTrigger: false
+ deviceTags:
+ - cSAXS
+ - optics
diff --git a/csaxs_bec/device_configs/ptycho_flomni.yaml b/csaxs_bec/device_configs/ptycho_flomni.yaml
index 78296a1..5321e12 100644
--- a/csaxs_bec/device_configs/ptycho_flomni.yaml
+++ b/csaxs_bec/device_configs/ptycho_flomni.yaml
@@ -437,7 +437,7 @@ cam_xeye:
description: Camera flOMNI Xray eye ID1
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
deviceConfig:
- camera_id: 1
+ camera_id: 11
bits_per_pixel: 24
num_rotation_90: 3
transpose: false
@@ -511,33 +511,33 @@ calculated_signal:
############################################################
#################### OMNY Pandabox #########################
############################################################
-omny_panda:
- readoutPriority: async
- deviceClass: csaxs_bec.devices.panda_box.panda_box_omny.PandaBoxOMNY
- deviceConfig:
- host: omny-panda.psi.ch
- signal_alias:
- FMC_IN.VAL1.Min: cap_voltage_fzp_y_min
- FMC_IN.VAL1.Max: cap_voltage_fzp_y_max
- FMC_IN.VAL1.Mean: cap_voltage_fzp_y_mean
- FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
- FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
- FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
- INENC1.VAL.Max: interf_st_fzp_y_max
- INENC1.VAL.Mean: interf_st_fzp_y_mean
- INENC1.VAL.Min: interf_st_fzp_y_min
- INENC2.VAL.Max: interf_st_fzp_x_max
- INENC2.VAL.Mean: interf_st_fzp_x_mean
- INENC2.VAL.Min: interf_st_fzp_x_min
- INENC3.VAL.Max: interf_st_rotz_max
- INENC3.VAL.Mean: interf_st_rotz_mean
- INENC3.VAL.Min: interf_st_rotz_min
- INENC4.VAL.Max: interf_st_rotx_max
- INENC4.VAL.Mean: interf_st_rotx_mean
- INENC4.VAL.Min: interf_st_rotx_min
- PCAP.GATE_DURATION.Value: pcap_gate_duration_value
- deviceTags:
- - detector
- enabled: true
- readOnly: false
- softwareTrigger: false
+# omny_panda:
+# readoutPriority: async
+# deviceClass: csaxs_bec.devices.panda_box.panda_box_omny.PandaBoxOMNY
+# deviceConfig:
+# host: omny-panda.psi.ch
+# signal_alias:
+# FMC_IN.VAL1.Min: cap_voltage_fzp_y_min
+# FMC_IN.VAL1.Max: cap_voltage_fzp_y_max
+# FMC_IN.VAL1.Mean: cap_voltage_fzp_y_mean
+# FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
+# FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
+# FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
+# INENC1.VAL.Max: interf_st_fzp_y_max
+# INENC1.VAL.Mean: interf_st_fzp_y_mean
+# INENC1.VAL.Min: interf_st_fzp_y_min
+# INENC2.VAL.Max: interf_st_fzp_x_max
+# INENC2.VAL.Mean: interf_st_fzp_x_mean
+# INENC2.VAL.Min: interf_st_fzp_x_min
+# INENC3.VAL.Max: interf_st_rotz_max
+# INENC3.VAL.Mean: interf_st_rotz_mean
+# INENC3.VAL.Min: interf_st_rotz_min
+# INENC4.VAL.Max: interf_st_rotx_max
+# INENC4.VAL.Mean: interf_st_rotx_mean
+# INENC4.VAL.Min: interf_st_rotx_min
+# PCAP.GATE_DURATION.Value: pcap_gate_duration_value
+# deviceTags:
+# - detector
+# enabled: true
+# readOnly: false
+# softwareTrigger: false
diff --git a/csaxs_bec/device_configs/simulated_omny/simulated_bl_endstation.yaml b/csaxs_bec/device_configs/simulated_omny/simulated_bl_endstation.yaml
index 2ac5201..5deb8c7 100644
--- a/csaxs_bec/device_configs/simulated_omny/simulated_bl_endstation.yaml
+++ b/csaxs_bec/device_configs/simulated_omny/simulated_bl_endstation.yaml
@@ -18,4 +18,7 @@ fsh:
readoutPriority: monitored
flomni:
- - !include ../ptycho_flomni.yaml
\ No newline at end of file
+ - !include ../ptycho_flomni.yaml
+
+machine:
+ - !include ../machine.yml
diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py
index e8d94d2..eccb857 100644
--- a/csaxs_bec/devices/jungfraujoch/eiger.py
+++ b/csaxs_bec/devices/jungfraujoch/eiger.py
@@ -36,6 +36,7 @@ import os
import time
from typing import TYPE_CHECKING, Literal
+import numpy as np
import yaml
from bec_lib.file_utils import get_full_path
from bec_lib.logger import bec_logger
@@ -117,6 +118,8 @@ class Eiger(PSIDeviceBase):
self._full_path = ""
self._num_triggers = 0
self._wait_for_on_complete = 20 # seconds
+ # Initial incident energy in keV, if restarted None
+ self._incident_energy: float | None = None
if self.device_manager is not None:
self.device_manager: DeviceManagerDS
self.scan_parameters: ScanServerScanInfo | None = None
@@ -272,7 +275,25 @@ class Eiger(PSIDeviceBase):
# TODO: Check mono energy from device in BEC
# Setting incident energy in keV
- incident_energy = 12.0
+
+ try:
+ incident_energy = self._get_beam_energy(self.device_manager)
+ if self._incident_energy is None:
+ self._incident_energy = round(float(incident_energy), 3)
+ elif not np.isclose(
+ self._incident_energy, incident_energy, atol=0.01
+ ): # 10 keV tolerance
+ logger.warning(
+ f"Incident energy changed from {self._incident_energy} keV to {incident_energy} keV for device {self.name}. "
+ )
+ self._incident_energy = round(float(incident_energy), 3)
+ except Exception as e:
+ logger.warning(f"Failed to set up beam energy for {self.name}: {e}")
+ incident_energy = 12.0 # default to 12 keV if error occurs
+ self._incident_energy = round(float(incident_energy), 3)
+
+ logger.info(f"Device {self.name} uses incident energy of {self._incident_energy} keV.")
+
# Setting up exp_time and num_triggers acquisition parameter
exp_time = self.scan_parameters.exp_time
if exp_time <= self._readout_time: # Exp_time must be at least the readout time
@@ -417,3 +438,20 @@ class Eiger(PSIDeviceBase):
self.jfj_preview_client.stop()
self.on_stop()
return super().on_destroy()
+
+ def _get_beam_energy(self, device_manager: DeviceManagerDS) -> float:
+ """
+ Fetch the beam energy from the device manager.
+
+ Args:
+ device_manager (DeviceManagerDS): The device manager to fetch the beam energy from.
+
+ Returns:
+ float: The beam energy in keV.
+ """
+ if hasattr(device_manager, "devices") and hasattr(device_manager.devices, "ccm_energy"):
+ energy = device_manager.devices.ccm_energy.read()[
+ device_manager.devices.ccm_energy.name
+ ]["value"]
+
+ return energy