From 26fe056c4a5c8ca8c95b8ca6f540487ef7575e0d Mon Sep 17 00:00:00 2001
From: Klaus Wakonig
Date: Fri, 7 Jul 2023 14:39:22 +0200
Subject: [PATCH] testing deployment
---
bec_plugins/__init__.py | 1 +
bec_plugins/bec_client/__init__.py | 1 +
.../{plugins => bec_client/hli}/__init__.py | 0
.../{plugins => }/bec_client/hli/spec_hli.py | 0
.../bec_client/plugins/LamNI/LamNI_logo.png | Bin
.../bec_client/plugins/LamNI/__init__.py | 2 +
.../bec_client/plugins/LamNI/bl_show_all.mac | 0
.../plugins/LamNI/lamni_optics_mixin.py | 45 +-
.../LamNI/load_additional_correction.py | 0
.../plugins/LamNI/x_ray_eye_align.py | 761 ++++++++++++++----
bec_plugins/bec_client/plugins/__init__.py | 3 +
.../bec_client/plugins/cSAXS/__init__.py | 0
.../bec_client/plugins/cSAXS/beamline_info.py | 108 +++
.../plugins/cSAXS/cSAXS_beamline.py | 0
.../startup/__init__.py} | 0
.../bec_client/startup/post_startup.py | 61 ++
bec_plugins/bec_client/startup/pre_startup.py | 25 +
.../bec_client/plugins/LamNI/__init__.py | 2 -
.../__init__.py} | 0
.../scan_plugins/LamNIFermatScan.py | 0
.../{plugins => }/scibec/lamni_config.py | 0
.../scibec/test_config_cSAXS.yaml | 0
deployment/autodeploy_version | 11 +
deployment/deploy.sh | 24 +
24 files changed, 865 insertions(+), 179 deletions(-)
create mode 100644 bec_plugins/bec_client/__init__.py
rename bec_plugins/{plugins => bec_client/hli}/__init__.py (100%)
rename bec_plugins/{plugins => }/bec_client/hli/spec_hli.py (100%)
rename bec_plugins/{plugins => }/bec_client/plugins/LamNI/LamNI_logo.png (100%)
create mode 100644 bec_plugins/bec_client/plugins/LamNI/__init__.py
rename bec_plugins/{plugins => }/bec_client/plugins/LamNI/bl_show_all.mac (100%)
rename bec_plugins/{plugins => }/bec_client/plugins/LamNI/lamni_optics_mixin.py (81%)
rename bec_plugins/{plugins => }/bec_client/plugins/LamNI/load_additional_correction.py (100%)
rename bec_plugins/{plugins => }/bec_client/plugins/LamNI/x_ray_eye_align.py (61%)
create mode 100644 bec_plugins/bec_client/plugins/__init__.py
rename bec_plugins/{plugins => }/bec_client/plugins/cSAXS/__init__.py (100%)
create mode 100644 bec_plugins/bec_client/plugins/cSAXS/beamline_info.py
rename bec_plugins/{plugins => }/bec_client/plugins/cSAXS/cSAXS_beamline.py (100%)
rename bec_plugins/{plugins/bec_client/startup/post-startup.py => bec_client/startup/__init__.py} (100%)
create mode 100644 bec_plugins/bec_client/startup/post_startup.py
create mode 100644 bec_plugins/bec_client/startup/pre_startup.py
delete mode 100644 bec_plugins/plugins/bec_client/plugins/LamNI/__init__.py
rename bec_plugins/{plugins/bec_client/startup/pre-startup.py => scan_server/__init__.py} (100%)
rename bec_plugins/{plugins => }/scan_server/scan_plugins/LamNIFermatScan.py (100%)
rename bec_plugins/{plugins => }/scibec/lamni_config.py (100%)
rename bec_plugins/{plugins => }/scibec/test_config_cSAXS.yaml (100%)
create mode 100644 deployment/autodeploy_version
create mode 100755 deployment/deploy.sh
diff --git a/bec_plugins/__init__.py b/bec_plugins/__init__.py
index e69de29..c8ba5d1 100644
--- a/bec_plugins/__init__.py
+++ b/bec_plugins/__init__.py
@@ -0,0 +1 @@
+from .bec_client import *
diff --git a/bec_plugins/bec_client/__init__.py b/bec_plugins/bec_client/__init__.py
new file mode 100644
index 0000000..ba24808
--- /dev/null
+++ b/bec_plugins/bec_client/__init__.py
@@ -0,0 +1 @@
+from .plugins import *
diff --git a/bec_plugins/plugins/__init__.py b/bec_plugins/bec_client/hli/__init__.py
similarity index 100%
rename from bec_plugins/plugins/__init__.py
rename to bec_plugins/bec_client/hli/__init__.py
diff --git a/bec_plugins/plugins/bec_client/hli/spec_hli.py b/bec_plugins/bec_client/hli/spec_hli.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/hli/spec_hli.py
rename to bec_plugins/bec_client/hli/spec_hli.py
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/LamNI_logo.png b/bec_plugins/bec_client/plugins/LamNI/LamNI_logo.png
similarity index 100%
rename from bec_plugins/plugins/bec_client/plugins/LamNI/LamNI_logo.png
rename to bec_plugins/bec_client/plugins/LamNI/LamNI_logo.png
diff --git a/bec_plugins/bec_client/plugins/LamNI/__init__.py b/bec_plugins/bec_client/plugins/LamNI/__init__.py
new file mode 100644
index 0000000..e651823
--- /dev/null
+++ b/bec_plugins/bec_client/plugins/LamNI/__init__.py
@@ -0,0 +1,2 @@
+from .load_additional_correction import lamni_read_additional_correction
+from .x_ray_eye_align import LamNI, XrayEyeAlign, MagLamNI, DataDrivenLamNI
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/bl_show_all.mac b/bec_plugins/bec_client/plugins/LamNI/bl_show_all.mac
similarity index 100%
rename from bec_plugins/plugins/bec_client/plugins/LamNI/bl_show_all.mac
rename to bec_plugins/bec_client/plugins/LamNI/bl_show_all.mac
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py b/bec_plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py
similarity index 81%
rename from bec_plugins/plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py
rename to bec_plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py
index 19a9059..ccf6d95 100644
--- a/bec_plugins/plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py
+++ b/bec_plugins/bec_client/plugins/LamNI/lamni_optics_mixin.py
@@ -1,9 +1,16 @@
+import builtins
import time
+
+from rich import box
from rich.console import Console
from rich.table import Table
-from rich import box
-from bec_client.plugins.cSAXS import fshclose
+from bec_client.plugins.cSAXS import epics_get, epics_put, fshclose
+
+# import builtins to avoid linter errors
+dev = builtins.__dict__.get("dev")
+umv = builtins.__dict__.get("umv")
+bec = builtins.__dict__.get("bec")
class LamNIOpticsMixin:
@@ -14,18 +21,22 @@ class LamNIOpticsMixin:
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
return param.get(var)
- def leyey_out(self):
+ def leye_out(self):
self.loptics_in()
fshclose()
leyey_out = self._get_user_param_safe("leyey", "out")
umv(dev.leyey, leyey_out)
epics_put("XOMNYI-XEYE-ACQ:0", 2)
- umv(dev.dttrz, 5830, dev.fttrz, 3338)
+ # move rotation stage to zero to avoid problems with wires
+ umv(dev.lsamrot, 0)
+ umv(dev.dttrz, 5854, dev.fttrz, 2395)
def leye_in(self):
bec.queue.next_dataset_number += 1
- umv(dev.dttrz, 5830 + 600, dev.fttrz, 3338 + 600)
+ # move rotation stage to zero to avoid problems with wires
+ umv(dev.lsamrot, 0)
+ umv(dev.dttrz, 6419.677, dev.fttrz, 2959.979)
while True:
moved_out = (input("Did the flight tube move out? (Y/n)") or "y").lower()
if moved_out == "y":
@@ -50,12 +61,12 @@ class LamNIOpticsMixin:
This will disable rt feedback, move the FZP and re-enabled the feedback.
"""
if "rtx" in dev and dev.rtx.enabled:
- dev.rtx.feedback_disable()
+ dev.rtx.controller.feedback_disable()
self._lfzp_in()
if "rtx" in dev and dev.rtx.enabled:
- dev.rtx.feedback_enable_with_reset()
+ dev.rtx.controller.feedback_enable_with_reset()
def loptics_in(self):
"""
@@ -67,7 +78,7 @@ class LamNIOpticsMixin:
def loptics_out(self):
"""Move out the lamni optics"""
if "rtx" in dev and dev.rtx.enabled:
- dev.rtx.feedback_disable()
+ dev.rtx.controller.feedback_disable()
# self.lcs_out()
self.losa_out()
@@ -77,7 +88,7 @@ class LamNIOpticsMixin:
if "rtx" in dev and dev.rtx.enabled:
time.sleep(1)
- dev.rtx.feedback_enable_with_reset()
+ dev.rtx.controller.feedback_enable_with_reset()
def lcs_in(self):
# umv lcsx -1.852 lcsy -0.095
@@ -114,15 +125,7 @@ class LamNIOpticsMixin:
distance = -loptz_val + 85.6 + 52
print(f"The sample is in a distance of {distance:.1f} mm from the FZP.")
- diameters = []
- diameters[0] = 80e-6
- diameters[1] = 100e-6
- diameters[2] = 120e-6
- diameters[3] = 150e-6
- diameters[4] = 170e-6
- diameters[5] = 200e-6
- diameters[6] = 220e-6
- diameters[7] = 250e-6
+ diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
mokev_val = dev.mokev.read()["mokev"]["value"]
console = Console()
@@ -142,7 +145,11 @@ class LamNIOpticsMixin:
beam_size = (
-diameter / (focal_distance * 1000) * (focal_distance * 1000 - distance) * 1e6
)
- table.add_row(f"{diameter}", f"{focal_distance:.2f} mm", f"{beam_size:.2f} microns")
+ table.add_row(
+ f"{diameter*1e6:.2f} microns",
+ f"{focal_distance:.2f} mm",
+ f"{beam_size:.2f} microns",
+ )
console.print(table)
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/load_additional_correction.py b/bec_plugins/bec_client/plugins/LamNI/load_additional_correction.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/plugins/LamNI/load_additional_correction.py
rename to bec_plugins/bec_client/plugins/LamNI/load_additional_correction.py
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/x_ray_eye_align.py b/bec_plugins/bec_client/plugins/LamNI/x_ray_eye_align.py
similarity index 61%
rename from bec_plugins/plugins/bec_client/plugins/LamNI/x_ray_eye_align.py
rename to bec_plugins/bec_client/plugins/LamNI/x_ray_eye_align.py
index b4010cb..6e1d2bb 100644
--- a/bec_plugins/plugins/bec_client/plugins/LamNI/x_ray_eye_align.py
+++ b/bec_plugins/bec_client/plugins/LamNI/x_ray_eye_align.py
@@ -1,6 +1,5 @@
import builtins
import datetime
-import math
import os
import subprocess
import threading
@@ -8,10 +7,11 @@ import time
from collections import defaultdict
from pathlib import Path
+import h5py
import numpy as np
-from bec_utils import bec_logger
-from bec_utils.logbook_connector import LogbookMessage
-from bec_utils.pdf_writer import PDFWriter
+from bec_lib.alarm_handler import AlarmBase
+from bec_lib.core import bec_logger
+from bec_lib.core.pdf_writer import PDFWriter
from typeguard import typechecked
from bec_client.plugins.cSAXS import epics_get, epics_put, fshclose, fshopen
@@ -19,6 +19,7 @@ from bec_client.plugins.cSAXS import epics_get, epics_put, fshclose, fshopen
from .lamni_optics_mixin import LamNIOpticsMixin
logger = bec_logger.logger
+bec = builtins.__dict__.get("bec")
class XrayEyeAlign:
@@ -33,6 +34,58 @@ class XrayEyeAlign:
self.scans = client.scans
self.xeye = self.device_manager.devices.xeye
self.alignment_values = defaultdict(list)
+ self._reset_init_values()
+ self.corr_pos_x = []
+ self.corr_pos_y = []
+ self.corr_angle = []
+ self.corr_pos_x_2 = []
+ self.corr_pos_y_2 = []
+ self.corr_angle_2 = []
+
+ def reset_correction(self):
+ self.corr_pos_x = []
+ self.corr_pos_y = []
+ self.corr_angle = []
+
+ def reset_correction_2(self):
+ self.corr_pos_x_2 = []
+ self.corr_pos_y_2 = []
+ self.corr_angle_2 = []
+
+ def reset_xray_eye_correction(self):
+ self.client.delete_global_var("tomo_fit_xray_eye")
+
+ @property
+ def tomo_fovx_offset(self):
+ val = self.client.get_global_var("tomo_fov_offset")
+ if val is None:
+ return 0.0
+ return val[0] / 1000
+
+ @tomo_fovx_offset.setter
+ @typechecked
+ def tomo_fovx_offset(self, val: float):
+ val_old = self.client.get_global_var("tomo_fov_offset")
+ if val_old is None:
+ val_old = [0.0, 0.0]
+ self.client.set_global_var("tomo_fov_offset", [val * 1000, val_old[1]])
+
+ @property
+ def tomo_fovy_offset(self):
+ val = self.client.get_global_var("tomo_fov_offset")
+ if val is None:
+ return 0.0
+ return val[1] / 1000
+
+ @tomo_fovy_offset.setter
+ @typechecked
+ def tomo_fovy_offset(self, val: float):
+ val_old = self.client.get_global_var("tomo_fov_offset")
+ if val_old is None:
+ val_old = [0.0, 0.0]
+ self.client.set_global_var("tomo_fov_offset", [val_old[0], val * 1000])
+
+ def _reset_init_values(self):
self.shift_xy = [0, 0]
self._xray_fov_xy = [0, 0]
@@ -93,6 +146,11 @@ class XrayEyeAlign:
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
def align(self):
+ # reset shift xy and fov params
+ self._reset_init_values()
+ self.reset_correction()
+ self.reset_correction_2()
+
# this makes sure we are in a defined state
self._disable_rt_feedback()
@@ -156,9 +214,9 @@ class XrayEyeAlign:
elif (
k == 1
): # received sample center value at samroy 0 ie the final base shift values
- print(
- f"Base shift values from movement are x {self.shift_xy[0]}, y {self.shift_xy[1]}"
- )
+ msg = f"Base shift values from movement are x {self.shift_xy[0]}, y {self.shift_xy[1]}"
+ print(msg)
+ logger.info(msg)
self.shift_xy[0] += (
self.alignment_values[0][0] - self.alignment_values[1][0]
) * 1000
@@ -170,7 +228,9 @@ class XrayEyeAlign:
)
self.scans.lamni_move_to_scan_center(
- self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
+ self.shift_xy[0] / 1000,
+ self.shift_xy[1] / 1000,
+ self.get_tomo_angle(),
).wait()
self.send_message("please wait ...")
@@ -179,7 +239,9 @@ class XrayEyeAlign:
time.sleep(1)
self.scans.lamni_move_to_scan_center(
- self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
+ self.shift_xy[0] / 1000,
+ self.shift_xy[1] / 1000,
+ self.get_tomo_angle(),
).wait()
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
@@ -196,12 +258,16 @@ class XrayEyeAlign:
self._disable_rt_feedback()
self.tomo_rotate((k - 1) * 45 - 45 / 2)
self.scans.lamni_move_to_scan_center(
- self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
+ self.shift_xy[0] / 1000,
+ self.shift_xy[1] / 1000,
+ self.get_tomo_angle(),
).wait()
self._disable_rt_feedback()
self.tomo_rotate((k - 1) * 45)
self.scans.lamni_move_to_scan_center(
- self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
+ self.shift_xy[0] / 1000,
+ self.shift_xy[1] / 1000,
+ self.get_tomo_angle(),
).wait()
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
@@ -228,7 +294,9 @@ class XrayEyeAlign:
self.shift_xy[0] = self.shift_xy[0] + _xrayeyalignmvx
self.shift_xy[1] = self.shift_xy[1] + _xrayeyalignmvy
self.scans.lamni_move_to_scan_center(
- self.shift_xy[0] / 1000, self.shift_xy[1] / 1000, self.get_tomo_angle()
+ self.shift_xy[0] / 1000,
+ self.shift_xy[1] / 1000,
+ self.get_tomo_angle(),
).wait()
print(
f"Current center horizontal {self.shift_xy[0]} vertical {self.shift_xy[1]}"
@@ -256,7 +324,7 @@ class XrayEyeAlign:
self.tomo_rotate(0)
print(
- "\n\nNEXT LOAD ALIGNMENT PARAMETERS\nby running lamni.align.read_alignment_parameters()\n"
+ "\n\nNEXT LOAD ALIGNMENT PARAMETERS\nby running lamni.align.read_xray_eye_correction()\n"
)
self.client.set_global_var("tomo_fov_offset", self.shift_xy)
@@ -274,7 +342,7 @@ class XrayEyeAlign:
)
alignment_values_file.write(f"{(k-2)*45}\t{fovx_offset}\t{fovy_offset}\n")
- def read_alignment_parameters(self, dir_path=os.path.expanduser("~/Data10/specES1/internal/")):
+ def read_xray_eye_correction(self, dir_path=os.path.expanduser("~/Data10/specES1/internal/")):
tomo_fit_xray_eye = np.zeros((2, 3))
with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
tomo_fit_xray_eye[0][0] = file.readline()
@@ -308,20 +376,150 @@ class XrayEyeAlign:
f"Y Offset {tomo_fit_xray_eye[1][2]}"
)
+ def lamni_compute_additional_correction_xeye_mu(self, angle):
+ tomo_fit_xray_eye = self.client.get_global_var("tomo_fit_xray_eye")
+ if tomo_fit_xray_eye is None:
+ print("Not applying any additional correction. No x-ray eye data available.\n")
+ return (0, 0)
+
+ # x amp, phase, offset, y amp, phase, offset
+ # 0 0 0 1 0 2 1 0 1 1 1 2
+ correction_x = (
+ tomo_fit_xray_eye[0][0] * np.sin(np.radians(angle) + tomo_fit_xray_eye[0][1])
+ + tomo_fit_xray_eye[0][2]
+ ) / 1000
+ correction_y = (
+ tomo_fit_xray_eye[1][0] * np.sin(np.radians(angle) + tomo_fit_xray_eye[1][1])
+ + tomo_fit_xray_eye[1][2]
+ ) / 1000
+
+ print(f"Xeye correction x {correction_x}, y {correction_y} for angle {angle}\n")
+ return (correction_x, correction_y)
+
+ def compute_additional_correction(self, angle):
+ if not self.corr_pos_x:
+ print("Not applying any additional correction. No data available.\n")
+ return (0, 0)
+
+ # find index of closest angle
+ for j, _ in enumerate(self.corr_pos_x):
+ newangledelta = np.fabs(self.corr_angle[j] - angle)
+ if j == 0:
+ angledelta = newangledelta
+ additional_correction_shift_x = self.corr_pos_x[j]
+ additional_correction_shift_y = self.corr_pos_y[j]
+ continue
+
+ if newangledelta < angledelta:
+ additional_correction_shift_x = self.corr_pos_x[j]
+ additional_correction_shift_y = self.corr_pos_y[j]
+ angledelta = newangledelta
+
+ if additional_correction_shift_x == 0 and angle < self.corr_angle[0]:
+ additional_correction_shift_x = self.corr_pos_x[0]
+ additional_correction_shift_y = self.corr_pos_y[0]
+
+ if additional_correction_shift_x == 0 and angle > self.corr_angle[-1]:
+ additional_correction_shift_x = self.corr_pos_x[-1]
+ additional_correction_shift_y = self.corr_pos_y[-1]
+ print(
+ f"Additional correction shifts: {additional_correction_shift_x} {additional_correction_shift_y}"
+ )
+ return (additional_correction_shift_x, additional_correction_shift_y)
+
+ def read_additional_correction(self, correction_file: str):
+ with open(correction_file, "r") as f:
+ num_elements = f.readline()
+ int_num_elements = int(num_elements.split(" ")[2])
+ print(int_num_elements)
+ corr_pos_x = []
+ corr_pos_y = []
+ corr_angle = []
+ for j in range(0, int_num_elements * 3):
+ line = f.readline()
+ value = line.split(" ")[2]
+ name = line.split(" ")[0].split("[")[0]
+ if name == "corr_pos_x":
+ corr_pos_x.append(float(value) / 1000)
+ elif name == "corr_pos_y":
+ corr_pos_y.append(float(value) / 1000)
+ elif name == "corr_angle":
+ corr_angle.append(float(value))
+ self.corr_pos_x = corr_pos_x
+ self.corr_pos_y = corr_pos_y
+ self.corr_angle = corr_angle
+ return
+
+ def compute_additional_correction_2(self, angle):
+ if not self.corr_pos_x_2:
+ print("Not applying any additional secondary correction. No data available.\n")
+ return (0, 0)
+
+ # find index of closest angle
+ for j, _ in enumerate(self.corr_pos_x_2):
+ newangledelta = np.fabs(self.corr_angle_2[j] - angle)
+ if j == 0:
+ angledelta = newangledelta
+ additional_correction_shift_x = self.corr_pos_x_2[j]
+ additional_correction_shift_y = self.corr_pos_y_2[j]
+ continue
+
+ if newangledelta < angledelta:
+ additional_correction_shift_x = self.corr_pos_x_2[j]
+ additional_correction_shift_y = self.corr_pos_y_2[j]
+ angledelta = newangledelta
+
+ if additional_correction_shift_x == 0 and angle < self.corr_angle_2[0]:
+ additional_correction_shift_x = self.corr_pos_x_2[0]
+ additional_correction_shift_y = self.corr_pos_y_2[0]
+
+ if additional_correction_shift_x == 0 and angle > self.corr_angle_2[-1]:
+ additional_correction_shift_x = self.corr_pos_x_2[-1]
+ additional_correction_shift_y = self.corr_pos_y_2[-1]
+ print(
+ f"Additional correction shifts 2: {additional_correction_shift_x} {additional_correction_shift_y}"
+ )
+ return (additional_correction_shift_x, additional_correction_shift_y)
+
+ def read_additional_correction_2(self, correction_file: str):
+ with open(correction_file, "r") as f:
+ num_elements = f.readline()
+ int_num_elements = int(num_elements.split(" ")[2])
+ print(int_num_elements)
+ corr_pos_x = []
+ corr_pos_y = []
+ corr_angle = []
+ for j in range(0, int_num_elements * 3):
+ line = f.readline()
+ value = line.split(" ")[2]
+ name = line.split(" ")[0].split("[")[0]
+ if name == "corr_pos_x":
+ corr_pos_x.append(float(value) / 1000)
+ elif name == "corr_pos_y":
+ corr_pos_y.append(float(value) / 1000)
+ elif name == "corr_angle":
+ corr_angle.append(float(value))
+ self.corr_pos_x_2 = corr_pos_x
+ self.corr_pos_y_2 = corr_pos_y
+ self.corr_angle_2 = corr_angle
+ return
+
class LamNI(LamNIOpticsMixin):
def __init__(self, client):
super().__init__()
self.client = client
self.align = XrayEyeAlign(client, self)
- self.corr_pos_x = []
- self.corr_pos_y = []
- self.corr_angle = []
+
self.check_shutter = True
self.check_light_available = True
self.check_fofb = True
self._check_msgs = []
- self.tomo_id = None
+ self.tomo_id = -1
+ self.special_angles = []
+ self.special_angle_repeats = 20
+ self.special_angle_tolerance = 20
+ self._current_special_angles = []
self._beam_is_okay = True
self._stop_beam_check_event = None
self.beam_check_thread = None
@@ -331,33 +529,38 @@ class LamNI(LamNIOpticsMixin):
f"Shutter: {self.check_shutter}\nFOFB: {self.check_fofb}\nLight available: {self.check_light_available}"
)
- def set_beamline_checks_enabled(self, val: bool):
+ @property
+ def beamline_checks_enabled(self):
+ return {
+ "shutter": self.check_shutter,
+ "fofb": self.check_fofb,
+ "light available": self.check_light_available,
+ }
+
+ @beamline_checks_enabled.setter
+ def beamline_checks_enabled(self, val: bool):
self.check_shutter = val
self.check_light_available = val
self.check_fofb = val
self.get_beamline_checks_enabled()
- @property
- def tomo_fovx_offset(self):
- val = self.client.get_global_var("tomo_fov_offset")
- if val is None:
- return 0.0
- return val[0] / 1000
+ def set_special_angles(self, angles: list, repeats: int = 20, tolerance: float = 0.5):
+ """Set the special angles for a tomo
- @tomo_fovx_offset.setter
- def tomo_fovx_offset(self, val: float):
- self.client.set_global_var("tomo_fov_offset", val)
+ Args:
+ angles (list): List of special angles
+ repeats (int, optional): Number of repeats at a special angle. Defaults to 20.
+ tolerance (float, optional): Number of repeats at a special angle. Defaults to 0.5.
- @property
- def tomo_fovy_offset(self):
- val = self.client.get_global_var("tomo_fov_offset")
- if val is None:
- return 0.0
- return val[1] / 1000
+ """
+ self.special_angles = angles
+ self.special_angle_repeats = repeats
+ self.special_angle_tolerance = tolerance
- @tomo_fovy_offset.setter
- def tomo_fovy_offset(self, val: float):
- self.client.set_global_var("tomo_fov_offset", val)
+ def remove_special_angles(self):
+ """Remove the special angles and set the number of repeats to 1"""
+ self.special_angles = []
+ self.special_angle_repeats = 1
@property
def tomo_shellstep(self):
@@ -423,6 +626,9 @@ class LamNI(LamNIOpticsMixin):
@lamni_piezo_range_x.setter
def lamni_piezo_range_x(self, val: float):
+ if dev.rtx.user_parameter and dev.rtx.user_parameter.get("large_range_scan", True):
+ self.client.set_global_var("lamni_piezo_range_x", val)
+ return
if val > 80:
raise ValueError("Piezo range cannot be larger than 80 um.")
self.client.set_global_var("lamni_piezo_range_x", val)
@@ -436,10 +642,24 @@ class LamNI(LamNIOpticsMixin):
@lamni_piezo_range_y.setter
def lamni_piezo_range_y(self, val: float):
+ if dev.rtx.user_parameter and dev.rtx.user_parameter.get("large_range_scan", True):
+ self.client.set_global_var("lamni_piezo_range_y", val)
+ return
if val > 80:
raise ValueError("Piezo range cannot be larger than 80 um.")
self.client.set_global_var("lamni_piezo_range_y", val)
+ @property
+ def corridor_size(self):
+ val = self.client.get_global_var("corridor_size")
+ if val is None:
+ val = -1
+ return val
+
+ @corridor_size.setter
+ def corridor_size(self, val: float):
+ self.client.set_global_var("corridor_size", val)
+
@property
def lamni_stitch_x(self):
val = self.client.get_global_var("lamni_stitch_x")
@@ -497,11 +717,48 @@ class LamNI(LamNIOpticsMixin):
def tomo_stitch_overlap(self, val: float):
self.client.set_global_var("tomo_stitch_overlap", val)
+ @property
+ def sample_name(self):
+ val = self.client.get_global_var("sample_name")
+ if val is None:
+ return "bec_test_sample"
+ return val
+
+ @sample_name.setter
+ @typechecked
+ def sample_name(self, val: str):
+ self.client.set_global_var("sample_name", val)
+
+ 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:
+ tags.append("BEC")
+ else:
+ tags = ["BEC"]
+ msg = bec.logbook.LogbookMessage()
+ msg.add_text(content).add_tag(tags)
+ self.client.logbook.send_logbook_message(msg)
+ except Exception:
+ logger.warning("Failed to write to scilog.")
+
def tomo_scan_projection(self, angle: float):
scans = builtins.__dict__.get("scans")
- bec = builtins.__dict__.get("bec")
- additional_correction = self.compute_additional_correction(angle)
- correction_xeye_mu = self.lamni_compute_additional_correction_xeye_mu(angle)
+
+ additional_correction = self.align.compute_additional_correction(angle)
+ additional_correction_2 = self.align.compute_additional_correction_2(angle)
+ correction_xeye_mu = self.align.lamni_compute_additional_correction_xeye_mu(angle)
self._current_scan_list = []
@@ -511,106 +768,42 @@ class LamNI(LamNIOpticsMixin):
self._current_scan_list.append(bec.queue.next_scan_number)
logger.info(
f"scans.lamni_fermat_scan(fov_size=[{self.lamni_piezo_range_x},{self.lamni_piezo_range_y}], step={self.tomo_shellstep}, stitch_x={0}, stitch_y={0}, stitch_overlap={1},"
- f"center_x={self.tomo_fovx_offset}, center_y={self.tomo_fovy_offset}, "
- f"shift_x={self.manual_shift_x+correction_xeye_mu[0]-additional_correction[0]}, "
- f"shift_y={self.manual_shift_y+correction_xeye_mu[1]-additional_correction[1]}, "
+ f"center_x={self.align.tomo_fovx_offset}, center_y={self.align.tomo_fovy_offset}, "
+ f"shift_x={self.manual_shift_x+correction_xeye_mu[0]-additional_correction[0]-additional_correction_2[0]}, "
+ f"shift_y={self.manual_shift_y+correction_xeye_mu[1]-additional_correction[1]-additional_correction_2[1]}, "
f"fov_circular={self.tomo_circfov}, angle={angle}, scan_type='fly')"
)
+ log_message = f"{str(datetime.datetime.now())}: LamNI scan projection at angle {angle}, scan 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])
+ 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],
step=self.tomo_shellstep,
stitch_x=stitch_x,
stitch_y=stitch_y,
stitch_overlap=self.tomo_stitch_overlap,
- center_x=self.tomo_fovx_offset,
- center_y=self.tomo_fovy_offset,
+ center_x=self.align.tomo_fovx_offset,
+ center_y=self.align.tomo_fovy_offset,
shift_x=(
- self.manual_shift_x + correction_xeye_mu[0] - additional_correction[0]
+ self.manual_shift_x
+ + correction_xeye_mu[0]
+ - additional_correction[0]
+ - additional_correction_2[0]
),
shift_y=(
- self.manual_shift_y + correction_xeye_mu[1] - additional_correction[1]
+ self.manual_shift_y
+ + correction_xeye_mu[1]
+ - additional_correction[1]
+ - additional_correction_2[1]
),
fov_circular=self.tomo_circfov,
angle=angle,
scan_type="fly",
exp_time=self.tomo_countingtime,
+ optim_trajectory_corridor=corridor_size,
)
- def lamni_compute_additional_correction_xeye_mu(self, angle):
- tomo_fit_xray_eye = self.client.get_global_var("tomo_fit_xray_eye")
- if tomo_fit_xray_eye is None:
- print("Not applying any additional correction. No x-ray eye data available.\n")
- return (0, 0)
-
- # x amp, phase, offset, y amp, phase, offset
- # 0 0 0 1 0 2 1 0 1 1 1 2
- correction_x = (
- tomo_fit_xray_eye[0][0] * math.sin(math.radians(angle) + tomo_fit_xray_eye[0][1])
- + tomo_fit_xray_eye[0][2]
- ) / 1000
- correction_y = (
- tomo_fit_xray_eye[1][0] * math.sin(math.radians(angle) + tomo_fit_xray_eye[1][1])
- + tomo_fit_xray_eye[1][2]
- ) / 1000
-
- print(f"Xeye correction x {correction_x}, y {correction_y} for angle {angle}\n")
- return (correction_x, correction_y)
-
- def compute_additional_correction(self, angle):
- if not self.corr_pos_x:
- print("Not applying any additional correction. No data available.\n")
- return (0, 0)
-
- # find index of closest angle
- for j, _ in enumerate(self.corr_pos_x):
- newangledelta = math.fabs(self.corr_angle[j] - angle)
- if j == 0:
- angledelta = newangledelta
- additional_correction_shift_x = self.corr_pos_x[j]
- additional_correction_shift_y = self.corr_pos_y[j]
- continue
-
- if newangledelta < angledelta:
- additional_correction_shift_x = self.corr_pos_x[j]
- additional_correction_shift_y = self.corr_pos_y[j]
- angledelta = newangledelta
-
- if additional_correction_shift_x == 0 and angle < self.corr_angle[0]:
- additional_correction_shift_x = self.corr_pos_x[0]
- additional_correction_shift_y = self.corr_pos_y[0]
-
- if additional_correction_shift_x == 0 and angle > self.corr_angle[-1]:
- additional_correction_shift_x = self.corr_pos_x[-1]
- additional_correction_shift_y = self.corr_pos_y[-1]
- logger.info(
- f"Additional correction shifts: {additional_correction_shift_x} {additional_correction_shift_y}"
- )
- return (additional_correction_shift_x, additional_correction_shift_y)
-
- def lamni_read_additional_correction(self, correction_file: str):
-
- with open(correction_file, "r") as f:
- num_elements = f.readline()
- int_num_elements = int(num_elements.split(" ")[2])
- print(int_num_elements)
- corr_pos_x = []
- corr_pos_y = []
- corr_angle = []
- for j in range(0, int_num_elements * 3):
- line = f.readline()
- value = line.split(" ")[2]
- name = line.split(" ")[0].split("[")[0]
- if name == "corr_pos_x":
- corr_pos_x.append(float(value) / 1000)
- elif name == "corr_pos_y":
- corr_pos_y.append(float(value) / 1000)
- elif name == "corr_angle":
- corr_angle.append(float(value))
- self.corr_pos_x = corr_pos_x
- self.corr_pos_y = corr_pos_y
- self.corr_angle = corr_angle
- return
-
def _run_beamline_checks(self):
msgs = []
dev = builtins.__dict__.get("dev")
@@ -622,7 +815,10 @@ class LamNI(LamNIOpticsMixin):
msgs.append("Check beam failed: Shutter is closed.")
if self.check_light_available:
machine_status = dev.sls_machine_status.read(cached=True)
- if machine_status["value"] not in ["Light Available", "Light-Available"]:
+ if machine_status["value"] not in [
+ "Light Available",
+ "Light-Available",
+ ]:
self._beam_is_okay = False
msgs.append("Check beam failed: Light not available.")
if self.check_fofb:
@@ -661,7 +857,7 @@ class LamNI(LamNIOpticsMixin):
def _wait_for_beamline_checks(self):
self._print_beamline_checks()
try:
- msg = LogbookMessage(self.client.logbook)
+ msg = bec.logbook.LogbookMessage()
msg.add_text(
f"Beamline checks failed at {str(datetime.datetime.now())}: {''.join(self._check_msgs)}
"
).add_tag(["BEC", "beam_check"])
@@ -678,7 +874,7 @@ class LamNI(LamNIOpticsMixin):
time.sleep(1)
try:
- msg = LogbookMessage(self.client.logbook)
+ msg = bec.logbook.LogbookMessage()
msg.add_text(
f"Operation resumed at {str(datetime.datetime.now())}.
"
).add_tag(["BEC", "beam_check"])
@@ -687,7 +883,14 @@ class LamNI(LamNIOpticsMixin):
logger.warning("Failed to send update to SciLog.")
def add_sample_database(
- self, samplename, date, eaccount, scan_number, setup, sample_additional_info, user
+ self,
+ samplename,
+ date,
+ eaccount,
+ scan_number,
+ setup,
+ sample_additional_info,
+ user,
):
"""Add a sample to the omny sample database. This also retrieves the tomo id."""
subprocess.run(
@@ -698,10 +901,31 @@ class LamNI(LamNIOpticsMixin):
tomo_number = int(tomo_number_file.read())
return tomo_number
+ def _at_each_angle(self, angle: float) -> None:
+ self.tomo_scan_projection(angle)
+ self.tomo_reconstruct()
+
+ ### XMCD ###
+ # 2 projections, 1 for each polarization state
+ # cp()
+ # self.tomo_scan_projection(angle)
+ # self.tomo_reconstruct()
+ # cm()
+ # self.tomo_scan_projection(angle)
+ # self.tomo_reconstruct()
+
def sub_tomo_scan(self, subtomo_number, start_angle=None):
"""start a subtomo"""
dev = builtins.__dict__.get("dev")
bec = builtins.__dict__.get("bec")
+ if self.tomo_id > 0:
+ tags = ["BEC_subtomo", self.sample_name, f"tomo_id_{self.tomo_id}"]
+ else:
+ tags = ["BEC_subtomo", self.sample_name]
+ self.write_to_scilog(
+ f"Starting subtomo: {subtomo_number}. First scan number: {bec.queue.next_scan_number}.",
+ tags,
+ )
if start_angle is None:
if subtomo_number == 1:
@@ -731,44 +955,73 @@ class LamNI(LamNIOpticsMixin):
endpoint=True,
):
successful = False
+ error_caught = False
if 0 <= angle < 360.05:
print(f"Starting LamNI scan for angle {angle}")
while not successful:
self._start_beam_check()
- self.tomo_scan_projection(angle)
+ if not self.special_angles:
+ self._current_special_angles = []
+ if self._current_special_angles:
+ next_special_angle = self._current_special_angles[0]
+ if np.isclose(angle, next_special_angle, atol=0.5):
+ self._current_special_angles.pop(0)
+ num_repeats = self.special_angle_repeats
+ else:
+ num_repeats = 1
+ try:
+ start_scan_number = bec.queue.next_scan_number
+ for i in range(num_repeats):
+ self._at_each_angle(angle)
+ error_caught = False
+ except AlarmBase as exc:
+ if exc.alarm_type == "TimeoutError":
+ bec.queue.request_queue_reset()
+ time.sleep(2)
+ error_caught = True
+ else:
+ raise exc
- if self._was_beam_okay():
+ if self._was_beam_okay() and not error_caught:
successful = True
else:
self._wait_for_beamline_checks()
- self.tomo_reconstruct()
- tomo_id = 0
- with open(
- os.path.expanduser("~/Data10/specES1/dat-files/tomography_scannumbers.txt"),
- "a+",
- ) as out_file:
- # pylint: disable=undefined-variable
- out_file.write(
- f"{bec.queue.next_scan_number-1} {angle} {dev.lsamrot.read()['lsamrot']['value']} {self.tomo_id} {subtomo_number} {0} {'lamni'}\n"
- )
+ 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)
+
+ def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None:
+ tomo_scan_numbers_file = os.path.expanduser(
+ "~/Data10/specES1/dat-files/tomography_scannumbers.txt"
+ )
+ with open(tomo_scan_numbers_file, "a+") as out_file:
+ # pylint: disable=undefined-variable
+ out_file.write(
+ f"{scan_number} {angle} {dev.lsamrot.read()['lsamrot']['value']:.3f} {self.tomo_id} {subtomo_number} {0} {'lamni'}\n"
+ )
def tomo_scan(self, subtomo_start=1, start_angle=None):
"""start a tomo scan"""
bec = builtins.__dict__.get("bec")
+ scans = builtins.__dict__.get("scans")
+ self._current_special_angles = self.special_angles.copy()
+
if subtomo_start == 1 and start_angle is None:
# pylint: disable=undefined-variable
self.tomo_id = self.add_sample_database(
- "bec_test_sample",
+ self.sample_name,
str(datetime.date.today()),
- "e20588",
+ bec.active_account.decode(),
bec.queue.next_scan_number,
"lamni",
"test additional info",
"BEC",
)
- for ii in range(subtomo_start, 9):
- self.sub_tomo_scan(ii, start_angle=start_angle)
- start_angle = None
+ self.write_pdf_report()
+ with scans.dataset_id_on_hold:
+ for ii in range(subtomo_start, 9):
+ self.sub_tomo_scan(ii, start_angle=start_angle)
+ start_angle = None
def tomo_parameters(self):
"""print and update the tomo parameters"""
@@ -785,12 +1038,13 @@ class LamNI(LamNIOpticsMixin):
print(
"For information, fov offset is rotating and finding the ROI, manual shift moves rotation center"
)
- print(f" _tomo_fovx_offset = {self.tomo_fovx_offset}")
- print(f" _tomo_fovy_offset = {self.tomo_fovy_offset}")
+ print(f" _tomo_fovx_offset = {self.align.tomo_fovx_offset}")
+ print(f" _tomo_fovy_offset = {self.align.tomo_fovy_offset}")
print(f" _manual_shift_x = {self.manual_shift_x}")
print(f" _manual_shift_y = {self.manual_shift_y}")
print(f"Angular step within sub-tomogram: {self.tomo_angle_stepsize} degrees")
- print(f"Resulting in number of projections: {360/self.tomo_angle_stepsize*8}\n")
+ print(f"Resulting in number of projections: {360/self.tomo_angle_stepsize*8}")
+ print(f"Sample name: {self.sample_name}\n")
user_input = input("Are these parameters correctly set for your scan? ")
if user_input == "y":
@@ -817,6 +1071,7 @@ class LamNI(LamNIOpticsMixin):
print(f"The angular step will be {360/tomo_numberofprojections}")
self.tomo_angle_stepsize = 360 / tomo_numberofprojections * 8
print(f"The angular step in a subtomogram it will be {self.tomo_angle_stepsize}")
+ self.sample_name = self._get_val("sample name", self.sample_name, str)
@staticmethod
def _get_val(msg: str, default_value, data_type):
@@ -856,9 +1111,8 @@ class LamNI(LamNIOpticsMixin):
piezo_range = f"{self.lamni_piezo_range_x:.2f}/{self.lamni_piezo_range_y:.2f}"
stitching = f"{self.lamni_stitch_x:.2f}/{self.lamni_stitch_y:.2f}"
dataset_id = str(self.client.queue.next_dataset_number)
- # pylint: disable=undefined-variable
- content = (
- f"{'Sample Name:':<{padding}}{'test':>{padding}}\n",
+ content = [
+ f"{'Sample Name:':<{padding}}{self.sample_name:>{padding}}\n",
f"{'Measurement ID:':<{padding}}{str(self.tomo_id):>{padding}}\n",
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
@@ -866,23 +1120,214 @@ class LamNI(LamNIOpticsMixin):
f"{'Number of projections:':<{padding}}{int(360 / self.tomo_angle_stepsize * 8):>{padding}}\n",
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(360 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n",
- f"{'Current photon energy:':<{padding}}{dev.mokev.read(cached=True)['mokev']['value']:>{padding}.4f}\n",
+ f"{'Current photon energy:':<{padding}}{dev.mokev.read(cached=True)['value']:>{padding}.4f}\n",
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
- f"{'Piezo range (FOV sample plane):':<{padding}}{piezo_range>{padding}}\n",
+ f"{'Piezo range (FOV sample plane):':<{padding}}{piezo_range:>{padding}}\n",
f"{'Restriction to circular FOV:':<{padding}}{self.tomo_circfov:>{padding}.2f}\n",
f"{'Stitching:':<{padding}}{stitching:>{padding}}\n",
f"{'Number of individual sub-tomograms:':<{padding}}{8:>{padding}}\n",
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
- )
-
- with PDFWriter(os.path.expanduser("~/Data10/")) as file:
+ ]
+ content = "".join(content)
+ user_target = os.path.expanduser(f"~/Data10/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
+ with PDFWriter(user_target) as file:
file.write(header)
file.write(content)
-
- msg = LogbookMessage(self.client.logbook)
- logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lamni_logo.png")
+ subprocess.run(
+ "xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &",
+ shell=True,
+ )
+ # status = subprocess.run(f"cp /tmp/spec-e20131-specES1.pdf {user_target}", shell=True)
+ msg = bec.logbook.LogbookMessage()
+ logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
msg.add_file(logo_path).add_text("".join(content).replace("\n", "
")).add_tag(
- ["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "LamNI"]
+ [
+ "BEC",
+ "tomo_parameters",
+ f"dataset_id_{dataset_id}",
+ "LamNI",
+ self.sample_name,
+ ]
)
self.client.logbook.send_logbook_message(msg)
+
+
+class MagLamNI(LamNI):
+ def sub_tomo_scan(self, subtomo_number, start_angle=None):
+ super().sub_tomo_scan(subtomo_number, start_angle)
+ # self.rotate_slowly(0)
+
+ def rotate_slowly(self, angle, step_size=20):
+ current_angle = dev.lsamrot.read(cached=True)["value"]
+ steps = int(np.ceil(np.abs(current_angle - angle) / step_size)) + 1
+ for target_angle in np.linspace(current_angle, angle, steps, endpoint=True):
+ umv(dev.lsamrot, target_angle)
+ scans.lamni_move_to_scan_center(
+ self.align.tomo_fovx_offset, self.align.tomo_fovy_offset, target_angle
+ )
+
+ def _at_each_angle(self, angle: float) -> None:
+ if "lamni_at_each_angle" in builtins.__dict__:
+ lamni_at_each_angle(self, angle)
+ return
+
+ self.tomo_scan_projection(angle)
+ self.tomo_reconstruct()
+
+ # # cm()
+ # # umv(dev.ppth,15.1762) #11.567 keV
+ # for ii in range(2):
+ # self.tomo_scan_projection(angle)
+ # self.tomo_reconstruct()
+ # # cp()
+ # # umv(dev.ppth,15.1827) #11.567 keV
+ # for ii in range(2):
+ # self.tomo_scan_projection(angle)
+ # self.tomo_reconstruct()
+
+
+class DataDrivenLamNI(LamNI):
+ def __init__(self, client):
+ super().__init__(client)
+ self.tomo_data = {}
+
+ def tomo_scan(
+ self,
+ subtomo_start=1,
+ start_index=None,
+ fname="~/Data10/data_driven_config/datadriven_params.h5",
+ ):
+ """start a tomo scan"""
+ bec = builtins.__dict__.get("bec")
+ scans = builtins.__dict__.get("scans")
+
+ fname = os.path.expanduser(fname)
+
+ if not os.path.exists(fname):
+ raise FileNotFoundError(f"Could not find datadriven params file in {fname}.")
+ content = f"Loading tomo parameters from {fname}."
+ logger.warning(content)
+ tags = ["Data_driven_file", "BEC"]
+ msg = bec.logbook.LogbookMessage()
+ msg.add_text(content).add_tag(tags)
+ self.client.logbook.send_logbook_message(msg)
+ self._update_tomo_data_from_file(fname)
+
+ self._current_special_angles = self.special_angles.copy()
+
+ if subtomo_start == 1 and start_index is None:
+ # pylint: disable=undefined-variable
+ self.tomo_id = self.add_sample_database(
+ self.sample_name,
+ str(datetime.date.today()),
+ bec.active_account.decode(),
+ bec.queue.next_scan_number,
+ "lamni",
+ "test additional info",
+ "BEC",
+ )
+ self.write_pdf_report()
+ with scans.dataset_id_on_hold:
+ self.sub_tomo_data_driven(start_index)
+
+ def sub_tomo_scan(self):
+ raise NotImplementedError(
+ "Cannot run sub_tomo_scan with data-driven LamNI. Please use lamni.tomo_scan(subtomo_start=) instead."
+ )
+
+ def _at_each_angle(
+ self,
+ angle=None,
+ stepsize=None,
+ loptz_pos=None,
+ manual_shift_x=0,
+ manual_shift_y=0,
+ ):
+ # Do something...
+ # self.tomo_parameters
+ self.manual_shift_x = manual_shift_x
+ self.manual_shift_y = manual_shift_y
+ self.tomo_shellstep = stepsize # in microns
+ if loptz_pos is not None:
+ dev.rtx.controller.feedback_disable()
+ umv(dev.loptz, loptz_pos)
+ super()._at_each_angle(angle=angle)
+
+ def sub_tomo_data_driven(self, start_index=None):
+ # for theta, stepsize, sample_to_focus, probe_diameter, subtomo_id in zip(*self.tomo_data.values()):
+
+ for scan_index, scan_data in enumerate(zip(*self.tomo_data.values())):
+ if start_index and scan_index < start_index:
+ continue
+ (
+ angle,
+ stepsize,
+ loptz_pos,
+ propagation_distance,
+ manual_shift_x,
+ manual_shift_y,
+ subtomo_number,
+ ) = scan_data
+ bec.metadata.update(
+ {key: float(val) for key, val in zip(self.tomo_data.keys(), scan_data)}
+ )
+ successful = False
+ error_caught = False
+ if 0 <= angle < 360.05:
+ print(f"Starting LamNI scan for angle {angle}")
+ while not successful:
+ self._start_beam_check()
+ if not self.special_angles:
+ self._current_special_angles = []
+ if self._current_special_angles:
+ next_special_angle = self._current_special_angles[0]
+ if np.isclose(angle, next_special_angle, atol=0.5):
+ self._current_special_angles.pop(0)
+ num_repeats = self.special_angle_repeats
+ else:
+ num_repeats = 1
+ try:
+ start_scan_number = bec.queue.next_scan_number
+ for i in range(num_repeats):
+ self._at_each_angle(
+ float(angle),
+ stepsize=float(stepsize),
+ loptz_pos=float(loptz_pos),
+ manual_shift_x=float(manual_shift_x),
+ manual_shift_y=float(manual_shift_y),
+ )
+ error_caught = False
+ except AlarmBase as exc:
+ if exc.alarm_type == "TimeoutError":
+ bec.queue.request_queue_reset()
+ time.sleep(2)
+ error_caught = True
+ else:
+ raise exc
+
+ if self._was_beam_okay() and not error_caught:
+ successful = True
+ else:
+ self._wait_for_beamline_checks()
+ end_scan_number = bec.queue.next_scan_number
+ for scan_nr in range(start_scan_number, end_scan_number):
+ self._write_tomo_scan_number(scan_nr, angle, subtomo_number)
+
+ def _update_tomo_data_from_file(self, fname: str) -> None:
+ with h5py.File(fname, "r") as file:
+ self.tomo_data["theta"] = np.array([*file["theta"]]).flatten()
+ self.tomo_data["stepsize"] = np.array([*file["stepsize"]]).flatten()
+ self.tomo_data["loptz"] = np.array([*file["loptz"]]).flatten()
+ self.tomo_data["propagation_distance"] = np.array(
+ [*file["relative_propagation_distance"]]
+ ).flatten()
+ self.tomo_data["manual_shift_x"] = np.array([*file["manual_shift_x"]]).flatten()
+ self.tomo_data["manual_shift_y"] = np.array([*file["manual_shift_y"]]).flatten()
+ self.tomo_data["subtomo_id"] = np.array([*file["subtomo_id"]]).flatten()
+
+ shapes = []
+ for data in self.tomo_data.values():
+ shapes.append(data.shape)
+ if len(set(shapes)) > 1:
+ raise ValueError(f"Tomo data file has entries of inconsistent lengths: {shapes}.")
diff --git a/bec_plugins/bec_client/plugins/__init__.py b/bec_plugins/bec_client/plugins/__init__.py
new file mode 100644
index 0000000..9558172
--- /dev/null
+++ b/bec_plugins/bec_client/plugins/__init__.py
@@ -0,0 +1,3 @@
+from .cSAXS import *
+
+# from .LamNI import *
diff --git a/bec_plugins/plugins/bec_client/plugins/cSAXS/__init__.py b/bec_plugins/bec_client/plugins/cSAXS/__init__.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/plugins/cSAXS/__init__.py
rename to bec_plugins/bec_client/plugins/cSAXS/__init__.py
diff --git a/bec_plugins/bec_client/plugins/cSAXS/beamline_info.py b/bec_plugins/bec_client/plugins/cSAXS/beamline_info.py
new file mode 100644
index 0000000..49e6647
--- /dev/null
+++ b/bec_plugins/bec_client/plugins/cSAXS/beamline_info.py
@@ -0,0 +1,108 @@
+import builtins
+
+from rich import box
+from rich.table import Table
+
+from bec_client.beamline_mixin import BeamlineShowInfo
+
+
+class BeamlineInfo(BeamlineShowInfo):
+ def show(self):
+ """Display information about the current beamline status"""
+ console = self._get_console()
+
+ table = Table(title="X12SA Info", box=box.SQUARE)
+ table.add_column("Key", justify="left")
+ table.add_column("Value", justify="left")
+
+ info = self._get_beamline_info_messages()
+ self._add_op_status(table, info)
+ self._add_id_gap(table, info)
+ self._add_storage_ring_vac(table, info)
+ self._add_shutter_status(table, info)
+ self._add_mokev(table, info)
+ self._add_fe_status(table, info)
+ self._add_es1_valve(table, info)
+ self._add_xbox1_pressure(table, info)
+ self._add_xbox2_pressure(table, info)
+
+ console.print(table)
+
+ def _add_op_status(self, table, info):
+ val = self._get_info_val(info, "x12sa_op_status")
+ if val not in ["attended"]:
+ return table.add_row("Beamline operation", val, style=self.ALARM_STYLE)
+ return table.add_row("Beamline operation", val, style=self.DEFAULT_STYLE)
+
+ def _add_shutter_status(self, table, info):
+ val = self._get_info_val(info, "x12sa_es1_shutter_status")
+ if val.lower() not in ["open"]:
+ return table.add_row("Shutter", val, style=self.ALARM_STYLE)
+ return table.add_row("Shutter", val, style=self.DEFAULT_STYLE)
+
+ def _add_storage_ring_vac(self, table, info):
+ val = self._get_info_val(info, "x12sa_storage_ring_vac")
+ if val.lower() not in ["ok"]:
+ return table.add_row("Storage ring vacuum", val, style=self.ALARM_STYLE)
+ return table.add_row("Storage ring vacuum", val, style=self.DEFAULT_STYLE)
+
+ def _add_es1_valve(self, table, info):
+ val = self._get_info_val(info, "x12sa_es1_valve")
+ if val.lower() not in ["open"]:
+ return table.add_row("ES1 valve", val, style=self.ALARM_STYLE)
+ return table.add_row("ES1 valve", val, style=self.DEFAULT_STYLE)
+
+ def _add_xbox1_pressure(self, table, info):
+ MAX_PRESSURE = 2e-6
+ val = info["x12sa_exposure_box1_pressure"]["value"]
+ if val > MAX_PRESSURE:
+ return table.add_row(
+ f"Exposure box 1 pressure (limit for opening the valve: {MAX_PRESSURE:.1e} mbar)",
+ f"{val:.1e} mbar",
+ style=self.ALARM_STYLE,
+ )
+ return table.add_row("Exposure box 1 pressure", f"{val:.1e} mbar", style=self.DEFAULT_STYLE)
+
+ def _add_xbox2_pressure(self, table, info):
+ MAX_PRESSURE = 2e-6
+ val = info["x12sa_exposure_box2_pressure"]["value"]
+ if val > MAX_PRESSURE:
+ return table.add_row(
+ f"Exposure box 2 pressure (limit for opening the valve: {MAX_PRESSURE:.1e} mbar)",
+ f"{val:.1e} mbar",
+ style=self.ALARM_STYLE,
+ )
+ return table.add_row("Exposure box 2 pressure", f"{val:.1e} mbar", style=self.DEFAULT_STYLE)
+
+ def _add_fe_status(self, table, info):
+ val = self._get_info_val(info, "x12sa_fe_status")
+ return table.add_row("Front end shutter", val, style=self.DEFAULT_STYLE)
+
+ def _add_id_gap(self, table, info):
+ val = info["x12sa_id_gap"]["value"]
+ if val > 8:
+ return table.add_row("ID gap", f"{val:.3f} mm", style=self.ALARM_STYLE)
+ return table.add_row("ID gap", f"{val:.3f} mm", style=self.DEFAULT_STYLE)
+
+ def _add_mokev(self, table, info):
+ val = info["x12sa_mokev"]["value"]
+ return table.add_row("Selected energy (mokev)", f"{val:.3f} keV", style=self.DEFAULT_STYLE)
+
+ def _get_beamline_info_messages(self) -> dict:
+ dev = builtins.__dict__.get("dev")
+
+ def _get_bl_msg(info, device_name):
+ info[device_name] = dev[device_name].read(cached=True)
+
+ info = {}
+ _get_bl_msg(info, "x12sa_op_status")
+ _get_bl_msg(info, "x12sa_storage_ring_vac")
+ _get_bl_msg(info, "x12sa_es1_shutter_status")
+ _get_bl_msg(info, "x12sa_id_gap")
+ _get_bl_msg(info, "x12sa_mokev")
+ _get_bl_msg(info, "x12sa_fe_status")
+ _get_bl_msg(info, "x12sa_es1_valve")
+ _get_bl_msg(info, "x12sa_exposure_box1_pressure")
+ _get_bl_msg(info, "x12sa_exposure_box2_pressure")
+
+ return info
diff --git a/bec_plugins/plugins/bec_client/plugins/cSAXS/cSAXS_beamline.py b/bec_plugins/bec_client/plugins/cSAXS/cSAXS_beamline.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/plugins/cSAXS/cSAXS_beamline.py
rename to bec_plugins/bec_client/plugins/cSAXS/cSAXS_beamline.py
diff --git a/bec_plugins/plugins/bec_client/startup/post-startup.py b/bec_plugins/bec_client/startup/__init__.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/startup/post-startup.py
rename to bec_plugins/bec_client/startup/__init__.py
diff --git a/bec_plugins/bec_client/startup/post_startup.py b/bec_plugins/bec_client/startup/post_startup.py
new file mode 100644
index 0000000..3c24542
--- /dev/null
+++ b/bec_plugins/bec_client/startup/post_startup.py
@@ -0,0 +1,61 @@
+"""
+Post startup script for the BEC client. This script is executed after the
+IPython shell is started. It is used to load the beamline specific
+information and to setup the prompts.
+
+The script is executed in the global namespace of the IPython shell. This
+means that all variables defined here are available in the shell.
+
+If needed, bec command-line arguments can be parsed here. For example, to
+parse the --session argument, add the following lines to the script:
+
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--session", help="Session name", type=str, default="my_default_session")
+ args = parser.parse_args()
+
+ if args.session == "my_session":
+ print("Loading my_session session")
+ from bec_plugins.bec_client.plugins.my_session import *
+ else:
+ print("Loading default session")
+ from bec_plugins.bec_client.plugins.default_session import *
+"""
+
+# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
+import argparse
+
+from bec_lib.core import bec_logger
+
+logger = bec_logger.logger
+
+logger.info("Using the cSAXS startup script.")
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
+args = parser.parse_args()
+
+if args.session == "LamNI":
+ print("Loading LamNI session")
+ from bec_plugins.bec_client.plugins.cSAXS import *
+ from bec_plugins.bec_client.plugins.LamNI import *
+
+ lamni = LamNI(bec)
+
+elif args.session == "cSAXS":
+ print("Loading cSAXS session")
+ from bec_plugins.bec_client.plugins.cSAXS import *
+
+
+# SETUP BEAMLINE INFO
+from bec_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
+
+from bec_plugins.bec_client.plugins.cSAXS.beamline_info import BeamlineInfo
+
+bec._beamline_mixin._bl_info_register(BeamlineInfo)
+bec._beamline_mixin._bl_info_register(SLSInfo)
+bec._beamline_mixin._bl_info_register(OperatorInfo)
+
+# SETUP PROMPTS
+bec._ip.prompts.username = args.session
+bec._ip.prompts.status = 1
diff --git a/bec_plugins/bec_client/startup/pre_startup.py b/bec_plugins/bec_client/startup/pre_startup.py
new file mode 100644
index 0000000..dcfa194
--- /dev/null
+++ b/bec_plugins/bec_client/startup/pre_startup.py
@@ -0,0 +1,25 @@
+"""
+Pre-startup script for BEC client. This script is executed before the BEC client
+is started. It can be used to set up the BEC client configuration. The script is
+executed in the global namespace of the BEC client. This means that all
+variables defined here are available in the BEC client.
+
+To set up the BEC client configuration, use the ServiceConfig class. For example,
+to set the configuration file path, add the following lines to the script:
+
+ import pathlib
+ from bec_lib.core import ServiceConfig
+
+ current_path = pathlib.Path(__file__).parent.resolve()
+ CONFIG_PATH = f"{current_path}/"
+
+ config = ServiceConfig(CONFIG_PATH)
+
+If this startup script defined a ServiceConfig object, the BEC client will use
+it to configure itself. Otherwise, the BEC client will use the default config.
+"""
+
+# example:
+# current_path = pathlib.Path(__file__).parent.resolve()
+# CONFIG_PATH = f"{current_path}/../../../bec_config.yaml"
+# config = ServiceConfig(CONFIG_PATH)
diff --git a/bec_plugins/plugins/bec_client/plugins/LamNI/__init__.py b/bec_plugins/plugins/bec_client/plugins/LamNI/__init__.py
deleted file mode 100644
index ff9c23c..0000000
--- a/bec_plugins/plugins/bec_client/plugins/LamNI/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .load_additional_correction import lamni_read_additional_correction
-from .x_ray_eye_align import LamNI, XrayEyeAlign
diff --git a/bec_plugins/plugins/bec_client/startup/pre-startup.py b/bec_plugins/scan_server/__init__.py
similarity index 100%
rename from bec_plugins/plugins/bec_client/startup/pre-startup.py
rename to bec_plugins/scan_server/__init__.py
diff --git a/bec_plugins/plugins/scan_server/scan_plugins/LamNIFermatScan.py b/bec_plugins/scan_server/scan_plugins/LamNIFermatScan.py
similarity index 100%
rename from bec_plugins/plugins/scan_server/scan_plugins/LamNIFermatScan.py
rename to bec_plugins/scan_server/scan_plugins/LamNIFermatScan.py
diff --git a/bec_plugins/plugins/scibec/lamni_config.py b/bec_plugins/scibec/lamni_config.py
similarity index 100%
rename from bec_plugins/plugins/scibec/lamni_config.py
rename to bec_plugins/scibec/lamni_config.py
diff --git a/bec_plugins/plugins/scibec/test_config_cSAXS.yaml b/bec_plugins/scibec/test_config_cSAXS.yaml
similarity index 100%
rename from bec_plugins/plugins/scibec/test_config_cSAXS.yaml
rename to bec_plugins/scibec/test_config_cSAXS.yaml
diff --git a/deployment/autodeploy_version b/deployment/autodeploy_version
new file mode 100644
index 0000000..2dac2c3
--- /dev/null
+++ b/deployment/autodeploy_version
@@ -0,0 +1,11 @@
+# This file is used to select the BEC and Ophyd Devices version for the auto deployment process.
+# Do not edit this file unless you know what you are doing!
+
+# The version can be a git tag, branch or commit hash.
+
+# BEC version to use
+BEC_AUTODEPLOY_VERSION="cli_launch"
+
+# ophyd_devices version to use
+OPHYD_DEVICES_AUTODEPLOY_VERSION="master"
+
diff --git a/deployment/deploy.sh b/deployment/deploy.sh
new file mode 100755
index 0000000..77f1a64
--- /dev/null
+++ b/deployment/deploy.sh
@@ -0,0 +1,24 @@
+# deployment script to be translated to Ansible
+BEAMLINE_REPO=gitlab.psi.ch:bec/csaxs-bec.git
+
+# start redis
+# docker run --network=host --name redis-bec -d redis
+# alternative:
+# conda install redis; redis-server &
+
+git clone git@$BEAMLINE_REPO
+
+# get the target versions for ophyd_devices and BEC
+source ./csaxs-bec/deployment/autodeploy_versions
+
+git clone -b $OPHYD_DEVICES_AUTODEPLOY_VERSION git@gitlab.psi.ch:bec/ophyd_devices.git
+git clone -b $BEC_AUTODEPLOY_VERSION git@gitlab.psi.ch:bec/bec.git
+
+# install BEC
+cd bec
+source ./bin/install_bec_dev.sh
+
+cd ../
+# start the BEC server
+bec-server start --config ./csaxs-bec/deployment/bec-server-config.yaml
+