Compare commits

...

4 Commits

Author SHA1 Message Date
x01dc
5e914c0a46 removed blchk from flomni
Some checks failed
CI for csaxs_bec / test (push) Failing after 1m54s
2026-03-09 15:04:49 +01:00
x01dc
2b48fb084b added umvr for tweak
Some checks failed
CI for csaxs_bec / test (push) Failing after 1m53s
2026-03-09 15:00:48 +01:00
x01dc
c0c77648d4 removed bl chk to replace by states
Some checks failed
CI for csaxs_bec / test (push) Has been cancelled
2026-03-09 14:59:28 +01:00
x01dc
3c50781101 fixed syntax
Some checks failed
CI for csaxs_bec / test (push) Failing after 1m53s
2026-03-09 13:37:26 +01:00
3 changed files with 38 additions and 274 deletions

View File

@@ -12,7 +12,6 @@ from bec_lib.pdf_writer import PDFWriter
from typeguard import typechecked from typeguard import typechecked
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import ( from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
BeamlineChecker,
OMNYTools, OMNYTools,
PtychoReconstructor, PtychoReconstructor,
TomoIDManager, TomoIDManager,
@@ -40,7 +39,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
self.init = LaMNIInitStages(client) self.init = LaMNIInitStages(client)
# Extracted collaborators # Extracted collaborators
self.bl_chk = BeamlineChecker(client)
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername) self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
self.tomo_id_manager = TomoIDManager() self.tomo_id_manager = TomoIDManager()
self.OMNYTools = OMNYTools(self.client) self.OMNYTools = OMNYTools(self.client)
@@ -61,20 +59,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
self.progress["total_projections"] = 1 self.progress["total_projections"] = 1
self.progress["angle"] = 0 self.progress["angle"] = 0
# ------------------------------------------------------------------
# Beamline checks — delegated to BeamlineChecker
# ------------------------------------------------------------------
@property
def beamline_checks_enabled(self):
return self.bl_chk.checks_enabled
@beamline_checks_enabled.setter
def beamline_checks_enabled(self, val: bool):
self.bl_chk.checks_enabled = val
def get_beamline_checks_enabled(self):
self.bl_chk.print_status()
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Special angles # Special angles
@@ -552,7 +536,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
print(f"Starting LamNI scan for angle {angle} in subtomo {subtomo_number}") print(f"Starting LamNI scan for angle {angle} in subtomo {subtomo_number}")
self._print_progress() self._print_progress()
while not successful: while not successful:
self.bl_chk.start()
if not self.special_angles: if not self.special_angles:
self._current_special_angles = [] self._current_special_angles = []
if self._current_special_angles: if self._current_special_angles:
@@ -579,10 +562,9 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
for scan_nr in range(start_scan_number, end_scan_number): for scan_nr in range(start_scan_number, end_scan_number):
self._write_tomo_scan_number(scan_nr, angle, subtomo_number) self._write_tomo_scan_number(scan_nr, angle, subtomo_number)
if self.bl_chk.stop() and not error_caught: #todo here bl chk, if ok then successfull true
successful = True successful = True
else:
self.bl_chk.wait_until_recovered()
def _golden(self, ii, howmany_sorted, maxangle=360, reverse=False): def _golden(self, ii, howmany_sorted, maxangle=360, reverse=False):
"""Return the ii-th golden ratio angle within sorted bunches and its subtomo number.""" """Return the ii-th golden ratio angle within sorted bunches and its subtomo number."""

View File

@@ -15,7 +15,11 @@ 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.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.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.gui_tools import flomniGuiTools
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
OMNYTools,
PtychoReconstructor,
TomoIDManager,
)
logger = bec_logger.logger logger = bec_logger.logger
@@ -65,9 +69,6 @@ class FlomniError(Exception):
# print("Please expicitely confirm y or n.") # print("Please expicitely confirm y or n.")
class FlomniInitStagesMixin: class FlomniInitStagesMixin:
def flomni_init_stages(self): def flomni_init_stages(self):
@@ -1159,18 +1160,12 @@ class Flomni(
super().__init__() super().__init__()
self.client = client self.client = client
self.device_manager = client.device_manager self.device_manager = client.device_manager
self.check_shutter = False
self.check_light_available = False
self.check_fofb = False
self._check_msgs = []
self.tomo_id = -1 self.tomo_id = -1
self.special_angles = [] self.special_angles = []
self.special_angle_repeats = 20 self.special_angle_repeats = 20
self.special_angle_tolerance = 20 self.special_angle_tolerance = 20
self._current_special_angles = [] self._current_special_angles = []
self._beam_is_okay = True self._beam_is_okay = True
self._stop_beam_check_event = None
self.beam_check_thread = None
self.corr_pos_y = [] self.corr_pos_y = []
self.corr_angle_y = [] self.corr_angle_y = []
self.corr_pos_y_2 = [] self.corr_pos_y_2 = []
@@ -1184,6 +1179,8 @@ class Flomni(
self.progress["angle"] = 0 self.progress["angle"] = 0
self.progress["tomo_type"] = 0 self.progress["tomo_type"] = 0
self.OMNYTools = OMNYTools(self.client) self.OMNYTools = OMNYTools(self.client)
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
self.tomo_id_manager = TomoIDManager()
self.align = XrayEyeAlign(self.client, self) self.align = XrayEyeAlign(self.client, self)
self.set_client(client) self.set_client(client)
@@ -1215,27 +1212,6 @@ class Flomni(
def axis_id_to_numeric(self, axis_id) -> int: def axis_id_to_numeric(self, axis_id) -> int:
return ord(axis_id.lower()) - 97 return ord(axis_id.lower()) - 97
def get_beamline_checks_enabled(self):
print(
f"Shutter: {self.check_shutter}\nFOFB: {self.check_fofb}\nLight available:"
f" {self.check_light_available}"
)
@property
def beamline_checks_enabled(self):
return {
"shutter": self.check_shutter,
"fofb": self.check_fofb,
"light available": self.check_light_available,
}
@beamline_checks_enabled.setter
def beamline_checks_enabled(self, val: bool):
self.check_shutter = val
self.check_light_available = val
self.check_fofb = val
self.get_beamline_checks_enabled()
def set_special_angles(self, angles: list, repeats: int = 20, tolerance: float = 0.5): def set_special_angles(self, angles: list, repeats: int = 20, tolerance: float = 0.5):
"""Set the special angles for a tomo """Set the special angles for a tomo
@@ -1379,6 +1355,7 @@ class Flomni(
@ptycho_reconstruct_foldername.setter @ptycho_reconstruct_foldername.setter
def ptycho_reconstruct_foldername(self, val: str): def ptycho_reconstruct_foldername(self, val: str):
self.client.set_global_var("ptycho_reconstruct_foldername", val) self.client.set_global_var("ptycho_reconstruct_foldername", val)
self.reconstructor.folder_name = val # keep reconstructor in sync
@property @property
def tomo_angle_stepsize(self): def tomo_angle_stepsize(self):
@@ -1486,7 +1463,6 @@ class Flomni(
if 0 <= angle < 180.05: if 0 <= angle < 180.05:
print(f"Starting flOMNI scan for angle {angle}") print(f"Starting flOMNI scan for angle {angle}")
while not successful: while not successful:
self._start_beam_check()
try: try:
start_scan_number = bec.queue.next_scan_number start_scan_number = bec.queue.next_scan_number
self.tomo_scan_projection(angle) self.tomo_scan_projection(angle)
@@ -1499,11 +1475,9 @@ class Flomni(
error_caught = True error_caught = True
else: else:
raise exc raise exc
#todo here was if blchk success, then setting to success true
if self._was_beam_okay() and not error_caught: successful = True
successful = True
else:
self._wait_for_beamline_checks()
end_scan_number = bec.queue.next_scan_number end_scan_number = bec.queue.next_scan_number
for scan_nr in range(start_scan_number, end_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)
@@ -1592,7 +1566,7 @@ class Flomni(
print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}") print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}")
self._print_progress() self._print_progress()
while not successful: while not successful:
self._start_beam_check() self.bl_chk._bl_chk_start()
if not self.special_angles: if not self.special_angles:
self._current_special_angles = [] self._current_special_angles = []
if self._current_special_angles: if self._current_special_angles:
@@ -1615,10 +1589,10 @@ class Flomni(
else: else:
raise exc raise exc
if self._was_beam_okay() and not error_caught: if self.bl_chk._bl_chk_stop() and not error_caught:
successful = True successful = True
else: else:
self._wait_for_beamline_checks() self.bl_chk._bl_chk_wait_until_recovered()
end_scan_number = bec.queue.next_scan_number end_scan_number = bec.queue.next_scan_number
for scan_nr in range(start_scan_number, end_scan_number): for scan_nr in range(start_scan_number, end_scan_number):
self._write_tomo_scan_number(scan_nr, angle, subtomo_number) self._write_tomo_scan_number(scan_nr, angle, subtomo_number)
@@ -1770,13 +1744,15 @@ class Flomni(
self, samplename, date, eaccount, scan_number, setup, sample_additional_info, user self, samplename, date, eaccount, scan_number, setup, sample_additional_info, user
): ):
"""Add a sample to the omny sample database. This also retrieves the tomo id.""" """Add a sample to the omny sample database. This also retrieves the tomo id."""
subprocess.run( return self.tomo_id_manager.register(
f"wget --user=omny --password=samples -q -O /tmp/currsamplesnr.txt 'https://omny.web.psi.ch/samples/newmeasurement.php?sample={samplename}&date={date}&eaccount={eaccount}&scannr={scan_number}&setup={setup}&additional={sample_additional_info}&user={user}'", sample_name=samplename,
shell=True, date=date,
eaccount=eaccount,
scan_number=scan_number,
setup=setup,
additional_info=sample_additional_info,
user=user,
) )
with open("/tmp/currsamplesnr.txt") as tomo_number_file:
tomo_number = int(tomo_number_file.read())
return tomo_number
def _at_each_angle(self, angle: float) -> None: def _at_each_angle(self, angle: float) -> None:
if "flomni_at_each_angle" in builtins.__dict__: if "flomni_at_each_angle" in builtins.__dict__:
@@ -1838,19 +1814,11 @@ class Flomni(
def tomo_reconstruct(self, base_path="~/Data10/specES1"): def tomo_reconstruct(self, base_path="~/Data10/specES1"):
"""write the tomo reconstruct file for the reconstruction queue""" """write the tomo reconstruct file for the reconstruction queue"""
bec = builtins.__dict__.get("bec") bec = builtins.__dict__.get("bec")
base_path = os.path.expanduser(base_path) self.reconstructor.write(
ptycho_queue_path = Path(os.path.join(base_path, self.ptycho_reconstruct_foldername)) scan_list=self._current_scan_list,
ptycho_queue_path.mkdir(parents=True, exist_ok=True) next_scan_number=bec.queue.next_scan_number,
base_path=base_path,
# pylint: disable=undefined-variable
last_scan_number = bec.queue.next_scan_number - 1
ptycho_queue_file = os.path.abspath(
os.path.join(ptycho_queue_path, f"scan_{last_scan_number:05d}.dat")
) )
with open(ptycho_queue_file, "w") as queue_file:
scans = " ".join([str(scan) for scan in self._current_scan_list])
queue_file.write(f"p.scan_number {scans}\n")
queue_file.write("p.check_nextscan_started 1\n")
def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None: def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None:
tomo_scan_numbers_file = os.path.expanduser( tomo_scan_numbers_file = os.path.expanduser(
@@ -2101,4 +2069,4 @@ if __name__ == "__main__":
builtins.__dict__["bec"] = bec builtins.__dict__["bec"] = bec
builtins.__dict__["umv"] = umv builtins.__dict__["umv"] = umv
flomni = Flomni(bec) flomni = Flomni(bec)
flomni.start_x_ray_eye_alignment() flomni.start_x_ray_eye_alignment()

View File

@@ -2,6 +2,7 @@ import builtins
import datetime import datetime
import fcntl import fcntl
import os import os
import socket
import subprocess import subprocess
import sys import sys
import termios import termios
@@ -10,6 +11,7 @@ import time
import tty import tty
from pathlib import Path from pathlib import Path
import epics
import numpy as np import numpy as np
from bec_lib import bec_logger from bec_lib import bec_logger
from rich import box from rich import box
@@ -26,7 +28,8 @@ if builtins.__dict__.get("bec") is not None:
def umv(*args): def umv(*args):
return scans.umv(*args, relative=False) return scans.umv(*args, relative=False)
def umvr(*args):
return scans.umv(*args, relative=True)
class OMNYToolsError(Exception): class OMNYToolsError(Exception):
pass pass
@@ -156,199 +159,6 @@ class OMNYTools:
import socket import socket
class BeamlineChecker:
"""Monitors beamline health during scans.
Runs checks in a background thread and blocks scan progress
until beam conditions are restored if they fail.
Usage:
checker = BeamlineChecker(client)
checker._bl_chk_start()
# ... run scan ...
beam_was_ok = checker._bl_chk_stop()
if not beam_was_ok:
checker._bl_chk_wait_until_recovered()
"""
def __init__(self, client):
self.client = client
self.check_shutter = True
self.check_light_available = True
self.check_fofb = True
self._beam_is_okay = True
self._stop_event = None
self._thread = None
self._local_network_warned = False
self._check_msgs = []
# ------------------------------------------------------------------
# Public control interface
# ------------------------------------------------------------------
def bl_chk_status(self):
"""Print and return the current enabled/disabled state of all checks."""
if self._is_local_network():
print("Beamline checks cannot be performed on this network (129.129.98.x) — skipping.")
return {}
status = {
"shutter": self.check_shutter,
"fofb": self.check_fofb,
"light available": self.check_light_available,
}
print(
f"Shutter: {self.check_shutter}\n"
f"FOFB: {self.check_fofb}\n"
f"Light available: {self.check_light_available}"
)
return status
def bl_chk_enable_all(self):
"""Enable all beamline checks."""
self.check_shutter = True
self.check_light_available = True
self.check_fofb = True
self.bl_chk_status()
def bl_chk_disable_all(self):
"""Disable all beamline checks."""
self.check_shutter = False
self.check_light_available = False
self.check_fofb = False
self.bl_chk_status()
def bl_chk_enable_shutter(self):
"""Enable the shutter check."""
self.check_shutter = True
self.bl_chk_status()
def bl_chk_disable_shutter(self):
"""Disable the shutter check."""
self.check_shutter = False
self.bl_chk_status()
def bl_chk_enable_fofb(self):
"""Enable the fast orbit feedback check."""
self.check_fofb = True
self.bl_chk_status()
def bl_chk_disable_fofb(self):
"""Disable the fast orbit feedback check."""
self.check_fofb = False
self.bl_chk_status()
def bl_chk_enable_light(self):
"""Enable the light available check."""
self.check_light_available = True
self.bl_chk_status()
def bl_chk_disable_light(self):
"""Disable the light available check."""
self.check_light_available = False
self.bl_chk_status()
def _bl_chk_start(self):
"""Start the background beam check thread."""
self._beam_is_okay = True
self._stop_event = threading.Event()
self._thread = threading.Thread(target=self._poll, daemon=True)
self._thread.start()
def _bl_chk_stop(self) -> bool:
"""Stop the background thread and return whether beam was okay throughout."""
self._stop_event.set()
self._thread.join()
return self._beam_is_okay
def _bl_chk_wait_until_recovered(self):
"""Block until all beamline checks pass again, logging to SciLog."""
self._log_failure_to_scilog()
while True:
self._beam_is_okay = True
self._check_msgs = self._run_checks()
if self._beam_is_okay:
break
self._print_msgs()
time.sleep(1)
self._log_recovery_to_scilog()
# ------------------------------------------------------------------
# Internal
# ------------------------------------------------------------------
def _is_local_network(self) -> bool:
"""Return True if running on the 129.129.98.x subnet."""
try:
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
return ip.startswith("129.129.98.")
except Exception:
return False
def _run_checks(self) -> list:
if self._is_local_network():
if not self._local_network_warned:
print("Beamline checks cannot be performed on this network (129.129.98.x) — skipping.")
self._local_network_warned = True
return []
msgs = []
dev = builtins.__dict__.get("dev")
try:
if self.check_shutter:
val = dev.x12sa_es1_shutter_status.read(cached=True)
if val["value"].lower() != "open":
self._beam_is_okay = False
msgs.append("Check beam failed: Shutter is closed.")
if self.check_light_available:
val = dev.sls_machine_status.read(cached=True)
if val["value"] not in ["Light Available", "Light-Available"]:
self._beam_is_okay = False
msgs.append("Check beam failed: Light not available.")
if self.check_fofb:
val = dev.sls_fast_orbit_feedback.read(cached=True)
if val["value"] != "running":
self._beam_is_okay = False
msgs.append("Check beam failed: Fast orbit feedback is not running.")
except Exception:
logger.warning("Failed to check beam.")
return msgs
def _poll(self):
while not self._stop_event.is_set():
self._check_msgs = self._run_checks()
if not self._beam_is_okay:
self._stop_event.set()
time.sleep(1)
def _print_msgs(self):
for msg in self._check_msgs:
logger.warning(msg)
def _log_failure_to_scilog(self):
self._print_msgs()
try:
bec = builtins.__dict__.get("bec")
msg = bec.logbook.LogbookMessage()
msg.add_text(
"<p><mark class='pen-red'><strong>Beamline checks failed at"
f" {str(datetime.datetime.now())}: {''.join(self._check_msgs)}</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
self.client.logbook.send_logbook_message(msg)
except Exception:
logger.warning("Failed to send beam failure update to SciLog.")
def _log_recovery_to_scilog(self):
try:
bec = builtins.__dict__.get("bec")
msg = bec.logbook.LogbookMessage()
msg.add_text(
"<p><mark class='pen-red'><strong>Operation resumed at"
f" {str(datetime.datetime.now())}.</strong></mark></p>"
).add_tag(["BEC", "beam_check"])
self.client.logbook.send_logbook_message(msg)
except Exception:
logger.warning("Failed to send beam recovery update to SciLog.")
class PtychoReconstructor: class PtychoReconstructor:
"""Writes ptychography reconstruction queue files after each scan projection. """Writes ptychography reconstruction queue files after each scan projection.
@@ -389,6 +199,10 @@ class PtychoReconstructor:
name the queue file. name the queue file.
base_path (str): Root path under which the queue folder lives. base_path (str): Root path under which the queue folder lives.
""" """
if not self._accounts_match():
logger.warning("Active BEC account does not match system user — skipping queue file write.")
return
base_path = os.path.expanduser(base_path) base_path = os.path.expanduser(base_path)
queue_path = Path(os.path.join(base_path, self.folder_name)) queue_path = Path(os.path.join(base_path, self.folder_name))
queue_path.mkdir(parents=True, exist_ok=True) queue_path.mkdir(parents=True, exist_ok=True)