Source code for csaxs_bec.bec_ipython_client.plugins.omny.omny_optics_mixin

import time
import numpy as np

from rich import box
from rich.console import Console
from rich.table import Table

from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose


[docs] class OMNYError(Exception): pass
[docs] class OMNYOpticsMixin: @staticmethod def _get_user_param_safe(device, var): param = dev[device].user_parameter if not param or param.get(var) is None: raise ValueError(f"Device {device} has no user parameter definition for {var}.") return param.get(var) def ooptics_in(self): self.ofzp_in() # ocs_in self.oosa_in() if "rtx" in dev and dev.rtx.enabled: dev.rtx.controller.feedback_enable() self.align.update_frame() user_input = input( "Is the direct beam gone on the xray eye? Do you see the cone of the FZP?" ) if user_input == "y": printf("Next oeye_out...\n") else: raise OMNYError("Failed to properly move in the Xray optics") def _oeyey_mv(self, position): # direction dependent speeds if dev.oeyez.get().readback < position: dev.oeyez.controller.socket_put_confirmed("axspeed[7]=15000") else: dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000") umv(dev.oeyey, position) dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000") def oeye_out(self): dev.fsh.fshclose() if self.OMNYTools.yesno("Did you move in the optics?"): umv(dev.oeyez, -2) self._oeyey_mv(-60.3) # free camera epics_put("XOMNYI-XEYE-ACQ:0", 2) else: raise OMNYError("The optics were not moved in. Please do so prior to eyey_out") self.OMNYTools.printgreen("Oeye is out.") def oeye_cam_in(self): if dev.oeyez.get().readback < -80: umv(dev.oeyez, -50) if np.fabs(dev.oeyey.get().readback + 4.8) > 0.1: self._oeyey_mv(-4.8) if np.fabs(dev.oeyez.get().readback + 2) > 0.1 or np.fabs(dev.oeyex.get().readback) > 0.1: umv(dev.oeyez, -2, dev.oeyex, 0) # if still too close in z -- safety check if np.fabs(dev.oeyez.get().readback + 2) > 0.1: raise OMNYError("The oeye is too close in z for transfer. ERROR! Aborting.") self.OMNYTools.printgreen("Oeye is at cam position.") def _oeye_xray_is_in(self) -> bool: omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in") omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in") omny_oeye_currentx = dev.oeyex.get().readback omny_oeye_currenty = dev.oeyey.get().readback if ( np.fabs(omny_oeye_currentx - omny_oeye_xray_inx) < 0.1 and np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) < 0.1 ): return True else: return False def oeye_xray_in(self): if self._oeye_xray_is_in(): pass else: # todo # self._otransfer_gripper_safe_xray_in_operation() # if(!_oshield_is_ST_closed()) # { # printf("The shield of the sample stage is not closed. Aborting.\n") # exit # } omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in") omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in") omny_oeye_xray_inz = self._get_user_param_safe("oeyez", "xray_in") self._oeyey_mv(omny_oeye_xray_iny) omny_oeye_currenty = dev.oeyey.get().readback if np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) > 0.1: raise OMNYError("The oeye did not move up.\n") umv(dev.oeyex, omny_oeye_xray_inx, dev.oeyez, omny_oeye_xray_inz) self.OMNYTools.printgreen("Oeye is at X-ray position.") # some notes for the vis microscope: # initial position for the vis light microscope # do not open the shield when the microscope is at the vis mic position # found eoeyx -45.13, z -84.9, y 0.64 # for a samy position of 2.8 with delta off # the osa position should be in z around 7.4. in x it seems better # around -0.6, where potentially xrays dont pass anymore # def _oosa_check_y(self): omny_oosa_currenty = dev.oosay.get().readback if np.fabs(omny_oosa_currenty - 0.9) > 0.05: umv(dev.oosay, 0.9) omny_oosa_currenty = dev.oosay.get().readback if np.fabs(omny_oosa_currenty - 0.9) > 0.05: raise OMNYError("oosay is not around 0.9. Aborting.") def _oosa_to_move_corridor(self): self._oosa_check_y() dev.oosax.limits = [-3, 3.7] # risk collision with shield umv(dev.oosax, -2) dev.oosax.read(cached=False) omny_oosa_currentx = dev.oosax.get().readback if np.fabs(omny_oosa_currentx + 2) > 0.1: raise OMNYError("oosax did not reach target position. Not moving in z.\n") def oosa_in(self): self._oosa_check_y() dev.oshield.read(cached=False) omny_oshield_current = dev.oshield.get().readback if omny_oshield_current < 15: self._oshield_ST_close() if self.near_field == False: x_in_pos = self._get_user_param_safe("oosax", "far_field_in") y_in_pos = self._get_user_param_safe("oosay", "far_field_in") z_in_pos = self._get_user_param_safe("oosaz", "far_field_in") print("OSA movement in far-field mode.") dev.oosaz.read(cached=False) omny_oosa_currentz = dev.oosaz.get().readback if omny_oosa_currentz < 6.4: self._oosa_to_move_corridor() dev.oosaz.limits = [6.4, 6.6] umv(dev.oosaz, z_in_pos) umv(dev.oosax, x_in_pos) umv(dev.oosay, y_in_pos) #### For the 30 nm FZP 220 um we use this part # umv oosaz 6.5 # umv oosax 3.2453 # umv oosay 0.386015 if self.near_field == True: x_in_pos = self._get_user_param_safe("oosax", "near_field_in") y_in_pos = self._get_user_param_safe("oosay", "near_field_in") z_in_pos = self._get_user_param_safe("oosaz", "near_field_in") print("OSA movement in near-field mode.") dev.oosaz.read(cached=False) omny_oosa_currentz = dev.oosaz.get().readback if omny_oosa_currentz > 0: self._oosa_to_move_corridor() dev.oosaz.limits = [-0.4, -0.6] umv(dev.oosaz, z_in_pos) umv(dev.oosax, x_in_pos) omny_osamy_current = dev.osamy.get().readback if omny_osamy_current < 3.25: umv(dev.oosay, y_in_pos) else: raise OMNYError("Failed to move oosa in. osamy position is too large.") self.OMNYTools.printgreen("OSA is in.") # todo # _omny_interferometer_align_tracking # rt_feedback_enable def oosa_out(self): self._oosa_check_y() dev.oshield.read(cached=False) omny_oshield_current = dev.oshield.get().readback if omny_oshield_current < 15: self._oshield_ST_close() omny_oosaz_current = dev.oosaz.get().readback if self.near_field == False: print("OSA movement in far-field mode.") if omny_oosaz_current < 6.4: self._oosa_to_move_corridor() dev.oosaz.limits = [6.4, 6.6] umv(dev.oosaz, 6.5) umv(dev.oosax, -2) if self.near_field == True: print("OSA movement in near-field mode.") if omny_oosaz_current > 0: self._oosa_to_move_corridor() dev.oosaz.limits = [-0.4, -0.6] umv(dev.oosaz, -0.45) umv(dev.oosax, -2) # todo _omny_interferometer_align_tracking self.OMNYTools.printgreen("OSA is out.") def oosa_move_out_of_shield(self): # todo: _omnycam_samplestage self._oosa_check_y() self._oosa_to_move_corridor() omny_osamx_current = dev.osamx.get().readback if np.fabs(omny_osamx_current) > 0.2: umv(dev.osamx, 0) omny_oosaz_current = dev.oosaz.get().readback if omny_oosaz_current > 0.1: dev.oosaz.limits = [-0.1, 0.1] umv(dev.oosaz, 0) self.OMNYTools.printgreen("OSA is out of shield.") def ofzp_out(self): if "rtx" in dev and dev.rtx.enabled: dev.rtx.controller.feedback_disable() y_out_pos = self._get_user_param_safe("ofzpy", "out") if np.fabs(dev.ofzpy.get().readback - y_out_pos) > 0.02: umv(dev.ofzpy, y_out_pos) self.OMNYTools.printgreen("FZP at out position") def ofzp_in(self): if "rtx" in dev and dev.rtx.enabled: dev.rtx.controller.feedback_disable() x_in_pos = self._get_user_param_safe("ofzpx", "in") y_in_pos = self._get_user_param_safe("ofzpy", "in") if np.fabs(dev.ofzpy.get().readback - y_in_pos) > 0.02: umv(dev.ofzpy, y_in_pos) if np.fabs(dev.ofzpx.get().readback - x_in_pos) > 0.02: umv(dev.ofzpx, x_in_pos) self.OMNYTools.printgreen("FZP at in position") # 220 mu FZP at ofzpz 31.8025 for eiger probe (about 2.4 mm propagation after focus) # umv(dev.ofzpy, 0.7944) # if np.fabs(dev.ofzpx.get().readback+0.4317)>0.05: # umv(dev.ofzpx, -0.4317) # note the 220 fzp also works for near field 6.2 kev by just moving back osa and fzp # ofzpz 24.8 leads to a 9.5 mm propagation distance. # With the 220 mu FZP this gives 100 nm pixel recons # for the oosa macro set near_field=1 # 170 mu FZP at 6.2 kev for large beam at ofzpz 31.8025 of about 58 mu diameter # 120 mu FZP at ofzpz 28.1991 # 250 mu FZP 60 nm at 5.65 keV # ofzpz 29.7 for propagation distance 2.2 # umv ofzpx -0.4457 # umv ofzpy 0.193630 # 150 um fzp, 60 nm, ofzpz 33.8 at 8.9 kev for propagation of 1.7 mm after focus # umv ofzpx -0.756678 # umv ofzpy 0.193515 # 250 um 30 nm FZP upper right # small abberrations, seems to give good results in weak objects # ofzpx -0.609240 # umv ofzpy 0.118265 # 250 um 30 nm FZP lower right very aberated # ofzpx -0.881935 # umv ofzpy 0.537050 # ofzpz 28.4027 # 5.30 mm prop at 8.9 keV, 45 nm pixel in near field # ofzpz 33.103 # 0.6 mm prop at 8.9 kev far field 7 m flight tube at foptz # ofzpz 49.4 is reachable just without interferometer swap # which at 6.2 keV and 250 um diam, 30 nm should gives a propagation of 0.8 after focus # and a beam size of 6 microns diamter ###coordinates 30 nm FZP for comparing them # not sure if that is really correct # FZP 1 - FZP 2 # FZP 5 # FZP 4 - FZP 1 # FZP ##upper right # umv ofzpx -0.6154 ofzpy 0.1183 # umv ocsx -0.6070 ocsy 0.0540 # lower right # umv ofzpx -0.8341 ofzpy 0.5683 # umv ocsx -0.3880 ocsy -0.3960 # lower left # umv ofzpx -0.3876 ofzpy 0.7902 # umv ocsx -0.8380 ocsy -0.6180 # upper left # umv ofzpx -0.1678 ofzpy 0.3403 # umv ocsx -1.0550 ocsy -0.1680 def ofzp_info(self, mokev_val=-1, ofzpz_val=-1): print(f"{ofzpz_val}") if mokev_val == -1: try: mokev_val = dev.mokev.readback.get() except: print( "Device mokev does not exist. You can specify the energy in keV as an argument instead." ) return if ofzpz_val == -1: ofzpz_val = dev.ofzpz.readback.get() distance = 66 + 2.4 + 31.8025 - ofzpz_val print( f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 60 nm FZP.\n" ) print(f"At the current energy of {mokev_val:.4f} keV we have following options:\n") diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6] console = Console() table = Table(title="Outermost zone width \033[1m60 nm\033[0m", box=box.SQUARE) table.add_column("Diameter", justify="center") table.add_column("Focal distance", justify="center") table.add_column("Current beam size", justify="center") wavelength = 1.2398e-9 / mokev_val for diameter in diameters: outermost_zonewidth = 60e-9 focal_distance = diameter * outermost_zonewidth / wavelength * 1000 beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9 table.add_row( f"{diameter*1e6:.2f} microns", f"{focal_distance:.2f} mm", f"{beam_size:.2f} microns", ) console.print(table) # 30 nm with additional spacer distance = 53.84 + 0.6 + 33.1 - ofzpz_val print( f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 30 nm FZP.\n" ) diameters = [150e-6, 250e-6] console = Console() table = Table(title="Outermost zone width \033[1m30 nm\033[0m", box=box.SQUARE) table.add_column("Diameter", justify="center") table.add_column("Focal distance", justify="center") table.add_column("Current beam size", justify="center") wavelength = 1.2398e-9 / mokev_val for diameter in diameters: outermost_zonewidth = 30e-9 focal_distance = diameter * outermost_zonewidth / wavelength * 1000 beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9 table.add_row( f"{diameter*1e6:.2f} microns", f"{focal_distance:.2f} mm", f"{beam_size:.2f} microns", ) console.print(table) print( "This function can be called with explicit energy and ofzpz position.\n Example: omny.ffzp_info(mokev_val=6.2, ofzpz_val=33.2)" )
# from flomni # oosaz_val = dev.oosaz.readback.get() # print("\nOSA Information:") # print(f" Current fosaz {fosaz_val:.1f}") # print( # f" The OSA will collide with a normal OMNY pin at fosaz \033[1m{(33-fosaz_val):.1f}\033[0m" # ) # print(f" Remaining space: \033[1m{-fosaz_val+(33-foptz_val):.1f}\033[0m")