diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/__init__.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/__init__.py index 5642f93..0e2d7a3 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/__init__.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/__init__.py @@ -1,6 +1,11 @@ -from .alignment import XrayEyeAlign +from .lamni_alignment_mixin import LamNIAlignmentMixin from .lamni import LamNI from .lamni_optics_mixin import LamNIInitError, LaMNIInitStages, LamNIOpticsMixin + __all__ = [ - "LamNI", "XrayEyeAlign", "LamNIInitError", "LaMNIInitStages", "LamNIOpticsMixin" -] \ No newline at end of file + "LamNI", + "LamNIAlignmentMixin", + "LamNIInitError", + "LaMNIInitStages", + "LamNIOpticsMixin", +] diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py index 658d463..d3b62ff 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni.py @@ -17,9 +17,8 @@ from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import ( TomoIDManager, ) from csaxs_bec.bec_ipython_client.plugins.LamNI.gui_tools import LamniGuiTools +from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni_alignment_mixin import LamNIAlignmentMixin -from .alignment import XrayEyeAlign -from .x_ray_eye_align import XrayEyeAlign as XrayEyeAlignGUI from .x_ray_eye_align import XrayEyeAlign as XrayEyeAlignGUI from .lamni_optics_mixin import LaMNIInitStages, LamNIOpticsMixin @@ -33,16 +32,22 @@ if builtins.__dict__.get("bec") is not None: umvr = builtins.__dict__.get("umvr") -class LamNI(LamNIOpticsMixin, LamniGuiTools): +class LamNI(LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools): def __init__(self, client): super().__init__() self.client = client self.set_client(client) - self.set_client(client) self.device_manager = client.device_manager - self.align = XrayEyeAlign(client, self) self.init = LaMNIInitStages(client) + # Correction state (owned by LamNIAlignmentMixin methods) + 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 = [] + # Extracted collaborators self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername) self.tomo_id_manager = TomoIDManager() @@ -64,7 +69,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): self.progress["total_projections"] = 1 self.progress["angle"] = 0 - # ------------------------------------------------------------------ # Special angles # ------------------------------------------------------------------ @@ -87,31 +91,15 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): self.special_angle_repeats = 1 # ------------------------------------------------------------------ - # RT feedback / interferometer helpers + # X-ray eye alignment entry points # ------------------------------------------------------------------ - - def start_x_ray_eye_alignment(self): - """Run the BEC GUI-based X-ray eye alignment procedure. - - Creates a fresh XrayEyeAlignGUI instance (which resets the correction - state) and calls its align() method. The GUI window is opened - automatically. Interrupt with Ctrl-C to abort. - """ - aligner = XrayEyeAlignGUI(self.client, self) - try: - aligner.align() - except KeyboardInterrupt as exc: - print("Alignment interrupted by user.") - raise exc - - def xrayeye_alignment_start(self, keep_shutter_open: bool = False): """Run the BEC GUI-based X-ray eye alignment procedure. - Creates a fresh XrayEyeAlignGUI instance (which resets the correction - state) and calls its align() method. The GUI window is opened - automatically. Interrupt with Ctrl-C to abort. + Creates a fresh :class:`XrayEyeAlignGUI` instance, which resets the + correction state, and calls its ``align()`` method. The GUI window + is opened automatically. Interrupt with Ctrl-C to abort. Args: keep_shutter_open: If True the shutter is left open between angle @@ -127,8 +115,8 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): def xrayeye_update_frame(self, keep_shutter_open: bool = False): """Capture a single fresh X-ray eye frame without running full alignment. - Useful for visually checking the sample position. Requires the X-ray - eye GUI to be open (call lamnigui_show_xeyealign() first if needed). + Useful for visually checking the sample position. Opens the X-ray eye + GUI if it is not already visible. Args: keep_shutter_open: If True the shutter is left open after the frame. @@ -136,6 +124,10 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): aligner = XrayEyeAlignGUI(self.client, self) aligner.update_frame(keep_shutter_open=keep_shutter_open) + # ------------------------------------------------------------------ + # RT feedback / interferometer helpers + # ------------------------------------------------------------------ + def rt_off(self): dev.rtx.enabled = False dev.rty.enabled = False @@ -388,7 +380,9 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): @golden_projections_at_0_deg_for_damage_estimation.setter def golden_projections_at_0_deg_for_damage_estimation(self, val: int): - self.client.set_global_var("golden_projections_at_0_deg_for_damage_estimation", val) + self.client.set_global_var( + "golden_projections_at_0_deg_for_damage_estimation", val + ) @property def sample_name(self): @@ -440,7 +434,7 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): ) # ------------------------------------------------------------------ - # Sample database — delegated to TomoIDManager in omny general tools + # Sample database — delegated to TomoIDManager # ------------------------------------------------------------------ def add_sample_database( @@ -464,9 +458,9 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): def tomo_scan_projection(self, angle: float): scans = builtins.__dict__.get("scans") - 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) + additional_correction = self.compute_additional_correction(angle) + additional_correction_2 = self.compute_additional_correction_2(angle) + correction_xeye_mu = self.lamni_compute_additional_correction_xeye_mu(angle) self._current_scan_list = [] @@ -484,8 +478,8 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): stitch_x=stitch_x, stitch_y=stitch_y, stitch_overlap=self.tomo_stitch_overlap, - center_x=self.align.tomo_fovx_offset, - center_y=self.align.tomo_fovy_offset, + center_x=self.tomo_fovx_offset, + center_y=self.tomo_fovy_offset, shift_x=( self.manual_shift_x + correction_xeye_mu[0] @@ -600,10 +594,8 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): for scan_nr in range(start_scan_number, end_scan_number): self._write_tomo_scan_number(scan_nr, angle, subtomo_number) - #todo here bl chk, if ok then successfull true successful = True - def _golden(self, ii, howmany_sorted, maxangle=360, reverse=False): """Return the ii-th golden ratio angle within sorted bunches and its subtomo number.""" golden = [] @@ -784,8 +776,8 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): print(f"Circular FOV diam = {self.tomo_circfov}") print(f"Reconstruction queue name = {self.ptycho_reconstruct_foldername}") print("FOV offset rotates to find the ROI; manual shift moves the rotation center.") - print(f" _tomo_fovx_offset = {self.align.tomo_fovx_offset}") - print(f" _tomo_fovy_offset = {self.align.tomo_fovy_offset}") + print(f" _tomo_fovx_offset = {self.tomo_fovx_offset}") + print(f" _tomo_fovy_offset = {self.tomo_fovy_offset}") print(f" _manual_shift_x = {self.manual_shift_x}") print(f" _manual_shift_y = {self.manual_shift_y}") print("") @@ -948,7 +940,6 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): ) self.client.logbook.send_logbook_message(msg) - def get_calibration_of_capstops_left_and_right(self): import time print(""" @@ -959,28 +950,28 @@ class LamNI(LamNIOpticsMixin, LamniGuiTools): Example: At 0 deg, accessible rty -60 to 51. So the init was 5 microns off. Then this routine here will provide data for the new capstop left and right. """) - + angle = 0 - umv(dev.lsamrot,0) + umv(dev.lsamrot, 0) print("Capstop right\nAngle, Voltage1, Voltage2") - mv(dev.lsamrot,361) + mv(dev.lsamrot, 361) while angle <= 360: angle = dev.lsamrot.readback.get() - voltage1=float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[1]")) - voltage2=float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[2]")) - if angle<360: + voltage1 = float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[1]")) + voltage2 = float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[2]")) + if angle < 360: print(f"{angle},{voltage1},{voltage2}") time.sleep(.3) time.sleep(10) print("\nCapstop left\nAngle, Voltage1, Voltage2") - mv(dev.lsamrot,-1) + mv(dev.lsamrot, -1) while angle > 0: angle = dev.lsamrot.readback.get() - voltage1=float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[1]")) - voltage2=float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[2]")) - if angle>0: + voltage1 = float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[1]")) + voltage2 = float(dev.lsamrot.controller.socket_put_and_receive("MG@AN[2]")) + if angle > 0: print(f"{angle},{voltage1},{voltage2}") time.sleep(.3) - print("Finished") \ No newline at end of file + print("Finished") diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_alignment_mixin.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_alignment_mixin.py new file mode 100644 index 0000000..bda8c1d --- /dev/null +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_alignment_mixin.py @@ -0,0 +1,282 @@ +"""LamNI alignment correction infrastructure. + +This mixin provides the correction infrastructure that is mixed directly into +the :class:`LamNI` class, mirroring the pattern used by +:class:`FlomniAlignmentMixin` in flomni.py. + +The mixin assumes the hosting class provides: + - ``self.client`` (BECClient) + +State that must be initialised in the host ``__init__``: + - ``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`` +""" +from __future__ import annotations + +import builtins +import os + +import numpy as np +from bec_lib import bec_logger +from typeguard import typechecked + +logger = bec_logger.logger + +dev = builtins.__dict__.get("dev") + + +class LamNIAlignmentMixin: + """Correction infrastructure for LamNI laminography. + + Mixes into :class:`LamNI`. All methods are accessible directly as + ``lamni.reset_correction()``, ``lamni.tomo_fovx_offset``, etc., + matching the FlOMNI user API. + """ + + # Pixel calibration: mm per pixel (0.2 mm FOV, 218 px) + PIXEL_CALIBRATION = 0.2 / 218 + + # ------------------------------------------------------------------ + # Correction reset + # ------------------------------------------------------------------ + + def reset_correction(self): + """Reset the look-up-table corrections to empty (iteration 1 and 2).""" + self.corr_pos_x = [] + self.corr_pos_y = [] + self.corr_angle = [] + + def reset_correction_2(self): + """Reset the second iteration look-up-table correction to empty.""" + self.corr_pos_x_2 = [] + self.corr_pos_y_2 = [] + self.corr_angle_2 = [] + + def reset_xray_eye_correction(self): + """Delete the X-ray eye sinusoidal fit from the BEC global variable store.""" + self.client.delete_global_var("tomo_fit_xray_eye") + + # ------------------------------------------------------------------ + # FOV offset properties (backed by BEC global variable) + # ------------------------------------------------------------------ + + @property + def tomo_fovx_offset(self): + """Horizontal FOV offset in mm (rotated with laminography geometry).""" + 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): + """Vertical FOV offset in mm (rotated with laminography geometry).""" + 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]) + + # ------------------------------------------------------------------ + # X-ray eye sinusoidal correction — read from files or GUI + # ------------------------------------------------------------------ + + # def read_xray_eye_correction(self, dir_path=None): + # """Load the sinusoidal X-ray eye fit from archived text files. + + # Files are written by :meth:`XrayEyeAlign.write_output` at the end of + # every alignment run and are the fallback when the GUI has been closed. + + # Args: + # dir_path: Directory containing ``ptychotomoalign_{A,B,C}{x,y}.txt``. + # Defaults to ``~/Data10/specES1/internal/``. + # """ + # if dir_path is None: + # dir_path = os.path.expanduser("~/Data10/specES1/internal/") + # tomo_fit_xray_eye = np.zeros((2, 3)) + # for i, axis in enumerate(["x", "y"]): + # for j, coeff in enumerate(["A", "B", "C"]): + # with open( + # os.path.join(dir_path, f"ptychotomoalign_{coeff}{axis}.txt"), "r" + # ) as f: + # tomo_fit_xray_eye[i][j] = f.readline() + # self.client.set_global_var("tomo_fit_xray_eye", tomo_fit_xray_eye.tolist()) + # print("New alignment parameters loaded from X-ray eye files:") + # self._print_xeye_fit(tomo_fit_xray_eye) + + def read_xray_eye_correction_from_gui(self): + """Load the sinusoidal X-ray eye fit from the live XRayEye GUI widget. + + Reads ``fit_params_x`` and ``fit_params_y`` from the ``omny_xray_gui`` + device and stores the result as + ``tomo_fit_xray_eye = [[Ax, Bx, Cx], [Ay, By, Cy]]`` + in the BEC global variable store. + + The stored array is consumed by + :meth:`lamni_compute_additional_correction_xeye_mu`. + + .. important:: + This method reads from the live GUI widget. If the XRayEye GUI + window has been closed since the alignment was performed the call + will fail. In that case use :meth:`read_xray_eye_correction` to + reload from the archived text files. + """ + _dev = builtins.__dict__.get("dev") + tomo_fit_xray_eye = np.zeros((2, 3)) + + params_x = _dev.omny_xray_gui.fit_params_x.get() + tomo_fit_xray_eye[0][0] = params_x["SineModel_0_amplitude"] + tomo_fit_xray_eye[0][1] = params_x["SineModel_0_shift"] + tomo_fit_xray_eye[0][2] = params_x["LinearModel_1_intercept"] + + params_y = _dev.omny_xray_gui.fit_params_y.get() + tomo_fit_xray_eye[1][0] = params_y["SineModel_0_amplitude"] + tomo_fit_xray_eye[1][1] = params_y["SineModel_0_shift"] + tomo_fit_xray_eye[1][2] = params_y["LinearModel_1_intercept"] + + self.client.set_global_var("tomo_fit_xray_eye", tomo_fit_xray_eye.tolist()) + print("New alignment parameters loaded from XRayEye GUI fit:") + self._print_xeye_fit(tomo_fit_xray_eye) + + @staticmethod + def _print_xeye_fit(fit): + """Pretty-print the 2×3 X-ray eye fit array.""" + print( + f" X: A={fit[0][0]:.4f}, B={fit[0][1]:.4f}, C={fit[0][2]:.4f}\n" + f" Y: A={fit[1][0]:.4f}, B={fit[1][1]:.4f}, C={fit[1][2]:.4f}" + ) + + def lamni_compute_additional_correction_xeye_mu(self, angle): + """Evaluate the sinusoidal X-ray eye correction at *angle* degrees. + + Returns: + tuple: ``(correction_x_mm, correction_y_mm)`` + """ + tomo_fit_xray_eye = self.client.get_global_var("tomo_fit_xray_eye") + if tomo_fit_xray_eye is None: + print("Not applying any X-ray eye correction. No fit data available.\n") + return (0, 0) + + 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:.6f} mm," + f" y={correction_y:.6f} mm @ angle={angle}\n" + ) + return (correction_x, correction_y) + + # ------------------------------------------------------------------ + # Additional look-up-table corrections (iteration 1 and 2) + # ------------------------------------------------------------------ + + def read_additional_correction(self, correction_file: str): + """Load the iteration-1 correction lookup table from *correction_file*.""" + self.corr_pos_x, self.corr_pos_y, self.corr_angle = self._read_correction_file_xy( + correction_file + ) + + def read_additional_correction_2(self, correction_file: str): + """Load the iteration-2 correction lookup table from *correction_file*.""" + self.corr_pos_x_2, self.corr_pos_y_2, self.corr_angle_2 = ( + self._read_correction_file_xy(correction_file) + ) + + def _read_correction_file_xy(self, correction_file: str): + """Parse a correction file containing ``corr_pos_x``, ``corr_pos_y`` + and ``corr_angle`` entries. + + Returns: + tuple: ``(corr_pos_x, corr_pos_y, corr_angle)`` as lists of floats. + """ + 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 _ in range(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)) + return corr_pos_x, corr_pos_y, corr_angle + + def compute_additional_correction(self, angle): + """Return the iteration-1 lookup-table correction for *angle*. + + Returns: + tuple: ``(shift_x_mm, shift_y_mm)`` + """ + return self._compute_correction_xy( + angle, self.corr_pos_x, self.corr_pos_y, self.corr_angle, label="1" + ) + + def compute_additional_correction_2(self, angle): + """Return the iteration-2 lookup-table correction for *angle*. + + Returns: + tuple: ``(shift_x_mm, shift_y_mm)`` + """ + return self._compute_correction_xy( + angle, self.corr_pos_x_2, self.corr_pos_y_2, self.corr_angle_2, label="2" + ) + + def _compute_correction_xy(self, angle, corr_pos_x, corr_pos_y, corr_angle, label=""): + """Find the correction for the closest angle in the lookup table.""" + if not corr_pos_x: + print(f"Not applying additional correction {label}. No data available.\n") + return (0, 0) + + shift_x = corr_pos_x[0] + shift_y = corr_pos_y[0] + angledelta = np.fabs(corr_angle[0] - angle) + + for j in range(1, len(corr_pos_x)): + newangledelta = np.fabs(corr_angle[j] - angle) + if newangledelta < angledelta: + shift_x = corr_pos_x[j] + shift_y = corr_pos_y[j] + angledelta = newangledelta + + if shift_x == 0 and angle < corr_angle[0]: + shift_x = corr_pos_x[0] + shift_y = corr_pos_y[0] + + if shift_x == 0 and angle > corr_angle[-1]: + shift_x = corr_pos_x[-1] + shift_y = corr_pos_y[-1] + + print(f"Additional correction {label}: x={shift_x}, y={shift_y}") + return (shift_x, shift_y) diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_optics_mixin.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_optics_mixin.py index f40fd88..7109c35 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_optics_mixin.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/lamni_optics_mixin.py @@ -193,7 +193,7 @@ class LamNIOpticsMixin: leyex_in = self._get_user_param_safe("leyex", "in") leyey_in = self._get_user_param_safe("leyey", "in") umv(dev.leyex, leyex_in, dev.leyey, leyey_in) - self.align.update_frame() + self.xrayeye_update_frame() def _lfzp_in(self): loptx_in = self._get_user_param_safe("loptx", "in") diff --git a/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py b/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py index 487b558..9d9b57a 100644 --- a/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py +++ b/csaxs_bec/bec_ipython_client/plugins/LamNI/x_ray_eye_align.py @@ -35,7 +35,7 @@ class XrayEyeAlign: coordinates (x and y) at 8 equally-spaced laminography angles over the full 360°, submits them to the XRayEye widget's fit tab, and then reads the resulting sinusoidal fit parameters back via - ``lamni.align.read_xray_eye_correction_from_gui()``. + ``lamni.read_xray_eye_correction_from_gui()``. The key difference from the FlOMNI alignment is that LamNI needs *both* x and y fits because the tilted rotation axis (61°) couples both @@ -50,6 +50,9 @@ class XrayEyeAlign: self.lamni = lamni self.device_manager = client.device_manager self.scans = client.scans + # Reset correction state on the lamni object (mirrors FlOMNI pattern) + self.lamni.reset_correction() + self.lamni.reset_xray_eye_correction() # alignment_values[k] = [x_mm, y_mm] # k=0 : FZP centre # k=1 : sample at 0° (reference; shift_xy computed from k=0 vs k=1) @@ -57,9 +60,6 @@ class XrayEyeAlign: # ... # k=8 : sample at 315° self.alignment_values: dict[int, list[float]] = {} - # Reset correction state via the existing alignment object on lamni - self.lamni.align.reset_correction() - self.lamni.align.reset_xray_eye_correction() # ------------------------------------------------------------------ # GUI shortcut @@ -171,9 +171,9 @@ class XrayEyeAlign: self._reset_init_values() # --- Step 0: FZP centre ------------------------------------------ - self._disable_rt_feedback() + #self._disable_rt_feedback() self.lamni.lfzp_in() - self._enable_rt_feedback() + #self._enable_rt_feedback() self.update_frame(keep_shutter_open) @@ -207,9 +207,9 @@ class XrayEyeAlign: self.gui.enable_submit_button(False) self.lamni.loptics_out() - self._disable_rt_feedback() - time.sleep(0.3) - self._enable_rt_feedback() + #self._disable_rt_feedback() + #time.sleep(0.3) + #self._enable_rt_feedback() self.update_frame(keep_shutter_open) self.send_message("Find the sample and submit its centre.") @@ -352,7 +352,7 @@ class XrayEyeAlign: # Read fit parameters from the GUI into the global variable store print("Loading new alignment parameters from X-ray eye GUI fit.") - self.lamni.align.read_xray_eye_correction_from_gui() + self.lamni.read_xray_eye_correction_from_gui() if keep_shutter_open: answer = input("Close the shutter now? [Y/n]: ").strip().lower() @@ -363,10 +363,9 @@ class XrayEyeAlign: else: print("Shutter left open.") - # Return to 0 deg and reset scan centre + # Return to 0 deg self._disable_rt_feedback() self.tomo_rotate(0) - umv(dev.rtx, 0) print( "Done. You are ready to remove the X-ray eye and start ptychography scans.\n" "Fine alignment: lamni.tomo_parameters(), then lamni.tomo_alignment_scan()" @@ -425,4 +424,4 @@ class XrayEyeAlign: # Push to XRayEye widget: feeds waveform_x (row 1) and waveform_y (row 2) self.gui.submit_fit_array(data) - print(f"Fit data submitted with shape {data.shape}:\n{data}") \ No newline at end of file + print(f"Fit data submitted with shape {data.shape}:\n{data}") diff --git a/docs/user/ptychography/lamni.md b/docs/user/ptychography/lamni.md index a65e424..dac61b8 100644 --- a/docs/user/ptychography/lamni.md +++ b/docs/user/ptychography/lamni.md @@ -41,13 +41,13 @@ With LamNI it can be difficult to relocate the sample between rotations. To keep `lamni.xrayeye_alignment_start(keep_shutter_open=True)` To manually reload the fit parameters after the procedure has completed: -`lamni.align.read_xray_eye_correction_from_gui()` +`lamni.read_xray_eye_correction_from_gui()` **Note:** this reads from the live GUI widget via the `omny_xray_gui` device. It only works as long as the XRayEye GUI window remains open. If the window has been closed, reload from the archived text files instead: -`lamni.align.read_xray_eye_correction()` +`lamni.read_xray_eye_correction()` (these files are written to `~/Data10/specES1/internal/xrayeye_alignmentvalues` at the end of every alignment run) The correction is applied at each projection angle via -`lamni.align.lamni_compute_additional_correction_xeye_mu(angle)` +`lamni.lamni_compute_additional_correction_xeye_mu(angle)` which is called automatically inside `lamni.tomo_scan_projection()`. To capture a single fresh frame without running the full alignment: @@ -68,12 +68,12 @@ The sample fine alignment can be obtained using ptychography. For this a short l * Record a last projection for all scans to reconstruct `lamni.tomo_scan_projection(0)` and wait for the reconstructions to be complete * Run `SPEC_ptycho_align.m` (in Matlab, **force ptycho=1**, and **correct scan numbers**) * Click the sample position in the Matlab GUI and then load the generated file by, for example - `lamni.align.read_additional_correction('/sls/X12SA/data/e20632/Data10/cxs_software/ptycho/correction_lamni_um_S05389_lamni_fit.txt')` -* With this alignment a second iteration could be performed. To read the second correction file use `lamni.align.read_additional_correction_2()` + `lamni.read_additional_correction('/sls/X12SA/data/e20632/Data10/cxs_software/ptycho/correction_lamni_um_S05389_lamni_fit.txt')` +* With this alignment a second iteration could be performed. To read the second correction file use `lamni.read_additional_correction_2()` #### Shifting the FOV -* `lamni.align.tomo_fovx/y_offset=value` [mm] will shift the field of view. Perform this adjustment from projections collected at **lsamrot 0 degrees**. +* `lamni.tomo_fovx/y_offset=value` [mm] will shift the field of view. Perform this adjustment from projections collected at **lsamrot 0 degrees**. ### Laminography scan @@ -89,9 +89,9 @@ Start the laminography scan by #### Reset corrections -* `lamni.align.reset_correction()` -* `lamni.align.reset_correction_2()` -* `lamni.align.reset_xray_eye_correction()` +* `lamni.reset_correction()` +* `lamni.reset_correction_2()` +* `lamni.reset_xray_eye_correction()` #### Adjusting beam size with feedback running @@ -203,4 +203,4 @@ Following functions exist to move the optics in and out, the naming is self-expl * `lamni.loptics_out()` * `lamni.losa_in()` * `lamni.losa_out()` -* `lamni.lfzp_in()` \ No newline at end of file +* `lamni.lfzp_in()`