diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index 87f3680..1ffeea8 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -1004,6 +1004,20 @@ class FlomniAlignmentMixin: with open(os.path.join(dir_path, "ptychotomoalign_Cy3.txt"), "r") as file: tomo_alignment_fit[1][4] = file.readline() + tomo_alignment_fit[0][0] = gui.flomni.xeyegui.XRayEye.Waveform.fit_x_SineModel.dap_params['amplitude'] + tomo_alignment_fit[0][1] = gui.flomni.xeyegui.XRayEye.Waveform.fit_x_SineModel.dap_params['frequency'] + tomo_alignment_fit[0][2] = gui.flomni.xeyegui.XRayEye.Waveform.fit_x_SineModel.dap_params['shift'] + tomo_alignment_fit[1][0] = gui.flomni.xeyegui.XRayEye.Waveform_0.fit_y_SineModel.dap_params['amplitude'] + tomo_alignment_fit[1][1] = gui.flomni.xeyegui.XRayEye.Waveform_0.fit_y_SineModel.dap_params['frequency'] + tomo_alignment_fit[1][2] = gui.flomni.xeyegui.XRayEye.Waveform_0.fit_y_SineModel.dap_params['shift'] + print("applying vertical default values from mirror calibration, not from fit!") + tomo_alignment_fit[1][0] = 0 + tomo_alignment_fit[1][1] = 0 + tomo_alignment_fit[1][2] = 0 + tomo_alignment_fit[1][3] = 0 + tomo_alignment_fit[1][4] = 0 + + print("New alignment parameters loaded:") print( f"X Amplitude {tomo_alignment_fit[0][0]}, " diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py b/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py index af52575..9c54b5d 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py @@ -37,24 +37,23 @@ class flomniGuiTools: def flomnigui_raise(self): self.gui.flomni.raise_window() - # def flomnigui_show_xeyealign(self): - # self.flomnigui_show_gui() - # if self.xeyegui is None: - # self.flomnigui_remove_all_docks() - # self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye") - # # start live - # if not dev.cam_xeye.live_mode: - # dev.cam_xeye.live_mode = True - def flomnigui_show_xeyealign(self): self.flomnigui_show_gui() if self._flomnigui_check_attribute_not_exists("xeyegui"): self.flomnigui_remove_all_docks() self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye") - # start live - if not dev.cam_xeye.live_mode: - dev.cam_xeye.live_mode = True + # start live + if not dev.cam_xeye.live_mode_enabled.get(): + # dev.cam_xeye.live_mode = True + dev.cam_xeye.live_mode_enabled.put(True) + self.xeyegui.switch_tab('alignment') + def flomnigui_show_xeyealign_fittab(self): + self.flomnigui_show_gui() + if self._flomnigui_check_attribute_not_exists("xeyegui"): + self.flomnigui_remove_all_docks() + self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye") + self.xeyegui.switch_tab('fit') def _flomnigui_check_attribute_not_exists(self, attribute_name): if hasattr(self.gui,"flomni"): @@ -156,8 +155,8 @@ class flomniGuiTools: ) self.progressbar.set_value([progress, subtomo_progress, 0]) if self.text_box is not None: - text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}" - self.text_box.set_plain_text(text) + text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}" + self.text_box.set_plain_text(text) if __name__ == "__main__": diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py index f156aad..3dfb7e2 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py @@ -5,9 +5,11 @@ import os import time from typing import TYPE_CHECKING +import numpy as np + from bec_lib import bec_logger -from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose +# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose logger = bec_logger.logger # import builtins to avoid linter errors @@ -22,7 +24,7 @@ if TYPE_CHECKING: class XrayEyeAlign: # pixel calibration, multiply to get mm - labview=False + test_wo_movements = True PIXEL_CALIBRATION = 0.1 / 113 # .2 with binning def __init__(self, client, flomni: Flomni) -> None: @@ -34,80 +36,74 @@ class XrayEyeAlign: self.flomni.reset_correction() self.flomni.reset_tomo_alignment_fit() + @property + def gui(self): + return self.flomni.xeyegui + def _reset_init_values(self): self.shift_xy = [0, 0] self._xray_fov_xy = [0, 0] - def save_frame(self): - epics_put("XOMNYI-XEYE-SAVFRAME:0", 1) + def update_frame(self, keep_shutter_open=False): - def update_frame(self,keep_shutter_open=False): - if self.labview: - epics_put("XOMNYI-XEYE-ACQDONE:0", 0) - - if not self.labview: - self.flomni.flomnigui_show_xeyealign() - if not dev.cam_xeye.live_mode: - dev.cam_xeye.live_mode = True + self.flomni.flomnigui_show_xeyealign() + if not dev.cam_xeye.live_mode_enabled.get(): + dev.cam_xeye.live_mode_enabled.put(True) - epics_put("XOMNYI-XEYE-ACQ:0", 1) - if self.labview: - # wait for start live - while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0: - time.sleep(0.5) - print("waiting for live view to start...") + self.gui.on_live_view_enabled(True) - fshopen() - - if self.labview: - epics_put("XOMNYI-XEYE-ACQDONE:0", 0) - - while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0: - print("waiting for new frame...") - time.sleep(0.5) + dev.omnyfsh.fshopen() time.sleep(0.5) # stop live view if not keep_shutter_open: - epics_put("XOMNYI-XEYE-ACQ:0", 0) + self.gui.on_live_view_enabled(False) time.sleep(0.1) - fshclose() - print("got new frame") + dev.omnyfsh.fshclose() + print("Received new frame.") else: print("Staying in live view, shutter is and remains open!") def tomo_rotate(self, val: float): - # pylint: disable=undefined-variable - umv(self.device_manager.devices.fsamroy, val) + if not self.test_wo_movements: + umv(self.device_manager.devices.fsamroy, val) def get_tomo_angle(self): return self.device_manager.devices.fsamroy.readback.get() def update_fov(self, k: int): - self._xray_fov_xy[0] = max(epics_get(f"XOMNYI-XEYE-XWIDTH_X:{k}"), self._xray_fov_xy[0]) - self._xray_fov_xy[1] = max(0, self._xray_fov_xy[0]) + self._xray_fov_xy[0] = max( + getattr(dev.omny_xray_gui, f"width_x_{k}").get(), self._xray_fov_xy[0] + ) + self._xray_fov_xy[1] = max( + getattr(dev.omny_xray_gui, f"width_y_{k}").get(), self._xray_fov_xy[1] + ) - @property - def movement_buttons_enabled(self): - return [epics_get("XOMNYI-XEYE-ENAMVX:0"), epics_get("XOMNYI-XEYE-ENAMVY:0")] - - @movement_buttons_enabled.setter - def movement_buttons_enabled(self, enabled: bool): - enabled = int(enabled) - epics_put("XOMNYI-XEYE-ENAMVX:0", enabled) - epics_put("XOMNYI-XEYE-ENAMVY:0", enabled) + def movement_buttons_enabled(self, enablex: bool, enabley: bool): + self.gui.on_motors_enable(enablex, enabley) def send_message(self, msg: str): - epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg) + print(f"In alginment GUI: {msg}") + self.gui.user_message = msg - def align(self,keep_shutter_open=False): + def align(self, keep_shutter_open=False): + self.flomni.flomnigui_show_xeyealign() if not keep_shutter_open: - print("This routine can be called with paramter keep_shutter_open=True to keep the shutter always open") + print( + "This routine can be called with paramter keep_shutter_open=True to keep the shutter always open" + ) self.send_message("Getting things ready. Please wait...") - #potential unresolved movement requests to zero - epics_put("XOMNYI-XEYE-MVX:0", 0) - epics_put("XOMNYI-XEYE-MVY:0", 0) + self.gui.enable_submit_button(False) + + # Initialize xray align device + # clear potential pending movement requests + dev.omny_xray_gui.mvx.set(0) + dev.omny_xray_gui.mvy.set(0) + # reset submit channel + dev.omny_xray_gui.submit.set(0) + + self.movement_buttons_enabled(False, False) # reset shift xy and fov params self._reset_init_values() @@ -117,45 +113,45 @@ class XrayEyeAlign: self.flomni.flomnigui_show_xeyealign() self.flomni.flomnigui_raise() - self.tomo_rotate(0) - epics_put("XOMNYI-XEYE-ANGLE:0", 0) + if not self.test_wo_movements: + self.tomo_rotate(0) - self.flomni.feye_in() + self.flomni.feye_in() self.flomni.laser_tracker_on() self.flomni.feedback_enable_with_reset() # disable movement buttons - self.movement_buttons_enabled = False + self.movement_buttons_enabled(False, False) sample_name = self.flomni.sample_get_name(0) - epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name) + self.gui.sample_name = sample_name # this makes sure we are in a defined state self.flomni.feedback_disable() - epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION) + if not self.test_wo_movements: + self.flomni.fosa_out() - self.flomni.fosa_out() + fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") + umv(dev.fsamx, fsamx_in - 0.25) - fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") - umv(dev.fsamx, fsamx_in - 0.25) + self.flomni.ffzp_in() - self.flomni.ffzp_in() self.update_frame(keep_shutter_open) - # enable submit buttons - self.movement_buttons_enabled = False - epics_put("XOMNYI-XEYE-SUBMIT:0", 0) - epics_put("XOMNYI-XEYE-STEP:0", 0) + self.gui.enable_submit_button(True) + dev.omny_xray_gui.step.set(0).wait() self.send_message("Submit center value of FZP.") k = 0 while True: - if epics_get("XOMNYI-XEYE-SUBMIT:0") == 1: - val_x = epics_get(f"XOMNYI-XEYE-XVAL_X:{k}") / 2 * self.PIXEL_CALIBRATION # in mm - self.alignment_values[k] = val_x + if dev.omny_xray_gui.submit.get() == 1: + + self.alignment_values[k] = ( + getattr(dev.omny_xray_gui, f"xval_x_{k}").get() / 2 * self.PIXEL_CALIBRATION + ) # in mm print(f"Clicked position {k}: x {self.alignment_values[k]}") rtx_position = dev.rtx.readback.get() / 1000 print(f"Current rtx position {rtx_position}") @@ -164,79 +160,70 @@ class XrayEyeAlign: if k == 0: # received center value of FZP self.send_message("please wait ...") - self.movement_buttons_enabled = False - epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button + self.movement_buttons_enabled(False, False) + self.gui.enable_submit_button(False) self.flomni.feedback_disable() - fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") - umv(dev.fsamx, fsamx_in) + if not self.test_wo_movements: + fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") + umv(dev.fsamx, fsamx_in) - self.flomni.foptics_out() + self.flomni.foptics_out() - self.flomni.feedback_disable() - umv(dev.fsamx, fsamx_in - 0.25) + time.sleep(0.5) - if self.labview: - self.update_frame(keep_shutter_open) - epics_put("XOMNYI-XEYE-RECBG:0", 1) - while epics_get("XOMNYI-XEYE-RECBG:0") == 1: - time.sleep(0.5) - print("waiting for background frame...") - - umv(dev.fsamx, fsamx_in) - time.sleep(0.5) self.flomni.feedback_enable_with_reset() self.update_frame(keep_shutter_open) - self.send_message("Adjust sample height and submit center") - epics_put("XOMNYI-XEYE-SUBMIT:0", 0) - self.movement_buttons_enabled = True + self.send_message("Step 1/5: Adjust sample height and submit center") + self.gui.enable_submit_button(True) + self.movement_buttons_enabled(True, True) elif 1 <= k < 5: # received sample center value at samroy 0 ... 315 self.send_message("please wait ...") - epics_put("XOMNYI-XEYE-SUBMIT:0", -1) - self.movement_buttons_enabled = False + self.gui.enable_submit_button(False) + self.movement_buttons_enabled(False, False) umv(dev.rtx, 0) self.tomo_rotate(k * 45) - epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle()) + dev.omny_xray_gui.angle.set(self.get_tomo_angle()) self.update_frame(keep_shutter_open) - self.send_message("Submit sample center") - epics_put("XOMNYI-XEYE-SUBMIT:0", 0) - epics_put("XOMNYI-XEYE-ENAMVX:0", 1) + self.send_message(f"Step {k+1}/5: Submit sample center") + self.gui.enable_submit_button(True) + self.movement_buttons_enabled(True, False) self.update_fov(k) elif k == 5: # received sample center value at samroy 270 and done self.send_message("done...") - epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button - self.movement_buttons_enabled = False + self.gui.enable_submit_button(False) + self.movement_buttons_enabled(False, False) self.update_fov(k) break k += 1 - epics_put("XOMNYI-XEYE-STEP:0", k) - - _xrayeyalignmvx = epics_get("XOMNYI-XEYE-MVX:0") + dev.omny_xray_gui.step.set(k) + # reset submit channel + dev.omny_xray_gui.submit.set(0) + _xrayeyalignmvx = dev.omny_xray_gui.mvx.get() if _xrayeyalignmvx != 0: umvr(dev.rtx, _xrayeyalignmvx) print(f"Current rtx position {dev.rtx.readback.get() / 1000}") - epics_put("XOMNYI-XEYE-MVX:0", 0) - if k > 0: - epics_put(f"XOMNYI-XEYE-STAGEPOSX:{k}", dev.rtx.readback.get() / 1000) time.sleep(3) + dev.omny_xray_gui.mvx.set(0) self.update_frame(keep_shutter_open) if k < 2: # allow movements, store movements to calculate center - _xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0") + _xrayeyalignmvy = dev.omny_xray_gui.mvy.get() if _xrayeyalignmvy != 0: self.flomni.feedback_disable() - umvr(dev.fsamy, _xrayeyalignmvy / 1000) + if not self.test_wo_movements: + umvr(dev.fsamy, _xrayeyalignmvy / 1000) time.sleep(2) - epics_put("XOMNYI-XEYE-MVY:0", 0) + dev.omny_xray_gui.mvy.set(0) self.flomni.feedback_enable_with_reset() self.update_frame(keep_shutter_open) - time.sleep(0.2) + time.sleep(0.1) self.write_output() fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2 @@ -246,22 +233,17 @@ class XrayEyeAlign: umv(dev.rtx, 0) - # free camera - if self.labview: - epics_put("XOMNYI-XEYE-ACQ:0", 2) - if keep_shutter_open and not self.labview: - if self.flomni.OMNYTools.yesno("Close the shutter now?","y"): - fshclose() - epics_put("XOMNYI-XEYE-ACQ:0", 0) - if not self.labview: - self.flomni.flomnigui_idle() - + if keep_shutter_open: + if self.flomni.OMNYTools.yesno("Close the shutter now?", "y"): + dev.omnyfsh.fshclose() + self.gui.on_live_view_enabled(False) + print("setting 'XOMNYI-XEYE-ACQ:0'") print( f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy" f" = {fovy:.0f} microns" ) - print("Use the matlab routine to FIT the current alignment...") + print("Check the fit in the GUI...") print("Then LOAD ALIGNMENT PARAMETERS by running flomni.read_alignment_offset()\n") @@ -269,9 +251,33 @@ class XrayEyeAlign: file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues") if not os.path.exists(file): os.makedirs(os.path.dirname(file), exist_ok=True) + with open(file, "w") as alignment_values_file: alignment_values_file.write("angle\thorizontal\n") + + # Initialize an empty list to store fovx values + fovx_list = [] + fovx_offsets = np.zeros(5) # holds offsets for k = 1..5 + for k in range(1, 6): fovx_offset = self.alignment_values[0] - self.alignment_values[k] + fovx_offsets[k - 1] = fovx_offset # store in array + + fovx_x = (k - 1) * 45 + fovx_list.append([fovx_x, fovx_offset * 1000]) # Append the data to the list + print(f"Writing to file new alignment: number {k}, value x {fovx_offset}") - alignment_values_file.write(f"{(k-1)*45}\t{fovx_offset*1000}\n") + alignment_values_file.write(f"{fovx_x}\t{fovx_offset * 1000}\n") + + # Now build final numpy array: + data = np.array( + [ + [0, 45, 90, 135, 180], # angles + fovx_offsets * 1000, # fovx_offset values + [0, 0, 0, 0, 0], + ] + ) + self.gui.submit_fit_array(data) + print(f"fit submited with {data}") + print("todo mirko: submitted data is 1000 fold in amplitude") + self.flomni.flomnigui_show_xeyealign_fittab() diff --git a/csaxs_bec/bec_widgets/widgets/client.py b/csaxs_bec/bec_widgets/widgets/client.py index ec2d37c..82a56c0 100644 --- a/csaxs_bec/bec_widgets/widgets/client.py +++ b/csaxs_bec/bec_widgets/widgets/client.py @@ -83,20 +83,6 @@ class XRayEye(RPCBase): Return the currently active ROI, or None if no ROI is active. """ - @property - @rpc_call - def enable_live_view(self): - """ - Get or set the live view enabled state. - """ - - @enable_live_view.setter - @rpc_call - def enable_live_view(self): - """ - Get or set the live view enabled state. - """ - @property @rpc_call def user_message(self): @@ -111,6 +97,30 @@ class XRayEye(RPCBase): None """ + @rpc_call + def on_live_view_enabled(self, enabled: "bool"): + """ + None + """ + + @rpc_call + def on_motors_enable(self, x_enable: "bool", y_enable: "bool"): + """ + Enable/Disable motor controls + + Args: + x_enable(bool): enable x motor controls + y_enable(bool): enable y motor controls + """ + + @rpc_call + def enable_submit_button(self, enable: "int"): + """ + Enable/disable submit button. + Args: + enable(int): -1 disable else enable + """ + @property @rpc_call def sample_name(self): @@ -139,6 +149,18 @@ class XRayEye(RPCBase): None """ + @rpc_call + def switch_tab(self, tab: "str"): + """ + None + """ + + @rpc_call + def submit_fit_array(self, fit_array): + """ + None + """ + class XRayEye2DControl(RPCBase): @rpc_call diff --git a/csaxs_bec/bec_widgets/widgets/xray_eye/x_ray_eye.py b/csaxs_bec/bec_widgets/widgets/xray_eye/x_ray_eye.py index 2ba48e7..a1585c8 100644 --- a/csaxs_bec/bec_widgets/widgets/xray_eye/x_ray_eye.py +++ b/csaxs_bec/bec_widgets/widgets/xray_eye/x_ray_eye.py @@ -5,6 +5,7 @@ from bec_lib.endpoints import MessageEndpoints from bec_qthemes import material_icon from bec_widgets import BECWidget, SafeProperty, SafeSlot from bec_widgets.widgets.plots.image.image import Image +from bec_widgets.widgets.plots.waveform.waveform import Waveform from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree from bec_widgets.widgets.plots.roi.image_roi import BaseROI, CircularROI, RectangularROI from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch @@ -21,7 +22,10 @@ from qtpy.QtWidgets import ( QToolButton, QVBoxLayout, QWidget, + QTextEdit, + QTabWidget ) +import time logger = bec_logger.logger CAMERA = ("cam_xeye", "image") @@ -124,8 +128,8 @@ class XRayEye2DControl(BECWidget, QWidget): class XRayEye(BECWidget, QWidget): - USER_ACCESS = ["active_roi", "enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter", - "sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"] + USER_ACCESS = ["active_roi", "user_message", "user_message.setter","on_live_view_enabled","on_motors_enable","enable_submit_button", + "sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter","switch_tab","submit_fit_array"] PLUGIN = True def __init__(self, parent=None, **kwargs): @@ -136,21 +140,29 @@ class XRayEye(BECWidget, QWidget): self._make_connections() # Connection to redis endpoints - self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")) + self.bec_dispatcher.connect_slot(self.getting_shutter_status, MessageEndpoints.device_readback("omnyfsh")) + self.bec_dispatcher.connect_slot(self.getting_camera_status, MessageEndpoints.device_read_configuration(CAMERA[0])) + self.connect_motors() self.resize(800, 600) QTimer.singleShot(0, self._init_gui_trigger) def _init_ui(self): - self.core_layout = QHBoxLayout(self) + self.root_layout = QVBoxLayout(self) + self.tab_widget = QTabWidget(parent=self) + self.root_layout.addWidget(self.tab_widget) - self.image = Image(parent=self) + self.alignment_tab = QWidget(parent=self) + self.core_layout = QHBoxLayout(self.alignment_tab) + + self.image = Image(parent=self.alignment_tab) self.image.enable_toolbar = False # Disable default toolbar to not allow to user set anything self.image.inner_axes = False # Disable inner axes to maximize image area - self.image.plot_item.vb.invertY(True) # #TODO Invert y axis to match logic of LabView GUI + self.image.enable_full_colorbar = True + self.image.invert_y = True # Invert y axis to match image coordinates # Control panel on the right: vertical layout inside a fixed-width widget - self.control_panel = QWidget(parent=self) + self.control_panel = QWidget(parent=self.alignment_tab) self.control_panel_layout = QVBoxLayout(self.control_panel) self.control_panel_layout.setContentsMargins(0, 0, 0, 0) self.control_panel_layout.setSpacing(10) @@ -166,16 +178,35 @@ class XRayEye(BECWidget, QWidget): self.live_preview_label = QLabel("Live Preview", parent=self) self.live_preview_toggle = ToggleSwitch(parent=self) self.live_preview_toggle.checked = False - header_row.addWidget(self.live_preview_label, 0, Qt.AlignVCenter) - header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignVCenter) + header_row.addWidget(self.live_preview_label, 0, Qt.AlignmentFlag.AlignVCenter) + header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignmentFlag.AlignVCenter) self.control_panel_layout.addLayout(header_row) + switch_row = QHBoxLayout() + switch_row.setContentsMargins(0, 0, 0, 0) + switch_row.setSpacing(8) + switch_row.addStretch() + self.camera_running_label = QLabel("Camera running", parent=self) + self.camera_running_toggle = ToggleSwitch(parent=self) + # self.camera_running_toggle.checked = False + self.camera_running_toggle.enabled.connect(self.camera_running_enabled) + self.shutter_label = QLabel("Shutter open", parent=self) + self.shutter_toggle = ToggleSwitch(parent=self) + # self.shutter_toggle.checked = False + self.shutter_toggle.enabled.connect(self.opening_shutter) + switch_row.addWidget(self.shutter_label, 0, Qt.AlignmentFlag.AlignVCenter) + switch_row.addWidget(self.shutter_toggle, 0, Qt.AlignmentFlag.AlignVCenter) + switch_row.addWidget(self.camera_running_label, 0, Qt.AlignmentFlag.AlignVCenter) + switch_row.addWidget(self.camera_running_toggle, 0, Qt.AlignmentFlag.AlignVCenter) + self.control_panel_layout.addLayout(switch_row) + + # separator self.control_panel_layout.addWidget(self._create_separator()) # 2D Positioner (fixed size) self.motor_control_2d = XRayEye2DControl(parent=self) - self.control_panel_layout.addWidget(self.motor_control_2d, 0, Qt.AlignTop | Qt.AlignCenter) + self.control_panel_layout.addWidget(self.motor_control_2d, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignCenter) # separator self.control_panel_layout.addWidget(self._create_separator()) @@ -190,9 +221,8 @@ class XRayEye(BECWidget, QWidget): # Submit button self.submit_button = QPushButton("Submit", parent=self) # Add to layout form - step_size_form.addWidget(QLabel("Horizontal", parent=self), 0, 0) + step_size_form.addWidget(QLabel("Step Size", parent=self), 0, 0) step_size_form.addWidget(self.step_size, 0, 1) - step_size_form.addWidget(QLabel("Vertical", parent=self), 1, 0) step_size_form.addWidget(self.submit_button, 2, 0, 1, 2) # Add form to control panel @@ -207,7 +237,8 @@ class XRayEye(BECWidget, QWidget): self.sample_name_line_edit.setReadOnly(True) form.addWidget(QLabel("Sample", parent=self), 0, 0) form.addWidget(self.sample_name_line_edit, 0, 1) - self.message_line_edit = QLineEdit(parent=self) + self.message_line_edit = QTextEdit(parent=self) + self.message_line_edit.setFixedHeight(60) self.message_line_edit.setReadOnly(True) form.addWidget(QLabel("Message", parent=self), 1, 0) form.addWidget(self.message_line_edit, 1, 1) @@ -217,12 +248,37 @@ class XRayEye(BECWidget, QWidget): self.control_panel.adjustSize() p_hint = self.control_panel.sizeHint() self.control_panel.setFixedWidth(p_hint.width()) - self.control_panel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) + self.control_panel.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding) # Core Layout: image (expanding) | control panel (fixed) self.core_layout.addWidget(self.image) self.core_layout.addWidget(self.control_panel) + self.tab_widget.addTab(self.alignment_tab, "Alignment") + + self.fit_tab = QWidget(parent=self) + self.fit_layout = QVBoxLayout(self.fit_tab) + self.waveform_x = Waveform(parent=self.fit_tab) + self.waveform_y = Waveform(parent=self.fit_tab) + self.waveform_x.plot(x=[0],y=[1], label="fit-x",dap="SineModel")#,dap_oversample=5) + self.waveform_y.plot(x=[0],y=[2], label="fit-y",dap="SineModel")#,dap_oversample=5) + self.fit_x = self.waveform_x.curves[0] + self.fit_y = self.waveform_y.curves[0] + + self.waveform_x.dap_params_update.connect(self.on_dap_params) + self.waveform_y.dap_params_update.connect(self.on_dap_params) + + + for wave in (self.waveform_x,self.waveform_y): + wave.x_label = "Angle (deg)" + wave.x_grid = True + wave.y_grid = True + wave.enable_toolbar = False + + self.fit_layout.addWidget(self.waveform_x) + self.fit_layout.addWidget(self.waveform_y) + self.tab_widget.addTab(self.fit_tab, "Fit") + def _make_connections(self): # Fetch initial state self.on_live_view_enabled(True) @@ -235,13 +291,14 @@ class XRayEye(BECWidget, QWidget): def _create_separator(self): sep = QFrame(parent=self) - sep.setFrameShape(QFrame.HLine) - sep.setFrameShadow(QFrame.Sunken) + sep.setFrameShape(QFrame.Shape.HLine) + sep.setFrameShadow(QFrame.Shadow.Sunken) sep.setLineWidth(1) return sep def _init_gui_trigger(self): self.dev.omny_xray_gui.read() + self.dev.omnyfsh.read() ################################################################################ # Device Connection logic @@ -254,7 +311,7 @@ class XRayEye(BECWidget, QWidget): for motor in possible_motors: if motor in self.dev: self.bec_dispatcher.connect_slot(self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor)) - logger.info(f"Succesfully connected to {motor}") + logger.info(f"Successfully connected to {motor}") ################################################################################ # Properties ported from the original OmnyAlignment, can be adjusted as needed @@ -290,6 +347,14 @@ class XRayEye(BECWidget, QWidget): ################################################################################ # Slots ported from the original OmnyAlignment, can be adjusted as needed ################################################################################ + + @SafeSlot(str) + def switch_tab(self,tab:str): + if tab == "fit": + self.tab_widget.setCurrentIndex(1) + else: + self.tab_widget.setCurrentIndex(0) + @SafeSlot() def get_roi_coordinates(self) -> dict | None: @@ -315,6 +380,42 @@ class XRayEye(BECWidget, QWidget): self.live_preview_toggle.checked = enabled self.live_preview_toggle.blockSignals(False) + @SafeSlot(bool) + def camera_running_enabled(self, enabled: bool): + logger.info(f"Camera running: {enabled}") + self.camera_running_toggle.blockSignals(True) + self.dev.get(CAMERA[0]).live_mode_enabled.put(enabled) + self.camera_running_toggle.checked = enabled + self.camera_running_toggle.blockSignals(False) + + @SafeSlot(dict,dict) + def getting_camera_status(self,data,meta): + print(f"msg:{data}") + live_mode_enabled = data.get("signals").get(f"{CAMERA[0]}_live_mode_enabled").get("value") + self.camera_running_toggle.blockSignals(True) + self.camera_running_toggle.checked = live_mode_enabled + self.camera_running_toggle.blockSignals(False) + + @SafeSlot(bool) + def opening_shutter(self, enabled: bool): + logger.info(f"Shutter changed from GUI to: {enabled}") + self.shutter_toggle.blockSignals(True) + if enabled: + self.dev.omnyfsh.fshopen() + else: + self.dev.omnyfsh.fshclose() + # self.shutter_toggle.checked = enabled + self.shutter_toggle.blockSignals(False) + + + @SafeSlot(dict,dict) + def getting_shutter_status(self,data,meta): + shutter_open = bool(data.get("signals").get("omnyfsh_shutter").get("value")) + self.shutter_toggle.blockSignals(True) + self.shutter_toggle.checked = shutter_open + self.shutter_toggle.blockSignals(False) + + @SafeSlot(bool, bool) def on_motors_enable(self, x_enable: bool, y_enable: bool): """ @@ -327,17 +428,38 @@ class XRayEye(BECWidget, QWidget): self.motor_control_2d.enable_controls_hor(x_enable) self.motor_control_2d.enable_controls_ver(y_enable) - @SafeSlot(int) - def enable_submit_button(self, enable: int): + @SafeSlot(bool) + def enable_submit_button(self, enable: bool): """ Enable/disable submit button. Args: enable(int): -1 disable else enable """ - if enable == -1: - self.submit_button.setEnabled(False) - else: + if enable: self.submit_button.setEnabled(True) + else: + self.submit_button.setEnabled(False) + + @SafeSlot(dict,dict) + def on_dap_params(self,data,meta): + print('#######################################') + print('getting dap parameters') + print(f"data: {data}") + print(f"meta: {meta}") + self.waveform_x.auto_range(True) + self.waveform_y.auto_range(True) + # self.bec_dispatcher.disconnect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")) + curve_id = meta.get("curve_id") + + + + if curve_id == "fit-x-SineModel": + self.dev.omny_xray_gui.fit_params_x.set(data).wait() + print(f"setting x data to {data}") + else: + self.dev.omny_xray_gui.fit_params_y.set(data).wait() + print(f"setting y data to {data}") + # self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")) @SafeSlot(bool, bool) def on_tomo_angle_readback(self, data: dict, meta: dict): @@ -354,31 +476,42 @@ class XRayEye(BECWidget, QWidget): data(dict): data from device meta(dict): metadata from device """ + print(f"data from device_updates: {data}") - signals = data.get('signals') - enable_live_preview = signals.get("omny_xray_gui_update_frame_acq").get('value') - enable_x_motor = signals.get("omny_xray_gui_enable_mv_x").get('value') - enable_y_motor = signals.get("omny_xray_gui_enable_mv_y").get('value') - self.on_live_view_enabled(bool(enable_live_preview)) - self.on_motors_enable(bool(enable_x_motor), bool(enable_y_motor)) + # signals = data.get('signals') + # enable_live_preview = signals.get("omny_xray_gui_update_frame_acq").get('value') + # enable_x_motor = signals.get("omny_xray_gui_enable_mv_x").get('value') + # enable_y_motor = signals.get("omny_xray_gui_enable_mv_y").get('value') + # self.on_live_view_enabled(bool(enable_live_preview)) + # self.on_motors_enable(bool(enable_x_motor), bool(enable_y_motor)) + + + # # Signals from epics gui device + # # send message + # user_message = signals.get("omny_xray_gui_send_message").get('value') + # self.user_message = user_message + # # sample name + # sample_message = signals.get("omny_xray_gui_sample_name").get('value') + # self.sample_name = sample_message + # # enable frame acquisition + # update_frame_acq = signals.get("omny_xray_gui_update_frame_acq").get('value') + # self.on_live_view_enabled(bool(update_frame_acq)) + # # enable submit button + # enable_submit_button = signals.get("omny_xray_gui_submit").get('value') + # self.enable_submit_button(enable_submit_button) + + @SafeSlot() + def submit_fit_array(self,fit_array): + + print(f"got fit array {fit_array}") + self.fit_x.set_data(x=fit_array[0],y=fit_array[1]) + self.fit_y.set_data(x=fit_array[0],y=fit_array[2]) - # Signals from epics gui device - # send message - user_message = signals.get("omny_xray_gui_send_message").get('value') - self.user_message = user_message - # sample name - sample_message = signals.get("omny_xray_gui_sample_name").get('value') - self.sample_name = sample_message - # enable frame acquisition - update_frame_acq = signals.get("omny_xray_gui_update_frame_acq").get('value') - self.on_live_view_enabled(bool(update_frame_acq)) - # enable submit button - enable_submit_button = signals.get("omny_xray_gui_submit").get('value') - self.enable_submit_button(enable_submit_button) @SafeSlot() def submit(self): """Execute submit action by submit button.""" + print('submit pushed') if self.roi_manager.single_active_roi is None: logger.warning("No active ROI") return @@ -400,12 +533,13 @@ class XRayEye(BECWidget, QWidget): # submit roi coordinates step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get('value')) - xval_x = getattr(self.dev.omny_xray_gui.xval_x, f"xval_x_{step}").set(roi_center_x) - xval_y = getattr(self.dev.omny_xray_gui.yval_y, f"yval_y_{step}").set(roi_center_y) - width_x = getattr(self.dev.omny_xray_gui.width_x, f"width_x_{step}").set(roi_width) - width_y = getattr(self.dev.omny_xray_gui.width_y, f"width_y_{step}").set(roi_height) + xval_x = getattr(self.dev.omny_xray_gui, f"xval_x_{step}").set(roi_center_x) + xval_y = getattr(self.dev.omny_xray_gui, f"yval_y_{step}").set(roi_center_y) + width_x = getattr(self.dev.omny_xray_gui, f"width_x_{step}").set(roi_width) + width_y = getattr(self.dev.omny_xray_gui, f"width_y_{step}").set(roi_height) self.dev.omny_xray_gui.submit.set(1) - + print('submit done') + def cleanup(self): """Cleanup connections on widget close -> disconnect slots and stop live mode of camera.""" self.bec_dispatcher.disconnect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")) @@ -416,9 +550,10 @@ if __name__ == "__main__": import sys from qtpy.QtWidgets import QApplication + from bec_widgets.utils import BECDispatcher app = QApplication(sys.argv) - + dispatcher = BECDispatcher(gui_id='xray') win = XRayEye() win.resize(1000, 800) diff --git a/csaxs_bec/device_configs/flomni_config.yaml b/csaxs_bec/device_configs/flomni_config.yaml index e2a20c5..8e5122f 100644 --- a/csaxs_bec/device_configs/flomni_config.yaml +++ b/csaxs_bec/device_configs/flomni_config.yaml @@ -414,9 +414,9 @@ cam_ids_rgb: deviceConfig: camera_id: 203 bits_per_pixel: 24 - num_rotation_90: 3 + num_rotation_90: 2 transpose: false - force_monochrome: true + force_monochrome: false m_n_colormode: 1 enabled: true onFailure: buffer @@ -450,19 +450,10 @@ omnyfsh: #################### GUI Signals ########################### ############################################################ omny_xray_gui: - description: Gui Epics signals - deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayEpicsGUI + description: Gui signals + deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayAlignGUI deviceConfig: {} enabled: true onFailure: buffer readOnly: false - readoutPriority: on_request - -calculated_signal: - description: Calculated signal from alignment for fit - deviceClass: ophyd_devices.ComputedSignal - deviceConfig: - compute_method: "def just_rand():\n return 42" - enabled: true - readOnly: false - readoutPriority: baseline \ No newline at end of file + readoutPriority: on_request \ No newline at end of file diff --git a/csaxs_bec/devices/omny/xray_epics_gui.py b/csaxs_bec/devices/omny/xray_epics_gui.py index 7db7bb7..5f783db 100644 --- a/csaxs_bec/devices/omny/xray_epics_gui.py +++ b/csaxs_bec/devices/omny/xray_epics_gui.py @@ -1,74 +1,46 @@ - -from ophyd import Component as Cpt +import numpy as np +from ophyd import Component as Cpt, Signal, EpicsSignal from ophyd import Device from ophyd import DynamicDeviceComponent as Dcpt -from ophyd import EpicsSignal +class OMNYXRayAlignGUI(Device): - -class OMNYXRayEpicsGUI(Device): - - save_frame = Cpt( - EpicsSignal, name="save_frame", read_pv="XOMNYI-XEYE-SAVFRAME:0",auto_monitor=True - ) - update_frame_acqdone = Cpt( - EpicsSignal, name="update_frame_acqdone", read_pv="XOMNYI-XEYE-ACQDONE:0",auto_monitor=True - ) - update_frame_acq = Cpt( - EpicsSignal, name="update_frame_acq", read_pv="XOMNYI-XEYE-ACQ:0",auto_monitor=True - ) - width_y_dynamic = { - f"width_y_{i}": (EpicsSignal, f"XOMNYI-XEYE-YWIDTH_Y:{i}", {"auto_monitor": True}) for i in range(0, 11) - } - width_y = Dcpt(width_y_dynamic) - width_x_dynamic = { - f"width_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-XWIDTH_X:{i}", {"auto_monitor": True}) for i in range(0, 11) - } - width_x = Dcpt(width_x_dynamic) - enable_mv_x = Cpt( - EpicsSignal, name="enable_mv_x", read_pv="XOMNYI-XEYE-ENAMVX:0",auto_monitor=True - ) - enable_mv_y = Cpt( - EpicsSignal, name="enable_mv_y", read_pv="XOMNYI-XEYE-ENAMVY:0",auto_monitor=True - ) - send_message = Cpt( - EpicsSignal, name="send_message", read_pv="XOMNYI-XEYE-MESSAGE:0.DESC",auto_monitor=True - ) - sample_name = Cpt( - EpicsSignal, name="sample_name", read_pv="XOMNYI-XEYE-SAMPLENAME:0.DESC",auto_monitor=True - ) - angle = Cpt( - EpicsSignal, name="angle", read_pv="XOMNYI-XEYE-ANGLE:0",auto_monitor=True - ) - pixel_size = Cpt( - EpicsSignal, name="pixel_size", read_pv="XOMNYI-XEYE-PIXELSIZE:0",auto_monitor=True - ) + update_frame_acqdone = Cpt(Signal, value=0) + update_frame_acq = Cpt(Signal, value=0) + enable_mv_x = Cpt(Signal, value=0) + enable_mv_y = Cpt(Signal, value=0) + send_message = Cpt(Signal, value=0) + sample_name = Cpt(Signal, value=0) + angle = Cpt(Signal, value=0) + pixel_size = Cpt(Signal, value=0) submit = Cpt( EpicsSignal, name="submit", read_pv="XOMNYI-XEYE-SUBMIT:0",auto_monitor=True - ) - step = Cpt( - EpicsSignal, name="step", read_pv="XOMNYI-XEYE-STEP:0",auto_monitor=True - ) - xval_x_dynamic = { - f"xval_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-XVAL_X:{i}", {"auto_monitor": True}) for i in range(0, 11) - } - xval_x = Dcpt(xval_x_dynamic) - yval_y_dynamic = { - f"yval_y_{i}": (EpicsSignal, f"XOMNYI-XEYE-YVAL_Y:{i}", {"auto_monitor": True}) for i in range(0, 11) - } - yval_y = Dcpt(yval_y_dynamic) - recbg = Cpt( - EpicsSignal, name="recbg", read_pv="XOMNYI-XEYE-RECBG:0",auto_monitor=True - ) - stage_pos_x_dynamic = { - f"stage_pos_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-STAGEPOSX:{i}", {"auto_monitor": True}) for i in range(1, 6) - } - stage_pos_x = Dcpt(stage_pos_x_dynamic) - mvx = Cpt( - EpicsSignal, name="mvx", read_pv="XOMNYI-XEYE-MVX:0",auto_monitor=True - ) - mvy = Cpt( - EpicsSignal, name="mvy", read_pv="XOMNYI-XEYE-MVY:0",auto_monitor=True - ) + ) + step = Cpt(Signal, value=0) + recbg = Cpt(Signal, value=0) + mvx = Cpt(Signal, value=0) + mvy = Cpt(Signal, value=0) + fit_array = Cpt(Signal, value=np.zeros((3, 10))) + fit_params_x = Cpt(Signal, value=np.zeros((2, 3))) + fit_params_y = Cpt(Signal, value=np.zeros((2, 3))) + # Generate width_y_0 to width_y_10 + for i in range(11): + locals()[f'width_y_{i}'] = Cpt(Signal, value=0) + + # Generate width_x_0 to width_x_10 + for i in range(11): + locals()[f'width_x_{i}'] = Cpt(Signal, value=0) + + # Generate xval_x_0 to xval_x_10 + for i in range(11): + locals()[f'xval_x_{i}'] = Cpt(Signal, value=0) + + # Generate yval_y_0 to yval_y_10 + for i in range(11): + locals()[f'yval_y_{i}'] = Cpt(Signal, value=0) + + # Generate stage_pos_x_1 to stage_pos_x_5 + for i in range(1, 6): + locals()[f'stage_pos_x_{i}'] = Cpt(Signal, value=0)