Compare commits
1 Commits
lamni_comm
...
fix/smarac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae0ad918a3 |
@@ -12,6 +12,7 @@ from bec_lib.pdf_writer import PDFWriter
|
||||
from typeguard import typechecked
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
|
||||
BeamlineChecker,
|
||||
OMNYTools,
|
||||
PtychoReconstructor,
|
||||
TomoIDManager,
|
||||
@@ -39,6 +40,7 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
|
||||
self.init = LaMNIInitStages(client)
|
||||
|
||||
# Extracted collaborators
|
||||
self.bl_chk = BeamlineChecker(client)
|
||||
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
|
||||
self.tomo_id_manager = TomoIDManager()
|
||||
self.OMNYTools = OMNYTools(self.client)
|
||||
@@ -59,6 +61,20 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
|
||||
self.progress["total_projections"] = 1
|
||||
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
|
||||
@@ -536,6 +552,7 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
|
||||
print(f"Starting LamNI scan for angle {angle} in subtomo {subtomo_number}")
|
||||
self._print_progress()
|
||||
while not successful:
|
||||
self.bl_chk.start()
|
||||
if not self.special_angles:
|
||||
self._current_special_angles = []
|
||||
if self._current_special_angles:
|
||||
@@ -562,9 +579,10 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
|
||||
for scan_nr in range(start_scan_number, end_scan_number):
|
||||
self._write_tomo_scan_number(scan_nr, angle, subtomo_number)
|
||||
|
||||
#todo here bl chk, if ok then successfull true
|
||||
successful = True
|
||||
|
||||
if self.bl_chk.stop() and not error_caught:
|
||||
successful = True
|
||||
else:
|
||||
self.bl_chk.wait_until_recovered()
|
||||
|
||||
def _golden(self, ii, howmany_sorted, maxangle=360, reverse=False):
|
||||
"""Return the ii-th golden ratio angle within sorted bunches and its subtomo number."""
|
||||
|
||||
@@ -37,6 +37,92 @@ class cSAXSInitSmaractStages:
|
||||
# ------------------------------
|
||||
# Internal helpers (runtime-based)
|
||||
# ------------------------------
|
||||
|
||||
def _ensure_all_session_devices_enabled(self, selection: set | None = None, try_enable: bool = True):
|
||||
"""
|
||||
Ensure all session devices (or a selection) that define 'bl_smar_stage' are enabled.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
selection : set | None
|
||||
If provided, only devices in this set are considered.
|
||||
try_enable : bool
|
||||
If True, attempt to set device.enabled = True for devices that expose 'enabled' and are False.
|
||||
If False, only report status without changing it.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
{
|
||||
"enabled_now": [device_names enabled by this call],
|
||||
"already_enabled": [device_names already enabled or without 'enabled' attr],
|
||||
"failed": [device_names that could not be enabled],
|
||||
"inaccessible": [device_names not accessible]
|
||||
}
|
||||
"""
|
||||
enabled_now = []
|
||||
already_enabled = []
|
||||
failed = []
|
||||
inaccessible = []
|
||||
|
||||
# Build axis map to restrict to SmarAct-based devices (same logic as other helpers)
|
||||
axis_map = self._build_session_axis_map(selection=selection)
|
||||
|
||||
for dev_name in sorted(axis_map.keys()):
|
||||
try:
|
||||
d = self._get_device_object(dev_name)
|
||||
if d is None:
|
||||
inaccessible.append(dev_name)
|
||||
logger.warning(f"[cSAXS] Device {dev_name} not accessible.")
|
||||
continue
|
||||
|
||||
# If device has no 'enabled' attribute, treat as already enabled/usable
|
||||
if not hasattr(d, "enabled"):
|
||||
already_enabled.append(dev_name)
|
||||
continue
|
||||
|
||||
# If already enabled
|
||||
try:
|
||||
if getattr(d, "enabled"):
|
||||
already_enabled.append(dev_name)
|
||||
continue
|
||||
except Exception:
|
||||
# If reading enabled fails, treat as inaccessible for safety
|
||||
failed.append(dev_name)
|
||||
logger.warning(f"[cSAXS] Could not read 'enabled' for {dev_name}.")
|
||||
continue
|
||||
|
||||
# Device exists and is disabled
|
||||
if try_enable:
|
||||
try:
|
||||
logger.info(f"[cSAXS] Enabling device {dev_name} (was disabled).")
|
||||
setattr(d, "enabled", True)
|
||||
# small delay to let device initialize if needed
|
||||
time.sleep(0.05)
|
||||
if getattr(d, "enabled"):
|
||||
enabled_now.append(dev_name)
|
||||
logger.info(f"[cSAXS] Device {dev_name} enabled.")
|
||||
else:
|
||||
failed.append(dev_name)
|
||||
logger.warning(f"[cSAXS] Device {dev_name} still disabled after enabling attempt.")
|
||||
except Exception as exc:
|
||||
failed.append(dev_name)
|
||||
logger.error(f"[cSAXS] Failed to enable {dev_name}: {exc}")
|
||||
else:
|
||||
# Not trying to enable, just report
|
||||
failed.append(dev_name)
|
||||
except Exception as exc:
|
||||
failed.append(dev_name)
|
||||
logger.error(f"[cSAXS] _ensure_all_session_devices_enabled error for {dev_name}: {exc}")
|
||||
|
||||
return {
|
||||
"enabled_now": enabled_now,
|
||||
"already_enabled": already_enabled,
|
||||
"failed": failed,
|
||||
"inaccessible": inaccessible,
|
||||
}
|
||||
|
||||
|
||||
def _yesno(self, question: str, default: str = "y") -> bool:
|
||||
"""
|
||||
Use OMNYTools.yesno if available; otherwise default to 'yes' (or fallback to input()).
|
||||
@@ -107,6 +193,7 @@ class cSAXSInitSmaractStages:
|
||||
# ------------------------------
|
||||
# Public API
|
||||
# ------------------------------
|
||||
|
||||
def smaract_reference_stages(self, force: bool = False, devices_to_reference=None):
|
||||
"""
|
||||
Reference SmarAct stages using runtime discovery.
|
||||
@@ -167,6 +254,19 @@ class cSAXSInitSmaractStages:
|
||||
devices_to_reference = [devices_to_reference]
|
||||
selection = set(devices_to_reference) if devices_to_reference else None
|
||||
|
||||
# First: ensure all relevant devices are enabled before attempting referencing
|
||||
enable_report = self._ensure_all_session_devices_enabled(selection=selection, try_enable=True)
|
||||
if enable_report["failed"]:
|
||||
logger.warning(
|
||||
"[cSAXS] Some devices could not be enabled before referencing: "
|
||||
+ ", ".join(sorted(enable_report["failed"]))
|
||||
)
|
||||
if enable_report["inaccessible"]:
|
||||
logger.warning(
|
||||
"[cSAXS] Some devices were inaccessible before referencing: "
|
||||
+ ", ".join(sorted(enable_report["inaccessible"]))
|
||||
)
|
||||
|
||||
# Build axis map for selected devices (or all devices present)
|
||||
axis_map = self._build_session_axis_map(selection=selection)
|
||||
if selection:
|
||||
@@ -174,7 +274,6 @@ class cSAXSInitSmaractStages:
|
||||
if unknown:
|
||||
print(f"Unknown devices requested or missing 'bl_smar_stage' (ignored): {unknown}")
|
||||
|
||||
|
||||
newly_referenced = []
|
||||
already_referenced = []
|
||||
failed = []
|
||||
@@ -191,6 +290,17 @@ class cSAXSInitSmaractStages:
|
||||
failed.append(dev_name)
|
||||
continue
|
||||
|
||||
# If device exposes 'enabled' and is False, skip (we already tried enabling above)
|
||||
try:
|
||||
if hasattr(d, "enabled") and not getattr(d, "enabled"):
|
||||
print(f"{dev_name}: device disabled, skipping.")
|
||||
failed.append(dev_name)
|
||||
continue
|
||||
except Exception:
|
||||
print(f"{dev_name}: could not read enabled state, skipping.")
|
||||
failed.append(dev_name)
|
||||
continue
|
||||
|
||||
try:
|
||||
is_ref = d.controller.axis_is_referenced(ch)
|
||||
|
||||
@@ -246,7 +356,17 @@ class cSAXSInitSmaractStages:
|
||||
def smaract_check_all_referenced(self):
|
||||
"""
|
||||
Check reference state for all SmarAct devices that define 'bl_smar_stage'.
|
||||
This now enables all relevant devices first (attempt), then performs the checks.
|
||||
"""
|
||||
# Attempt to enable all relevant devices first (do not force enabling if you prefer)
|
||||
enable_report = self._ensure_all_session_devices_enabled(selection=None, try_enable=True)
|
||||
if enable_report["enabled_now"]:
|
||||
print("Now enabled devices which were disabled before: " + ", ".join(sorted(enable_report["enabled_now"])))
|
||||
if enable_report["failed"]:
|
||||
print("Could not enable: " + ", ".join(sorted(enable_report["failed"])))
|
||||
if enable_report["inaccessible"]:
|
||||
print("Inaccessible: " + ", ".join(sorted(enable_report["inaccessible"])))
|
||||
|
||||
axis_map = self._build_session_axis_map(selection=None)
|
||||
for dev_name in sorted(axis_map.keys()):
|
||||
ch = axis_map[dev_name]
|
||||
@@ -254,6 +374,16 @@ class cSAXSInitSmaractStages:
|
||||
if d is None:
|
||||
print(f"{dev_name}: device not accessible or unsupported.")
|
||||
continue
|
||||
|
||||
# Skip devices that expose 'enabled' and are False
|
||||
try:
|
||||
if hasattr(d, "enabled") and not getattr(d, "enabled"):
|
||||
print(f"{dev_name} (axis {ch}) is disabled; skipping reference check.")
|
||||
continue
|
||||
except Exception:
|
||||
print(f"{dev_name} (axis {ch}) enabled-state unknown; skipping.")
|
||||
continue
|
||||
|
||||
try:
|
||||
if d.controller.axis_is_referenced(ch):
|
||||
print(f"{dev_name} (axis {ch}) is referenced.")
|
||||
@@ -262,6 +392,7 @@ class cSAXSInitSmaractStages:
|
||||
except Exception as e:
|
||||
print(f"Error checking {dev_name} (axis {ch}): {e}")
|
||||
|
||||
|
||||
def smaract_components_to_initial_position(self, devices_to_move=None):
|
||||
"""
|
||||
Move selected (or all) SmarAct-based components to their configured init_position.
|
||||
|
||||
@@ -15,11 +15,7 @@ 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.omny.omny_general_tools import (
|
||||
OMNYTools,
|
||||
PtychoReconstructor,
|
||||
TomoIDManager,
|
||||
)
|
||||
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -69,6 +65,9 @@ class FlomniError(Exception):
|
||||
# print("Please expicitely confirm y or n.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class FlomniInitStagesMixin:
|
||||
|
||||
def flomni_init_stages(self):
|
||||
@@ -1160,12 +1159,18 @@ class Flomni(
|
||||
super().__init__()
|
||||
self.client = client
|
||||
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.special_angles = []
|
||||
self.special_angle_repeats = 20
|
||||
self.special_angle_tolerance = 20
|
||||
self._current_special_angles = []
|
||||
self._beam_is_okay = True
|
||||
self._stop_beam_check_event = None
|
||||
self.beam_check_thread = None
|
||||
self.corr_pos_y = []
|
||||
self.corr_angle_y = []
|
||||
self.corr_pos_y_2 = []
|
||||
@@ -1179,8 +1184,6 @@ class Flomni(
|
||||
self.progress["angle"] = 0
|
||||
self.progress["tomo_type"] = 0
|
||||
self.OMNYTools = OMNYTools(self.client)
|
||||
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
|
||||
self.tomo_id_manager = TomoIDManager()
|
||||
self.align = XrayEyeAlign(self.client, self)
|
||||
self.set_client(client)
|
||||
|
||||
@@ -1212,6 +1215,27 @@ class Flomni(
|
||||
def axis_id_to_numeric(self, axis_id) -> int:
|
||||
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):
|
||||
"""Set the special angles for a tomo
|
||||
|
||||
@@ -1355,7 +1379,6 @@ class Flomni(
|
||||
@ptycho_reconstruct_foldername.setter
|
||||
def ptycho_reconstruct_foldername(self, val: str):
|
||||
self.client.set_global_var("ptycho_reconstruct_foldername", val)
|
||||
self.reconstructor.folder_name = val # keep reconstructor in sync
|
||||
|
||||
@property
|
||||
def tomo_angle_stepsize(self):
|
||||
@@ -1463,6 +1486,7 @@ class Flomni(
|
||||
if 0 <= angle < 180.05:
|
||||
print(f"Starting flOMNI scan for angle {angle}")
|
||||
while not successful:
|
||||
self._start_beam_check()
|
||||
try:
|
||||
start_scan_number = bec.queue.next_scan_number
|
||||
self.tomo_scan_projection(angle)
|
||||
@@ -1475,9 +1499,11 @@ class Flomni(
|
||||
error_caught = True
|
||||
else:
|
||||
raise exc
|
||||
#todo here was if blchk success, then setting to success true
|
||||
successful = True
|
||||
|
||||
|
||||
if self._was_beam_okay() and not error_caught:
|
||||
successful = True
|
||||
else:
|
||||
self._wait_for_beamline_checks()
|
||||
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)
|
||||
@@ -1566,7 +1592,7 @@ class Flomni(
|
||||
print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}")
|
||||
self._print_progress()
|
||||
while not successful:
|
||||
self.bl_chk._bl_chk_start()
|
||||
self._start_beam_check()
|
||||
if not self.special_angles:
|
||||
self._current_special_angles = []
|
||||
if self._current_special_angles:
|
||||
@@ -1589,10 +1615,10 @@ class Flomni(
|
||||
else:
|
||||
raise exc
|
||||
|
||||
if self.bl_chk._bl_chk_stop() and not error_caught:
|
||||
if self._was_beam_okay() and not error_caught:
|
||||
successful = True
|
||||
else:
|
||||
self.bl_chk._bl_chk_wait_until_recovered()
|
||||
self._wait_for_beamline_checks()
|
||||
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)
|
||||
@@ -1744,15 +1770,13 @@ class Flomni(
|
||||
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."""
|
||||
return self.tomo_id_manager.register(
|
||||
sample_name=samplename,
|
||||
date=date,
|
||||
eaccount=eaccount,
|
||||
scan_number=scan_number,
|
||||
setup=setup,
|
||||
additional_info=sample_additional_info,
|
||||
user=user,
|
||||
subprocess.run(
|
||||
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}'",
|
||||
shell=True,
|
||||
)
|
||||
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:
|
||||
if "flomni_at_each_angle" in builtins.__dict__:
|
||||
@@ -1814,11 +1838,19 @@ class Flomni(
|
||||
def tomo_reconstruct(self, base_path="~/Data10/specES1"):
|
||||
"""write the tomo reconstruct file for the reconstruction queue"""
|
||||
bec = builtins.__dict__.get("bec")
|
||||
self.reconstructor.write(
|
||||
scan_list=self._current_scan_list,
|
||||
next_scan_number=bec.queue.next_scan_number,
|
||||
base_path=base_path,
|
||||
base_path = os.path.expanduser(base_path)
|
||||
ptycho_queue_path = Path(os.path.join(base_path, self.ptycho_reconstruct_foldername))
|
||||
ptycho_queue_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 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:
|
||||
tomo_scan_numbers_file = os.path.expanduser(
|
||||
@@ -2069,4 +2101,4 @@ if __name__ == "__main__":
|
||||
builtins.__dict__["bec"] = bec
|
||||
builtins.__dict__["umv"] = umv
|
||||
flomni = Flomni(bec)
|
||||
flomni.start_x_ray_eye_alignment()
|
||||
flomni.start_x_ray_eye_alignment()
|
||||
|
||||
@@ -2,7 +2,6 @@ import builtins
|
||||
import datetime
|
||||
import fcntl
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
@@ -11,7 +10,6 @@ import time
|
||||
import tty
|
||||
from pathlib import Path
|
||||
|
||||
import epics
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from rich import box
|
||||
@@ -28,8 +26,7 @@ if builtins.__dict__.get("bec") is not None:
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
def umvr(*args):
|
||||
return scans.umv(*args, relative=True)
|
||||
|
||||
|
||||
class OMNYToolsError(Exception):
|
||||
pass
|
||||
@@ -159,6 +156,199 @@ class OMNYTools:
|
||||
|
||||
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:
|
||||
"""Writes ptychography reconstruction queue files after each scan projection.
|
||||
|
||||
@@ -199,10 +389,6 @@ class PtychoReconstructor:
|
||||
name the queue file.
|
||||
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)
|
||||
queue_path = Path(os.path.join(base_path, self.folder_name))
|
||||
queue_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user