Samcam image preview and smargon waiting
This commit is contained in:
@@ -448,7 +448,7 @@ samimg:
|
||||
prefix: 'X06DA-SAMCAM:image1:'
|
||||
deviceTags:
|
||||
- detector
|
||||
enabled: true
|
||||
enabled: false
|
||||
readoutPriority: async
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
@@ -599,7 +599,7 @@ abr:
|
||||
shx:
|
||||
description: SmarGon X axis
|
||||
deviceClass: pxiii_bec.devices.SmarGonAxis
|
||||
deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
deviceConfig: {prefix: 'SCS', low_limit: -2, high_limit: 2, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
@@ -608,7 +608,7 @@ shx:
|
||||
shy:
|
||||
description: SmarGon Y axis
|
||||
deviceClass: pxiii_bec.devices.SmarGonAxis
|
||||
deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
deviceConfig: {prefix: 'SCS', low_limit: -2, high_limit: 2, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
@@ -626,7 +626,7 @@ shz:
|
||||
chi:
|
||||
description: SmarGon CHI axis
|
||||
deviceClass: pxiii_bec.devices.SmarGonAxis
|
||||
deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
deviceConfig: {prefix: 'SCS', low_limit: 0, high_limit: 40, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
@@ -641,17 +641,3 @@ phi:
|
||||
readoutPriority: monitored
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
|
||||
|
||||
# samimgs:
|
||||
# description: Sample camera image
|
||||
# deviceClass: ophyd_devices.devices.areadetector.plugins.ImagePlugin_V35
|
||||
# deviceConfig: {prefix: 'X06DA-SAMCAM:image1:', foo: 'bar'}
|
||||
# onFailure: buffer
|
||||
# enabled: false
|
||||
# readoutPriority: monitored
|
||||
# readOnly: true
|
||||
# softwareTrigger: false
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ class AerotechAbrMixin(CustomDeviceMixin):
|
||||
scan_range_y = scanargs["range"]
|
||||
scan_steps_y = scanargs["steps"]
|
||||
d["scan_command"] = AbrCmd.VERTICAL_LINE_SCAN
|
||||
d["var_1"] = scan_range_y / scan_steps_y
|
||||
d["var_1"] = scan_range_y / scan_steps_y
|
||||
d["var_2"] = scan_steps_y
|
||||
d["var_3"] = scan_exp_time
|
||||
d["var_4"] = 0
|
||||
|
||||
@@ -8,10 +8,10 @@ import types
|
||||
from ophyd import Component, PVPositioner, Signal, EpicsSignal, EpicsSignalRO, Kind, PositionerBase
|
||||
from ophyd.status import Status, MoveStatus
|
||||
|
||||
from .A3200enums import AbrMode
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from .A3200enums import AbrMode
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class A3200Axis(PVPositioner):
|
||||
# Patching the parent's PVs into the axis class to check for direct/locked mode
|
||||
if parent is None:
|
||||
|
||||
def maybe_add_prefix(self, instance, kw, suffix):
|
||||
def maybe_add_prefix(self, _, kw, suffix):
|
||||
# Patched not to enforce parent prefix when no parent
|
||||
if kw in self.add_prefix:
|
||||
return suffix
|
||||
|
||||
@@ -20,9 +20,11 @@ class NDArrayPreview(Device):
|
||||
This is a monolithic class to display images from AreaDetector's
|
||||
ImagePlugin without the use of DynamicDeviceComponent or multiple
|
||||
interitance (that doesn't work with BEC).
|
||||
|
||||
NOTE: As an explicit request, it doesnt record the data, unless
|
||||
"""
|
||||
# Subscriptions for plotting image
|
||||
USER_ACCESS = ["image"]
|
||||
USER_ACCESS = ["image", "savemode"]
|
||||
SUB_MONITOR = "device_monitor_2d"
|
||||
_default_sub = SUB_MONITOR
|
||||
|
||||
@@ -37,7 +39,7 @@ class NDArrayPreview(Device):
|
||||
derived_from="array_data",
|
||||
shape=("array_size_z", "array_size_y", "array_size_x"),
|
||||
num_dimensions="ndimensions",
|
||||
kind=Kind.normal,
|
||||
kind=Kind.omitted,
|
||||
)
|
||||
|
||||
def read(self):
|
||||
@@ -47,6 +49,14 @@ class NDArrayPreview(Device):
|
||||
self._run_subs(sub_type=self.SUB_MONITOR, value=image)
|
||||
return super().read()
|
||||
|
||||
def savemode(self, save=False):
|
||||
""" Toggle save mode for the shaped image"""
|
||||
#pylint: disable=protected-access
|
||||
if save:
|
||||
self.shaped_image._kind = Kind.normal
|
||||
else:
|
||||
self.shaped_image._kind = Kind.omitted
|
||||
|
||||
def image(self):
|
||||
""" Fallback method in case image streaming fills up the BEC"""
|
||||
array_size = (self.array_size_z.get(), self.array_size_y.get(), self.array_size_x.get())
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import time
|
||||
import requests
|
||||
from ophyd import Component, Device, Kind, Signal, SignalRO
|
||||
from threading import Thread
|
||||
from ophyd import Component, Device, Kind, Signal, SignalRO, PVPositioner
|
||||
from ophyd.status import SubscriptionStatus
|
||||
|
||||
try:
|
||||
from bec_lib import bec_logger
|
||||
@@ -36,7 +38,7 @@ class SmarGonSignal(Signal):
|
||||
timestamp = time.time()
|
||||
|
||||
# Perform the actual write to SmargoPolo
|
||||
r = self.parent._go_n_put(f"{self.write_addr}?{self.addr.upper()}={value}", **kwargs)
|
||||
r = self.parent._go_n_put(f"{self.write_addr}?{self.addr.upper()}={value}")
|
||||
|
||||
old_value = self._readback
|
||||
self._timestamp = timestamp
|
||||
@@ -79,23 +81,32 @@ class SmarGonSignalRO(Signal):
|
||||
TODO: Add monitoring
|
||||
"""
|
||||
|
||||
def __init__(self, *args, read_addr="readbackSCS", **kwargs):
|
||||
def __init__(self, *args, read_addr="readbackSCS", auto_monitor=False, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._metadata["write_access"] = False
|
||||
self.read_addr = read_addr
|
||||
self.addr = self.parent.name
|
||||
|
||||
if auto_monitor:
|
||||
self._mon = Thread(target=self.poll, daemon=True)
|
||||
self._mon.start()
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
r = self.parent._go_n_get(self.read_addr)
|
||||
# print(r)
|
||||
|
||||
if isinstance(r, dict):
|
||||
self.put(r[self.addr.upper()], force=True)
|
||||
else:
|
||||
self.put(r, force=True)
|
||||
return super().get(*args, **kwargs)
|
||||
return self._readback
|
||||
|
||||
def poll(self, *args, **kwargs):
|
||||
""" Fooo"""
|
||||
while True:
|
||||
time.sleep(0.2)
|
||||
self.get()
|
||||
|
||||
class SmarGonAxis(Device):
|
||||
class SmarGonAxis(PVPositioner):
|
||||
"""SmarGon client deice
|
||||
|
||||
This class controls the SmarGon goniometer via the REST interface.
|
||||
@@ -107,11 +118,12 @@ class SmarGonAxis(Device):
|
||||
mode = Component(SmarGonSignalRO, read_addr="mode", kind=Kind.config)
|
||||
|
||||
# Axis parameters
|
||||
readback = Component(SmarGonSignalRO, kind=Kind.hinted)
|
||||
readback = Component(SmarGonSignalRO, kind=Kind.hinted, auto_monitor=True)
|
||||
setpoint = Component(SmarGonSignal, kind=Kind.normal)
|
||||
done = Component(SignalRO, value=1, kind=Kind.normal)
|
||||
# moving = Component(SmarGonMovingSignalRO, kind=Kind.config)
|
||||
moving = 1
|
||||
_tol = 0.001
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -122,7 +134,6 @@ class SmarGonAxis(Device):
|
||||
read_attrs=None,
|
||||
configuration_attrs=None,
|
||||
parent=None,
|
||||
device_manager=None,
|
||||
sg_url: str = "http://x06da-smargopolo.psi.ch:3000",
|
||||
low_limit=None,
|
||||
high_limit=None,
|
||||
@@ -132,7 +143,7 @@ class SmarGonAxis(Device):
|
||||
self.__class__.__dict__["setpoint"].kwargs["write_addr"] = f"target{prefix}"
|
||||
self.__class__.__dict__["setpoint"].kwargs["low_limit"] = low_limit
|
||||
self.__class__.__dict__["setpoint"].kwargs["high_limit"] = high_limit
|
||||
|
||||
self.__class__.__dict__["sg_url"].kwargs["value"] = sg_url
|
||||
super().__init__(
|
||||
prefix=prefix,
|
||||
name=name,
|
||||
@@ -140,11 +151,8 @@ class SmarGonAxis(Device):
|
||||
read_attrs=read_attrs,
|
||||
configuration_attrs=configuration_attrs,
|
||||
parent=parent,
|
||||
# device_manager=device_manager,
|
||||
**kwargs,
|
||||
)
|
||||
self.sg_url._metadata["write_access"] = False
|
||||
self.sg_url.set(sg_url, force=True).wait()
|
||||
|
||||
def initialize(self):
|
||||
"""Helper function for initial readings"""
|
||||
@@ -153,6 +161,24 @@ class SmarGonAxis(Device):
|
||||
r = self._go_n_get("corr_type")
|
||||
print(r)
|
||||
|
||||
def move(self, position, wait=True, timeout=None, moved_cb=None):
|
||||
|
||||
status = self.setpoint.set(position, settle_time=0.1)
|
||||
|
||||
if not wait:
|
||||
return status
|
||||
else:
|
||||
status.wait()
|
||||
|
||||
def on_target(*, value, **_):
|
||||
distance = abs(value-position)
|
||||
print(distance)
|
||||
return bool(distance<self._tol)
|
||||
status = SubscriptionStatus(
|
||||
self.readback, on_target, timeout=timeout, settle_time=0.1
|
||||
)
|
||||
return status
|
||||
|
||||
def _go_n_get(self, address, **kwargs):
|
||||
"""Helper function to connect to smargopolo"""
|
||||
cmd = f"{self.sg_url.get()}/{address}"
|
||||
|
||||
@@ -7,27 +7,20 @@ Created on Thu Jun 27 17:28:43 2024
|
||||
@author: mohacsi_i
|
||||
"""
|
||||
import json
|
||||
import enum
|
||||
from time import sleep, time
|
||||
from threading import Thread
|
||||
import zmq
|
||||
import numpy as np
|
||||
from ophyd import Device, Signal, Component, Kind, DeviceStatus
|
||||
from ophyd import Device, Signal, Component, Kind
|
||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
||||
CustomDetectorMixin,
|
||||
PSIDetectorBase,
|
||||
)
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
ZMQ_TOPIC_FILTER = b''
|
||||
|
||||
|
||||
class StdDaqPreviewState(enum.IntEnum):
|
||||
"""Standard DAQ ophyd device states"""
|
||||
UNKNOWN = 0
|
||||
DETACHED = 1
|
||||
MONITORING = 2
|
||||
ZMQ_TOPIC_FILTER = b""
|
||||
|
||||
|
||||
class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
@@ -35,6 +28,7 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
|
||||
Parent class: CustomDetectorMixin
|
||||
"""
|
||||
|
||||
_mon = None
|
||||
|
||||
def on_stage(self):
|
||||
@@ -43,9 +37,7 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
self.parent.unstage()
|
||||
sleep(0.5)
|
||||
|
||||
logger.info(
|
||||
f"[{self.parent.name}] Attaching monitor to {self.parent.url.get()}"
|
||||
)
|
||||
logger.info(f"[{self.parent.name}] Attaching monitor to {self.parent.url.get()}")
|
||||
self.parent.connect()
|
||||
self._stop_polling = False
|
||||
self._mon = Thread(target=self.poll, daemon=True)
|
||||
@@ -66,7 +58,6 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
|
||||
def poll(self):
|
||||
"""Collect streamed updates"""
|
||||
self.parent.status.set(StdDaqPreviewState.MONITORING, force=True)
|
||||
try:
|
||||
t_last = time()
|
||||
while True:
|
||||
@@ -82,7 +73,8 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
# Length and throtling checks
|
||||
if len(r) != 2:
|
||||
logger.warning(
|
||||
f"[{self.parent.name}] Received malformed array of length {len(r)}")
|
||||
f"[{self.parent.name}] Received malformed array of length {len(r)}"
|
||||
)
|
||||
t_curr = time()
|
||||
t_elapsed = t_curr - t_last
|
||||
if t_elapsed < self.parent.throttle.get():
|
||||
@@ -94,26 +86,26 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
|
||||
# Update image and update subscribers
|
||||
header = json.loads(meta)
|
||||
if header["type"] == "uint16":
|
||||
image = np.frombuffer(data, dtype=np.uint16)
|
||||
if header["type"] == "uint8":
|
||||
image = np.frombuffer(data, dtype=np.uint8)
|
||||
if image.size != np.prod(header['shape']):
|
||||
image = np.frombuffer(data, dtype=header["type"])
|
||||
if image.size != np.prod(header["shape"]):
|
||||
err = f"Unexpected array size of {image.size} for header: {header}"
|
||||
raise ValueError(err)
|
||||
image = image.reshape(header['shape'])
|
||||
image = image.reshape(header["shape"])
|
||||
|
||||
# Update image and update subscribers
|
||||
self.parent.frameno.put(header['frame'], force=True)
|
||||
self.parent.image_shape.put(header['shape'], force=True)
|
||||
self.parent.image.put(image, force=True)
|
||||
self.parent.array_counter.put(header["frame"], force=True)
|
||||
self.parent.ndimensions.put(len(header["shape"]), force=True)
|
||||
self.parent.array_size.put(header["shape"], force=True)
|
||||
# self.parent.array_data.put(data, force=True)
|
||||
self.parent.shaped_image.put(image, force=True)
|
||||
|
||||
self.parent._last_image = image
|
||||
self.parent._run_subs(sub_type=self.parent.SUB_MONITOR, value=image)
|
||||
t_last = t_curr
|
||||
logger.info(
|
||||
f"[{self.parent.name}] Updated frame {header['frame']}\t"
|
||||
f"Shape: {header['shape']}\tMean: {np.mean(image):.3f}"
|
||||
)
|
||||
)
|
||||
except ValueError:
|
||||
# Happens when ZMQ partially delivers the multipart message
|
||||
pass
|
||||
@@ -125,37 +117,39 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
raise
|
||||
finally:
|
||||
self._mon = None
|
||||
self.parent.status.set(StdDaqPreviewState.DETACHED, force=True)
|
||||
logger.info(f"[{self.parent.name}]\tDetaching monitor")
|
||||
|
||||
|
||||
class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
"""Detector wrapper class around the StdDaq preview image stream.
|
||||
|
||||
This was meant to provide live image stream directly from the StdDAQ
|
||||
but also works with other ARRAY v1 streamers, like the AreaDetector
|
||||
ZMQ plugin.
|
||||
Note that the preview stream must be already throtled in order to cope
|
||||
with the incoming data and the python class might throttle it further.
|
||||
This was meant to provide live image stream directly from the StdDAQ but
|
||||
also works with other ARRAY v1 streamers, like the AreaDetector ZMQ plugin.
|
||||
Note that the preview stream must be already throtled in order to cope with
|
||||
the incoming data and the python class might throttle it further.
|
||||
|
||||
NOTE: As an explicit request, it does not record the image data.
|
||||
|
||||
You can add a preview widget to the dock by:
|
||||
cam_widget = gui.add_dock('cam_dock1').add_widget('BECFigure').image('daq_stream1')
|
||||
"""
|
||||
|
||||
# Subscriptions for plotting image
|
||||
USER_ACCESS = ["get_image"]
|
||||
USER_ACCESS = ["image", "savemode"]
|
||||
SUB_MONITOR = "device_monitor_2d"
|
||||
_default_sub = SUB_MONITOR
|
||||
|
||||
custom_prepare_cls = StdDaqPreviewMixin
|
||||
|
||||
# Status attributes
|
||||
# Configuration attributes
|
||||
url = Component(Signal, kind=Kind.config, metadata={"write_access": False})
|
||||
throttle = Component(Signal, value=0.25, kind=Kind.config)
|
||||
status = Component(Signal, value=StdDaqPreviewState.UNKNOWN, kind=Kind.omitted, metadata={"write_access": False})
|
||||
frameno = Component(Signal, kind=Kind.hinted, metadata={"write_access": False})
|
||||
image_shape = Component(Signal, kind=Kind.normal, metadata={"write_access": False})
|
||||
# FIXME: The BEC client caches the read()s from the last 50 scans
|
||||
image = Component(Signal, kind=Kind.omitted, metadata={"write_access": False})
|
||||
# Streamed data status
|
||||
array_counter = Component(Signal, kind=Kind.hinted, metadata={"write_access": False})
|
||||
ndimensions = Component(Signal, kind=Kind.normal, metadata={"write_access": False})
|
||||
array_size = Component(Signal, kind=Kind.normal, metadata={"write_access": False})
|
||||
# array_data = Component(Signal, kind=Kind.omitted, metadata={"write_access": False})
|
||||
shaped_image = Component(Signal, kind=Kind.omitted, metadata={"write_access": False})
|
||||
_last_image = None
|
||||
|
||||
def __init__(
|
||||
@@ -183,8 +177,16 @@ class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
sleep(1)
|
||||
self._socket.connect(self.url.get())
|
||||
|
||||
def get_image(self):
|
||||
"""
|
||||
def savemode(self, save=False):
|
||||
""" Toggle save mode for the shaped image"""
|
||||
#pylint: disable=protected-access
|
||||
if save:
|
||||
self.shaped_image._kind = Kind.normal
|
||||
else:
|
||||
self.shaped_image._kind = Kind.omitted
|
||||
|
||||
def image(self):
|
||||
"""
|
||||
Gets the last image as an attribute in case image must be abandoned
|
||||
due to some caching on the BEC.
|
||||
"""
|
||||
|
||||
@@ -8,4 +8,4 @@ from .A3200 import AerotechAbrStage
|
||||
from .A3200utils import A3200Axis
|
||||
from .SmarGon import SmarGonAxis
|
||||
from .StdDaqPreview import StdDaqPreviewDetector
|
||||
from .NDArrayPreview import NDArrayPreview
|
||||
from .NDArrayPreview import NDArrayPreview
|
||||
|
||||
Reference in New Issue
Block a user