Compare commits

..

31 Commits

Author SHA1 Message Date
x12sa
d9fc3094b6 fixed width for logo in scilog
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m59s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m57s
2026-03-25 13:39:07 +01:00
x12sa
88df4781ec tags added
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m54s
2026-03-24 15:36:25 +01:00
x12sa
3b474c89c8 removed write subtomo to scilog 2026-03-24 15:34:36 +01:00
x12sa
68cc13e1d3 alignment scans to scilog 2026-03-24 15:33:14 +01:00
x12sa
700f3f9bb9 scilog tag added 2026-03-24 15:27:25 +01:00
x12sa
15a4d45f68 moved tomo_reconstruct to tomo_scan_projection
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m59s
2026-03-24 15:00:17 +01:00
x12sa
7c7f877d78 new logos for logbook 2026-03-24 14:59:50 +01:00
x12sa
5d61d756c9 logo and scilog newline fixed
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m56s
2026-03-23 17:00:28 +01:00
x12sa
b37ae3ef57 wip message to scilog when tomo starts
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m55s
2026-03-23 16:29:30 +01:00
x12sa
76ed858e5c added heartbeat, start and remaining time to progress
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m57s
2026-03-23 15:58:54 +01:00
x12sa
a0555def4d changed progress dict to global variable
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m56s
2026-03-23 15:48:17 +01:00
x12sa
c1ad2fc4c3 pdf status report fixes
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m57s
2026-03-23 12:38:37 +01:00
x12sa
9eee4ee1f7 minor fixes during testing
All checks were successful
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m55s
2026-03-19 11:17:41 +01:00
c97b00cc8c fix: flomni async readout
All checks were successful
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m57s
2026-03-19 11:13:26 +01:00
d6a4fd37fc fix(mcs): fix _progress_udpate
Some checks failed
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Has been cancelled
2026-03-19 11:11:54 +01:00
6d4c9d90fc fix(mcs): omit_mca_callbacks if stop is called. 2026-03-19 11:11:54 +01:00
87163cc3f1 docs: run build on py 3-12
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m57s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 4s
CI for csaxs_bec / test (push) Successful in 1m56s
2026-03-18 14:35:42 +01:00
7c17a3ae40 ci: add rtd workflow
Some checks failed
CI for csaxs_bec / test (push) Has been cancelled
2026-03-18 14:34:52 +01:00
663d22fff4 fix(flomni): indentation error in if statement
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m57s
CI for csaxs_bec / test (push) Successful in 1m55s
2026-03-18 14:02:44 +01:00
3ca29dd0dd fix(x_ray_eye): added 20s rpc timeout to most rpc calls 2026-03-18 13:57:08 +01:00
0ac37f538b fix(gui_tools): flomni gui tools timeout optimization 2026-03-18 13:57:06 +01:00
x12sa
43ae732e34 added check for optics in etc to only move if not already there
All checks were successful
CI for csaxs_bec / test (pull_request) Successful in 1m59s
CI for csaxs_bec / test (push) Successful in 1m56s
2026-03-17 16:07:17 +01:00
x12sa
583b15b772 fix number projections when starting with start_angle, correction in progress reporting
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m58s
2026-03-17 15:29:23 +01:00
x12sa
4967474271 removed fsh commands
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m57s
2026-03-17 14:48:05 +01:00
x12sa
8d6a2b0f5c writing to spec log file removed (was for old webpage) 2026-03-17 14:46:09 +01:00
x12sa
dcde0e783e fix angle reversal at start with start angle pecified
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m59s
2026-03-17 14:42:52 +01:00
x12sa
58cd6bdaf7 fixes run 1
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m58s
2026-03-17 14:14:14 +01:00
x12sa
68320e1944 check tracker signal before rt move, which is otherwise stuck forever.
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m54s
2026-03-16 17:55:37 +01:00
x12sa
5ff32decc4 gui delay startup, flomni loading params fix 2026-03-16 17:54:56 +01:00
x12sa
2c31d79f1b loading of fit params from gui and from files
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m56s
2026-03-16 12:39:35 +01:00
x12sa
3ce6bbc134 check attr exist in gui remove all docs
All checks were successful
CI for csaxs_bec / test (push) Successful in 1m55s
2026-03-16 12:10:29 +01:00
24 changed files with 938 additions and 433 deletions

View File

@@ -0,0 +1,21 @@
name: Read the Docs Deploy Trigger
on:
push:
branches:
- main
workflow_dispatch:
jobs:
trigger-rtd-webhook:
runs-on: ubuntu-latest
steps:
- name: Trigger Read the Docs webhook
env:
RTD_TOKEN: ${{ secrets.RTD_TOKEN }}
run: |
curl --fail --show-error --silent \
-X POST \
-d "branches=${{ github.ref_name }}" \
-d "token=${RTD_TOKEN}" \
"https://readthedocs.org/api/v2/webhook/sls-csaxs/270162/"

View File

@@ -8,15 +8,14 @@ version: 2
build:
os: ubuntu-20.04
tools:
python: "3.10"
python: "3.12"
jobs:
pre_install:
- pip install .
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: docs/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
# formats:
@@ -24,6 +23,5 @@ sphinx:
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
install:
- requirements: docs/requirements.txt

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -356,18 +356,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
# Logging helpers
# ------------------------------------------------------------------
def write_to_spec_log(self, content):
try:
with open(
os.path.expanduser(
"~/Data10/specES1/log-files/specES1_started_2022_11_30_1313.log"
),
"a",
) as log_file:
log_file.write(content)
except Exception:
logger.warning("Failed to write to spec log file (omny web page).")
def write_to_scilog(self, content, tags: list = None):
try:
if tags is not None:
@@ -439,7 +427,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
f"{str(datetime.datetime.now())}: LamNI scan projection at angle {angle},"
f" scan number {bec.queue.next_scan_number}.\n"
)
self.write_to_spec_log(log_message)
corridor_size = self.corridor_size if self.corridor_size > 0 else None
scans.lamni_fermat_scan(
fov_size=[self.lamni_piezo_range_x, self.lamni_piezo_range_y],
@@ -779,7 +766,7 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
user_input = input("Are these parameters correctly set for your scan? ")
if user_input == "y":
print("good then")
print("OK. continue.")
return
self.tomo_countingtime = self._get_val("<ctime> s", self.tomo_countingtime, float)

View File

@@ -5,7 +5,7 @@ from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
dev = builtins.__dict__.get("dev")
@@ -172,7 +172,7 @@ class LamNIOpticsMixin:
def leye_out(self):
self.loptics_in()
fshclose()
dev.omnyfsh.fshopen()
leyey_out = self._get_user_param_safe("leyey", "out")
umv(dev.leyey, leyey_out)

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
import time
import numpy as np
from rich import box
from rich.console import Console
from rich.table import Table
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put
class FlomniOpticsMixin:
@@ -16,12 +16,18 @@ class FlomniOpticsMixin:
return param.get(var)
def feye_out(self):
fshclose()
dev.omnyfsh.fshclose()
self.foptics_in()
self.flomnigui_show_xeyealign()
self.xrayeye_update_frame()
if self.OMNYTools.yesno("Did the direct beam on the xray eye disappear?"):
print("excellent.")
else:
print("Aborting. With visible parts of the direct beam on the xray eye, it cannot be removed.")
return
feyex_out = self._get_user_param_safe("feyex", "out")
umv(dev.feyex, feyex_out)
epics_put("XOMNYI-XEYE-ACQ:0", 2)
# move rotation stage to zero to avoid problems with wires
umv(dev.fsamroy, 0)
# umv(dev.fttrx1, 9.2)
@@ -32,16 +38,37 @@ class FlomniOpticsMixin:
feyex_in = self._get_user_param_safe("feyex", "in")
feyey_in = self._get_user_param_safe("feyey", "in")
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
#self.align.update_frame()
current_feyex = dev.feyex.readback.get()
current_feyey = dev.feyey.readback.get()
# check if both are close enough (within 0.01)
if np.isclose(current_feyex, feyex_in, atol=0.01) and np.isclose(current_feyey, feyey_in, atol=0.01):
# both already in position → do nothing
pass
else:
# move both axes to the desired "in" positions
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
def _ffzp_in(self):
foptx_in = self._get_user_param_safe("foptx", "in")
fopty_in = self._get_user_param_safe("fopty", "in")
umv(dev.foptx, foptx_in)
umv(
dev.fopty, fopty_in
) # for 7.2567 keV and 150 mu, 60 nm fzp, loptz 83.6000 for propagation 1.4 mm
current_foptx = dev.foptx.readback.get()
current_fopty = dev.fopty.readback.get()
tol = 0.003
# if either axis is outside the tolerance → move both
need_move_optics = (
not np.isclose(current_foptx, foptx_in, atol=tol) or
not np.isclose(current_fopty, fopty_in, atol=tol)
)
if need_move_optics:
umv(dev.foptx, foptx_in, dev.fopty, fopty_in) # for 7.2567 keV and 150 mu, 60 nm fzp, loptz 83.6000 for propagation 1.4 mm
else:
print("FZP is already at the in position.")
def ffzp_in(self):
"""
@@ -84,19 +111,85 @@ class FlomniOpticsMixin:
# umv(dev.losax, -1.4850, dev.losay, -0.1930)
# umv(dev.losaz, 1.0000)
# 7.2, 150
fosax_in = self._get_user_param_safe("fosax", "in")
fosay_in = self._get_user_param_safe("fosay", "in")
fosaz_in = self._get_user_param_safe("fosaz", "in")
# tighten limits
dev.fosax.limits = [fosax_in - 0.1, fosax_in + 0.1]
dev.fosay.limits = [fosay_in - 0.1, fosay_in + 0.1]
dev.fosaz.limits = [fosaz_in - 0.1, fosaz_in + 0.1]
umv(dev.fosax, fosax_in, dev.fosay, fosay_in)
umv(dev.fosaz, fosaz_in)
current_fosax = dev.fosax.readback.get()
current_fosay = dev.fosay.readback.get()
current_fosaz = dev.fosaz.readback.get()
# tolerance
tol = 0.003
need_move_osa = (
not np.isclose(current_fosax, fosax_in, atol=tol) or
not np.isclose(current_fosay, fosay_in, atol=tol) or
not np.isclose(current_fosaz, fosaz_in, atol=tol)
)
if need_move_osa:
umv(dev.fosax, fosax_in, dev.fosay, fosay_in)
umv(dev.fosaz, fosaz_in)
else:
print("OSA is already at the IN position.")
# 11 kev
# umv(dev.losax, -1.161000, dev.losay, -0.196)
# umv(dev.losaz, 1.0000)
def _check_eye_out_and_optics_in(self, tol=0.003):
# --- expected IN positions ---
foptx_in = self._get_user_param_safe("foptx", "in")
fopty_in = self._get_user_param_safe("fopty", "in")
foptz_in = self._get_user_param_safe("foptz", "in")
# --- expected OUT condition for the X-ray eye ---
# eye is OUT when it is *not within tolerance* of its IN position
feyex_out = self._get_user_param_safe("feyex", "out")
# --- current positions ---
cx_feyex = dev.feyex.readback.get()
cx_foptx = dev.foptx.readback.get()
cx_fopty = dev.fopty.readback.get()
cx_foptz = dev.foptz.readback.get()
# --- check eye OUT ---
eye_out = (
np.isclose(cx_feyex, feyex_out, atol=tol)
)
# --- check optics IN ---
optics_in = (
np.isclose(cx_foptx, foptx_in, atol=tol) and
np.isclose(cx_fopty, fopty_in, atol=tol) and
np.isclose(cx_foptz, foptz_in, atol=tol)
)
fosax_in = self._get_user_param_safe("fosax", "in")
fosay_in = self._get_user_param_safe("fosay", "in")
fosaz_in = self._get_user_param_safe("fosaz", "in")
cx_fosax = dev.fosax.readback.get()
cx_fosay = dev.fosay.readback.get()
cx_fosaz = dev.fosaz.readback.get()
osa_in = (
np.isclose(cx_fosax, fosax_in, atol=tol) and
np.isclose(cx_fosay, fosay_in, atol=tol) and
np.isclose(cx_fosaz, fosaz_in, atol=tol)
)
return eye_out and optics_in and osa_in
def fosa_out(self):
self.ensure_fheater_up()
curtain_is_triggered = dev.foptz.controller.fosaz_light_curtain_is_triggered()

View File

@@ -1,4 +1,5 @@
import builtins
import time
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
@@ -17,10 +18,17 @@ class flomniGuiToolsError(Exception):
class flomniGuiTools:
GUI_RPC_TIMEOUT = 20
def __init__(self):
self.text_box = None
self.progressbar = None
self.flomni_window = None
self.xeyegui = None
self.pdf_viewer = None
self.idle_text_box = None
self.camera_gripper_image = None
self.camera_overview_image = None
def set_client(self, client):
self.client = client
@@ -28,9 +36,11 @@ class flomniGuiTools:
def flomnigui_show_gui(self):
if "flomni" in self.gui.windows:
self.flomni_window = self.gui.windows["flomni"]
self.gui.flomni.raise_window()
else:
self.gui.new("flomni")
self.flomni_window = self.gui.new("flomni", timeout=self.GUI_RPC_TIMEOUT)
time.sleep(1)
def flomnigui_stop_gui(self):
self.gui.flomni.hide()
@@ -40,9 +50,11 @@ class flomniGuiTools:
def flomnigui_show_xeyealign(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("xeyegui"):
if self._flomnigui_is_missing("xeyegui"):
self.flomnigui_remove_all_docks()
self.xeyegui = self.gui.flomni.new("XRayEye", object_name="xrayeye")
self.xeyegui = self.gui.flomni.new(
"XRayEye", object_name="xrayeye", timeout=self.GUI_RPC_TIMEOUT
)
# start live
if not dev.cam_xeye.live_mode_enabled.get():
dev.cam_xeye.live_mode_enabled.put(True)
@@ -50,9 +62,11 @@ class flomniGuiTools:
def flomnigui_show_xeyealign_fittab(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("xeyegui"):
if self._flomnigui_is_missing("xeyegui"):
self.flomnigui_remove_all_docks()
self.xeyegui = self.gui.flomni.new("XRayEye")
self.xeyegui = self.gui.flomni.new(
"XRayEye", object_name="xrayeye", timeout=self.GUI_RPC_TIMEOUT
)
self.xeyegui.switch_tab("fit")
def _flomnigui_check_attribute_not_exists(self, attribute_name):
@@ -68,31 +82,39 @@ class flomniGuiTools:
return False
return True
def _flomnigui_is_missing(self, attribute_name):
widget = getattr(self, attribute_name, None)
if widget is None:
return True
if hasattr(widget, "_is_deleted") and widget._is_deleted():
return True
return False
def flomnigui_show_cameras(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists(
"cam_flomni_gripper"
) or self._flomnigui_check_attribute_not_exists("cam_flomni_overview"):
self.flomnigui_remove_all_docks()
camera_gripper_image = self.gui.flomni.new("Image")
self.camera_gripper_image = self.gui.flomni.new("Image")
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
camera_gripper_image.image(device="cam_flomni_gripper", signal="preview")
camera_gripper_image.lock_aspect_ratio = True
camera_gripper_image.enable_fps_monitor = True
camera_gripper_image.enable_toolbar = False
camera_gripper_image.outer_axes = False
camera_gripper_image.inner_axes = False
self.camera_gripper_image.image(device="cam_flomni_gripper", signal="preview")
self.camera_gripper_image.lock_aspect_ratio = True
self.camera_gripper_image.enable_fps_monitor = True
self.camera_gripper_image.enable_toolbar = False
self.camera_gripper_image.outer_axes = False
self.camera_gripper_image.inner_axes = False
dev.cam_flomni_gripper.start_live_mode()
else:
print("Cannot open camera_gripper. Device does not exist.")
camera_overview_image = self.gui.flomni.new("Image")
self.camera_overview_image = self.gui.flomni.new("Image")
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
camera_overview_image.image(device="cam_flomni_overview", signal="preview")
camera_overview_image.lock_aspect_ratio = True
camera_overview_image.enable_fps_monitor = True
camera_overview_image.enable_toolbar = False
camera_overview_image.outer_axes = False
camera_overview_image.inner_axes = False
self.camera_overview_image.image(device="cam_flomni_overview", signal="preview")
self.camera_overview_image.lock_aspect_ratio = True
self.camera_overview_image.enable_fps_monitor = True
self.camera_overview_image.enable_toolbar = False
self.camera_overview_image.outer_axes = False
self.camera_overview_image.inner_axes = False
dev.cam_flomni_overview.start_live_mode()
else:
print("Cannot open camera_overview. Device does not exist.")
@@ -101,15 +123,21 @@ class flomniGuiTools:
# dev.cam_flomni_overview.stop_live_mode()
# dev.cam_flomni_gripper.stop_live_mode()
# dev.cam_xeye.live_mode = False
self.gui.flomni.delete_all()
if hasattr(self.gui, "flomni"):
self.gui.flomni.delete_all(timeout=self.GUI_RPC_TIMEOUT)
self.progressbar = None
self.text_box = None
self.xeyegui = None
self.pdf_viewer = None
self.idle_text_box = None
self.camera_gripper_image = None
self.camera_overview_image = None
def flomnigui_idle(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
if self._flomnigui_is_missing("idle_text_box"):
self.flomnigui_remove_all_docks()
idle_text_box = self.gui.flomni.new("TextBox")
self.idle_text_box = self.gui.flomni.new("TextBox")
text = (
"<pre>"
+ "██████╗ ███████╗ ██████╗ ███████╗██╗ ██████╗ ███╗ ███╗███╗ ██╗██╗\n"
@@ -120,7 +148,7 @@ class flomniGuiTools:
+ "╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝\n"
+ "</pre>"
)
idle_text_box.set_html_text(text)
self.idle_text_box.set_html_text(text)
def flomnigui_docs(self, filename: str | None = None):
import csaxs_bec
@@ -164,7 +192,7 @@ class flomniGuiTools:
# --- GUI handling (active existence check) ----------------------------
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("PdfViewerWidget"):
if self._flomnigui_is_missing("pdf_viewer"):
self.flomnigui_remove_all_docks()
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
@@ -182,7 +210,7 @@ class flomniGuiTools:
def flomnigui_show_progress(self):
self.flomnigui_show_gui()
if self._flomnigui_check_attribute_not_exists("progressbar"):
if self._flomnigui_is_missing("progressbar"):
self.flomnigui_remove_all_docks()
# Add a new dock with a RingProgressBar widget
self.progressbar = self.gui.flomni.new("RingProgressBar")
@@ -195,6 +223,14 @@ class flomniGuiTools:
self._flomnigui_update_progress()
def _flomnigui_update_progress(self):
"""Update the progress ring bar and center label from the current progress state.
``self.progress`` is backed by the BEC global variable ``tomo_progress``
(see :class:`_ProgressProxy` in ``flomni.py``), so this method reflects
the live state that is also accessible from other BEC client sessions via::
client.get_global_var("tomo_progress")
"""
main_progress_ring = self.progressbar.rings[0]
subtomo_progress_ring = self.progressbar.rings[1]
if self.progressbar is not None:
@@ -207,7 +243,43 @@ class flomniGuiTools:
main_progress_ring.set_value(progress)
subtomo_progress_ring.set_value(subtomo_progress)
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
# --- format start time for display --------------------------------
start_str = self.progress.get("tomo_start_time")
if start_str is not None:
import datetime as _dt
start_display = _dt.datetime.fromisoformat(start_str).strftime("%Y-%m-%d %H:%M:%S")
else:
start_display = "N/A"
# --- format estimated remaining time ------------------------------
remaining_s = self.progress.get("estimated_remaining_time")
if remaining_s is not None and remaining_s >= 0:
import datetime as _dt
remaining_s = int(remaining_s)
h, rem = divmod(remaining_s, 3600)
m, s = divmod(rem, 60)
if h > 0:
eta_display = f"{h}h {m:02d}m {s:02d}s"
elif m > 0:
eta_display = f"{m}m {s:02d}s"
else:
eta_display = f"{s}s"
else:
eta_display = "N/A"
# ------------------------------------------------------------------
text = (
f"Progress report:\n"
f" Tomo type: {self.progress['tomo_type']}\n"
f" Projection: {self.progress['projection']:.0f}\n"
f" Total projections expected {self.progress['total_projections']:.1f}\n"
f" Angle: {self.progress['angle']:.1f}\n"
f" Current subtomo: {self.progress['subtomo']}\n"
f" Current projection within subtomo: {self.progress['subtomo_projection']}\n"
f" Total projections per subtomo: {int(self.progress['subtomo_total_projections'])}\n"
f" Scan started: {start_display}\n"
f" Est. remaining: {eta_display}"
)
self.progressbar.set_center_label(text)

View File

@@ -231,10 +231,6 @@ class XrayEyeAlign:
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
self.tomo_rotate(0)
umv(dev.rtx, 0)
if keep_shutter_open:
if self.flomni.OMNYTools.yesno("Close the shutter now?", "y"):
dev.omnyfsh.fshclose()
@@ -251,9 +247,14 @@ class XrayEyeAlign:
print("Automatically loading new alignment parameters from xray eye alignment.\n")
self.flomni.read_alignment_offset()
self.flomni.read_alignment_offset(get_data_from_gui=True)
self.tomo_rotate(0)
umv(dev.rtx, 0)
print("You are ready to remove the xray eye and start ptychography scans.")
print("Fine alignment: flomni.tomo_parameters() , then flomni.tomo_alignment_scan()")
print("After that, run the fit in Matlab and load the new fit flomni.read_alignment_offset()")
def write_output(self):
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

View File

@@ -852,18 +852,6 @@ class OMNY(
def sample_name(self):
return dev.omny_samples.get_sample_name_in_samplestage()
def write_to_spec_log(self, content):
try:
with open(
os.path.expanduser(
"~/Data10/specES1/log-files/specES1_started_2022_11_30_1313.log"
),
"a",
) as log_file:
log_file.write(content)
except Exception:
logger.warning("Failed to write to spec log file (omny web page).")
def write_to_scilog(self, content, tags: list = None):
try:
if tags is not None:
@@ -1288,7 +1276,6 @@ class OMNY(
f"{str(datetime.datetime.now())}: omny scan projection at angle {angle}, scan"
f" number {bec.queue.next_scan_number}.\n"
)
self.write_to_spec_log(log_message)
# self.write_to_scilog(log_message, ["BEC_scans", self.sample_name])
scans.omny_fermat_scan(
fovx=self.fovx,

View File

@@ -48,7 +48,7 @@ class OMNYOpticsMixin:
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
def oeye_out(self):
fshclose()
dev.omnyfsh.fshclose()
if self.OMNYTools.yesno("Did you move in the optics?"):
umv(dev.oeyez, -2)
self._oeyey_mv(-60.3)

View File

@@ -38,12 +38,14 @@ class XRayEye(RPCBase):
None
"""
@rpc_timeout(20)
@rpc_call
def on_live_view_enabled(self, enabled: "bool"):
"""
None
"""
@rpc_timeout(20)
@rpc_call
def on_motors_enable(self, x_enable: "bool", y_enable: "bool"):
"""
@@ -54,6 +56,7 @@ class XRayEye(RPCBase):
y_enable(bool): enable y motor controls
"""
@rpc_timeout(20)
@rpc_call
def enable_submit_button(self, enable: "bool"):
"""
@@ -90,12 +93,14 @@ class XRayEye(RPCBase):
None
"""
@rpc_timeout(20)
@rpc_call
def switch_tab(self, tab: "str"):
"""
None
"""
@rpc_timeout(20)
@rpc_call
def submit_fit_array(self, fit_array):
"""

View File

@@ -4,6 +4,7 @@ from bec_lib import bec_logger
from bec_lib.endpoints import MessageEndpoints
from bec_qthemes import material_icon
from bec_widgets import BECWidget, SafeProperty, SafeSlot
from bec_widgets.utils.rpc_decorator import rpc_timeout
from bec_widgets.widgets.plots.image.image import Image
from bec_widgets.widgets.plots.waveform.waveform import Waveform
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
@@ -367,6 +368,7 @@ class XRayEye(BECWidget, QWidget):
return self.message_line_edit.toPlainText()
@user_message.setter
@rpc_timeout(20)
def user_message(self, message: str):
self.message_line_edit.setText(message)
@@ -375,6 +377,7 @@ class XRayEye(BECWidget, QWidget):
return self.sample_name_line_edit.text()
@sample_name.setter
@rpc_timeout(20)
def sample_name(self, message: str):
self.sample_name_line_edit.setText(message)
@@ -395,6 +398,7 @@ class XRayEye(BECWidget, QWidget):
################################################################################
@SafeSlot(str)
@rpc_timeout(20)
def switch_tab(self, tab: str):
if tab == "fit":
self.tab_widget.setCurrentIndex(1)
@@ -412,6 +416,7 @@ class XRayEye(BECWidget, QWidget):
return roi.get_coordinates()
@SafeSlot(bool)
@rpc_timeout(20)
def on_live_view_enabled(self, enabled: bool):
logger.info(f"Live view is enabled: {enabled}")
self.live_preview_toggle.blockSignals(True)
@@ -460,6 +465,7 @@ class XRayEye(BECWidget, QWidget):
self.shutter_toggle.blockSignals(False)
@SafeSlot(bool, bool)
@rpc_timeout(20)
def on_motors_enable(self, x_enable: bool, y_enable: bool):
"""
Enable/Disable motor controls
@@ -472,6 +478,7 @@ class XRayEye(BECWidget, QWidget):
self.motor_control_2d.enable_controls_ver(y_enable)
@SafeSlot(bool)
@rpc_timeout(20)
def enable_submit_button(self, enable: bool):
"""
Enable/disable submit button.
@@ -509,6 +516,7 @@ class XRayEye(BECWidget, QWidget):
print(f"meta: {meta}")
@SafeSlot()
@rpc_timeout(20)
def submit_fit_array(self, fit_array):
self.tab_widget.setCurrentIndex(1)
# self.fix_x.title = " got fit array"

View File

@@ -227,7 +227,7 @@ ftransy:
readoutPriority: baseline
connectionTimeout: 20
userParameter:
sensor_voltage: -2.4
sensor_voltage: -1.1
ftransz:
description: Sample transer Z
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
@@ -344,6 +344,9 @@ rtx:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
limits:
- -200
- 200
axis_Id: A
host: mpc2844.psi.ch
port: 2222
@@ -361,6 +364,9 @@ rty:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
limits:
- -100
- 100
axis_Id: B
host: mpc2844.psi.ch
port: 2222
@@ -376,6 +382,9 @@ rtz:
description: flomni rt
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
deviceConfig:
limits:
- -100
- 100
axis_Id: C
host: mpc2844.psi.ch
port: 2222
@@ -386,6 +395,16 @@ rtz:
readoutPriority: on_request
connectionTimeout: 20
rt_flyer:
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniFlyer
deviceConfig:
host: mpc2844.psi.ch
port: 2222
readoutPriority: async
connectionTimeout: 20
enabled: true
readOnly: False
############################################################
####################### Cameras ############################
############################################################

View File

@@ -317,8 +317,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
try:
scan_done = bool(value == self._num_total_triggers)
self.progress.put(value=value, max_value=self._num_total_triggers, done=scan_done)
if scan_done:
self._scan_done_event.set()
except Exception:
content = traceback.format_exc()
logger.info(f"Device {self.name} error: {content}")
@@ -393,6 +391,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
self._current_data_index = 0
# NOTE Make sure that the signal that omits mca callbacks is cleared
# DO NOT REMOVE!!
self._omit_mca_callbacks.clear()
# For a fly scan we need to start the mcs card ourselves
@@ -563,8 +562,9 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
def on_stop(self) -> None:
"""Hook called when the device is stopped. In addition, any status that is registered through cancel_on_stop will be cancelled here."""
self.stop_all.put(1)
self.erase_all.put(1)
with suppress_mca_callbacks(self):
self.stop_all.put(1)
self.erase_all.put(1)
def mcs_recovery(self, timeout: int = 1) -> None:
"""

View File

@@ -1,20 +1,18 @@
import threading
import time
from typing import List
import numpy as np
from bec_lib import bec_logger, messages
from bec_lib.endpoints import MessageEndpoints
from bec_lib import bec_logger
from ophyd import Component as Cpt
from ophyd import Device, PositionerBase, Signal
from ophyd.status import wait as status_wait
from ophyd.utils import LimitError
from ophyd_devices import AsyncMultiSignal, DeviceStatus, ProgressSignal
from ophyd_devices.utils.controller import Controller, threadlocked
from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected
from prettytable import PrettyTable
from csaxs_bec.devices.omny.rt.rt_ophyd import (
BECConfigError,
RtCommunicationError,
RtError,
RtReadbackSignal,
@@ -394,7 +392,7 @@ class RtFlomniController(Controller):
val = float(self.socket_put_and_receive(f"j{axis_number}").strip())
return val
def laser_tracker_check_signalstrength(self):
def laser_tracker_check_signalstrength(self, verbose=True):
if not self.laser_tracker_check_enabled():
returnval = "disabled"
else:
@@ -405,9 +403,10 @@ class RtFlomniController(Controller):
rtx = self.device_manager.devices.rtx
min_signal = rtx.user_parameter.get("min_signal")
low_signal = rtx.user_parameter.get("low_signal")
print(f"low signal: {low_signal}")
print(f"min signal: {min_signal}")
print(f"signal: {signal}")
if verbose:
print(f"low signal: {low_signal}")
print(f"min signal: {min_signal}")
print(f"signal: {signal}")
if signal < min_signal:
time.sleep(1)
if signal < min_signal:
@@ -431,27 +430,6 @@ class RtFlomniController(Controller):
t.add_row([i, self.read_ssi_interferometer(i)])
print(t)
def _get_signals_from_table(self, return_table) -> dict:
self.average_stdeviations_x_st_fzp += float(return_table[4])
self.average_stdeviations_y_st_fzp += float(return_table[7])
signals = {
"target_x": {"value": float(return_table[2])},
"average_x_st_fzp": {"value": float(return_table[3])},
"stdev_x_st_fzp": {"value": float(return_table[4])},
"target_y": {"value": float(return_table[5])},
"average_y_st_fzp": {"value": float(return_table[6])},
"stdev_y_st_fzp": {"value": float(return_table[7])},
"average_rotz": {"value": float(return_table[8])},
"stdev_rotz": {"value": float(return_table[9])},
"average_stdeviations_x_st_fzp": {
"value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1)
},
"average_stdeviations_y_st_fzp": {
"value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1)
},
}
return signals
@threadlocked
def start_scan(self):
if not self.feedback_is_running():
@@ -491,91 +469,6 @@ class RtFlomniController(Controller):
current_position_in_scan = int(float(return_table[2]))
return (mode, number_of_positions_planned, current_position_in_scan)
def read_positions_from_sampler(self):
# this was for reading after the scan completed
number_of_samples_to_read = 1 # self.get_scan_status()[1] #number of valid samples, will be updated upon first data read
read_counter = 0
self.average_stdeviations_x_st_fzp = 0
self.average_stdeviations_y_st_fzp = 0
self.average_lamni_angle = 0
mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status()
# if not (mode==2 or mode==3):
# error
self.device_manager.connector.set(
MessageEndpoints.device_status("rt_scan"),
messages.DeviceStatusMessage(
device="rt_scan", status=1, metadata=self.readout_metadata
),
)
# while scan is running
while mode > 0:
# TODO here?: scan abortion if no progress in scan *raise error
# logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}")
mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status()
time.sleep(0.01)
if current_position_in_scan > 5:
while current_position_in_scan > read_counter + 1:
return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",")
# logger.info(f"{return_table}")
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
read_counter = read_counter + 1
signals = self._get_signals_from_table(return_table)
self.publish_device_data(signals=signals, point_id=int(return_table[0]))
time.sleep(0.05)
# read the last samples even though scan is finished already
while number_of_positions_planned > read_counter:
return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",")
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
# logger.info(f"{return_table}")
read_counter = read_counter + 1
signals = self._get_signals_from_table(return_table)
self.publish_device_data(signals=signals, point_id=int(return_table[0]))
self.device_manager.connector.set(
MessageEndpoints.device_status("rt_scan"),
messages.DeviceStatusMessage(
device="rt_scan", status=0, metadata=self.readout_metadata
),
)
logger.info(
"Flomni statistics: Average of all standard deviations: x"
f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y"
f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}"
)
def publish_device_data(self, signals, point_id):
self.device_manager.connector.set_and_publish(
MessageEndpoints.device_read("rt_flomni"),
messages.DeviceMessage(
signals=signals, metadata={"point_id": point_id, **self.readout_metadata}
),
)
def start_readout(self):
readout = threading.Thread(target=self.read_positions_from_sampler)
readout.start()
def kickoff(self, metadata):
self.readout_metadata = metadata
while not self._min_scan_buffer_reached:
time.sleep(0.001)
self.start_scan()
time.sleep(0.1)
self.start_readout()
class RtFlomniReadbackSignal(RtReadbackSignal):
@retry_once
@@ -621,6 +514,18 @@ class RtFlomniSetpointSignal(RtSetpointSignal):
"The interferometer feedback is not running. Either it is turned off or and"
" interferometer error occured."
)
tracker_status = self.parent.controller.laser_tracker_check_signalstrength()
if tracker_status == "toolow":
print(
"The interferometer signal is too low for movements. Realignment required."
)
raise RtError(
"The interferometer signal is too low for movements. Realignment required."
)
self.set_with_feedback_disabled(val)
def set_with_feedback_disabled(self, val):
@@ -831,6 +736,185 @@ class RtFlomniMotor(Device, PositionerBase):
return super().stop(success=success)
class RtFlomniFlyer(Device):
USER_ACCESS = ["controller"]
data = Cpt(
AsyncMultiSignal,
name="data",
signals=[
"target_x",
"average_x_st_fzp",
"stdev_x_st_fzp",
"target_y",
"average_y_st_fzp",
"stdev_y_st_fzp",
"average_rotz",
"stdev_rotz",
"average_stdeviations_x_st_fzp",
"average_stdeviations_y_st_fzp",
],
ndim=1,
async_update={"type": "add", "max_shape": [None]},
max_size=1000,
)
progress = Cpt(
ProgressSignal, doc="ProgressSignal indicating the progress of the device during a scan."
)
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
host="mpc2844.psi.ch",
port=2222,
socket_cls=SocketIO,
device_manager=None,
**kwargs,
):
super().__init__(prefix=prefix, name=name, parent=parent, **kwargs)
self.shutdown_event = threading.Event()
self.controller = RtFlomniController(
socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager
)
self.average_stdeviations_x_st_fzp = 0
self.average_stdeviations_y_st_fzp = 0
self.average_lamni_angle = 0
self.readout_thread = None
self.scan_done_event = threading.Event()
self.scan_done_event.set()
def read_positions_from_sampler(self, status: DeviceStatus):
"""
Read the positions from the sampler and update the data signal.
This function runs in a separate thread and continuously checks the
scan status.
Args:
status (DeviceStatus): The status object to update when the readout is complete.
"""
read_counter = 0
self.average_stdeviations_x_st_fzp = 0
self.average_stdeviations_y_st_fzp = 0
self.average_lamni_angle = 0
mode, number_of_positions_planned, current_position_in_scan = (
self.controller.get_scan_status()
)
# while scan is running
while mode > 0 and not self.shutdown_event.wait(0.01):
# logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}")
mode, number_of_positions_planned, current_position_in_scan = (
self.controller.get_scan_status()
)
if current_position_in_scan > 5:
while current_position_in_scan > read_counter + 1:
return_table = (
self.controller.socket_put_and_receive(f"r{read_counter}")
).split(",")
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
self.progress.put(
value=read_counter, max_value=number_of_positions_planned, done=False
)
read_counter = read_counter + 1
signals = self._get_signals_from_table(return_table)
self.data.set(signals)
if self.shutdown_event.wait(0.05):
logger.info("Shutdown event set, stopping readout.")
# if we are here, the shutdown_event is set. We can exit the readout loop.
status.set_finished()
return
# read the last samples even though scan is finished already
while number_of_positions_planned > read_counter and not self.shutdown_event.is_set():
return_table = (self.controller.socket_put_and_receive(f"r{read_counter}")).split(",")
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
self.progress.put(value=read_counter, max_value=number_of_positions_planned, done=False)
read_counter = read_counter + 1
signals = self._get_signals_from_table(return_table)
self.data.set(signals)
# NOTE: No need to set the status to failed if the shutdown_event is set.
# The stop() method will take care of that.
status.set_finished()
self.progress.put(value=read_counter, max_value=number_of_positions_planned, done=True)
logger.info(
"Flomni statistics: Average of all standard deviations: x"
f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y"
f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}"
)
def _get_signals_from_table(self, return_table) -> dict:
self.average_stdeviations_x_st_fzp += float(return_table[4])
self.average_stdeviations_y_st_fzp += float(return_table[7])
signals = {
"target_x": {"value": float(return_table[2])},
"average_x_st_fzp": {"value": float(return_table[3])},
"stdev_x_st_fzp": {"value": float(return_table[4])},
"target_y": {"value": float(return_table[5])},
"average_y_st_fzp": {"value": float(return_table[6])},
"stdev_y_st_fzp": {"value": float(return_table[7])},
"average_rotz": {"value": float(return_table[8])},
"stdev_rotz": {"value": float(return_table[9])},
"average_stdeviations_x_st_fzp": {
"value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1)
},
"average_stdeviations_y_st_fzp": {
"value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1)
},
}
return signals
def stage(self):
self.shutdown_event.clear()
self.scan_done_event.set()
return super().stage()
def start_readout(self, status: DeviceStatus):
self.readout_thread = threading.Thread(
target=self.read_positions_from_sampler, args=(status,)
)
self.readout_thread.start()
def kickoff(self) -> DeviceStatus:
self.shutdown_event.clear()
self.scan_done_event.clear()
while not self.controller._min_scan_buffer_reached and not self.shutdown_event.wait(0.001):
...
self.controller.start_scan()
self.shutdown_event.wait(0.1)
status = DeviceStatus(self)
status.set_finished()
return status
def complete(self) -> DeviceStatus:
"""Wait until the flyer is done."""
if self.scan_done_event.is_set():
# if the scan_done_event is already set, we can return a finished status immediately
status = DeviceStatus(self)
status.set_finished()
return status
status = DeviceStatus(self)
self.start_readout(status)
status.add_callback(lambda *args, **kwargs: self.scan_done_event.set())
return status
def stop(self, *, success=False):
self.shutdown_event.set()
self.scan_done_event.set()
if self.readout_thread is not None:
self.readout_thread.join()
return super().stop(success=success)
if __name__ == "__main__":
rtcontroller = RtFlomniController(
socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222, device_manager=None

View File

@@ -48,6 +48,7 @@ class OMNYFastShutter(PSIDeviceBase, Device):
def fshopen(self):
"""Open the fast shutter."""
if self._check_if_cSAXS_shutter_exists_in_config():
self.shutter.put(1)
return self.device_manager.devices["fsh"].fshopen()
else:
self.shutter.put(1)
@@ -55,6 +56,7 @@ class OMNYFastShutter(PSIDeviceBase, Device):
def fshclose(self):
"""Close the fast shutter."""
if self._check_if_cSAXS_shutter_exists_in_config():
self.shutter.put(0)
return self.device_manager.devices["fsh"].fshclose()
else:
self.shutter.put(0)

View File

@@ -27,20 +27,19 @@ from bec_lib import bec_logger, messages
from bec_lib.alarm_handler import Alarms
from bec_lib.endpoints import MessageEndpoints
from bec_server.scan_server.errors import ScanAbortion
from bec_server.scan_server.scans import SyncFlyScanBase
from bec_server.scan_server.scans import AsyncFlyScanBase
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
logger = bec_logger.logger
class FlomniFermatScan(SyncFlyScanBase):
class FlomniFermatScan(AsyncFlyScanBase):
scan_name = "flomni_fermat_scan"
scan_type = "fly"
required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"]
arg_input = {}
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
use_scan_progress_report = True
def __init__(
self,
@@ -104,6 +103,14 @@ class FlomniFermatScan(SyncFlyScanBase):
self.zshift = -100
self.flomni_rotation_status = None
def scan_report_instructions(self):
"""Scan report instructions for the progress bar"""
yield from self.stubs.scan_report_instruction({"device_progress": ["rt_flyer"]})
@property
def monitor_sync(self) -> str:
return "rt_flyer"
def initialize(self):
self.scan_motors = []
self.update_readout_priority()
@@ -113,10 +120,6 @@ class FlomniFermatScan(SyncFlyScanBase):
self.positions, corridor_size=self.optim_trajectory_corridor
)
@property
def monitor_sync(self):
return "rt_flomni"
def reverse_trajectory(self):
"""
Reverse the trajectory. Every other scan should be reversed to
@@ -290,26 +293,18 @@ class FlomniFermatScan(SyncFlyScanBase):
return np.array(positions)
def scan_core(self):
# use a device message to receive the scan number and
# scan ID before sending the message to the device server
yield from self.stubs.kickoff(device="rtx")
while True:
yield from self.stubs.read(group="monitored")
status = self.connector.get(MessageEndpoints.device_status("rt_scan"))
if status:
status_id = status.content.get("status", 1)
request_id = status.metadata.get("RID")
if status_id == 0 and self.metadata.get("RID") == request_id:
break
if status_id == 2 and self.metadata.get("RID") == request_id:
raise ScanAbortion(
"An error occured during the flomni readout:"
f" {status.metadata.get('error')}"
)
# send off the flyer
yield from self.stubs.kickoff(device="rt_flyer")
# start the readout loop of the flyer
status = yield from self.stubs.complete(device="rt_flyer", wait=False)
# read the monitors until the flyer is done
while not status.done:
yield from self.stubs.read(group="monitored", point_id=self.point_id)
self.point_id += 1
time.sleep(1)
logger.debug("reading monitors")
# yield from self.device_rpc("rtx", "controller.kickoff")
def move_to_start(self):
"""return to the start position"""
@@ -336,6 +331,7 @@ class FlomniFermatScan(SyncFlyScanBase):
yield from self.read_scan_motors()
self.prepare_positions()
yield from self._prepare_setup()
yield from self.scan_report_instructions()
yield from self.open_scan()
yield from self.stage()
yield from self.run_baseline_reading()

View File

@@ -50,16 +50,15 @@ Manually move the gripper to a transfer position
After the sample transfer the sample stage moved to the measurement position with your new sample. The Xray eye will automatically move in and the shutter will open. You may already see the sample in the omny xeye interface running on the windows computer.
If you see your sample already at the approximately correct height, you can skip steps 1 to 3. Otherwise adjust the height:
1. `flomni.rt_feedback_disable()` disable the closed loop operation to allow movement of coarse stages
1. `flomni.feedback_disable()` disable the closed loop operation to allow movement of coarse stages
1. `umvr(dev.fsamy, 0.01)`, attention: unit <mm>, move the sample stage relative up (positive) or down (negative) until the sample is approximately vertically centered in xray eye screen
1. `flomni.xrayeye_update_frame()` will update the current image on the xray eye screen
1. `flomni.xrayeye_alignment_start()` start the coarse alignment of the sample by measuring (clicking in the X-ray eye software) the sample position at 0, 45, 90, 135, 180 degrees. Then use the matlab routine `SPEC_ptycho_align.m` to fit this data.
1. `flomni.read_alignment_offset()` read the generated alignment data.
1. `flomni.xrayeye_alignment_start()` start the coarse alignment of the sample by measuring (clicking in the X-ray eye software) the sample position at 0, 45, 90, 135, 180 degrees. The GUI will present a fit of this data, which is automatically loaded to BEC for aligning the sample.
#### Fine alignment
After the xrayeyealign, a fine alignment needs to be performed using ptychography.
_To bypass the fine alignment: `feye_out`_
_To bypass the fine alignment: `flomni.feye_out`_
1. `flomni.tomo_parameters()` Adjust the ptychographic scan parameters for performing an alignment scan. Typically FOVX = FOVX(Xrayeye)+20 mu, shell step = beamsize/2.5, number of projections and tomo mode are ignored in the alignment scans.
@@ -71,13 +70,13 @@ _To bypass the fine alignment: `feye_out`_
Now that the sample is aligned, the tomographic measurement can be performed.
1. `flomni.tomo_parameters()` adjust the scan parameters for the tomographic scan. This includes the parameters for ptychographic scans of projections plus the strategy for angular sampling. The vertical shift adjusts the field of view, up (positive) or down (negative). After adjusting the numbers, type again `flomni.tomo_parameters()` and verify that they are correct.
1. `flomni.tomo_scan_projection(angle)` perform a ptychographic scan at the rotation angle <angle>. Launch the tomographic measurement by `flomni.tomo_scan()`.
1. Before changing sample, verify that all subtomograms were completely acquired using the `tomo_recons matlab` script.
1. Before changing sample, verify that all subtomograms were completely acquired using the tomo_reconstruction matlab script.
#### If something went wrong…
A __single projection__ is to be repeated use
`flomni.tomo_scan_projection(<angle>)`. The target angle of scans can be found in the second column of the file in
`~/Data10/specES1/dat-files/omni_scannumbers.txt`
`~/data/raw/logs/tomography_scannumbers.txt`
To continue an __interrupted tomography scan__:

View File

@@ -26,7 +26,7 @@ The effective position of the axis of rotation shifts with sample thickness or m
1. `dev.lsamx` and `dev.lsamy` will print current position and the center value. Update the center value by
`dev.lsamx.update_user_parameter({'center':8.69})`
`dev.lsamy.update_user_parameter({'center':8.69})`
1. close the shutter: `fshclose()`
1. close the shutter: `dev.omnyfsh.fshclose()`
#### X-ray eye alignment

View File

@@ -217,6 +217,16 @@ def test_mcs_card_csaxs_complete_and_stop(mock_mcs_csaxs: MCSCardCSAXS):
assert not mcs._start_monitor_async_data_emission.is_set()
def test_mcs_on_stop(mock_mcs_csaxs: MCSCardCSAXS):
"""Test that on stop sets the omit_mca_callbacks flag. Also test that on stage clears the omit_mca_callbacks flag."""
mcs = mock_mcs_csaxs
assert mcs._omit_mca_callbacks.is_set() is False
mcs.stop()
assert mcs._omit_mca_callbacks.is_set() is True
mcs.stage()
assert mcs._omit_mca_callbacks.is_set() is False
def test_mcs_recovery(mock_mcs_csaxs: MCSCardCSAXS):
mcs = mock_mcs_csaxs
# Simulate ongoing acquisition