From 37981c7c2d5ad57a3abd6cf5a89979d09fd10b32 Mon Sep 17 00:00:00 2001 From: Shekar V Date: Wed, 31 Jul 2024 15:18:17 -0400 Subject: [PATCH 1/4] Added floco lock and cs8recovery procedure --- daq_lib.py | 20 +++++++++++++++++++- daq_macros.py | 18 ++++++++++++++++++ start_bs.py | 3 ++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/daq_lib.py b/daq_lib.py index e8ec44f2..47422473 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -28,7 +28,11 @@ import ispybLib except Exception as e: logger.error("daq_lib: ISPYB import error, %s" % e) - + +if daq_utils.beamline in ["amx", "fmx"]: + from start_bs import gov_mon_signal + + #all keys below are created as beamlineComm PVs. if adding items here, be sure to add them to the simulator var_list = {'beam_check_flag':0,'overwrite_check_flag':1,'omega':0.00,'kappa':0.00,'phi':0.00,'theta':0.00,'distance':10.00,'rot_dist0':300.0,'inc0':1.00,'exptime0':5.00,'file_prefix0':'lowercase','numstart0':0,'col_start0':0.00,'col_end0':1.00,'scan_axis':'omega','wavelength0':1.1,'datum_omega':0.00,'datum_kappa':0.00,'datum_phi':0.00,'size_mode':0,'spcgrp':1,'state':"Idle",'state_percent':0,'datafilename':'none','active_sweep':-1,'html_logging':1,'take_xtal_pics':0,'px_id':'none','xtal_id':'none','current_pinpos':0,'sweep_count':0,'group_name':'none','mono_energy_target':1.1,'mono_wave_target':1.1,'energy_inflection':12398.5,'energy_peak':12398.5,'wave_inflection':1.0,'wave_peak':1.0,'energy_fall':12398.5,'wave_fall':1.0,'beamline_merit':0,'fprime_peak':0.0,'f2prime_peak':0.0,'fprime_infl':0.0,'f2prime_infl':0.0,'program_state':"Program Ready",'filter':0,'edna_aimed_completeness':0.99,'edna_aimed_ISig':2.0,'edna_aimed_multiplicity':'auto','edna_aimed_resolution':'auto','mono_energy_current':1.1,'mono_energy_scan_step':1,'mono_wave_current':1.1,'mono_scan_points':21,'mounted_pin':(db_lib.beamlineInfo(daq_utils.beamline, 'mountedSample')["sampleID"]),'pause_button_state':'Pause','vector_on':0,'vector_fpp':1,'vector_step':0.0,'vector_translation':0.0,'xia2_on':0,'grid_exptime':0.2,'grid_imwidth':0.2,'choochResultFlag':"0",'xrecRasterFlag':"0"} @@ -218,6 +222,20 @@ def unlockGUI(): def lockGUI(): logger.info('locking GUI') beamline_support.set_any_epics_pv(daq_utils.beamlineComm+"zinger_flag","VAL",-1) + +def govMonOn(): + gov_mon_signal.set(1) + +def govMonOff(): + gov_mon_signal.set(0) + +def flocoLock(): + lockGUI() + govMonOff() + +def flocoUnlock(): + unlockGUI() + govMonOn() def refreshGuiTree(): beamline_support.set_any_epics_pv(daq_utils.beamlineComm+"live_q_change_flag","VAL",1) diff --git a/daq_macros.py b/daq_macros.py index 4e6da73a..ebc03dbd 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -259,6 +259,24 @@ def run_robot_recovery_procedure(): # Park Gripper and cool gripper robot_lib.cooldownGripper() +def recoverCS8(): + logger.info("Starting CS8 recovery") + logger.info("Recovering robot") + robot_lib.recoverRobot() + logger.info("Drying gripper") + robot_lib.dryGripper() + rotCP = beamline_lib.motorPosFromDescriptor("dewarRot") + logger.info(f"Rotate dewar +1 degree, from {rotCP} to {rotCP+1}") + beamline_lib.mvaDescriptor("dewarRot",rotCP + 1) + logger.info(f"Rotate dewar -1 degree, from {rotCP+1} to {rotCP}") + beamline_lib.mvaDescriptor("dewarRot",rotCP) + logger.info("Setting robot state to SE") + gov_lib.setGovRobot(gov_robot, 'SE') + logger.info("Setting robot state to M") + gov_lib.setGovRobot(gov_robot, 'M') + logger.info("Setting robot state to SE") + gov_lib.setGovRobot(gov_robot, 'SE') + def run_top_view_optimized(): RE(topview_optimized()) diff --git a/start_bs.py b/start_bs.py index 3d5f7499..9f8d5b18 100755 --- a/start_bs.py +++ b/start_bs.py @@ -143,6 +143,7 @@ class SampleXYZ(Device): loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") + gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon") elif beamline == "fmx": @@ -176,7 +177,7 @@ class SampleXYZ(Device): loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") - + gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon") import setenergy_lsdc elif beamline=="nyx": From 2b30cb4730fd463ac0f02e01163177a18dc65a73 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Sat, 2 Nov 2024 10:26:55 -0400 Subject: [PATCH 2/4] Changes to bluesky plans at AMX: - Added floco recovery functions - Check for robot arm speed - Autorecovery during EMBL mount procedure - Added home pins bluesky plan - Removed unnecessary signals from topcam device - Added Ransac Linear regression estimator to topcam --- daq_lib.py | 33 ++++++++++-- daq_macros.py | 40 ++++++++++++++ embl_robot.py | 17 ++++-- mxbluesky/devices/auto_center.py | 3 +- mxbluesky/devices/auto_recovery.py | 34 ++++++++++++ mxbluesky/devices/generic.py | 52 ++++++++++++++++++ mxbluesky/devices/top_align.py | 57 +++++--------------- mxbluesky/plans/auto_recovery.py | 52 ++++++++++++++++++ mxbluesky/plans/top_view.py | 85 ++++++++++++++++++++++-------- start_bs.py | 13 ++++- 10 files changed, 309 insertions(+), 77 deletions(-) create mode 100644 mxbluesky/devices/auto_recovery.py create mode 100644 mxbluesky/plans/auto_recovery.py diff --git a/daq_lib.py b/daq_lib.py index 47422473..36099082 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -16,7 +16,7 @@ from daq_utils import getBlConfig from config_params import * from kafka_producer import send_kafka_message -from start_bs import govs, gov_robot, flyer, RE +from start_bs import govs, gov_robot, flyer, RE, gonio, robot_arm import gov_lib from bluesky.preprocessors import finalize_wrapper import bluesky.plan_stubs as bps @@ -236,6 +236,22 @@ def flocoLock(): def flocoUnlock(): unlockGUI() govMonOn() + +def flocoStopOperations(): + daq_macros.run_recovery_procedure(stop=True) + lockGUI() + +def flocoContinueOperations(): + daq_macros.run_recovery_procedure(stop=False) + flocoUnlock() + beamline_support.set_any_epics_pv(daq_utils.beamlineComm + "command_s", "VAL", + json.dumps( + { + "function": "runDCQueue", + "args": [], + "kwargs": {}, + } + )) def refreshGuiTree(): beamline_support.set_any_epics_pv(daq_utils.beamlineComm+"live_q_change_flag","VAL",1) @@ -379,14 +395,25 @@ def waitBeam(): gui_message("Waiting for beam. Type beamCheckOff() in lsdcServer window to continue.") time.sleep(1.0) +def waitRobotArm(): + waiting = True + while waiting: + gui_message("Robot arm speed not at 100%. Set speed to 100") + time.sleep(1.0) + if robot_arm.is_full_speed(): + waiting = False + def runDCQueue(): #maybe don't run rasters from here??? global abort_flag autoMounted = 0 #this means the mount was performed from a runQueue, as opposed to a manual mount button push logger.info("running queue in daq server") while (1): - if (getBlConfig("queueCollect") == 1 and getBlConfig(BEAM_CHECK) == 1): - waitBeam() + if (getBlConfig("queueCollect") == 1): + if (getBlConfig(BEAM_CHECK) == 1): + waitBeam() + if not start_bs.robot_arm.is_full_speed(): + waitRobotArm() if (abort_flag): abort_flag = 0 #careful about when to reset this return diff --git a/daq_macros.py b/daq_macros.py index ebc03dbd..ff1a521a 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -253,11 +253,22 @@ def run_robot_recovery_procedure(): """ Generic recovery procedure to be used during automated collection""" # Recover robot + logging.info("Running robot recovery procedure (nuclear option)") + logger.info("Recovering robot") robot_lib.recoverRobot() # Dry Gripper + logger.info("Drying gripper") robot_lib.dryGripper() # Park Gripper and cool gripper + logger.info("Cooling gripper") robot_lib.cooldownGripper() + logger.info("Homing pins") + from start_bs import home_pins + RE(home_pins()) + logger.info("Setting robot state to SE") + gov_lib.setGovRobot(gov_robot, 'SE') + logger.info("Turning govmon ON") + daq_lib.govMonOn() def recoverCS8(): logger.info("Starting CS8 recovery") @@ -277,6 +288,35 @@ def recoverCS8(): logger.info("Setting robot state to SE") gov_lib.setGovRobot(gov_robot, 'SE') +def run_recovery_procedure(stop=True): + """ + Manual recovery procedure used in daq_lib.flocoStopOperations and daq_lib.flocoContinueOperations + """ + logger.info(f"Running recovery procedure: {'stop operations' if stop else 'continue operations'}") + logger.info("Recovering robot") + robot_lib.recoverRobot() + if not getBlConfig("robot_online") or not getBlConfig("mountEnabled"): + logger.info("Robot is offline or mount is disabled, sample found in gripper. Stopping recovery...") + return + logger.info("Drying gripper") + robot_lib.dryGripper() + logger.info("Homing pins") + from start_bs import home_pins + RE(home_pins()) + if stop: + logger.info("Disabling mount") + disableMount() + logger.info("Turning robot off") + robotOff() + else: + logger.info("Enabling mount") + enableMount() + logger.info("Turning robot on") + robotOn() + logger.info("Setting robot state to SE") + gov_lib.setGovRobot(gov_robot, 'SE') + + def run_top_view_optimized(): RE(topview_optimized()) diff --git a/embl_robot.py b/embl_robot.py index 6b9b516f..5ae6cd2d 100644 --- a/embl_robot.py +++ b/embl_robot.py @@ -275,9 +275,20 @@ def mount(self, gov_robot, puckPos,pinPos,sampID,**kwargs): except Exception as e: e_s = str(e) message = "ROBOT mount ERROR: " + e_s - daq_lib.gui_message(message) - logger.error(message) - return MOUNT_FAILURE + if "Load ABORT with exception Timeout" in e_s: + daq_macros.run_robot_recovery_procedure() + try: + RobotControlLib.mount(absPos) + except Exception as e: + e_s = str(e) + message = "ROBOT mount ERROR: " + e_s + daq_lib.gui_message(message) + logger.error(message) + return MOUNT_FAILURE + else: + daq_lib.gui_message(message) + logger.error(message) + return MOUNT_FAILURE else: time.sleep(0.5) if (getPvDesc("sampleDetected") == 0): diff --git a/mxbluesky/devices/auto_center.py b/mxbluesky/devices/auto_center.py index c8434076..f021ed21 100644 --- a/mxbluesky/devices/auto_center.py +++ b/mxbluesky/devices/auto_center.py @@ -4,6 +4,7 @@ from ophyd import (DeviceStatus, Component as Cpt, Signal, EpicsSignal, Device, DDC_EpicsSignal, DDC_EpicsSignalRO, ADComponent as ADCpt, ImagePlugin, ROIPlugin, TransformPlugin, StatsPlugin, ProcessPlugin, SingleTrigger, ProsilicaDetector) +from ophyd.utils.errors import WaitTimeoutError from pathlib import Path import requests from bluesky.utils import FailedStatus @@ -210,7 +211,7 @@ def trigger(self): try: status = super().trigger() status.wait(6) - except AttributeError: + except (AttributeError, WaitTimeoutError) as e: raise FailedStatus return status diff --git a/mxbluesky/devices/auto_recovery.py b/mxbluesky/devices/auto_recovery.py new file mode 100644 index 00000000..f136a8b8 --- /dev/null +++ b/mxbluesky/devices/auto_recovery.py @@ -0,0 +1,34 @@ +from ophyd import Device, EpicsSignalRO, EpicsSignal, Component as Cpt +from ophyd.status import SubscriptionStatus + + +class PYZHomer(Device): + + status = Cpt(EpicsSignalRO, "XF:17IDB-ES:AMX{Sentinel}Homing_Sts") + home_actuate = Cpt(EpicsSignal, "XF:17ID:AMX{Sentinel}pin_home") + + kill_home = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Sentinel}Homing_Kill") + kill_py = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PY}Cmd:Kill-Cmd") + kill_pz = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PY}Cmd:Kill-Cmd") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def trigger(self): + + def callback_homed(value, old_value, **kwargs): + if old_value == 1 and value == 0: + return True + else: + return False + + self.home_actuate.put(1) + + homing_status = SubscriptionStatus( + self.status, + callback_homed, + run=False, + timeout=180, + ) + + return homing_status diff --git a/mxbluesky/devices/generic.py b/mxbluesky/devices/generic.py index 2e458240..345bfaab 100644 --- a/mxbluesky/devices/generic.py +++ b/mxbluesky/devices/generic.py @@ -23,3 +23,55 @@ class GoniometerStack(Device): o = Cpt(EpicsMotor, "-Ax:O}Mtr") py = Cpt(EpicsMotor, "-Ax:PY}Mtr") pz = Cpt(EpicsMotor, "-Ax:PZ}Mtr") + + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Renaming to match MD2 GonioDevice + self.x = self.gx + self.cx = self.gx + self.y = self.py + self.cy = self.py + self.z = self.pz + self.cz = self.pz + self.omega = self.o + + +class Dewar(Device): + rotation = Cpt(EpicsSignal, "{Dew:1-Ax:R}Virtual") + rotation_motor = Cpt(EpicsMotor, "{Dew:1-Ax:R}Mtr") + + def rotate(self, rotation_angle, absolute=True): + def check_value_sink(*, old_value, value, **kwargs): + "Return True when the movement is complete, False otherwise." + return old_value == 1 and value == 0 + + def check_value_raise(*, old_value, value, **kwargs): + "Return True when the movement is started, False otherwise." + return old_value == 0 and value == 1 + + status = SubscriptionStatus( + self.rotation_motor.motor_done_move, check_value_sink + ) + if not self.rotation_motor.motor_done_move.get(): + raise RuntimeError("Dewar rotation motor already moving.") + ### Maybe don't raise an error here but rather do a timeout retry? + if absolute: + self.rotation.set(rotation_angle) + else: + current_angle = self.rotation.get() + self.rotation.set(current_angle + rotation_angle) + status.wait() + status = SubscriptionStatus( + self.rotation_motor.motor_done_move, check_value_raise + ) + status.wait() + +class RobotArm(Device): + speed = Cpt(EpicsSignal, '{EMBL}:RobotSpeed') + + def is_full_speed(self): + # Checks if the robot speed is 100% + if self.speed.get() < 100: + return False + return True diff --git a/mxbluesky/devices/top_align.py b/mxbluesky/devices/top_align.py index c8c0e19d..a0b8e238 100644 --- a/mxbluesky/devices/top_align.py +++ b/mxbluesky/devices/top_align.py @@ -68,31 +68,18 @@ class CVPlugin(PluginBase): class StandardProsilica(SingleTrigger, ProsilicaDetector): - image = Cpt(ImagePlugin, "image1:") roi1 = Cpt(ROIPlugin, "ROI1:") roi2 = Cpt(ROIPlugin, "ROI2:") - roi3 = Cpt(ROIPlugin, "ROI3:") - roi4 = Cpt(ROIPlugin, "ROI4:") trans1 = Cpt(TransformPlugin, "Trans1:") proc1 = Cpt(ProcessPlugin, "Proc1:") - stats1 = Cpt(StatsPlugin, "Stats1:") - stats2 = Cpt(StatsPlugin, "Stats2:") - stats3 = Cpt(StatsPlugin, "Stats3:") - stats4 = Cpt(StatsPlugin, "Stats4:") - stats5 = Cpt(StatsPlugin, "Stats5:") class EpicsMotorSPMG(EpicsMotor): SPMG = Cpt(EpicsSignal, ".SPMG") class TopAlignCam(StandardProsilica): - _default_read_attrs = ["cv1", "tiff"] + _default_read_attrs = ["cv1"] cv1 = Cpt(CVPlugin, "CV1:") - tiff = Cpt( - TIFFPluginWithFileStore, - "TIFF1:", - write_path_template=f"/nsls2/data/{daq_utils.beamline}/legacy/topcam", - ) cam_mode = Cpt(Signal, value=None, kind="config") pix_per_um = Cpt(Signal, value=0.164, doc="pixels per um") out10_buffer = Cpt( @@ -124,7 +111,6 @@ def _update_stage_sigs(self, *args, **kwargs): Coarse align takes place during SE -> TA transition Fine face takes place during TA -> SA transition """ - self.tiff.stage_sigs.update([("enable", 0)]) self.stage_sigs.clear() self.stage_sigs.update( [ @@ -136,7 +122,6 @@ def _update_stage_sigs(self, *args, **kwargs): ("cv1.inputs.input5", 13), ("cv1.inputs.input6", 1), ("trans1.nd_array_port", "PROC1"), - ("tiff.nd_array_port", "CV1"), ] ) if self.cam_mode.get() == CamMode.COARSE_ALIGN.value: @@ -370,37 +355,19 @@ def callback_unarmed(value, old_value, **kwargs): ) -class TopAlignerSlow(TopAlignerBase): +class TopAlignerSlow(TopAlignCam): def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def _configure_device(self, *args, **kwargs): - - self.stage_sigs.clear() - self.stage_sigs.update( - [ - ("topcam.cam.trigger_mode", 5), - ("topcam.cam.image_mode", 1), - ("topcam.cam.acquire", 1), + super().__init__(f"{device_prefix}{{Cam:9}}",*args, **kwargs) + self.cam_mode.set(CamMode.COARSE_ALIGN.value) + self.cam.stage_sigs.update( + [ + ("trigger_mode", 5), + ("image_mode", 1), + #("acquire", 1), ] ) - self.topcam.cam_mode.set(CamMode.COARSE_ALIGN.value) self.read_attrs = [ - "topcam.cv1.outputs.output8", - "topcam.cv1.outputs.output9", - "topcam.cv1.outputs.output10", + "cv1.outputs.output8", + "cv1.outputs.output9", + "cv1.outputs.output10", ] - - def trigger(self): - return self.topcam.trigger() - - def read(self): - return self.topcam.read() - - - - - - - - diff --git a/mxbluesky/plans/auto_recovery.py b/mxbluesky/plans/auto_recovery.py new file mode 100644 index 00000000..6ccb94bd --- /dev/null +++ b/mxbluesky/plans/auto_recovery.py @@ -0,0 +1,52 @@ +from mxbluesky.devices.auto_recovery import PYZHomer +from mxbluesky.devices.generic import GoniometerStack +from bluesky import plans as bp, plan_stubs as bps +from bluesky.preprocessors import reset_positions_decorator +from ophyd.status import StatusTimeoutError +from ophyd.signal import EpicsSignal +from functools import wraps + +def home_pins_plan(govmon: EpicsSignal, goniomon: EpicsSignal, pyz_homer: PYZHomer, gonio: GoniometerStack): + @reset_positions_decorator([govmon, goniomon]) + def home_pins_inner(): + + yield from bps.mv( + govmon, 0, + goniomon, 0, + settle_time=1, + ) + + yield from bps.mv( + pyz_homer.kill_py, 1, + pyz_homer.kill_pz, 1, + settle_time=1, + ) + + yield from bps.mv(gonio.o, 90) + + try: + yield from bp.count([pyz_homer], 1) + + except StatusTimeoutError as e: + print(f'Caught {e} during pinYZ home attempt, retrying') + + # kill 2x + yield from bps.mv( + pyz_homer.kill_py, 1, + pyz_homer.kill_pz, 1, + settle_time=1, + ) + yield from bps.mv( + pyz_homer.kill_py, 1, + pyz_homer.kill_pz, 1, + settle_time=1, + ) + yield from bp.count([pyz_homer, gonio.o], 1) + + yield from bps.mv(gonio.o, 0) + + #def get_home_pins_plan(): + # return home_pins_inner() + + #return get_home_pins_plan + return home_pins_inner diff --git a/mxbluesky/plans/top_view.py b/mxbluesky/plans/top_view.py index 1f44be88..c3aee728 100644 --- a/mxbluesky/plans/top_view.py +++ b/mxbluesky/plans/top_view.py @@ -5,9 +5,9 @@ import numpy as np from bluesky.preprocessors import finalize_decorator from bluesky.utils import FailedStatus -from ophyd.utils import WaitTimeoutError +from ophyd.utils import WaitTimeoutError, StatusTimeoutError from scipy.interpolate import interp1d - +from sklearn.linear_model import LinearRegression, RANSACRegressor import gov_lib from mxbluesky.devices.top_align import GovernorError, CamMode from mxbluesky.plans.utils import mv_with_retry, mvr_with_retry @@ -19,17 +19,18 @@ top_aligner_fast, top_aligner_slow, work_pos, + top_cam_reset_signal ) logger = getLogger() def cleanup_topcam(): try: - yield from bps.abs_set(top_aligner_slow.topcam.cam.acquire, 1, wait=True, timeout=4) + yield from bps.abs_set(top_aligner_slow.cam.acquire, 1, wait=True, timeout=4) yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True, timeout=4) except WaitTimeoutError as error: logger.exception(f"Exception in cleanup_topcam, trying again: {error}") - yield from bps.abs_set(top_aligner_slow.topcam.cam.acquire, 1, wait=True, timeout=4) + yield from bps.abs_set(top_aligner_slow.cam.acquire, 1, wait=True, timeout=4) yield from bps.abs_set(top_aligner_fast.zebra.pos_capt.direction, 0, wait=True, timeout=4) @@ -42,25 +43,32 @@ def inner_pseudo_fly_scan(*args, **kwargs): d = np.pi / 180 # rot axis calculation, use linear regression - A_rot = np.matrix( - [[np.cos(omega * d), np.sin(omega * d), 1] for omega in omegas] - ) + + omegas_rad = np.array(omegas) * d + X = np.vstack([np.cos(omegas_rad), np.sin(omegas_rad)]).T - b_rot = db[scan_uid].table()[top_aligner_fast.topcam.out9_buffer.name][ + y = np.array(db[scan_uid].table()[top_aligner_fast.topcam.out9_buffer.name][ 1 - ] - p = ( - np.linalg.inv(A_rot.transpose() * A_rot) - * A_rot.transpose() - * np.matrix(b_rot).transpose() - ) - - delta_z_pix, delta_y_pix, rot_axis_pix = p[0], p[1], p[2] + ]).reshape(-1,1) + try: + ransac_kwargs = { + 'estimator': LinearRegression(), + 'min_samples': 5, + 'residual_threshold': 10, + 'random_state': 0, + } + delta_z_pix, delta_y_pix = fit_ransac_linear(X, y, **ransac_kwargs) + except ValueError as e: + logger.error(f"Error using RANSAC estimator: {e}. Using linear instead") + delta_z_pix, delta_y_pix = fit_linear(X, y) + + print(f"Ransac Regression predictions: {delta_z_pix}, {delta_y_pix}") + delta_y, delta_z = ( delta_y_pix / top_aligner_fast.topcam.pix_per_um.get(), delta_z_pix / top_aligner_fast.topcam.pix_per_um.get(), ) - + # face on calculation b = db[scan_uid].table()[top_aligner_fast.topcam.out10_buffer.name][1] @@ -86,20 +94,35 @@ def setup_transition_signals(target_state, zebra_dir, cam_mode): yield from bps.abs_set(top_aligner_fast.topcam.cam_mode, cam_mode, wait=True, timeout=4) yield from bps.sleep(0.1) +def reset_top_cam(): + yield from bps.abs_set(top_aligner_slow.cam.acquire, 0, wait=True) + yield from bps.sleep(3) + yield from bps.abs_set(top_cam_reset_signal, 1, wait=True) + yield from bps.sleep(12) + n = 1 + # sometimes array_callbacks not setup properly, need to find out why + while top_aligner_slow.cam.array_callbacks.get() != 1 and n <= 10: + yield from bps.abs_set(top_cam_reset_signal, 1, wait=True) + yield from bps.sleep(12) + logger.info(f'rebooting topcam after {n} out of 10 attempts') + n += 1 + yield from bps.abs_set(top_aligner_slow.cam.acquire, 1, wait=True) + yield from bps.sleep(3) # Sleeping here to prevent interference with count @finalize_decorator(cleanup_topcam) def topview_optimized(): - logger.info("Starting topview") + logger.info("Starting topview --") try: # horizontal bump calculation, don't move just yet to avoid disturbing gov scan_uid = yield from bp.count([top_aligner_slow], 1) - except FailedStatus: + except (FailedStatus, WaitTimeoutError, StatusTimeoutError) as e: + yield from reset_top_cam() scan_uid = yield from bp.count([top_aligner_slow], 1) logger.info(f"Finished top aligner slow scan {scan_uid}") - x = db[scan_uid].table()[top_aligner_slow.topcam.cv1.outputs.output8.name][1] - delta_x = ((top_aligner_slow.topcam.roi2.size.x.get() / 2) - - x) / top_aligner_slow.topcam.pix_per_um.get() + x = db[scan_uid].table()[top_aligner_slow.cv1.outputs.output8.name][1] + delta_x = ((top_aligner_slow.roi2.size.x.get() / 2) - + x) / top_aligner_slow.pix_per_um.get() logger.info(f"Horizontal bump calc finished: {delta_x}") # update work positions yield from set_TA_work_pos(delta_x=delta_x) @@ -111,7 +134,7 @@ def topview_optimized(): except WaitTimeoutError as error: logger.exception(f"Exception while setting SE to TA signals, trying again: {error}") yield from setup_transition_signals("TA", 0, CamMode.COARSE_ALIGN.value) - + logger.info("Starting 1st inner fly scan") try: @@ -143,6 +166,7 @@ def topview_optimized(): except WaitTimeoutError as error: logger.exception(f"Exception while setting TA to SA signals, trying again: {error}") yield from setup_transition_signals("SA", 1, CamMode.FINE_FACE.value) + logger.info("Starting 2nd inner fly scan") try: delta_y, delta_z, omega_min = yield from inner_pseudo_fly_scan( @@ -195,3 +219,18 @@ def set_SA_work_pos(delta_y, delta_z, current_y=None, current_z= None, omega=0): work_pos.gpz, current_z - delta_z, wait=True ) yield from bps.abs_set(work_pos.o, omega, wait=True) + +def fit_linear(X,y): + reg = LinearRegression().fit(X,y) + return reg.coef_[0] + +def fit_ransac_linear(X,y, min_n_inliers=10, **kwargs): + reg = RANSACRegressor(**kwargs).fit(X,y) + mask = reg.inlier_mask_ + masked_X = X[mask, :] + masked_y = y[mask, :] + if len(masked_y) < min_n_inliers: + raise ValueError( + f'Too few inliers. Identified {len(masked_y)}, need {min_n_inliers}.' + ) + return reg.estimator_.coef_[0] diff --git a/start_bs.py b/start_bs.py index 9f8d5b18..4b66eb41 100755 --- a/start_bs.py +++ b/start_bs.py @@ -113,6 +113,8 @@ class SampleXYZ(Device): omega = Cpt(EpicsMotor, ':O}Mtr') if (beamline=="amx"): + from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, + TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm) mercury = ABBIXMercury('XF:17IDB-ES:AMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] @@ -139,12 +141,19 @@ class SampleXYZ(Device): mount_pos = MountPositions("XF:17IDB-ES:AMX", name="mount_pos") two_click_low = TwoClickLowMag("XF:17IDB-ES:AMX{Cam:6}", name="two_click_low") low_mag_cam_reset_signal = EpicsSignal('XF:17IDB-CT:AMX{IOC:CAM06}:SysReset') + top_cam_reset_signal = EpicsSignal('XF:17IDB-CT:AMX{IOC:CAM09}:SysReset') gonio = GoniometerStack("XF:17IDB-ES:AMX{Gon:1", name="gonio") loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") - gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon") - + gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon", name="govmon") + gonio_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}goniomon", name="goniomon") + from mxbluesky.devices.auto_recovery import PYZHomer + from mxbluesky.plans.auto_recovery import home_pins_plan + pyz_homer = PYZHomer("", name="pyz_homer") + dewar = Dewar("XF:17IDB-ES:AMX", name="dewar") + home_pins = home_pins_plan(gov_mon_signal, gonio_mon_signal, pyz_homer, gonio) + robot_arm = RobotArm("XF:17IDB-ES:AMX", name="robot_arm") elif beamline == "fmx": mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') From 0892a53f2d0413bd75335a24f64a1e8df07191b3 Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Wed, 13 Nov 2024 09:54:12 -0500 Subject: [PATCH 3/4] Cleaned up start_bs and fixed auto recovery device --- mxbluesky/devices/auto_recovery.py | 2 +- start_bs.py | 38 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/mxbluesky/devices/auto_recovery.py b/mxbluesky/devices/auto_recovery.py index f136a8b8..468eca8d 100644 --- a/mxbluesky/devices/auto_recovery.py +++ b/mxbluesky/devices/auto_recovery.py @@ -9,7 +9,7 @@ class PYZHomer(Device): kill_home = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Sentinel}Homing_Kill") kill_py = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PY}Cmd:Kill-Cmd") - kill_pz = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PY}Cmd:Kill-Cmd") + kill_pz = Cpt(EpicsSignal, "XF:17IDB-ES:AMX{Gon:1-Ax:PZ}Cmd:Kill-Cmd") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/start_bs.py b/start_bs.py index 4b66eb41..c7808ff4 100755 --- a/start_bs.py +++ b/start_bs.py @@ -115,21 +115,25 @@ class SampleXYZ(Device): if (beamline=="amx"): from mxbluesky.devices import (WorkPositions, TwoClickLowMag, LoopDetector, MountPositions, TopAlignerFast, TopAlignerSlow, GoniometerStack, Dewar, RobotArm) + from mxbluesky.devices import Dewar, RobotArm + from mxbluesky.devices.auto_recovery import PYZHomer + from mxbluesky.plans.auto_recovery import home_pins_plan + from mxtools.vector_program import VectorProgram + from mxtools.flyer import MXFlyer + from mxtools.raster_flyer import MXRasterFlyer + from embl_robot import EMBLRobot + mercury = ABBIXMercury('XF:17IDB-ES:AMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] vdcm = VerticalDCM('XF:17IDA-OP:AMX{Mono:DCM', name='vdcm') zebra = Zebra('XF:17IDB-ES:AMX{Zeb:2}:', name='zebra') eiger = EigerSingleTriggerV26('XF:17IDB-ES:AMX{Det:Eig9M}', name='eiger', beamline=beamline) - from mxtools.vector_program import VectorProgram vector_program = VectorProgram('XF:17IDB-ES:AMX{Gon:1-Vec}', name='vector_program') - from mxtools.flyer import MXFlyer flyer = MXFlyer(vector_program, zebra, eiger) - from mxtools.raster_flyer import MXRasterFlyer raster_flyer = MXRasterFlyer(vector_program, zebra, eiger) samplexyz = SampleXYZ("XF:17IDB-ES:AMX{Gon:1-Ax", name="samplexyz") - from embl_robot import EMBLRobot robot = EMBLRobot() govs = _make_governors("XF:17IDB-ES:AMX", name="govs") gov_robot = govs.gov.Robot @@ -148,29 +152,33 @@ class SampleXYZ(Device): top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon", name="govmon") gonio_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}goniomon", name="goniomon") - from mxbluesky.devices.auto_recovery import PYZHomer - from mxbluesky.plans.auto_recovery import home_pins_plan pyz_homer = PYZHomer("", name="pyz_homer") dewar = Dewar("XF:17IDB-ES:AMX", name="dewar") home_pins = home_pins_plan(gov_mon_signal, gonio_mon_signal, pyz_homer, gonio) robot_arm = RobotArm("XF:17IDB-ES:AMX", name="robot_arm") -elif beamline == "fmx": +elif beamline == "fmx": + from mxbluesky.devices import Dewar, RobotArm + from mxtools.vector_program import VectorProgram + from mxbluesky.devices.auto_recovery import PYZHomer + from mxbluesky.plans.auto_recovery import home_pins_plan + from mxtools.vector_program import VectorProgram + from mxtools.flyer import MXFlyer + from mxtools.raster_flyer import MXRasterFlyer + from embl_robot import EMBLRobot + import setenergy_lsdc + mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') mercury.read_attrs = ['mca.spectrum', 'mca.preset_live_time', 'mca.rois.roi0.count', 'mca.rois.roi1.count', 'mca.rois.roi2.count', 'mca.rois.roi3.count'] vdcm = VerticalDCM('XF:17IDA-OP:FMX{Mono:DCM', name='vdcm') zebra = Zebra('XF:17IDC-ES:FMX{Zeb:3}:', name='zebra') eiger = EigerSingleTriggerV26('XF:17IDC-ES:FMX{Det:Eig16M}', name='eiger', beamline=beamline) - from mxtools.vector_program import VectorProgram vector_program = VectorProgram('XF:17IDC-ES:FMX{Gon:1-Vec}', name='vector_program') - from mxtools.flyer import MXFlyer flyer = MXFlyer(vector_program, zebra, eiger) - from mxtools.raster_flyer import MXRasterFlyer raster_flyer = MXRasterFlyer(vector_program, zebra, eiger) samplexyz = SampleXYZ("XF:17IDC-ES:FMX{Gon:1-Ax", name="samplexyz") - from embl_robot import EMBLRobot robot = EMBLRobot() govs = _make_governors("XF:17IDC-ES:FMX", name="govs") gov_robot = govs.gov.Robot @@ -186,8 +194,12 @@ class SampleXYZ(Device): loop_detector = LoopDetector(name="loop_detector") top_aligner_fast = TopAlignerFast(name="top_aligner_fast", gov_robot=gov_robot) top_aligner_slow = TopAlignerSlow(name="top_aligner_slow") - gov_mon_signal = EpicsSignal("XF:17ID:AMX{Karen}govmon") - import setenergy_lsdc + gov_mon_signal = EpicsSignal("XF:17ID:FMX{Karen}govmon", name="govmon") + gonio_mon_signal = EpicsSignal("XF:17ID:FMX{Karen}goniomon", name="goniomon") + pyz_homer = PYZHomer("", name="pyz_homer") + dewar = Dewar("XF:17IDC-ES:FMX", name="dewar") + home_pins = home_pins_plan(gov_mon_signal, gonio_mon_signal, pyz_homer, gonio) + robot_arm = RobotArm("XF:17IDC-ES:FMX", name="robot_arm") elif beamline=="nyx": mercury = ABBIXMercury('XF:17IDC-ES:FMX{Det:Mer}', name='mercury') From afff0f9f4d904e57b6ec438d18b3b9bb9c82d20e Mon Sep 17 00:00:00 2001 From: vshekar1 Date: Thu, 21 Nov 2024 18:03:25 -0500 Subject: [PATCH 4/4] Updates to floco functions --- daq_lib.py | 30 ++++++++++++++++----------- daq_macros.py | 57 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/daq_lib.py b/daq_lib.py index 36099082..d88caf92 100644 --- a/daq_lib.py +++ b/daq_lib.py @@ -238,20 +238,26 @@ def flocoUnlock(): govMonOn() def flocoStopOperations(): - daq_macros.run_recovery_procedure(stop=True) - lockGUI() + try: + daq_macros.run_recovery_procedure(stop=True) + lockGUI() + except Exception as e: + logger.exception("Error encountered while running flocoStopOperations. Stopping...") def flocoContinueOperations(): - daq_macros.run_recovery_procedure(stop=False) - flocoUnlock() - beamline_support.set_any_epics_pv(daq_utils.beamlineComm + "command_s", "VAL", - json.dumps( - { - "function": "runDCQueue", - "args": [], - "kwargs": {}, - } - )) + try: + daq_macros.run_recovery_procedure(stop=False) + flocoUnlock() + beamline_support.set_any_epics_pv(daq_utils.beamlineComm + "command_s", "VAL", + json.dumps( + { + "function": "runDCQueue", + "args": [], + "kwargs": {}, + } + )) + except Exception as e: + logger.exception("Error encountered while running flocoContinueOperations. Stopping...") def refreshGuiTree(): beamline_support.set_any_epics_pv(daq_utils.beamlineComm+"live_q_change_flag","VAL",1) diff --git a/daq_macros.py b/daq_macros.py index ff1a521a..7560d912 100644 --- a/daq_macros.py +++ b/daq_macros.py @@ -259,6 +259,9 @@ def run_robot_recovery_procedure(): # Dry Gripper logger.info("Drying gripper") robot_lib.dryGripper() + # Dry Gripper + logger.info("Drying gripper, again") + robot_lib.dryGripper() # Park Gripper and cool gripper logger.info("Cooling gripper") robot_lib.cooldownGripper() @@ -293,28 +296,42 @@ def run_recovery_procedure(stop=True): Manual recovery procedure used in daq_lib.flocoStopOperations and daq_lib.flocoContinueOperations """ logger.info(f"Running recovery procedure: {'stop operations' if stop else 'continue operations'}") - logger.info("Recovering robot") - robot_lib.recoverRobot() - if not getBlConfig("robot_online") or not getBlConfig("mountEnabled"): - logger.info("Robot is offline or mount is disabled, sample found in gripper. Stopping recovery...") - return - logger.info("Drying gripper") - robot_lib.dryGripper() - logger.info("Homing pins") - from start_bs import home_pins - RE(home_pins()) + def check_robot(): + if not getBlConfig("robot_online") or not getBlConfig("mountEnabled"): + raise ValueError("Robot is offline or mount is disabled, sample found in gripper. Stopping recovery...") + + def run_home_pin(): + from start_bs import home_pins + RE(home_pins()) + + def gov_state_to_se(): + gov_lib.setGovRobot(gov_robot, 'SE') + + def check_robot_speed(): + if not robot_arm.is_full_speed(): + logger.error("Robot arm speed is NOT 100%") + + def check_beam(): + if not (getPvDesc("beamAvailable") or getBlConfig(BEAM_CHECK) == 0): + logger.error("Beam not available, please open shutter") + + steps_to_run = {"Recover robot": robot_lib.recoverRobot, + "Check robot status": check_robot, + "Dry gripper": robot_lib.dryGripper, + "Home pin": run_home_pin, } if stop: - logger.info("Disabling mount") - disableMount() - logger.info("Turning robot off") - robotOff() + steps_to_run.update({"Disable mount": disableMount, "Robot off": robotOff}) else: - logger.info("Enabling mount") - enableMount() - logger.info("Turning robot on") - robotOn() - logger.info("Setting robot state to SE") - gov_lib.setGovRobot(gov_robot, 'SE') + steps_to_run.update({"Enable mount": enableMount, "Robot on": robotOn}) + + steps_to_run.update({"Move Governor to SE": gov_state_to_se, + "Checking robot arm speed": check_robot_speed, + "Checking beam": check_beam}) + + for i, (step_message, func) in enumerate(steps_to_run.items()): + logger.info(f"Step {i+1} of {len(steps_to_run)}: {step_message}") + func() + logger.info(f"Completed step: {step_message}") def run_top_view_optimized():