diff --git a/pxiii_bec/devices/NDArrayPreview.py b/pxiii_bec/devices/NDArrayPreview.py index 0689bea..2bd5762 100644 --- a/pxiii_bec/devices/NDArrayPreview.py +++ b/pxiii_bec/devices/NDArrayPreview.py @@ -11,7 +11,7 @@ Created on Wed Jan 29 2025 @author: mohacsi_i """ import numpy as np -from ophyd import Device, Component, EpicsSignal, Kind, Staged +from ophyd import Device, Component, EpicsSignal, EpicsSignalWithRBV, Kind, Staged from ophyd.areadetector import NDDerivedSignal @@ -20,24 +20,13 @@ from bec_lib import bec_logger logger = bec_logger.logger class SilentNDDerivedSignal(NDDerivedSignal): - def inverse(self, value): - """Shape the flat array to send as a result of ``.get``""" - array_shape = self.derived_shape[: self.derived_ndims] - if not any(array_shape): - raise RuntimeWarning(f"Invalid array size {self.derived_shape}") - return self._readback - - array_len = np.prod(array_shape) - if len(value) < array_len: - raise RuntimeWarning( - f"cannot reshape array of size {len(value)} " - f"into shape {tuple(array_shape)}. Check IOC configuration." - ) - return self._readback - - return np.asarray(value[:array_len]).reshape(array_shape) - - + """Silent version of NDDerivedSignal, it does not spam the terminal on + every defective frame (shit happens, ok?).""" + def _array_shape_callback(self, **kwargs): + try: + super()._array_shape_callback(**kwargs) + except RuntimeError: + pass class NDArrayPreview(Device): @@ -56,13 +45,15 @@ class NDArrayPreview(Device): _default_sub = SUB_MONITOR # Status attributes + min_callback_time = Component( + EpicsSignalWithRBV, "MinCallbackTime", kind=Kind.config, put_complete=True) array_size_x = Component(EpicsSignal, "ArraySize0_RBV", kind=Kind.config) array_size_y = Component(EpicsSignal, "ArraySize1_RBV", kind=Kind.config) array_size_z = Component(EpicsSignal, "ArraySize2_RBV", kind=Kind.config) ndimensions = Component(EpicsSignal, "NDimensions_RBV", kind=Kind.config) array_data = Component(EpicsSignal, "ArrayData", kind=Kind.omitted) shaped_image = Component( - NDDerivedSignal, + SilentNDDerivedSignal, derived_from="array_data", shape=("array_size_z", "array_size_y", "array_size_x"), num_dimensions="ndimensions", diff --git a/pxiii_bec/devices/SmarGon2.py b/pxiii_bec/devices/SmarGon2.py index 0d188ee..1f916fc 100644 --- a/pxiii_bec/devices/SmarGon2.py +++ b/pxiii_bec/devices/SmarGon2.py @@ -10,6 +10,7 @@ import time import threading from threading import Thread, Lock import requests +from collections import OrderedDict from requests.adapters import HTTPAdapter, Retry from ophyd import Component, Kind, Signal, PVPositioner from ophyd.status import SubscriptionStatus @@ -57,10 +58,11 @@ class LimitedSmarGonSignal(Signal): if value > hil: raise ValueError(f"Target {value} outside of limits {self.limits}") - def put(self, value, *, timestamp=None, **kwargs): + def put(self, value, *, timestamp=None, force=False, metadata=None, **kwargs,): """Overriden put to add communication with smargopolo""" # Validate new value and get timestamp - self.check_value(value) + if not force: + self.check_value(value) if timestamp is None: timestamp = time.time() @@ -86,7 +88,7 @@ class SmarGonAxis(PVPositioner): This class controls the SmarGon goniometer via the REST interface. All SmarGon axes share a common mutex to manage actual HW access. """ - USER_ACCESS = ["omove"] + USER_ACCESS = ["omove", "oldmove"] # Status attributes sg_url = Component(Signal, kind=Kind.config, metadata={"write_access": False}) @@ -133,7 +135,7 @@ class SmarGonAxis(PVPositioner): ) def on_target(): - """NOTE: This assumes that both readback and setpoint is always up to date""" + """Monitors the setpoint and readback and calculates the on_target flag""" time.sleep(2) while True: # Read back target and setpoint values @@ -154,11 +156,12 @@ class SmarGonAxis(PVPositioner): self._mon = threading.Thread(target=on_target, daemon=True) self._mon.start() - def move(self, position, wait=True, timeout=None, moved_cb=None): + def omove(self, position, wait=True, timeout=None, moved_cb=None): """Move command that's masked by BEC""" - return self.omove(position, wait, timeout, moved_cb) + self.done.put(0, force=True) + return self.move(position, wait, timeout, moved_cb) - def omove(self, position, wait=True, timeout=2.0, moved_cb=None): + def oldmove(self, position, wait=True, timeout=2.0, moved_cb=None): """Original move command without the BEC wrappers""" status = self.setpoint.set(position, settle_time=0.1).wait() if not wait: @@ -172,7 +175,21 @@ class SmarGonAxis(PVPositioner): status = SubscriptionStatus(self.readback, on_target, timeout=timeout, settle_time=0.1) return status + def describe(self): + """Workaround to schema expected by the BEC""" + d = super().describe() + d[str(self.name)] = d[f"{self.name}_readback"] + return d + + + def read(self) -> OrderedDict[str, dict]: + """Workaround to schema expected by the BEC""" + d = super().read() + d[str(self.name)] = d[f"{self.name}_readback"] + return d + def _pos_changed(self, timestamp=None, value=None, **kwargs): + """Remove EPICS dependency""" pass def _go_n_get(self, address, **kwargs): diff --git a/pxiii_bec/devices/__init__.py b/pxiii_bec/devices/__init__.py index f853f9a..f486996 100644 --- a/pxiii_bec/devices/__init__.py +++ b/pxiii_bec/devices/__init__.py @@ -6,7 +6,7 @@ Ophyd devices for the PX III beamline, including the MX specific Aerotech A3200 """ from .A3200 import AerotechAbrStage from .A3200utils import A3200Axis -from .SmarGon import SmarGonAxis +from .SmarGon2 import SmarGonAxis from .StdDaqPreview import StdDaqPreviewDetector from .NDArrayPreview import NDArrayPreview from .SamCamDetector import SamCamDetector