From 19182daa471fcceb85a3e4729df116cfe64d59a1 Mon Sep 17 00:00:00 2001 From: appel_c Date: Tue, 12 May 2026 11:08:42 +0200 Subject: [PATCH] wip bugfixes --- superxas_bec/devices/timepix/timepix.py | 61 ++++++++++--------- .../timepix_fly_client/timepix_fly_backend.py | 5 +- .../timepix_fly_client/timepix_fly_client.py | 8 ++- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/superxas_bec/devices/timepix/timepix.py b/superxas_bec/devices/timepix/timepix.py index c22f8c7..80b8b46 100644 --- a/superxas_bec/devices/timepix/timepix.py +++ b/superxas_bec/devices/timepix/timepix.py @@ -12,7 +12,6 @@ import os import threading import time import traceback -from functools import partial from typing import TYPE_CHECKING, Any, Literal import numpy as np @@ -20,12 +19,13 @@ from bec_lib.file_utils import get_full_path from bec_lib.logger import bec_logger from ophyd import ADBase from ophyd import Component as Cpt -from ophyd import EpicsSignalRO, EpicsSignalWithRBV +from ophyd import Signal from ophyd_devices import ( - AndStatus, AsyncSignal, CompareStatus, DeviceStatus, + EpicsSignalRO, + EpicsSignalWithRBV, ExceptionStatus, FileEventSignal, PreviewSignal, @@ -201,6 +201,7 @@ class Timepix(PSIDeviceBase, TimePixControl): "set_pixel_map", "set_pixel_map_from_json_file", "set_enable_xes", + "set_enable_image_writing", ] xes_data = Cpt( @@ -514,6 +515,15 @@ class Timepix(PSIDeviceBase, TimePixControl): """ self.enable_xes = enable + def set_enable_image_writing(self, enable: bool) -> None: + """ + Enable or disable image writing to file through the HDF5 plugin. + + Args: + enable (bool): Whether to enable image writing. + """ + self.hdf.enable.set(1 if enable else 0).wait(timeout=self._pv_timeout) + @property def enable_xes(self) -> bool: """Get whether XES data acquisition is enabled.""" @@ -701,18 +711,18 @@ class Timepix(PSIDeviceBase, TimePixControl): file_path = "/".join(self._full_path.split("/")[:-1]) file_name = self._full_path.split("/")[-1] self.cam.array_callbacks.set(1).wait(5) # Enable array callbacks - # self.hdf.enable.set(1).wait(5) # Enable HDF5 plugin self.hdf.file_path.set(file_path).wait(5) self.hdf.file_name.set(file_name).wait(5) # Setup file writing for the total expected number of images self.hdf.num_capture.set(self._n_images).wait(5) - self.hdf.capture.put(1) - self.file_event.put( - file_path=self._full_path, - done=False, - successful=False, - hinted_h5_entries={"data": "/entry/data/data"}, - ) + if self.hdf.enable.get() == 1: + self.hdf.capture.put(1) + self.file_event.put( + file_path=self._full_path, + done=False, + successful=False, + hinted_h5_entries={"data": "/entry/data/data"}, + ) # ------------------------- # XES specific staging @@ -807,13 +817,15 @@ class Timepix(PSIDeviceBase, TimePixControl): def on_complete(self) -> DeviceStatus | StatusBase | None: """Called to inquire if a device has completed a scans.""" # Status Camera - status_camera = CompareStatus(self.cam.acquire_busy, ACQUIRESTATUS.DONE) + return_status = CompareStatus(self.cam.acquire_busy, ACQUIRESTATUS.DONE) # Status Writer st1 = CompareStatus(self.hdf.capture, ACQUIRESTATUS.DONE) - st2 = CompareStatus(self.hdf.write_file, ACQUIRESTATUS.DONE) - st3 = ExceptionStatus(self.hdf.write_status, 0, operation="!=") - status_written_images = CompareStatus(self.hdf.num_captured, self._n_images) - status_writer = st1 & st2 & status_written_images + status_writer = None + if self.hdf.enable.get() == 1: + st2 = CompareStatus(self.hdf.write_file, ACQUIRESTATUS.DONE) + st3 = ExceptionStatus(self.hdf.write_status, 0, operation="!=") + status_written_images = CompareStatus(self.hdf.num_captured, self._n_images) + status_writer = st1 & st2 & status_written_images & st3 # Status Backend status_backend = None @@ -822,25 +834,18 @@ class Timepix(PSIDeviceBase, TimePixControl): status_backend = self.backend.on_complete(status=status_backend) # Combine the statuses if status_backend is not None: - return_status = status_backend & status_camera & status_writer & st3 - else: - return_status = status_camera & status_writer & st3 - # We check the write_status if it goes to write error - # st3 = ExceptionStatus(self.hdf.write_status, 0, operation="!=") + return_status = status_backend & return_status + if status_writer is not None: + return_status = return_status & status_writer - # def _failed_to_write_callback(status, ret_status: AndStatus): - # if status.done and status.success: - # if not ret_status.done: - # ret_status.set_exception(f"Error while writing on {self.name}") - - self.cancel_on_stop(st3) - # st3.add_callback(partial(_failed_to_write_callback, ret_status=return_status)) return_status.add_callback(self._complete_callback) self.cancel_on_stop(return_status) return return_status def _complete_callback(self, status: CompareStatus) -> None: """Callback for when the device completes a scan.""" + if self.hdf.enable.get() != 1: # TODO: Not sure if we should support disabled file writing. + return if status.success: self.file_event.put( file_path=self._full_path, # pylint: disable:protected-access diff --git a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py index 73da390..bc7f8bd 100644 --- a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py +++ b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py @@ -12,7 +12,6 @@ hooks for all the relevant ophyd interface, 'on_stage', from __future__ import annotations import json -import signal import socket import threading import time @@ -239,6 +238,7 @@ class TimepixFlyBackend: def on_destroy(self): """Hook for on_destroy logic.""" + time_started = time.time() self.timepix_fly_client.shutdown() self._data_thread_shutdown_event.set() if self._data_thread is not None and self._data_thread.is_alive(): @@ -255,6 +255,9 @@ class TimepixFlyBackend: except Exception: content = traceback.format_exc() logger.error(f"Error closing socket server: {content}") + logger.info( + f"Timepix Fly backend destroyed and resources cleaned up after {time.time() - time_started:.3f} seconds." + ) def on_stop(self): """Hook for on_stop logic.""" diff --git a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py index 2411c7f..e45c4aa 100644 --- a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py +++ b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py @@ -270,16 +270,18 @@ class TimepixFlyClient: continue if status in success: dev_status.set_finished() - logger.debug(f"Status callback finished in succes: {status.value}") + logger.debug(f"Status callback finished in success: {status.value}") self._status_callbacks.pop(cb_id) elif status in error: try: last_error = self.last_error() raise TimePixStatusError( - f"TimePixFly Backend state '{status.value}' is in list of specified errors {error}. Last error message: {last_error.message}" + f"TimePixFly state '{status.value}': {last_error.message}" ) except Exception as e: - logger.error(f"Error in status callback from TimepixFly Backend: {e}") + logger.error( + f"Error in status callback for '{status.value}' from TimepixFly backend: {e}" + ) dev_status.set_exception(e) self._status_callbacks.pop(cb_id) # Reset the _started flag if the status is in CONFIG.