From 5d87b030185f72cb500319bef2d1c9307ee4e904 Mon Sep 17 00:00:00 2001 From: x01dc Date: Fri, 28 Nov 2025 17:27:48 +0100 Subject: [PATCH] wip changes from the beamline --- .../plugins/flomni/gui_tools.py | 2 +- csaxs_bec/device_configs/flomni_config.yaml | 12 +++ csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py | 92 +++++++++++++------ csaxs_bec/scans/flomni_fermat_scan.py | 24 ++--- 4 files changed, 87 insertions(+), 43 deletions(-) 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..080726e 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py @@ -156,7 +156,7 @@ 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']}" + 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) diff --git a/csaxs_bec/device_configs/flomni_config.yaml b/csaxs_bec/device_configs/flomni_config.yaml index e2a20c5..4b9703c 100644 --- a/csaxs_bec/device_configs/flomni_config.yaml +++ b/csaxs_bec/device_configs/flomni_config.yaml @@ -365,6 +365,18 @@ rtz: readOnly: false readoutPriority: on_request +rt_flyer: + description: flomni rt flyer + deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniFlyer + deviceConfig: + host: mpc2844.psi.ch + port: 2222 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: async + + ############################################################ ####################### Cameras ############################ ############################################################ diff --git a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py index 7ea5496..98a55b8 100644 --- a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py @@ -10,7 +10,7 @@ from ophyd import Component as Cpt from ophyd import Device, DeviceStatus, PositionerBase, Signal from ophyd.status import wait as status_wait from ophyd.utils import LimitError -from ophyd_devices.utils.bec_signals import AsyncSignal +from ophyd_devices.utils.bec_signals import AsyncMultiSignal, ProgressSignal from ophyd_devices.utils.controller import Controller, threadlocked from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected from prettytable import PrettyTable @@ -741,7 +741,26 @@ class RtFlomniMotor(Device, PositionerBase): class RtFlomniFlyer(Device): USER_ACCESS = ["controller"] - data = Cpt(AsyncSignal, "data", ndim=1, async_update={"type": "add", "max_shape": [None]}) + data = Cpt( + AsyncMultiSignal, + name="data", + signals=[ + "target_x", + "average_x_st_fzp", + "stdev_x_st_fzp", + "target_y", + "average_y_st_fzp", + "stdev_y_st_fzp", + "average_rotz", + "stdev_rotz", + "average_stdeviations_x_st_fzp", + "average_stdeviations_y_st_fzp", + ], + ndim=0, + max_size=10000, + async_update={"type": "add", "max_shape": [None]}, + ) + progress = Cpt(ProgressSignal, name="progress") def __init__( self, @@ -765,15 +784,11 @@ class RtFlomniFlyer(Device): read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, - host=host, - port=port, - socket_cls=socket_cls, - device_manager=device_manager, **kwargs, ) self.shutdown_event = threading.Event() self.controller = RtFlomniController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.average_stdeviations_x_st_fzp = 0 self.average_stdeviations_y_st_fzp = 0 @@ -805,11 +820,20 @@ class RtFlomniFlyer(Device): ) if current_position_in_scan > 5: while current_position_in_scan > read_counter + 1: - return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",") + return_table = ( + self.controller.socket_put_and_receive(f"r{read_counter}") + ).split(",") logger.info(f"Read {read_counter} out of {number_of_positions_planned}") read_counter = read_counter + 1 signals = self._get_signals_from_table(return_table) self.data.set(signals) + self.progress.put( + value=read_counter, + max_value=number_of_positions_planned, + done=(number_of_positions_planned == read_counter), + ) + logger.info(f"Updating progress: {read_counter} / {number_of_positions_planned}") + if self.shutdown_event.wait(0.05): logger.info("Shutdown event set, stopping readout.") @@ -819,57 +843,65 @@ class RtFlomniFlyer(Device): # read the last samples even though scan is finished already while number_of_positions_planned > read_counter and not self.shutdown_event.is_set(): - return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",") + return_table = (self.controller.socket_put_and_receive(f"r{read_counter}")).split(",") logger.info(f"Read {read_counter} out of {number_of_positions_planned}") read_counter = read_counter + 1 signals = self._get_signals_from_table(return_table) + logger.info(f"Setting data: {signals}") self.data.set(signals) + self.progress.put( + value=read_counter, + max_value=number_of_positions_planned, + done=(number_of_positions_planned == read_counter), + ) + logger.info(f"Updating progress: {read_counter} / {number_of_positions_planned}") + # NOTE: No need to set the status to failed if the shutdown_event is set. # The stop() method will take care of that. status.set_finished() - logger.info( - "Flomni statistics: Average of all standard deviations: x" - f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y" - f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}" - ) + if read_counter != 0: + logger.info( + "Flomni statistics: Average of all standard deviations: x" + f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y" + f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}" + ) + def _get_signals_from_table(self, return_table) -> dict: self.average_stdeviations_x_st_fzp += float(return_table[4]) self.average_stdeviations_y_st_fzp += float(return_table[7]) signals = { - "target_x": {"value": float(return_table[2])}, - "average_x_st_fzp": {"value": float(return_table[3])}, - "stdev_x_st_fzp": {"value": float(return_table[4])}, - "target_y": {"value": float(return_table[5])}, - "average_y_st_fzp": {"value": float(return_table[6])}, - "stdev_y_st_fzp": {"value": float(return_table[7])}, - "average_rotz": {"value": float(return_table[8])}, - "stdev_rotz": {"value": float(return_table[9])}, - "average_stdeviations_x_st_fzp": { + f"{self.name}_data_target_x": {"value": float(return_table[2])}, + f"{self.name}_data_average_x_st_fzp": {"value": float(return_table[3])}, + f"{self.name}_data_stdev_x_st_fzp": {"value": float(return_table[4])}, + f"{self.name}_data_target_y": {"value": float(return_table[5])}, + f"{self.name}_data_average_y_st_fzp": {"value": float(return_table[6])}, + f"{self.name}_data_stdev_y_st_fzp": {"value": float(return_table[7])}, + f"{self.name}_data_average_rotz": {"value": float(return_table[8])}, + f"{self.name}_data_stdev_rotz": {"value": float(return_table[9])}, + f"{self.name}_data_average_stdeviations_x_st_fzp": { "value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1) }, - "average_stdeviations_y_st_fzp": { + f"{self.name}_data_average_stdeviations_y_st_fzp": { "value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1) }, } return signals def start_readout(self, status: DeviceStatus): + logger.info("Starting readout thread.") readout = threading.Thread(target=self.read_positions_from_sampler, args=(status,)) readout.start() - def kickoff(self): + def kickoff(self) -> DeviceStatus: self.shutdown_event.clear() - while not self._min_scan_buffer_reached and not self.shutdown_event.wait(0.001): + while not self.controller._min_scan_buffer_reached and not self.shutdown_event.wait(0.001): ... - self.start_scan() + self.controller.start_scan() self.shutdown_event.wait(0.1) - - def complete(self) -> DeviceStatus: - """Wait until the flyer is done.""" status = DeviceStatus(self) self.start_readout(status) return status diff --git a/csaxs_bec/scans/flomni_fermat_scan.py b/csaxs_bec/scans/flomni_fermat_scan.py index c507f02..ae4ed1d 100644 --- a/csaxs_bec/scans/flomni_fermat_scan.py +++ b/csaxs_bec/scans/flomni_fermat_scan.py @@ -26,12 +26,12 @@ import numpy as np from bec_lib import bec_logger, messages from bec_lib.endpoints import MessageEndpoints from bec_server.scan_server.errors import ScanAbortion -from bec_server.scan_server.scans import SyncFlyScanBase +from bec_server.scan_server.scans import AsyncFlyScanBase logger = bec_logger.logger -class FlomniFermatScan(SyncFlyScanBase): +class FlomniFermatScan(AsyncFlyScanBase): scan_name = "flomni_fermat_scan" scan_type = "fly" required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"] @@ -97,6 +97,7 @@ class FlomniFermatScan(SyncFlyScanBase): logger.warning("The zshift is smaller than -100 um. It will be limited to -100 um.") self.zshift = -100 self.flomni_rotation_status = None + self.flyer_num_pos = 0 def initialize(self): self.scan_motors = [] @@ -107,10 +108,6 @@ class FlomniFermatScan(SyncFlyScanBase): self.positions, corridor_size=self.optim_trajectory_corridor ) - @property - def monitor_sync(self): - return "rt_flomni" - def reverse_trajectory(self): """ Reverse the trajectory. Every other scan should be reversed to @@ -135,13 +132,13 @@ class FlomniFermatScan(SyncFlyScanBase): if flip_axes: self.positions = np.flipud(self.positions) - self.num_pos = len(self.positions) + self.flyer_num_pos = len(self.positions) self._check_min_positions() def _check_min_positions(self): - if self.num_pos < 20: + if self.flyer_num_pos < 20: raise ScanAbortion( - f"The number of positions must exceed 20. Currently: {self.num_pos}." + f"The number of positions must exceed 20. Currently: {self.flyer_num_pos}." ) def _prepare_setup(self): @@ -150,6 +147,8 @@ class FlomniFermatScan(SyncFlyScanBase): yield from self.stubs.send_rpc_and_wait("rty", "set", self.positions[0][1]) + yield from self.stubs.scan_report_instruction({"device_progress": ["rt_flyer"]}) + def _prepare_setup_part2(self): if self.flomni_rotation_status: self.flomni_rotation_status.wait() @@ -280,12 +279,13 @@ class FlomniFermatScan(SyncFlyScanBase): return np.array(positions) def scan_core(self): - yield from self.stubs.kickoff(device="rt_flyer") - status = yield from self.stubs.complete(device="rt_flyer", wait=False) + status = yield from self.stubs.kickoff(device="rt_flyer", wait=False) while not status.done: - yield from self.stubs.read(group="monitored") + yield from self.stubs.read(group="monitored", point_id=self.point_id) time.sleep(1) + self.point_id += 1 logger.debug("reading monitors") + self.num_pos = self.point_id def move_to_start(self): """return to the start position"""