Compare commits
2 Commits
test_pseud
...
fix/mcs-ca
| Author | SHA1 | Date | |
|---|---|---|---|
| 90e0f0f3b3 | |||
|
80afe2e3f0
|
@@ -1,21 +0,0 @@
|
||||
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/"
|
||||
@@ -8,14 +8,15 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
python: "3.10"
|
||||
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:
|
||||
@@ -23,5 +24,6 @@ sphinx:
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
|
||||
|
||||
@@ -356,6 +356,18 @@ 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:
|
||||
@@ -427,6 +439,7 @@ 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],
|
||||
@@ -766,7 +779,7 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools):
|
||||
|
||||
user_input = input("Are these parameters correctly set for your scan? ")
|
||||
if user_input == "y":
|
||||
print("OK. continue.")
|
||||
print("good then")
|
||||
return
|
||||
|
||||
self.tomo_countingtime = self._get_val("<ctime> s", self.tomo_countingtime, float)
|
||||
|
||||
@@ -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
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
|
||||
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()
|
||||
dev.omnyfsh.fshopen()
|
||||
fshclose()
|
||||
leyey_out = self._get_user_param_safe("leyey", "out")
|
||||
umv(dev.leyey, leyey_out)
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ if builtins.__dict__.get("bec") is not None:
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
@@ -36,15 +35,12 @@ def umv(*args):
|
||||
class FlomniToolsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FlomniInitError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FlomniError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# class FlomniTools:
|
||||
# def yesno(self, message: str, default="none", autoconfirm=0) -> bool:
|
||||
# if autoconfirm and default == "y":
|
||||
@@ -88,16 +84,13 @@ class FlomniInitStagesMixin:
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
sensor_voltage_target = dev.ftransy.user_parameter.get("sensor_voltage")
|
||||
sensor_voltage = float(dev.ftransy.controller.socket_put_and_receive("MG@AN[1]").strip())
|
||||
|
||||
if not np.isclose(sensor_voltage, sensor_voltage_target, 0.25):
|
||||
print(
|
||||
f"Sensor voltage of the gripper is {sensor_voltage}, while target from config is {sensor_voltage_target}"
|
||||
)
|
||||
print(
|
||||
"Verify that the value is acceptable and update config file. Reload config and start again."
|
||||
)
|
||||
print(f"Sensor voltage of the gripper is {sensor_voltage}, while target from config is {sensor_voltage_target}")
|
||||
print("Verify that the value is acceptable and update config file. Reload config and start again.")
|
||||
return
|
||||
|
||||
print("Starting to drive ftransy to +y limit")
|
||||
@@ -125,10 +118,9 @@ class FlomniInitStagesMixin:
|
||||
dev.feyex.limits = [-30, -1]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"Init of foptz. Can the stage move to the upstream limit without collision?"
|
||||
):
|
||||
print("OK. continue.")
|
||||
|
||||
if self.OMNYTools.yesno("Init of foptz. Can the stage move to the upstream limit without collision?"):
|
||||
print("good then")
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -181,10 +173,8 @@ class FlomniInitStagesMixin:
|
||||
dev.fsamy.limits = [2, 3.1]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"Init of tracking stages. Did you remove the outer laser flight tubes?"
|
||||
):
|
||||
print("OK. continue.")
|
||||
if self.OMNYTools.yesno("Init of tracking stages. Did you remove the outer laser flight tubes?"):
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -200,7 +190,7 @@ class FlomniInitStagesMixin:
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno("Init of sample stage. Is the piezo at about 0 deg?"):
|
||||
print("OK. continue.")
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -216,10 +206,8 @@ class FlomniInitStagesMixin:
|
||||
print("done")
|
||||
|
||||
print("Initializing UPR stage.")
|
||||
if self.OMNYTools.yesno(
|
||||
"To ensure that the end switches work, please check that they are currently not pushed. Is everything okay?"
|
||||
):
|
||||
print("OK. continue.")
|
||||
if self.OMNYTools.yesno("To ensure that the end switches work, please check that they are currently not pushed. Is everything okay?"):
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -240,7 +228,7 @@ class FlomniInitStagesMixin:
|
||||
continue
|
||||
break
|
||||
if self.OMNYTools.yesno("Shall I start the index search?"):
|
||||
print("OK. continue.. Starting index search.")
|
||||
print("good then. Starting index search.")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -258,10 +246,8 @@ class FlomniInitStagesMixin:
|
||||
dev.fsamroy.limits = [-5, 365]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"Init of foptx. Can the stage move to the positive limit without collision? Attention: tracker flight tube!"
|
||||
):
|
||||
print("OK. continue.")
|
||||
if self.OMNYTools.yesno("Init of foptx. Can the stage move to the positive limit without collision? Attention: tracker flight tube!"):
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -285,7 +271,7 @@ class FlomniInitStagesMixin:
|
||||
break
|
||||
|
||||
if self.OMNYTools.yesno("Start limit switch search of fopty?"):
|
||||
print("OK. continue.")
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
@@ -439,7 +425,7 @@ class FlomniSampleTransferMixin:
|
||||
def ftransfer_flomni_stage_in(self):
|
||||
time.sleep(1)
|
||||
sample_in_position = dev.flomni_samples.is_sample_slot_used(0)
|
||||
# bool(float(dev.flomni_samples.sample_placed.sample0.get()))
|
||||
#bool(float(dev.flomni_samples.sample_placed.sample0.get()))
|
||||
if not sample_in_position:
|
||||
raise FlomniError("There is no sample in the sample stage. Aborting.")
|
||||
self.reset_correction()
|
||||
@@ -452,21 +438,7 @@ class FlomniSampleTransferMixin:
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
dev.fsamx.limits = [fsamx_in - 0.4, fsamx_in + 0.4]
|
||||
|
||||
print("Moving X-ray eye in.")
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"Please confirm that this is ok with the flight tube. This check is to be removed after commissioning",
|
||||
"n",
|
||||
):
|
||||
print("OK. continue.")
|
||||
else:
|
||||
print("Stopping.")
|
||||
raise FlomniError("Manual abort of x-ray eye in.")
|
||||
|
||||
self.feye_in()
|
||||
print("Moving X-ray optics out.")
|
||||
self.foptics_out()
|
||||
self.xrayeye_update_frame()
|
||||
#self.flomnigui_idle()
|
||||
|
||||
def laser_tracker_show_all(self):
|
||||
dev.rtx.controller.laser_tracker_show_all()
|
||||
@@ -570,6 +542,13 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
self.flomnigui_show_cameras()
|
||||
|
||||
|
||||
if self.OMNYTools.yesno("Please confirm that there is currently no sample in the gripper. It would be dropped!", "y"):
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
raise FlomniError("The sample transfer was manually aborted.")
|
||||
|
||||
self.ftransfer_gripper_move(position)
|
||||
|
||||
self.ftransfer_controller_enable_mount_mode()
|
||||
@@ -609,7 +588,7 @@ class FlomniSampleTransferMixin:
|
||||
self.check_sensor_connected()
|
||||
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
# bool(float(dev.flomni_samples.sample_in_gripper.get()))
|
||||
#bool(float(dev.flomni_samples.sample_in_gripper.get()))
|
||||
if not sample_in_gripper:
|
||||
raise FlomniError("The gripper does not carry a sample.")
|
||||
|
||||
@@ -661,7 +640,7 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
def ftransfer_sample_change(self, new_sample_position: int):
|
||||
self.check_tray_in()
|
||||
# sample_in_gripper = dev.flomni_samples.sample_in_gripper.get()
|
||||
# sample_in_gripper = dev.flomni_samples.sample_in_gripper.get()
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
if sample_in_gripper:
|
||||
raise FlomniError("There is already a sample in the gripper. Aborting.")
|
||||
@@ -669,30 +648,28 @@ class FlomniSampleTransferMixin:
|
||||
self.check_position_is_valid(new_sample_position)
|
||||
|
||||
if new_sample_position == 0:
|
||||
raise FlomniError(
|
||||
"The new sample to place cannot be the sample in the sample stage. Aborting."
|
||||
)
|
||||
raise FlomniError("The new sample to place cannot be the sample in the sample stage. Aborting.")
|
||||
|
||||
# sample_placed = getattr(
|
||||
# dev.flomni_samples.sample_placed, f"sample{new_sample_position}"
|
||||
# ).get()
|
||||
# sample_placed = getattr(
|
||||
# dev.flomni_samples.sample_placed, f"sample{new_sample_position}"
|
||||
# ).get()
|
||||
sample_placed = dev.flomni_samples.is_sample_slot_used(new_sample_position)
|
||||
if not sample_placed:
|
||||
raise FlomniError(
|
||||
f"There is currently no sample in position [{new_sample_position}]. Aborting."
|
||||
)
|
||||
|
||||
# sample_in_sample_stage = dev.flomni_samples.sample_placed.sample0.get()
|
||||
# sample_in_sample_stage = dev.flomni_samples.sample_placed.sample0.get()
|
||||
sample_in_sample_stage = dev.flomni_samples.is_sample_slot_used(0)
|
||||
if sample_in_sample_stage:
|
||||
# find a new home for the sample...
|
||||
empty_slots = []
|
||||
# for name, val in dev.flomni_samples.read().items():
|
||||
# if "flomni_samples_sample_placed_sample" not in name:
|
||||
# continue
|
||||
# if val.get("value") == 0:
|
||||
# empty_slots.append(int(name.split("flomni_samples_sample_placed_sample")[1]))
|
||||
for j in range(1, 20):
|
||||
# for name, val in dev.flomni_samples.read().items():
|
||||
# if "flomni_samples_sample_placed_sample" not in name:
|
||||
# continue
|
||||
# if val.get("value") == 0:
|
||||
# empty_slots.append(int(name.split("flomni_samples_sample_placed_sample")[1]))
|
||||
for j in range(1,20):
|
||||
if not dev.flomni_samples.is_sample_slot_used(j):
|
||||
empty_slots.append(j)
|
||||
if not empty_slots:
|
||||
@@ -774,13 +751,11 @@ class FlomniSampleTransferMixin:
|
||||
return
|
||||
|
||||
if self.OMNYTools.yesno("All OK? Continue?", "y"):
|
||||
print("OK. continue.")
|
||||
print("good then")
|
||||
dev.ftransy.controller.socket_put_confirmed("confirm=1")
|
||||
else:
|
||||
print("Stopping.")
|
||||
raise FlomniError(
|
||||
"User abort sample transfer."
|
||||
)
|
||||
exit
|
||||
|
||||
def ftransfer_gripper_is_open(self) -> bool:
|
||||
status = bool(float(dev.ftransy.controller.socket_put_and_receive("MG @OUT[9]").strip()))
|
||||
@@ -788,7 +763,7 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
def ftransfer_gripper_open(self):
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
# dev.flomni_samples.sample_in_gripper.get()
|
||||
#dev.flomni_samples.sample_in_gripper.get()
|
||||
if sample_in_gripper:
|
||||
raise FlomniError(
|
||||
"Cannot open gripper. There is still a sample in the gripper! Aborting."
|
||||
@@ -803,18 +778,14 @@ class FlomniSampleTransferMixin:
|
||||
def ftransfer_gripper_move(self, position: int):
|
||||
self.check_position_is_valid(position)
|
||||
|
||||
#this is not used for sample stage position!
|
||||
self._ftransfer_shiftx = -0.15
|
||||
self._ftransfer_shiftx = -0.2
|
||||
self._ftransfer_shiftz = -0.5
|
||||
|
||||
fsamx_pos = dev.fsamx.readback.get()
|
||||
if position == 0 and fsamx_pos > -160:
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"May the flomni stage be moved out for the sample change? Feedback will be disabled and alignment will be lost!",
|
||||
"y",
|
||||
):
|
||||
print("OK. continue.")
|
||||
if self.OMNYTools.yesno("May the flomni stage be moved out for the sample change? Feedback will be disabled and alignment will be lost!", "y"):
|
||||
print("good then")
|
||||
self.ftransfer_flomni_stage_out()
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -824,7 +795,7 @@ class FlomniSampleTransferMixin:
|
||||
self.check_tray_in()
|
||||
|
||||
if position == 0:
|
||||
umv(dev.ftransx, 11, dev.ftransz, 3.5950)
|
||||
umv(dev.ftransx, 10.715 + 0.2, dev.ftransz, 3.5950)
|
||||
if position == 1:
|
||||
umv(
|
||||
dev.ftransx,
|
||||
@@ -969,7 +940,7 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
class FlomniAlignmentMixin:
|
||||
import csaxs_bec
|
||||
import os
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure this is a Path object, not a string
|
||||
@@ -979,13 +950,10 @@ class FlomniAlignmentMixin:
|
||||
|
||||
# Build the absolute path correctly
|
||||
default_correction_file = (
|
||||
csaxs_bec_basepath.parent
|
||||
/ "bec_ipython_client"
|
||||
/ "plugins"
|
||||
/ "flomni"
|
||||
/ default_correction_file_rel
|
||||
csaxs_bec_basepath.parent / 'bec_ipython_client' / 'plugins' / 'flomni' / default_correction_file_rel
|
||||
).resolve()
|
||||
|
||||
|
||||
def reset_correction(self, use_default_correction=True):
|
||||
"""
|
||||
Reset the correction to the default values.
|
||||
@@ -1015,62 +983,56 @@ class FlomniAlignmentMixin:
|
||||
|
||||
def read_alignment_offset(
|
||||
self,
|
||||
dir_path=os.path.expanduser("~/data/raw/logs/"),
|
||||
dir_path=os.path.expanduser("~/Data10/specES1/internal/"),
|
||||
setup="flomni",
|
||||
use_vertical_default_values=True,
|
||||
get_data_from_gui=False,
|
||||
):
|
||||
"""
|
||||
Read the alignment parameters from xray eye fit.
|
||||
|
||||
"""
|
||||
tomo_alignment_fit = np.zeros((2, 5))
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
|
||||
# tomo_alignment_fit[0][0] = file.readline()
|
||||
|
||||
if not get_data_from_gui:
|
||||
"""
|
||||
Read the alignment parameters from xray eye fit.
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
|
||||
# tomo_alignment_fit[0][1] = file.readline()
|
||||
|
||||
"""
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][0] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
|
||||
# tomo_alignment_fit[0][2] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][1] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
|
||||
# tomo_alignment_fit[1][0] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][2] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
|
||||
# tomo_alignment_fit[1][1] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][0] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
|
||||
# tomo_alignment_fit[1][2] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][1] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
|
||||
# tomo_alignment_fit[1][3] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][2] = file.readline()
|
||||
# with open(os.path.join(dir_path, "ptychotomoalign_Cy3.txt"), "r") as file:
|
||||
# tomo_alignment_fit[1][4] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][3] = file.readline()
|
||||
params = dev.omny_xray_gui.fit_params_x.get()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cy3.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][4] = file.readline()
|
||||
#amplitude
|
||||
tomo_alignment_fit[0][0] = params['SineModel_0_amplitude']
|
||||
#phase
|
||||
tomo_alignment_fit[0][1] = params['SineModel_0_shift']
|
||||
#offset
|
||||
tomo_alignment_fit[0][2] = params['LinearModel_1_intercept']
|
||||
print("applying vertical default values from mirror calibration, not from fit!")
|
||||
tomo_alignment_fit[1][0] = 0
|
||||
tomo_alignment_fit[1][1] = 0
|
||||
tomo_alignment_fit[1][2] = 0
|
||||
tomo_alignment_fit[1][3] = 0
|
||||
tomo_alignment_fit[1][4] = 0
|
||||
|
||||
print("New alignment parameters loaded from filesystem, meaning Matlab fit:")
|
||||
|
||||
else:
|
||||
params = dev.omny_xray_gui.fit_params_x.get()
|
||||
|
||||
# amplitude
|
||||
tomo_alignment_fit[0][0] = params["SineModel_0_amplitude"]
|
||||
# phase
|
||||
tomo_alignment_fit[0][1] = params["SineModel_0_shift"]
|
||||
# offset
|
||||
tomo_alignment_fit[0][2] = params["LinearModel_1_intercept"]
|
||||
print("applying vertical default values from mirror calibration, not from fit!")
|
||||
tomo_alignment_fit[1][0] = 0
|
||||
tomo_alignment_fit[1][1] = 0
|
||||
tomo_alignment_fit[1][2] = 0
|
||||
tomo_alignment_fit[1][3] = 0
|
||||
tomo_alignment_fit[1][4] = 0
|
||||
print("New alignment parameters loaded based on Xray eye alignment GUI:")
|
||||
|
||||
print("New alignment parameters loaded:")
|
||||
print(
|
||||
f"X Amplitude {tomo_alignment_fit[0][0]}, "
|
||||
f"X Phase {tomo_alignment_fit[0][1]}, "
|
||||
@@ -1217,7 +1179,7 @@ class Flomni(
|
||||
FlomniAlignmentMixin,
|
||||
FlomniOpticsMixin,
|
||||
cSAXSBeamlineChecks,
|
||||
flomniGuiTools,
|
||||
flomniGuiTools
|
||||
):
|
||||
def __init__(self, client):
|
||||
super().__init__()
|
||||
@@ -1249,10 +1211,7 @@ class Flomni(
|
||||
|
||||
def start_x_ray_eye_alignment(self, keep_shutter_open=False):
|
||||
|
||||
if self.OMNYTools.yesno(
|
||||
"Starting Xrayeye alignment. Deleting any potential existing alignment for this sample.",
|
||||
"y",
|
||||
):
|
||||
if self.OMNYTools.yesno("Starting Xrayeye alignment. Deleting any potential existing alignment for this sample.", "y"):
|
||||
self.align = XrayEyeAlign(self.client, self)
|
||||
try:
|
||||
self.align.align(keep_shutter_open)
|
||||
@@ -1264,7 +1223,7 @@ class Flomni(
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
raise exc
|
||||
|
||||
def xrayeye_update_frame(self, keep_shutter_open=False):
|
||||
def xrayeye_update_frame(self,keep_shutter_open=False):
|
||||
self.align.update_frame(keep_shutter_open)
|
||||
|
||||
def xrayeye_alignment_start(self, keep_shutter_open=False):
|
||||
@@ -1482,6 +1441,18 @@ class Flomni(
|
||||
def sample_name(self):
|
||||
return self.sample_get_name(0)
|
||||
|
||||
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:
|
||||
@@ -1503,8 +1474,6 @@ class Flomni(
|
||||
return
|
||||
dev = builtins.__dict__.get("dev")
|
||||
bec = builtins.__dict__.get("bec")
|
||||
|
||||
self.feye_out()
|
||||
tags = ["BEC_alignment_tomo", self.sample_name]
|
||||
self.write_to_scilog(
|
||||
f"Starting alignment scan. First scan number: {bec.queue.next_scan_number}.", tags
|
||||
@@ -1531,17 +1500,16 @@ class Flomni(
|
||||
error_caught = True
|
||||
else:
|
||||
raise exc
|
||||
# todo here was if blchk success, then setting to success true
|
||||
#todo here was if blchk success, then setting to success true
|
||||
successful = True
|
||||
|
||||
|
||||
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)
|
||||
|
||||
print("Alignment scan finished. Please run SPEC_ptycho_align and load the new fit.")
|
||||
|
||||
umv(dev.fsamroy, 0)
|
||||
self.OMNYTools.printgreenbold(
|
||||
"\n\nAlignment scan finished. Please run SPEC_ptycho_align and load the new fit by flomni.read_alignment_offset() ."
|
||||
)
|
||||
|
||||
def _write_subtomo_to_scilog(self, subtomo_number):
|
||||
dev = builtins.__dict__.get("dev")
|
||||
@@ -1575,9 +1543,6 @@ class Flomni(
|
||||
|
||||
self._write_subtomo_to_scilog(subtomo_number)
|
||||
|
||||
if start_angle is not None:
|
||||
print(f"Sub tomo scan with start angle {start_angle} requested.")
|
||||
|
||||
if start_angle is None:
|
||||
if subtomo_number == 1:
|
||||
start_angle = 0
|
||||
@@ -1598,75 +1563,25 @@ class Flomni(
|
||||
|
||||
# _tomo_shift_angles (potential global variable)
|
||||
_tomo_shift_angles = 0
|
||||
# compute number of projections
|
||||
|
||||
start = start_angle + _tomo_shift_angles
|
||||
|
||||
if subtomo_number % 2: # odd = forward
|
||||
max_allowed_angle = 180.05 + self.tomo_angle_stepsize
|
||||
proposed_end = start + 180
|
||||
angle_end = min(proposed_end, max_allowed_angle)
|
||||
span = angle_end - start
|
||||
|
||||
else: # even = reverse
|
||||
min_allowed_angle = 0
|
||||
proposed_end = start - 180
|
||||
angle_end = max(proposed_end, min_allowed_angle)
|
||||
span = start - angle_end
|
||||
|
||||
# number of projections needed to maintain step size
|
||||
N = int(span / self.tomo_angle_stepsize) + 1
|
||||
|
||||
angles = np.linspace(start, angle_end, num=N, endpoint=True)
|
||||
|
||||
if subtomo_number % 2: # odd subtomos → forward direction
|
||||
# clamp end angle to max allowed
|
||||
max_allowed_angle = 180.05 + self.tomo_angle_stepsize
|
||||
proposed_end = start + 180
|
||||
angle_end = min(proposed_end, max_allowed_angle)
|
||||
|
||||
angles = np.linspace(start, angle_end, num=N, endpoint=True)
|
||||
|
||||
else: # even subtomos → reverse direction
|
||||
# go FROM start_angle down toward 0
|
||||
min_allowed_angle = 0
|
||||
proposed_end = start - 180
|
||||
angle_end = max(proposed_end, min_allowed_angle)
|
||||
|
||||
angles = np.linspace(start, angle_end, num=N, endpoint=True)
|
||||
|
||||
for i, angle in enumerate(angles):
|
||||
|
||||
angle_end = start_angle + 180
|
||||
angles = np.linspace(
|
||||
start_angle + _tomo_shift_angles,
|
||||
angle_end,
|
||||
num=int(180 / self.tomo_angle_stepsize) + 1,
|
||||
endpoint=True,
|
||||
)
|
||||
# reverse even sub-tomograms
|
||||
if not (subtomo_number % 2):
|
||||
angles = np.flip(angles)
|
||||
for angle in angles:
|
||||
self.progress["subtomo"] = subtomo_number
|
||||
|
||||
# --- NEW LOGIC FOR OFFSET WHEN start_angle IS SPECIFIED ---
|
||||
if i == 0:
|
||||
step = self.tomo_angle_stepsize
|
||||
sa = start_angle
|
||||
|
||||
if start_angle is None:
|
||||
# normal operation: always start at zero
|
||||
self._subtomo_offset = 0
|
||||
|
||||
else:
|
||||
if subtomo_number % 2: # odd = forward direction
|
||||
self._subtomo_offset = round(sa / step)
|
||||
else: # even = reverse direction
|
||||
self._subtomo_offset = round((180 - sa) / step)
|
||||
|
||||
# progress index must always increase
|
||||
self.progress["subtomo_projection"] = self._subtomo_offset + i
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# existing progress fields
|
||||
self.progress["subtomo_total_projections"] = int(180 / self.tomo_angle_stepsize)
|
||||
self.progress["subtomo_projection"] = np.where(angles == angle)[0][0]
|
||||
self.progress["subtomo_total_projections"] = 180 / self.tomo_angle_stepsize
|
||||
self.progress["projection"] = (subtomo_number - 1) * self.progress[
|
||||
"subtomo_total_projections"
|
||||
] + self.progress["subtomo_projection"]
|
||||
self.progress["total_projections"] = 180 / self.tomo_angle_stepsize * 8
|
||||
self.progress["angle"] = angle
|
||||
|
||||
# finally do the scan at this angle
|
||||
self._tomo_scan_at_angle(angle, subtomo_number)
|
||||
|
||||
def _tomo_scan_at_angle(self, angle, subtomo_number):
|
||||
@@ -1676,7 +1591,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.bl_chk._bl_chk_start()
|
||||
if not self.special_angles:
|
||||
self._current_special_angles = []
|
||||
if self._current_special_angles:
|
||||
@@ -1699,10 +1614,10 @@ class Flomni(
|
||||
else:
|
||||
raise exc
|
||||
|
||||
# if self.bl_chk._bl_chk_stop() and not error_caught:
|
||||
successful = True
|
||||
# else:
|
||||
# self.bl_chk._bl_chk_wait_until_recovered()
|
||||
if self.bl_chk._bl_chk_stop() and not error_caught:
|
||||
successful = True
|
||||
else:
|
||||
self.bl_chk._bl_chk_wait_until_recovered()
|
||||
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)
|
||||
@@ -1710,16 +1625,6 @@ class Flomni(
|
||||
def tomo_scan(self, subtomo_start=1, start_angle=None, projection_number=None):
|
||||
"""start a tomo scan"""
|
||||
|
||||
if not self._check_eye_out_and_optics_in():
|
||||
print(
|
||||
"Attention: The setup is not in measurement condition.\nXray eye might be IN or the Xray optics OUT."
|
||||
)
|
||||
if self.OMNYTools.yesno("Shall I continue?", "n"):
|
||||
print("OK")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
|
||||
self.flomnigui_show_progress()
|
||||
|
||||
bec = builtins.__dict__.get("bec")
|
||||
@@ -1733,19 +1638,19 @@ class Flomni(
|
||||
):
|
||||
|
||||
# pylint: disable=undefined-variable
|
||||
# if bec.active_account != "":
|
||||
# self.tomo_id = self.add_sample_database(
|
||||
# self.sample_name,
|
||||
# str(datetime.date.today()),
|
||||
# bec.active_account,
|
||||
# bec.queue.next_scan_number,
|
||||
# "flomni",
|
||||
# "test additional info",
|
||||
# "BEC",
|
||||
# )
|
||||
# self.write_pdf_report()
|
||||
# else:
|
||||
self.tomo_id = 0
|
||||
if bec.active_account != "":
|
||||
self.tomo_id = self.add_sample_database(
|
||||
self.sample_name,
|
||||
str(datetime.date.today()),
|
||||
bec.active_account.decode(),
|
||||
bec.queue.next_scan_number,
|
||||
"flomni",
|
||||
"test additional info",
|
||||
"BEC",
|
||||
)
|
||||
self.write_pdf_report()
|
||||
else:
|
||||
self.tomo_id = 0
|
||||
|
||||
with scans.dataset_id_on_hold:
|
||||
if self.tomo_type == 1:
|
||||
@@ -1850,11 +1755,6 @@ class Flomni(
|
||||
else:
|
||||
raise FlomniError("undefined tomo type")
|
||||
|
||||
self.progress["projection"] = self.progress["total_projections"]
|
||||
self.progress["subtomo_projection"] = self.progress["subtomo_total_projections"]
|
||||
self._print_progress()
|
||||
self.OMNYTools.printgreenbold("Tomoscan finished")
|
||||
|
||||
def _print_progress(self):
|
||||
print("\x1b[95mProgress report:")
|
||||
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
|
||||
@@ -1936,7 +1836,7 @@ class Flomni(
|
||||
|
||||
return angle, subtomo_number
|
||||
|
||||
def tomo_reconstruct(self, base_path="~/data/raw/logs/reconstruction_queue"):
|
||||
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(
|
||||
@@ -1946,7 +1846,9 @@ class Flomni(
|
||||
)
|
||||
|
||||
def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None:
|
||||
tomo_scan_numbers_file = os.path.expanduser("~/data/raw/logs/tomography_scannumbers.txt")
|
||||
tomo_scan_numbers_file = os.path.expanduser(
|
||||
"~/tomography_scannumbers.txt"
|
||||
)
|
||||
with open(tomo_scan_numbers_file, "a+") as out_file:
|
||||
# pylint: disable=undefined-variable
|
||||
out_file.write(
|
||||
@@ -1991,6 +1893,7 @@ class Flomni(
|
||||
f"{str(datetime.datetime.now())}: flomni 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.flomni_fermat_scan(
|
||||
fovx=self.fovx,
|
||||
@@ -2046,6 +1949,7 @@ class Flomni(
|
||||
)
|
||||
print(f"\nSample name: {self.sample_name}\n")
|
||||
|
||||
|
||||
if self.OMNYTools.yesno("Are these parameters correctly set for your scan?", "y"):
|
||||
print("... excellent!")
|
||||
else:
|
||||
@@ -2193,4 +2097,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()
|
||||
@@ -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
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
|
||||
|
||||
|
||||
class FlomniOpticsMixin:
|
||||
@@ -16,18 +16,12 @@ class FlomniOpticsMixin:
|
||||
return param.get(var)
|
||||
|
||||
def feye_out(self):
|
||||
dev.omnyfsh.fshclose()
|
||||
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)
|
||||
@@ -38,37 +32,16 @@ class FlomniOpticsMixin:
|
||||
|
||||
feyex_in = self._get_user_param_safe("feyex", "in")
|
||||
feyey_in = self._get_user_param_safe("feyey", "in")
|
||||
|
||||
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)
|
||||
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
|
||||
#self.align.update_frame()
|
||||
|
||||
def _ffzp_in(self):
|
||||
foptx_in = self._get_user_param_safe("foptx", "in")
|
||||
fopty_in = self._get_user_param_safe("fopty", "in")
|
||||
|
||||
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.")
|
||||
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
|
||||
|
||||
def ffzp_in(self):
|
||||
"""
|
||||
@@ -111,85 +84,19 @@ 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]
|
||||
|
||||
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.")
|
||||
umv(dev.fosax, fosax_in, dev.fosay, fosay_in)
|
||||
umv(dev.fosaz, fosaz_in)
|
||||
|
||||
# 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()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import builtins
|
||||
import time
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
|
||||
@@ -18,17 +17,10 @@ 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
|
||||
@@ -36,11 +28,9 @@ 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.flomni_window = self.gui.new("flomni", timeout=self.GUI_RPC_TIMEOUT)
|
||||
time.sleep(1)
|
||||
self.gui.new("flomni")
|
||||
|
||||
def flomnigui_stop_gui(self):
|
||||
self.gui.flomni.hide()
|
||||
@@ -50,11 +40,9 @@ class flomniGuiTools:
|
||||
|
||||
def flomnigui_show_xeyealign(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_is_missing("xeyegui"):
|
||||
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.xeyegui = self.gui.flomni.new(
|
||||
"XRayEye", object_name="xrayeye", timeout=self.GUI_RPC_TIMEOUT
|
||||
)
|
||||
self.xeyegui = self.gui.flomni.new("XRayEye", object_name="xrayeye")
|
||||
# start live
|
||||
if not dev.cam_xeye.live_mode_enabled.get():
|
||||
dev.cam_xeye.live_mode_enabled.put(True)
|
||||
@@ -62,11 +50,9 @@ class flomniGuiTools:
|
||||
|
||||
def flomnigui_show_xeyealign_fittab(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_is_missing("xeyegui"):
|
||||
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.xeyegui = self.gui.flomni.new(
|
||||
"XRayEye", object_name="xrayeye", timeout=self.GUI_RPC_TIMEOUT
|
||||
)
|
||||
self.xeyegui = self.gui.flomni.new("XRayEye")
|
||||
self.xeyegui.switch_tab("fit")
|
||||
|
||||
def _flomnigui_check_attribute_not_exists(self, attribute_name):
|
||||
@@ -82,39 +68,31 @@ 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()
|
||||
self.camera_gripper_image = self.gui.flomni.new("Image")
|
||||
camera_gripper_image = self.gui.flomni.new("Image")
|
||||
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
|
||||
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
|
||||
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
|
||||
dev.cam_flomni_gripper.start_live_mode()
|
||||
else:
|
||||
print("Cannot open camera_gripper. Device does not exist.")
|
||||
self.camera_overview_image = self.gui.flomni.new("Image")
|
||||
camera_overview_image = self.gui.flomni.new("Image")
|
||||
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
|
||||
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
|
||||
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
|
||||
dev.cam_flomni_overview.start_live_mode()
|
||||
else:
|
||||
print("Cannot open camera_overview. Device does not exist.")
|
||||
@@ -123,21 +101,15 @@ class flomniGuiTools:
|
||||
# dev.cam_flomni_overview.stop_live_mode()
|
||||
# dev.cam_flomni_gripper.stop_live_mode()
|
||||
# dev.cam_xeye.live_mode = False
|
||||
if hasattr(self.gui, "flomni"):
|
||||
self.gui.flomni.delete_all(timeout=self.GUI_RPC_TIMEOUT)
|
||||
self.gui.flomni.delete_all()
|
||||
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_is_missing("idle_text_box"):
|
||||
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.idle_text_box = self.gui.flomni.new("TextBox")
|
||||
idle_text_box = self.gui.flomni.new("TextBox")
|
||||
text = (
|
||||
"<pre>"
|
||||
+ "██████╗ ███████╗ ██████╗ ███████╗██╗ ██████╗ ███╗ ███╗███╗ ██╗██╗\n"
|
||||
@@ -148,7 +120,7 @@ class flomniGuiTools:
|
||||
+ "╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝\n"
|
||||
+ "</pre>"
|
||||
)
|
||||
self.idle_text_box.set_html_text(text)
|
||||
idle_text_box.set_html_text(text)
|
||||
|
||||
def flomnigui_docs(self, filename: str | None = None):
|
||||
import csaxs_bec
|
||||
@@ -192,7 +164,7 @@ class flomniGuiTools:
|
||||
# --- GUI handling (active existence check) ----------------------------
|
||||
self.flomnigui_show_gui()
|
||||
|
||||
if self._flomnigui_is_missing("pdf_viewer"):
|
||||
if self._flomnigui_check_attribute_not_exists("PdfViewerWidget"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
|
||||
|
||||
@@ -210,7 +182,7 @@ class flomniGuiTools:
|
||||
|
||||
def flomnigui_show_progress(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_is_missing("progressbar"):
|
||||
if self._flomnigui_check_attribute_not_exists("progressbar"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
# Add a new dock with a RingProgressBar widget
|
||||
self.progressbar = self.gui.flomni.new("RingProgressBar")
|
||||
@@ -235,16 +207,7 @@ class flomniGuiTools:
|
||||
main_progress_ring.set_value(progress)
|
||||
subtomo_progress_ring.set_value(subtomo_progress)
|
||||
|
||||
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'])}"
|
||||
)
|
||||
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']}"
|
||||
self.progressbar.set_center_label(text)
|
||||
|
||||
|
||||
|
||||
@@ -231,6 +231,10 @@ 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()
|
||||
@@ -247,14 +251,9 @@ class XrayEyeAlign:
|
||||
|
||||
print("Automatically loading new alignment parameters from xray eye alignment.\n")
|
||||
|
||||
self.flomni.read_alignment_offset(get_data_from_gui=True)
|
||||
self.flomni.read_alignment_offset()
|
||||
|
||||
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")
|
||||
|
||||
@@ -852,6 +852,18 @@ 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:
|
||||
@@ -1276,6 +1288,7 @@ 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,
|
||||
|
||||
@@ -48,7 +48,7 @@ class OMNYOpticsMixin:
|
||||
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
|
||||
|
||||
def oeye_out(self):
|
||||
dev.omnyfsh.fshclose()
|
||||
fshclose()
|
||||
if self.OMNYTools.yesno("Did you move in the optics?"):
|
||||
umv(dev.oeyez, -2)
|
||||
self._oeyey_mv(-60.3)
|
||||
|
||||
@@ -38,14 +38,12 @@ 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"):
|
||||
"""
|
||||
@@ -56,7 +54,6 @@ class XRayEye(RPCBase):
|
||||
y_enable(bool): enable y motor controls
|
||||
"""
|
||||
|
||||
@rpc_timeout(20)
|
||||
@rpc_call
|
||||
def enable_submit_button(self, enable: "bool"):
|
||||
"""
|
||||
@@ -93,14 +90,12 @@ 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):
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -368,7 +367,6 @@ 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)
|
||||
|
||||
@@ -377,7 +375,6 @@ 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)
|
||||
|
||||
@@ -398,7 +395,6 @@ class XRayEye(BECWidget, QWidget):
|
||||
################################################################################
|
||||
|
||||
@SafeSlot(str)
|
||||
@rpc_timeout(20)
|
||||
def switch_tab(self, tab: str):
|
||||
if tab == "fit":
|
||||
self.tab_widget.setCurrentIndex(1)
|
||||
@@ -416,7 +412,6 @@ 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)
|
||||
@@ -465,7 +460,6 @@ 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
|
||||
@@ -478,7 +472,6 @@ 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.
|
||||
@@ -516,7 +509,6 @@ 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"
|
||||
|
||||
@@ -227,7 +227,7 @@ ftransy:
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
sensor_voltage: -1.1
|
||||
sensor_voltage: -2.4
|
||||
ftransz:
|
||||
description: Sample transer Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -344,9 +344,6 @@ 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
|
||||
@@ -364,9 +361,6 @@ 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
|
||||
@@ -382,9 +376,6 @@ 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
|
||||
@@ -395,16 +386,6 @@ 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 ############################
|
||||
############################################################
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
galilrioesxbox:
|
||||
description: Galil RIO for remote gain switching and slow reading ES XBox
|
||||
deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
|
||||
deviceConfig:
|
||||
host: galilrioesft.psi.ch
|
||||
enabled: true
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
bpm1:
|
||||
readoutPriority: baseline
|
||||
deviceClass: csaxs_bec.devices.pseudo_devices.bpm.BPM
|
||||
deviceConfig:
|
||||
blade_t: galilrioesxbox.analog_in.ch0
|
||||
blade_r: galilrioesxbox.analog_in.ch1
|
||||
blade_b: galilrioesxbox.analog_in.ch2
|
||||
blade_l: galilrioesxbox.analog_in.ch3
|
||||
enabled: true
|
||||
readOnly: false
|
||||
softwareTrigger: true
|
||||
needs:
|
||||
- galilrioesxbox
|
||||
|
||||
@@ -104,7 +104,7 @@ DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("B", CHANNELREFERENCE.A),
|
||||
("C", CHANNELREFERENCE.T0), # T0
|
||||
("D", CHANNELREFERENCE.C),
|
||||
("E", CHANNELREFERENCE.B), # B One extra pulse once shutter closes for MCS
|
||||
("E", CHANNELREFERENCE.D), # D One extra pulse once shutter closes for MCS
|
||||
("F", CHANNELREFERENCE.E), # E + 1mu s
|
||||
("G", CHANNELREFERENCE.T0),
|
||||
("H", CHANNELREFERENCE.G),
|
||||
@@ -213,23 +213,8 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
|
||||
# NOTE Burst delay should be set to 0, don't remove as this will not be checked
|
||||
# Also set the burst count to 1 to only have a single pulse for DDG1.
|
||||
# As the IOC may be out of sync with the HW, we make sure that we set the default parameters
|
||||
# in the IOC to the expected values. In the past, we've experienced that IOC and HW can go out
|
||||
# of sync.
|
||||
self.burst_delay.put(1)
|
||||
time.sleep(0.02) # Give HW time to process
|
||||
self.burst_delay.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_count.put(2)
|
||||
time.sleep(0.02)
|
||||
self.burst_count.put(1)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_mode.put(1)
|
||||
time.sleep(0.02)
|
||||
self.burst_mode.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
def keep_shutter_open_during_scan(self, open: True) -> None:
|
||||
"""
|
||||
@@ -306,24 +291,17 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# Burst Period DDG1
|
||||
# Set burst_period to shutter width
|
||||
# c/t0 + self._shutter_to_open_delay + exp_time * burst_count
|
||||
# SHUTTER WIDTH timing consists of the delay for the shutter to open
|
||||
# + the exposure time * frames per trigger
|
||||
shutter_width = self._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
# TOTAL EXPOSURE accounts for the shutter to open AND close. In addition, we add
|
||||
# a short additional delay of 3e-6 to allow for the extra trigger through 'ef'
|
||||
# (delay of 1e-6, width of 1e-6)
|
||||
total_exposure_time = 2 * self._shutter_to_open_delay + exp_time * frames_per_trigger + 3e-6
|
||||
shutter_width = (
|
||||
self._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
) # Shutter starts closing at end of exposure
|
||||
if self.burst_period.get() != shutter_width:
|
||||
# The burst_period has to be slightly longer
|
||||
self.burst_period.put(total_exposure_time)
|
||||
self.burst_period.put(shutter_width)
|
||||
|
||||
# Trigger DDG2
|
||||
# a = t0 + 2ms, b = a + 1us
|
||||
# a has reference to t0, b has reference to a
|
||||
# AB is delayed by the shutter opening time, and the falling edge indicates the shutter has
|
||||
# fully closed, it has to be considered as the blocking signal for the next acquisition to start.
|
||||
# PS: + 3e-6
|
||||
self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=shutter_width)
|
||||
# Add delay of self._shutter_to_open_delay to allow shutter to open
|
||||
self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=1e-6)
|
||||
|
||||
# Trigger shutter
|
||||
# d = c/t0 + self._shutter_to_open_delay + exp_time * burst_count + 1ms
|
||||
@@ -343,7 +321,7 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=0)
|
||||
else:
|
||||
self.set_delay_pairs(channel="ef", delay=1e-6, width=1e-6)
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=1e-6)
|
||||
|
||||
# NOTE Add additional sleep to make sure that the IOC and DDG HW process the values properly
|
||||
# This value has been choosen empirically after testing with the HW. It's
|
||||
|
||||
@@ -29,7 +29,6 @@ from ophyd_devices import DeviceStatus, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
BURSTCONFIG,
|
||||
CHANNELREFERENCE,
|
||||
OUTPUTPOLARITY,
|
||||
STATUSBITS,
|
||||
@@ -38,6 +37,7 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
||||
ChannelConfig,
|
||||
DelayGeneratorCSAXS,
|
||||
LiteralChannels,
|
||||
BURSTCONFIG,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
@@ -138,24 +138,6 @@ class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# Set burst config
|
||||
self.burst_config.put(BURSTCONFIG.FIRST_CYCLE.value)
|
||||
|
||||
# TODO As the IOC may be out of sync with the HW, we make sure that we set the default parameters
|
||||
# in the IOC to the expected values. In the past, we've experienced that IOC and HW can go out
|
||||
# of sync.
|
||||
self.burst_delay.put(1)
|
||||
time.sleep(0.02) # Give HW time to process
|
||||
self.burst_delay.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_count.put(2)
|
||||
time.sleep(0.02)
|
||||
self.burst_count.put(1)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_mode.put(1)
|
||||
time.sleep(0.02)
|
||||
self.burst_mode.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from typing import TYPE_CHECKING, Callable, Literal
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd.utils.errors import WaitTimeoutError
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, Kind
|
||||
from ophyd_devices import (
|
||||
@@ -513,22 +512,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
# that the acquisition finishes on the card and that data is emitted to BEC. If the acquisition
|
||||
# was already finished (i.e. normal step scan sends 1 extra pulse per burst cycle), this will
|
||||
# not have any effect as the card will already be in DONE state and signal.
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
expected_points = int(
|
||||
self.scan_info.msg.num_points
|
||||
* self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
)
|
||||
|
||||
status = CompareStatus(self.current_channel, expected_points-1, operation_success=">=")
|
||||
try:
|
||||
status.wait(timeout=5)
|
||||
except WaitTimeoutError:
|
||||
text = f"Device {self.name} received num points {self.current_channel.get()} / {expected_points}. Device timed out after 5s."
|
||||
logger.error(text)
|
||||
raise TimeoutError(text)
|
||||
|
||||
# Manually set the last advance
|
||||
self.software_channel_advance.put(1)
|
||||
self.software_channel_advance.put(1)
|
||||
|
||||
# Prepare and register status callback for the async monitoring loop
|
||||
status_async_data = StatusBase(obj=self)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import threading
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib import bec_logger, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
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,
|
||||
@@ -90,8 +92,7 @@ class RtFlomniController(Controller):
|
||||
parent._min_scan_buffer_reached = False
|
||||
start_time = time.time()
|
||||
for pos_index, pos in enumerate(positions):
|
||||
cmd = f"s{pos[0]:.05f},{pos[1]:.05f},{pos[2]:.05f}"
|
||||
parent.socket_put_and_receive(cmd)
|
||||
parent.socket_put_and_receive(f"s{pos[0]:.05f},{pos[1]:.05f},{pos[2]:.05f}")
|
||||
if pos_index > 100:
|
||||
parent._min_scan_buffer_reached = True
|
||||
parent._min_scan_buffer_reached = True
|
||||
@@ -173,12 +174,11 @@ class RtFlomniController(Controller):
|
||||
self.set_device_read_write("foptx", False)
|
||||
self.set_device_read_write("fopty", False)
|
||||
|
||||
def move_samx_to_scan_region(self, cenx: float, move_in_this_routine: bool = False):
|
||||
# attention. a movement will clear all positions in the rt trajectory generator!
|
||||
if move_in_this_routine == True:
|
||||
self.device_manager.devices.rtx.obj.move(cenx, wait=True)
|
||||
def move_samx_to_scan_region(self, fovx: float, cenx: float):
|
||||
#new routine not using fovx anymore
|
||||
self.device_manager.devices.rtx.obj.move(cenx, wait=True)
|
||||
time.sleep(0.05)
|
||||
# at cenx we expect the PID to be close to zero for a good fsamx position
|
||||
#at cenx we expect the PID to be close to zero for a good fsamx position
|
||||
if self.rt_pid_voltage is None:
|
||||
rtx = self.device_manager.devices.rtx
|
||||
self.rt_pid_voltage = rtx.user_parameter.get("rt_pid_voltage")
|
||||
@@ -188,31 +188,29 @@ class RtFlomniController(Controller):
|
||||
)
|
||||
logger.info(f"Using PID voltage from rtx user parameter: {self.rt_pid_voltage}")
|
||||
expected_voltage = self.rt_pid_voltage
|
||||
# logger.info(f"Expected PID voltage: {expected_voltage}")
|
||||
#logger.info(f"Expected PID voltage: {expected_voltage}")
|
||||
logger.info(f"Current PID voltage: {self.get_pid_x()}")
|
||||
wait_on_exit = False
|
||||
# we allow 2V range from center, this corresponds to 30 microns
|
||||
#we allow 2V range from center, this corresponds to 30 microns
|
||||
if np.abs(self.get_pid_x() - expected_voltage) < 2:
|
||||
logger.info("No correction of fsamx needed")
|
||||
else:
|
||||
fsamx = self.device_manager.devices.fsamx
|
||||
fsamx.obj.controller.socket_put_confirmed("axspeed[4]=0.1*stppermm[4]")
|
||||
while True:
|
||||
# when we correct, then to 1 V, within 15 microns
|
||||
#when we correct, then to 1 V, within 15 microns
|
||||
if np.abs(self.get_pid_x() - expected_voltage) < 1:
|
||||
logger.info("No further correction needed")
|
||||
break
|
||||
wait_on_exit = True
|
||||
# disable FZP piezo feedback
|
||||
#disable FZP piezo feedback
|
||||
self.socket_put("v0")
|
||||
fsamx.read_only = False
|
||||
logger.info(f"Current PID voltage: {self.get_pid_x()}")
|
||||
# here we accumulate the correction
|
||||
#here we accumulate the correction
|
||||
fsamx.obj.pid_x_correction -= (self.get_pid_x() - expected_voltage) * 0.006
|
||||
fsamx_in = fsamx.user_parameter.get("in")
|
||||
logger.info(
|
||||
f"Moving fsamx to {cenx / 1000 * 0.7 + fsamx.obj.pid_x_correction}, PID portion of that {fsamx.obj.pid_x_correction}"
|
||||
)
|
||||
logger.info(f"Moving fsamx to {cenx / 1000 * 0.7 + fsamx.obj.pid_x_correction}, PID portion of that {fsamx.obj.pid_x_correction}")
|
||||
fsamx.obj.move(fsamx_in + cenx / 1000 * 0.7 + fsamx.obj.pid_x_correction, wait=True)
|
||||
fsamx.read_only = True
|
||||
time.sleep(0.1)
|
||||
@@ -221,7 +219,7 @@ class RtFlomniController(Controller):
|
||||
|
||||
if wait_on_exit:
|
||||
time.sleep(1)
|
||||
# enable fast FZP feedback again
|
||||
#enable fast FZP feedback again
|
||||
self.socket_put("v1")
|
||||
|
||||
@threadlocked
|
||||
@@ -392,7 +390,7 @@ class RtFlomniController(Controller):
|
||||
val = float(self.socket_put_and_receive(f"j{axis_number}").strip())
|
||||
return val
|
||||
|
||||
def laser_tracker_check_signalstrength(self, verbose=True):
|
||||
def laser_tracker_check_signalstrength(self):
|
||||
if not self.laser_tracker_check_enabled():
|
||||
returnval = "disabled"
|
||||
else:
|
||||
@@ -403,10 +401,9 @@ 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")
|
||||
if verbose:
|
||||
print(f"low signal: {low_signal}")
|
||||
print(f"min signal: {min_signal}")
|
||||
print(f"signal: {signal}")
|
||||
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:
|
||||
@@ -430,6 +427,27 @@ 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():
|
||||
@@ -469,6 +487,91 @@ 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
|
||||
@@ -514,18 +617,6 @@ 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):
|
||||
@@ -736,185 +827,6 @@ 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
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import time
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Kind, Signal
|
||||
from ophyd_devices.interfaces.base_classes.psi_pseudo_device_base import PSIPseudoDeviceBase
|
||||
from ophyd_devices.utils.bec_processed_signal import BECProcessedSignal
|
||||
|
||||
|
||||
class BPM(PSIPseudoDeviceBase):
|
||||
"""BPM positioner pseudo device."""
|
||||
|
||||
# Blade signals, a,b,c,d
|
||||
top = Cpt(
|
||||
BECProcessedSignal, name="top", model_config=None, kind=Kind.config, doc="... top blade"
|
||||
)
|
||||
right = Cpt(
|
||||
BECProcessedSignal, name="right", model_config=None, kind=Kind.config, doc="... right blade"
|
||||
)
|
||||
bot = Cpt(
|
||||
BECProcessedSignal, name="bot", model_config=None, kind=Kind.config, doc="... bot blade"
|
||||
)
|
||||
left = Cpt(
|
||||
BECProcessedSignal, name="left", model_config=None, kind=Kind.config, doc="... left blade"
|
||||
)
|
||||
|
||||
# Virtual signals
|
||||
pos_x = Cpt(
|
||||
BECProcessedSignal, name="pos_x", model_config=None, kind=Kind.config, doc="... pos_x"
|
||||
)
|
||||
pos_y = Cpt(
|
||||
BECProcessedSignal, name="pos_y", model_config=None, kind=Kind.config, doc="... pos_y"
|
||||
)
|
||||
diagonal = Cpt(
|
||||
BECProcessedSignal, name="diagonal", model_config=None, kind=Kind.config, doc="... diagonal"
|
||||
)
|
||||
intensity = Cpt(
|
||||
BECProcessedSignal,
|
||||
name="intensity",
|
||||
model_config=None,
|
||||
kind=Kind.config,
|
||||
doc="... intensity",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
blade_t: str,
|
||||
blade_r: str,
|
||||
blade_b: str,
|
||||
blade_l: str,
|
||||
device_manager=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name=name, device_manager=device_manager, **kwargs)
|
||||
# Get all blade signal objects from utility method
|
||||
signal_t = self.top.get_device_object_from_bec(
|
||||
object_name=blade_t, signal_name=self.name, device_manager=device_manager
|
||||
)
|
||||
signal_r = self.right.get_device_object_from_bec(
|
||||
object_name=blade_r, signal_name=self.name, device_manager=device_manager
|
||||
)
|
||||
signal_b = self.bot.get_device_object_from_bec(
|
||||
object_name=blade_b, signal_name=self.name, device_manager=device_manager
|
||||
)
|
||||
signal_l = self.left.get_device_object_from_bec(
|
||||
object_name=blade_l, signal_name=self.name, device_manager=device_manager
|
||||
)
|
||||
|
||||
# Set compute methods for blade signals and virtual signals
|
||||
self.top.set_compute_method(self._compute_blade_signal, signal=signal_t)
|
||||
self.right.set_compute_method(self._compute_blade_signal, signal=signal_r)
|
||||
self.bot.set_compute_method(self._compute_blade_signal, signal=signal_b)
|
||||
self.left.set_compute_method(self._compute_blade_signal, signal=signal_l)
|
||||
|
||||
self.intensity.set_compute_method(
|
||||
self._compute_intensity, top=self.top, right=self.right, bot=self.bot, left=self.left
|
||||
)
|
||||
self.pos_x.set_compute_method(
|
||||
self._compute_pos_x, left=self.left, top=self.top, right=self.right, bot=self.bot
|
||||
)
|
||||
self.pos_y.set_compute_method(
|
||||
self._compute_pos_y, left=self.left, top=self.top, right=self.right, bot=self.bot
|
||||
)
|
||||
self.diagonal.set_compute_method(
|
||||
self._compute_diagonal, left=self.left, top=self.top, right=self.right, bot=self.bot
|
||||
)
|
||||
|
||||
def _compute_blade_signal(self, signal: Signal) -> float:
|
||||
return signal.get()
|
||||
|
||||
def _compute_intensity(self, top: Signal, right: Signal, bot: Signal, left: Signal) -> float:
|
||||
intensity = top.get() + right.get() + bot.get() + left.get()
|
||||
return intensity
|
||||
|
||||
def _compute_pos_x(self, left: Signal, top: Signal, right: Signal, bot: Signal) -> float:
|
||||
sum_left = left.get() + top.get()
|
||||
sum_right = right.get() + bot.get()
|
||||
sum_total = sum_left + sum_right
|
||||
if sum_total == 0:
|
||||
return 0.0
|
||||
return (sum_left - sum_right) / sum_total
|
||||
|
||||
def _compute_pos_y(self, left: Signal, top: Signal, right: Signal, bot: Signal) -> float:
|
||||
sum_top = top.get() + right.get()
|
||||
sum_bot = bot.get() + left.get()
|
||||
sum_total = sum_top + sum_bot
|
||||
if sum_total == 0:
|
||||
return 0.0
|
||||
return (sum_top - sum_bot) / sum_total
|
||||
|
||||
def _compute_diagonal(self, left: Signal, top: Signal, right: Signal, bot: Signal) -> float:
|
||||
sum_diag1 = left.get() + right.get()
|
||||
sum_diag2 = top.get() + bot.get()
|
||||
sum_total = sum_diag1 + sum_diag2
|
||||
if sum_total == 0:
|
||||
return 0.0
|
||||
return (sum_diag1 - sum_diag2) / sum_total
|
||||
@@ -1 +0,0 @@
|
||||
# from ophyd
|
||||
@@ -27,19 +27,20 @@ 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 AsyncFlyScanBase
|
||||
from bec_server.scan_server.scans import SyncFlyScanBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class FlomniFermatScan(AsyncFlyScanBase):
|
||||
class FlomniFermatScan(SyncFlyScanBase):
|
||||
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,
|
||||
@@ -103,14 +104,6 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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()
|
||||
@@ -120,6 +113,10 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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
|
||||
@@ -168,8 +165,7 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
if self.flomni_rotation_status:
|
||||
self.flomni_rotation_status.wait()
|
||||
|
||||
# rtx_status = yield from self.stubs.set(device="rtx", value=self.positions[0][0], wait=False)
|
||||
rtx_status = yield from self.stubs.set(device="rtx", value=self.cenx, wait=False)
|
||||
rtx_status = yield from self.stubs.set(device="rtx", value=self.positions[0][0], wait=False)
|
||||
rtz_status = yield from self.stubs.set(device="rtz", value=self.positions[0][2], wait=False)
|
||||
|
||||
yield from self.stubs.send_rpc_and_wait("rtx", "controller.laser_tracker_on")
|
||||
@@ -177,15 +173,13 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
rtx_status.wait()
|
||||
rtz_status.wait()
|
||||
|
||||
# status = yield from self.stubs.send_rpc("rtx", "move", self.cenx)
|
||||
# status.wait()
|
||||
yield from self._transfer_positions_to_flomni()
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.move_samx_to_scan_region", self.fovx, self.cenx
|
||||
)
|
||||
tracker_signal_status = yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.laser_tracker_check_signalstrength"
|
||||
)
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.move_samx_to_scan_region", self.cenx
|
||||
)
|
||||
# self.device_manager.connector.send_client_info(tracker_signal_status)
|
||||
if tracker_signal_status == "low":
|
||||
error_info = messages.ErrorInfo(
|
||||
@@ -293,29 +287,33 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
return np.array(positions)
|
||||
|
||||
def scan_core(self):
|
||||
# send off the flyer
|
||||
yield from self.stubs.kickoff(device="rt_flyer")
|
||||
# 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')}"
|
||||
)
|
||||
|
||||
# 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"""
|
||||
# in flomni, we need to move to the start position of the next scan, which is the end position of the current scan
|
||||
# this method is called in finalize and overwrites the default move_to_start()
|
||||
if isinstance(self.positions, np.ndarray) and len(self.positions[-1]) == 3:
|
||||
# yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=self.positions[-1])
|
||||
# in x we move to cenx, then we avoid jumps in centering routine
|
||||
value = self.positions[-1]
|
||||
value[0] = self.cenx
|
||||
yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=value)
|
||||
yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=self.positions[-1])
|
||||
return
|
||||
|
||||
logger.warning("No positions found to return to start")
|
||||
@@ -331,7 +329,6 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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()
|
||||
|
||||
@@ -50,15 +50,16 @@ 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.feedback_disable()` disable the closed loop operation to allow movement of coarse stages
|
||||
1. `flomni.rt_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. The GUI will present a fit of this data, which is automatically loaded to BEC for aligning the sample.
|
||||
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.
|
||||
|
||||
#### Fine alignment
|
||||
|
||||
After the xrayeyealign, a fine alignment needs to be performed using ptychography.
|
||||
_To bypass the fine alignment: `flomni.feye_out`_
|
||||
_To bypass the fine alignment: `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.
|
||||
@@ -70,13 +71,13 @@ _To bypass the fine alignment: `flomni.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_reconstruction matlab script.
|
||||
1. Before changing sample, verify that all subtomograms were completely acquired using the `tomo_recons 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
|
||||
`~/data/raw/logs/tomography_scannumbers.txt`
|
||||
`~/Data10/specES1/dat-files/omni_scannumbers.txt`
|
||||
|
||||
To continue an __interrupted tomography scan__:
|
||||
|
||||
|
||||
@@ -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: `dev.omnyfsh.fshclose()`
|
||||
1. close the shutter: `fshclose()`
|
||||
|
||||
#### X-ray eye alignment
|
||||
|
||||
|
||||
@@ -287,20 +287,19 @@ def test_ddg1_stage(mock_ddg1: DDG1):
|
||||
mock_ddg1.stage()
|
||||
|
||||
shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
total_exposure = 2 * mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger + 3e-6
|
||||
|
||||
assert np.isclose(mock_ddg1.burst_mode.get(), 1) # burst mode is enabled
|
||||
assert np.isclose(mock_ddg1.burst_delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), total_exposure)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), shutter_width)
|
||||
|
||||
# Trigger DDG2 through EXT/EN
|
||||
assert np.isclose(mock_ddg1.ab.delay.get(), 2e-3)
|
||||
assert np.isclose(mock_ddg1.ab.width.get(), shutter_width)
|
||||
assert np.isclose(mock_ddg1.ab.width.get(), 1e-6)
|
||||
# Shutter channel cd
|
||||
assert np.isclose(mock_ddg1.cd.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.cd.width.get(), shutter_width)
|
||||
# MCS channel ef or gate
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 1e-6)
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.ef.width.get(), 1e-6)
|
||||
|
||||
assert mock_ddg1.staged == ophyd.Staged.yes
|
||||
|
||||
Reference in New Issue
Block a user