Bump
This commit is contained in:
@@ -11,6 +11,19 @@ eyex:
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
|
||||
eyey:
|
||||
readoutPriority: baseline
|
||||
description: X-ray eye axis Y
|
||||
deviceClass: tomcat_bec.devices.psimotor.EpicsMotorEC
|
||||
deviceConfig:
|
||||
prefix: MTEST-X05LA-ES2-XRAYEYE:M2
|
||||
deviceTags:
|
||||
- xray-eye
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
|
||||
eyez:
|
||||
readoutPriority: baseline
|
||||
description: X-ray eye axis Z
|
||||
@@ -38,18 +51,18 @@ femto_mean_curr:
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
|
||||
es1_roty:
|
||||
readoutPriority: monitored
|
||||
description: 'Test rotation stage'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: X02DA-ES1-SMP1:ROTY
|
||||
deviceTags:
|
||||
- es1-sam
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
# es1_roty:
|
||||
# readoutPriority: monitored
|
||||
# description: 'Test rotation stage'
|
||||
# deviceClass: ophyd.EpicsMotor
|
||||
# deviceConfig:
|
||||
# prefix: X02DA-ES1-SMP1:ROTY
|
||||
# deviceTags:
|
||||
# - es1-sam
|
||||
# onFailure: buffer
|
||||
# enabled: true
|
||||
# readOnly: false
|
||||
# softwareTrigger: false
|
||||
|
||||
# es1_ismc:
|
||||
# description: 'Automation1 iSMC interface'
|
||||
@@ -64,18 +77,18 @@ es1_roty:
|
||||
# readoutPriority: monitored
|
||||
# softwareTrigger: false
|
||||
|
||||
es1_tasks:
|
||||
description: 'Automation1 task management interface'
|
||||
deviceClass: tomcat_bec.devices.aa1Tasks
|
||||
deviceConfig:
|
||||
prefix: 'X02DA-ES1-SMP1:TASK:'
|
||||
deviceTags:
|
||||
- es1
|
||||
enabled: false
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
# es1_tasks:
|
||||
# description: 'Automation1 task management interface'
|
||||
# deviceClass: tomcat_bec.devices.aa1Tasks
|
||||
# deviceConfig:
|
||||
# prefix: 'X02DA-ES1-SMP1:TASK:'
|
||||
# deviceTags:
|
||||
# - es1
|
||||
# enabled: false
|
||||
# onFailure: buffer
|
||||
# readOnly: false
|
||||
# readoutPriority: monitored
|
||||
# softwareTrigger: false
|
||||
|
||||
|
||||
# es1_psod:
|
||||
@@ -92,18 +105,18 @@ es1_tasks:
|
||||
# softwareTrigger: true
|
||||
|
||||
|
||||
es1_ddaq:
|
||||
description: 'Automation1 position recording interface'
|
||||
deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection
|
||||
deviceConfig:
|
||||
prefix: 'X02DA-ES1-SMP1:ROTY:DDC:'
|
||||
deviceTags:
|
||||
- es1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
# es1_ddaq:
|
||||
# description: 'Automation1 position recording interface'
|
||||
# deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection
|
||||
# deviceConfig:
|
||||
# prefix: 'X02DA-ES1-SMP1:ROTY:DDC:'
|
||||
# deviceTags:
|
||||
# - es1
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readOnly: false
|
||||
# readoutPriority: monitored
|
||||
# softwareTrigger: false
|
||||
|
||||
|
||||
#camera:
|
||||
@@ -145,6 +158,7 @@ gfdaq:
|
||||
data_source_name: 'gfcam'
|
||||
deviceTags:
|
||||
- std-daq
|
||||
- daq
|
||||
- gfcam
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
@@ -159,6 +173,7 @@ gf_stream0:
|
||||
url: 'tcp://129.129.95.111:20000'
|
||||
deviceTags:
|
||||
- std-daq
|
||||
- preview
|
||||
- gfcam
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
@@ -187,8 +202,10 @@ pcodaq:
|
||||
deviceConfig:
|
||||
ws_url: 'ws://129.129.95.111:8081'
|
||||
rest_url: 'http://129.129.95.111:5010'
|
||||
data_source_name: 'pcocam'
|
||||
deviceTags:
|
||||
- std-daq
|
||||
- daq
|
||||
- pcocam
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
@@ -203,6 +220,7 @@ pco_stream0:
|
||||
url: 'tcp://129.129.95.111:20010'
|
||||
deviceTags:
|
||||
- std-daq
|
||||
- preview
|
||||
- pcocam
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
|
||||
@@ -41,9 +41,9 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
# Fish out our configuration from scaninfo (via explicit or generic addressing)
|
||||
# NOTE: Scans don't have to fully configure the device
|
||||
d = {}
|
||||
scan_parameters = self.parent.scaninfo.scan_msg.scan_parameters
|
||||
std_daq_params = scan_parameters.get("std_daq_params")
|
||||
|
||||
# scan_parameters = self.parent.scaninfo.scan_msg.scan_parameters
|
||||
# std_daq_params = scan_parameters.get("std_daq_params")
|
||||
|
||||
if "kwargs" in self.parent.scaninfo.scan_msg.info:
|
||||
scanargs = self.parent.scaninfo.scan_msg.info["kwargs"]
|
||||
if "image_width" in scanargs and scanargs["image_width"] is not None:
|
||||
@@ -53,8 +53,8 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
if "nr_writers" in scanargs and scanargs["nr_writers"] is not None:
|
||||
d["nr_writers"] = scanargs["nr_writers"]
|
||||
if "system_config" in scanargs and scanargs["system_config"] is not None:
|
||||
if scanargs['system_config']['file_directory']:
|
||||
file_directory = scanargs['system_config']['file_directory']
|
||||
if scanargs["system_config"]["file_directory"]:
|
||||
file_directory = scanargs["system_config"]["file_directory"]
|
||||
### to be used in the future to substitute the procedure using file path
|
||||
if "file_path" in scanargs and scanargs["file_path"] is not None:
|
||||
self.parent.file_path.set(scanargs["file_path"].replace("data", "gpfs")).wait()
|
||||
@@ -91,7 +91,6 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
if points_valid:
|
||||
d["num_points_total"] = num_points
|
||||
|
||||
|
||||
# Perform bluesky-style configuration
|
||||
if len(d) > 0:
|
||||
# Configure new run (will restart the stdDAQ)
|
||||
@@ -101,9 +100,9 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
sleep(0.5)
|
||||
|
||||
# Try to start a new run (reconnects)
|
||||
if std_daq_params.get("reconnect",True):
|
||||
self.parent.bluestage()
|
||||
|
||||
# if std_daq_params.get("reconnect", True):
|
||||
self.parent.bluestage()
|
||||
|
||||
# And start status monitoring
|
||||
self._mon = Thread(target=self.monitor, daemon=True)
|
||||
self._mon.start()
|
||||
@@ -179,6 +178,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
file_prefix = Component(Signal, value="file", kind=Kind.config)
|
||||
# Configuration attributes
|
||||
rest_url = Component(Signal, kind=Kind.config, metadata={"write_access": False})
|
||||
datasource = Component(Signal, kind=Kind.config, metadata={"write_access": False})
|
||||
cfg_detector_name = Component(Signal, kind=Kind.config)
|
||||
cfg_detector_type = Component(Signal, kind=Kind.config)
|
||||
cfg_bit_depth = Component(Signal, kind=Kind.config)
|
||||
@@ -213,7 +213,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
)
|
||||
self.ws_url.set(ws_url, force=True).wait()
|
||||
self.rest_url.set(rest_url, force=True).wait()
|
||||
self.data_source_name = data_source_name
|
||||
self.datasource.set(data_source_name, force=True).wait()
|
||||
|
||||
# Connect to the DAQ and initialize values
|
||||
try:
|
||||
@@ -335,15 +335,6 @@ class StdDaqClient(PSIDeviceBase):
|
||||
sleep(1)
|
||||
self.get_daq_config(update=True)
|
||||
|
||||
# def configure(self, d:dict):
|
||||
# if "num_points_total" in d:
|
||||
# num_points = d.pop("num_points_total")
|
||||
# self.num_images.set(num_points).wait()
|
||||
# super().configure(d)
|
||||
# self.set_daq_config()
|
||||
# sleep(1)
|
||||
# self.get_daq_config(update=True)
|
||||
|
||||
def bluestage(self):
|
||||
"""Stages the stdDAQ
|
||||
|
||||
@@ -355,27 +346,12 @@ class StdDaqClient(PSIDeviceBase):
|
||||
if self.state() != "idle":
|
||||
raise RuntimeError(f"[{self.name}] stdDAQ can't stage from state: {self.state()}")
|
||||
|
||||
# Must make sure that image size matches the data source
|
||||
if self.data_source_name is not None:
|
||||
cam_img_w = self.device_manager.devices[self.data_source_name].cfgRoiX.get()
|
||||
cam_img_h = self.device_manager.devices[self.data_source_name].cfgRoiY.get()
|
||||
daq_img_w = self.cfg_pixel_width.get()
|
||||
daq_img_h = self.cfg_pixel_height.get()
|
||||
|
||||
if not (daq_img_w == cam_img_w and daq_img_h == cam_img_h):
|
||||
raise RuntimeError(
|
||||
f"[{self.name}] stdDAQ image resolution ({daq_img_w} , {daq_img_h}) does not match camera with ({cam_img_w} , {cam_img_h})"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"[{self.name}] stdDAQ image resolution ({daq_img_w} , {daq_img_h}) matches camera with ({cam_img_w} , {cam_img_h})"
|
||||
)
|
||||
# Ensure expected shape
|
||||
self.validate()
|
||||
|
||||
file_path = self.file_path.get()
|
||||
num_images = self.num_images.get()
|
||||
file_prefix = self.name
|
||||
file_prefix = self.file_prefix.get()
|
||||
print(file_prefix)
|
||||
num_images = self.num_images.get()
|
||||
|
||||
# New connection
|
||||
self._wsclient = self.connect()
|
||||
@@ -407,9 +383,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
print(f"[{self.name}] Started stdDAQ in: {reply['status']}")
|
||||
return
|
||||
|
||||
raise RuntimeError(
|
||||
f"[{self.name}] Failed to start the stdDAQ in 1 tries, reason: {reply['reason']}"
|
||||
)
|
||||
raise RuntimeError(f"[{self.name}] Failed to start the stdDAQ, reason: {reply['reason']}")
|
||||
|
||||
def blueunstage(self):
|
||||
"""Unstages the stdDAQ
|
||||
@@ -464,6 +438,25 @@ class StdDaqClient(PSIDeviceBase):
|
||||
status = SubscriptionStatus(self.runstatus, is_running, settle_time=0.5)
|
||||
return status
|
||||
|
||||
def validate(self):
|
||||
"""Validate camera state
|
||||
|
||||
Ensure that data source shape matches with the shape expected by the stdDAQ.
|
||||
"""
|
||||
# Must make sure that image size matches the data source
|
||||
source = self.datasource.get()
|
||||
if source is not None and len(source) > 0:
|
||||
if source == "gfcam":
|
||||
cam_img_w = self.device_manager.devices[source].cfgRoiX.get()
|
||||
cam_img_h = self.device_manager.devices[source].cfgRoiY.get()
|
||||
daq_img_w = self.cfg_pixel_width.get()
|
||||
daq_img_h = self.cfg_pixel_height.get()
|
||||
|
||||
if not (daq_img_w == cam_img_w and daq_img_h == cam_img_h):
|
||||
raise RuntimeError(
|
||||
f"[{self.name}] stdDAQ image resolution ({daq_img_w} , {daq_img_h}) does not match camera with ({cam_img_w} , {cam_img_h})"
|
||||
)
|
||||
|
||||
def get_daq_config(self, update=False) -> dict:
|
||||
"""Read the current configuration from the DAQ"""
|
||||
r = requests.get(self.rest_url.get() + "/api/config/get", params={"user": "ioc"}, timeout=2)
|
||||
|
||||
@@ -23,107 +23,23 @@ logger = bec_logger.logger
|
||||
ZMQ_TOPIC_FILTER = b''
|
||||
|
||||
|
||||
class StdDaqPreviewState(enum.IntEnum):
|
||||
"""Standard DAQ ophyd device states"""
|
||||
UNKNOWN = 0
|
||||
DETACHED = 1
|
||||
MONITORING = 2
|
||||
|
||||
|
||||
class StdDaqPreviewMixin(CustomDetectorMixin):
|
||||
"""Setup class for the standard DAQ preview stream
|
||||
|
||||
Parent class: CustomDetectorMixin
|
||||
"""
|
||||
_mon = None
|
||||
|
||||
def on_stage(self):
|
||||
"""Start listening for preview data stream"""
|
||||
if self._mon is not None:
|
||||
self.parent.unstage()
|
||||
sleep(0.5)
|
||||
|
||||
self.parent.connect()
|
||||
self._stop_polling = False
|
||||
self._mon = Thread(target=self.poll, daemon=True)
|
||||
self._mon.start()
|
||||
self.parent.arm()
|
||||
|
||||
def on_unstage(self):
|
||||
"""Stop a running preview"""
|
||||
if self._mon is not None:
|
||||
self._stop_polling = True
|
||||
# Might hang on recv_multipart
|
||||
self._mon.join(timeout=1)
|
||||
# So also disconnect the socket
|
||||
self.parent._socket.disconnect(self.parent.url.get())
|
||||
self.parent.disarm()
|
||||
|
||||
def on_stop(self):
|
||||
"""Stop a running preview"""
|
||||
self.on_unstage()
|
||||
|
||||
def poll(self):
|
||||
"""Collect streamed updates"""
|
||||
self.parent.status.set(StdDaqPreviewState.MONITORING, force=True)
|
||||
try:
|
||||
t_last = time()
|
||||
while True:
|
||||
try:
|
||||
# Exit loop and finish monitoring
|
||||
if self._stop_polling:
|
||||
logger.info(f"[{self.parent.name}]\tDetaching monitor")
|
||||
break
|
||||
|
||||
# pylint: disable=no-member
|
||||
r = self.parent._socket.recv_multipart(flags=zmq.NOBLOCK)
|
||||
|
||||
# Length and throtling checks
|
||||
if len(r) != 2:
|
||||
logger.warning(
|
||||
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():
|
||||
sleep(0.1)
|
||||
continue
|
||||
|
||||
# Unpack the Array V1 reply to metadata and array data
|
||||
meta, data = r
|
||||
|
||||
# Update image and update subscribers
|
||||
header = json.loads(meta)
|
||||
if header["type"] == "uint16":
|
||||
image = np.frombuffer(data, dtype=np.uint16)
|
||||
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'])
|
||||
|
||||
# Update image and update subscribers
|
||||
self.parent.frame.put(header['frame'], force=True)
|
||||
self.parent.image_shape.put(header['shape'], force=True)
|
||||
self.parent.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
|
||||
except zmq.error.Again:
|
||||
# Happens when receive queue is empty
|
||||
sleep(0.1)
|
||||
except Exception as ex:
|
||||
logger.info(f"[{self.parent.name}]\t{str(ex)}")
|
||||
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.
|
||||
|
||||
@@ -135,7 +51,7 @@ class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
cam_widget = gui.add_dock('cam_dock1').add_widget('BECFigure').image('daq_stream1')
|
||||
"""
|
||||
# Subscriptions for plotting image
|
||||
USER_ACCESS = ["kickoff", "get_last_image"]
|
||||
USER_ACCESS = ["arm", "disarm", "get_last_image"]
|
||||
SUB_MONITOR = "device_monitor_2d"
|
||||
_default_sub = SUB_MONITOR
|
||||
|
||||
@@ -144,19 +60,19 @@ class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
# Status attributes
|
||||
url = Component(Signal, kind=Kind.config)
|
||||
throttle = Component(Signal, value=0.25, kind=Kind.config)
|
||||
status = Component(Signal, value=StdDaqPreviewState.UNKNOWN, kind=Kind.omitted)
|
||||
frame = Component(Signal, kind=Kind.hinted)
|
||||
image_shape = Component(Signal, kind=Kind.normal)
|
||||
# FIXME: The BEC client caches the read()s from the last 50 scans
|
||||
image = Component(Signal, kind=Kind.omitted)
|
||||
_last_image = None
|
||||
_stop_polling = True
|
||||
_mon = None
|
||||
|
||||
def __init__(
|
||||
self, *args, url: str = "tcp://129.129.95.38:20000", parent: Device = None, **kwargs
|
||||
) -> None:
|
||||
super().__init__(*args, parent=parent, **kwargs)
|
||||
self.url._metadata["write_access"] = False
|
||||
self.status._metadata["write_access"] = False
|
||||
self.image._metadata["write_access"] = False
|
||||
self.frame._metadata["write_access"] = False
|
||||
self.image_shape._metadata["write_access"] = False
|
||||
@@ -185,9 +101,92 @@ class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
def get_image(self):
|
||||
return self._last_image
|
||||
|
||||
def kickoff(self) -> DeviceStatus:
|
||||
""" The DAQ was not meant to be toggled"""
|
||||
return DeviceStatus(self, done=True, success=True, settle_time=0.1)
|
||||
def arm(self):
|
||||
"""Start listening for preview data stream"""
|
||||
if self._mon is not None:
|
||||
self.unstage()
|
||||
sleep(0.5)
|
||||
|
||||
self.connect()
|
||||
self._stop_polling = False
|
||||
self._mon = Thread(target=self.poll, daemon=True)
|
||||
self._mon.start()
|
||||
|
||||
|
||||
|
||||
def disarm(self):
|
||||
"""Stop a running preview"""
|
||||
if self._mon is not None:
|
||||
self._stop_polling = True
|
||||
# Might hang on recv_multipart
|
||||
self._mon.join(timeout=1)
|
||||
# So also disconnect the socket (if not already disconnected)
|
||||
try:
|
||||
self._socket.disconnect(self.url.get())
|
||||
except zmq.error.ZMQError:
|
||||
pass
|
||||
|
||||
|
||||
def poll(self):
|
||||
"""Collect streamed updates"""
|
||||
try:
|
||||
t_last = time()
|
||||
while True:
|
||||
try:
|
||||
# Exit loop and finish monitoring
|
||||
if self._stop_polling:
|
||||
break
|
||||
|
||||
# pylint: disable=no-member
|
||||
r = self._socket.recv_multipart(flags=zmq.NOBLOCK)
|
||||
|
||||
# Length and throtling checks
|
||||
if len(r) != 2:
|
||||
logger.warning(
|
||||
f"[{self.name}] Received malformed array of length {len(r)}")
|
||||
t_curr = time()
|
||||
t_elapsed = t_curr - t_last
|
||||
if t_elapsed < self.throttle.get():
|
||||
sleep(0.1)
|
||||
continue
|
||||
|
||||
# Unpack the Array V1 reply to metadata and array data
|
||||
meta, data = r
|
||||
|
||||
# Update image and update subscribers
|
||||
header = json.loads(meta)
|
||||
image = None
|
||||
if header["type"] == "uint16":
|
||||
image = np.frombuffer(data, dtype=np.uint16)
|
||||
|
||||
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'])
|
||||
|
||||
# Update image and update subscribers
|
||||
self.frame.put(header['frame'], force=True)
|
||||
self.image_shape.put(header['shape'], force=True)
|
||||
self.image.put(image, force=True)
|
||||
self._last_image = image
|
||||
self._run_subs(sub_type=self.SUB_MONITOR, value=image)
|
||||
t_last = t_curr
|
||||
logger.info(
|
||||
f"[{self.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
|
||||
except zmq.error.Again:
|
||||
# Happens when receive queue is empty
|
||||
sleep(0.1)
|
||||
except Exception as ex:
|
||||
logger.info(f"[{self.name}]\t{str(ex)}")
|
||||
raise
|
||||
finally:
|
||||
self._mon = None
|
||||
logger.info(f"[{self.name}]\tDetaching monitor")
|
||||
|
||||
|
||||
# Automatically connect to MicroSAXS testbench if directly invoked
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from .tutorial_fly_scan import AcquireDark, AcquireWhite, AcquireRefs, AcquireProjections, TutorialFlyScanContLine
|
||||
from .tomcat_scans import TomcatSnapNStep, TomcatSimpleSequence
|
||||
from .advanced_scans import AcquireRefsV2, AcquireDarkV2, AcquireWhiteV2
|
||||
|
||||
383
tomcat_bec/scans/advanced_scans.py
Normal file
383
tomcat_bec/scans/advanced_scans.py
Normal file
@@ -0,0 +1,383 @@
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.device import DeviceBase
|
||||
from bec_server.scan_server.scans import Acquire, AsyncFlyScanBase
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class Shutter:
|
||||
""" Shutter status """
|
||||
CLOSED = 0
|
||||
OPEN = 1
|
||||
|
||||
|
||||
class AcquireDarkV2(Acquire):
|
||||
scan_name = "acquire_dark_v2"
|
||||
required_kwargs = ["exp_burst"]
|
||||
gui_config = {"Acquisition parameters": ["exp_burst"]}
|
||||
|
||||
def __init__(self, exp_burst: int, file_prefix="", **kwargs):
|
||||
"""
|
||||
Acquire dark images. This scan is used to acquire dark images. Dark images are images taken with the shutter
|
||||
closed and no beam on the sample. Dark images are used to correct the data images for dark current.
|
||||
|
||||
NOTE: this scan has a special operation mode that does not call
|
||||
|
||||
Args:
|
||||
exp_burst : int
|
||||
Number of dark images to acquire (no default)
|
||||
file_prefix : str
|
||||
File prefix
|
||||
|
||||
Examples:
|
||||
>>> scans.acquire_dark(5)
|
||||
|
||||
"""
|
||||
super().__init__(exp_burst=exp_burst, file_prefix="", **kwargs)
|
||||
self.burst_at_each_point = 1 # At each point, how many times I want to individually trigger
|
||||
self.exp_burst = exp_burst
|
||||
self.file_prefix = file_prefix
|
||||
|
||||
def pre_scan(self):
|
||||
""" Close the shutter before scan"""
|
||||
yield from self.stubs.set(device=["eyex"], value=[Shutter.CLOSED])
|
||||
return super().pre_scan()
|
||||
|
||||
def direct(self):
|
||||
""" Direct scan procedure call"""
|
||||
# Collect relevant devices
|
||||
self.cams = [cam.name for cam in self.device_manager.devices.get_devices_with_tags("camera") if cam.enabled]
|
||||
self.prev = [pre.name for pre in self.device_manager.devices.get_devices_with_tags("preview") if pre.enabled]
|
||||
self.daqs = [daq.name for daq in self.device_manager.devices.get_devices_with_tags("daq") if daq.enabled]
|
||||
|
||||
# Do not call stage, as there's no ScanInfo emitted for direct call
|
||||
for daq in self.daqs:
|
||||
cam = yield from self.stubs.send_rpc_and_wait(daq, "datasource.get")
|
||||
prefix = f"{self.file_prefix}_{cam}_dark"
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "file_prefix.set", prefix)
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "num_images.set", self.exp_burst)
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "bluestage")
|
||||
for prev in self.prev:
|
||||
yield from self.stubs.send_rpc_and_wait(prev, "arm")
|
||||
for cam in self.cams:
|
||||
yield from self.stubs.send_rpc_and_wait(cam, "configure", {'exposure_num_burst': self.exp_burst})
|
||||
yield from self.stubs.send_rpc_and_wait(cam, "bluestage")
|
||||
|
||||
yield from self.pre_scan()
|
||||
yield from self.scan_core()
|
||||
yield from self.finalize()
|
||||
yield from self.unstage()
|
||||
yield from self.cleanup()
|
||||
|
||||
|
||||
class AcquireWhiteV2(Acquire):
|
||||
scan_name = "acquire_white_v2"
|
||||
gui_config = {"Acquisition parameters": ["exp_burst"]}
|
||||
|
||||
def __init__(self, motor: DeviceBase, exp_burst: int, sample_position_out: float, sample_angle_out: float, file_prefix: str="", **kwargs):
|
||||
"""
|
||||
Acquire flat field images. This scan is used to acquire flat field images. The flat field image is an image taken
|
||||
with the shutter open but the sample out of the beam. Flat field images are used to correct the data images for
|
||||
non-uniformity in the detector.
|
||||
|
||||
Args:
|
||||
motor : DeviceBase
|
||||
Motor to be moved to move the sample out of beam
|
||||
exp_burst : int
|
||||
Number of flat field images to acquire (no default)
|
||||
sample_position_out : float
|
||||
Position to move the sample stage to position the sample out of beam and take flat field images
|
||||
sample_angle_out : float
|
||||
Angular position where to take the flat field images
|
||||
|
||||
Examples:
|
||||
>>> scans.acquire_white(dev.samx, 5, 20)
|
||||
|
||||
"""
|
||||
super().__init__(exp_burst=exp_burst, **kwargs)
|
||||
self.exp_burst = exp_burst
|
||||
self.file_prefix = file_prefix
|
||||
self.burst_at_each_point = 1
|
||||
|
||||
self.scan_motors = [motor, "eyez"]
|
||||
# self.scan_motors = [motor, "es1_roty"]
|
||||
self.out_position = [sample_position_out, sample_angle_out]
|
||||
|
||||
def pre_scan(self):
|
||||
""" Open the shutter before scan"""
|
||||
# Move sample out
|
||||
yield from self._move_scan_motors_and_wait(self.out_position)
|
||||
# Open the main shutter (TODO change to the correct shutter device)
|
||||
yield from self.stubs.set(device=["eyex"], value=[Shutter.OPEN])
|
||||
|
||||
return super().pre_scan()
|
||||
|
||||
def cleanup(self):
|
||||
""" Close the shutter after scan"""
|
||||
# Close fast shutter
|
||||
yield from self.stubs.set(device=["eyex"], value=[Shutter.CLOSED])
|
||||
return super().cleanup()
|
||||
|
||||
def direct(self):
|
||||
""" Direct scan procedure call"""
|
||||
# Collect relevant devices
|
||||
self.cams = [cam.name for cam in self.device_manager.devices.get_devices_with_tags("camera") if cam.enabled]
|
||||
self.prev = [pre.name for pre in self.device_manager.devices.get_devices_with_tags("preview") if pre.enabled]
|
||||
self.daqs = [daq.name for daq in self.device_manager.devices.get_devices_with_tags("daq") if daq.enabled]
|
||||
|
||||
# Do not call stage, as there's no ScanInfo emitted for direct call
|
||||
for daq in self.daqs:
|
||||
cam = yield from self.stubs.send_rpc_and_wait(daq, "datasource.get")
|
||||
prefix = f"{self.file_prefix}_{cam}_white"
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "file_prefix.set", prefix)
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "num_images.set", self.exp_burst)
|
||||
yield from self.stubs.send_rpc_and_wait(daq, "bluestage")
|
||||
for prev in self.prev:
|
||||
yield from self.stubs.send_rpc_and_wait(prev, "arm")
|
||||
for cam in self.cams:
|
||||
yield from self.stubs.send_rpc_and_wait(cam, "configure", {'exposure_num_burst': self.exp_burst})
|
||||
yield from self.stubs.send_rpc_and_wait(cam, "bluestage")
|
||||
|
||||
yield from self.pre_scan()
|
||||
yield from self.scan_core()
|
||||
yield from self.finalize()
|
||||
yield from self.unstage()
|
||||
yield from self.cleanup()
|
||||
|
||||
|
||||
# class AcquireProjections(AsyncFlyScanBase):
|
||||
# scan_name = "acquire_projections"
|
||||
# gui_config = {
|
||||
# "Motor": ["motor"],
|
||||
# "Acquisition parameters": ["sample_position_in", "start_angle", "angular_range" ],
|
||||
# "Camera": ["exp_time", "exp_burst"]
|
||||
# }
|
||||
|
||||
# def __init__(self,
|
||||
# motor: DeviceBase,
|
||||
# exp_burst: int,
|
||||
# sample_position_in: float,
|
||||
# start_angle: float,
|
||||
# angular_range: float,
|
||||
# exp_time:float,
|
||||
# **kwargs):
|
||||
# """
|
||||
# Acquire projection images.
|
||||
|
||||
# Args:
|
||||
# motor : DeviceBase
|
||||
# Motor to move continuously from start to stop position
|
||||
# exp_burst : int
|
||||
# Number of flat field images to acquire (no default)
|
||||
# sample_position_in : float
|
||||
# Position to move the sample stage to position the sample in the beam
|
||||
# start_angle : float
|
||||
# Angular start position for the scan
|
||||
# angular_range : float
|
||||
# Angular range
|
||||
# exp_time : float, optional
|
||||
# Exposure time [ms]. If not specified, the currently configured value on the camera will be used
|
||||
# exp_period : float, optional
|
||||
# Exposure period [ms]. If not specified, the currently configured value on the camera will be used
|
||||
# image_width : int, optional
|
||||
# ROI size in the x-direction [pixels]. If not specified, the currently configured value on the camera will be used
|
||||
# image_height : int, optional
|
||||
# ROI size in the y-direction [pixels]. If not specified, the currently configured value on the camera will be used
|
||||
# acq_mode : str, optional
|
||||
# Predefined acquisition mode (default= 'default')
|
||||
# file_path : str, optional
|
||||
# File path for standard daq
|
||||
# ddc_trigger : int, optional
|
||||
# Drive Data Capture Trigger
|
||||
# ddc_source0 : int, optional
|
||||
# Drive Data capture Input0
|
||||
|
||||
# Returns:
|
||||
# ScanReport
|
||||
|
||||
# Examples:
|
||||
# >>> scans.acquire_projections()
|
||||
|
||||
# """
|
||||
# self.motor = motor
|
||||
# super().__init__(exp_time=exp_time,**kwargs)
|
||||
|
||||
# self.burst_at_each_point = 1
|
||||
# self.sample_position_in = sample_position_in
|
||||
# self.start_angle = start_angle
|
||||
# self.angular_range = angular_range
|
||||
|
||||
# self.dark_shutter_pos_out = 1 ### change with a variable
|
||||
# self.dark_shutter_pos_in = 0 ### change with a variable
|
||||
|
||||
# def update_scan_motors(self):
|
||||
# return [self.motor]
|
||||
|
||||
# def prepare_positions(self):
|
||||
# self.positions = np.array([[self.start_angle], [self.start_angle+self.angular_range]])
|
||||
# self.num_pos = None
|
||||
# yield from self._set_position_offset()
|
||||
|
||||
# def scan_core(self):
|
||||
|
||||
# # move to in position and go to start angular position
|
||||
# yield from self.stubs.set(device=["eyez", self.motor], value=[self.sample_position_in, self.positions[0][0]])
|
||||
|
||||
# # open the shutter
|
||||
# yield from self.stubs.set(device="eyex", value=self.dark_shutter_pos_out)
|
||||
# # TODO add opening of fast shutter
|
||||
|
||||
# # start the flyer
|
||||
# flyer_request = yield from self.stubs.set(
|
||||
# device=self.motor, value=self.positions[1][0], wait=False
|
||||
# )
|
||||
|
||||
# self.connector.send_client_info(
|
||||
# "Starting the scan", show_asap=True, rid=self.metadata.get("RID")
|
||||
# )
|
||||
|
||||
# yield from self.stubs.trigger()
|
||||
|
||||
# while not flyer_request.done:
|
||||
|
||||
# yield from self.stubs.read(
|
||||
# group="monitored", point_id=self.point_id
|
||||
# )
|
||||
# time.sleep(1)
|
||||
|
||||
# # increase the point id
|
||||
# self.point_id += 1
|
||||
|
||||
# self.num_pos = self.point_id
|
||||
|
||||
|
||||
class AcquireRefsV2(Acquire):
|
||||
scan_name = "acquire_refs_v2"
|
||||
gui_config = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
motor: DeviceBase,
|
||||
num_darks: int = 0,
|
||||
num_flats: int = 0,
|
||||
sample_angle_out: float = 0,
|
||||
sample_position_in: float = 0,
|
||||
sample_position_out: float = 1,
|
||||
file_prefix_dark: str = 'tmp_dark',
|
||||
file_prefix_white: str = 'tmp_white',
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Acquire reference images (darks + whites) and return to beam position.
|
||||
|
||||
Reference images are acquired automatically in an optimized sequence and
|
||||
the sample is returned to the sample_in_position afterwards.
|
||||
|
||||
Args:
|
||||
motor : DeviceBase
|
||||
Motor to be moved to move the sample out of beam
|
||||
num_darks : int , optional
|
||||
Number of dark field images to acquire
|
||||
num_flats : int , optional
|
||||
Number of white field images to acquire
|
||||
sample_angle_out : float , optional
|
||||
Angular position where to take the flat field images
|
||||
sample_position_in : float , optional
|
||||
Sample stage X position for sample in beam [um]
|
||||
sample_position_out : float ,optional
|
||||
Sample stage X position for sample out of the beam [um]
|
||||
exp_time : float, optional
|
||||
Exposure time [ms]. If not specified, the currently configured value
|
||||
on the camera will be used
|
||||
exp_period : float, optional
|
||||
Exposure period [ms]. If not specified, the currently configured value
|
||||
on the camera will be used
|
||||
image_width : int, optional
|
||||
ROI size in the x-direction [pixels]. If not specified, the currently
|
||||
configured value on the camera will be used
|
||||
image_height : int, optional
|
||||
ROI size in the y-direction [pixels]. If not specified, the currently
|
||||
configured value on the camera will be used
|
||||
acq_mode : str, optional
|
||||
Predefined acquisition mode (default= 'default')
|
||||
file_path : str, optional
|
||||
File path for standard daq
|
||||
|
||||
Returns:
|
||||
ScanReport
|
||||
|
||||
Examples:
|
||||
>>> scans.acquire_refs(sample_angle_out=90, sample_position_in=10, num_darks=5, num_flats=5, exp_time=0.1)
|
||||
|
||||
"""
|
||||
self.motor = motor
|
||||
super().__init__(**kwargs)
|
||||
self.sample_position_in = sample_position_in
|
||||
self.sample_position_out = sample_position_out
|
||||
self.sample_angle_out = sample_angle_out
|
||||
self.num_darks = num_darks
|
||||
self.num_flats = num_flats
|
||||
self.file_prefix_dark = file_prefix_dark
|
||||
self.file_prefix_white = file_prefix_white
|
||||
self.scan_parameters["std_daq_params"] = {"reconnect": False}
|
||||
|
||||
def stage(self):
|
||||
"""Wrapped scan doesn't need staging"""
|
||||
yield None
|
||||
|
||||
def scan_core(self):
|
||||
|
||||
if self.num_darks:
|
||||
msg = f"Acquiring {self.num_darks} dark images"
|
||||
logger.warning(msg)
|
||||
self.connector.send_client_info(
|
||||
msg,
|
||||
show_asap=True,
|
||||
rid=self.metadata.get("RID"),
|
||||
)
|
||||
|
||||
darks = AcquireDarkV2(
|
||||
exp_burst=self.num_darks,
|
||||
# file_prefix=self.file_prefix_dark,
|
||||
device_manager=self.device_manager,
|
||||
metadata=self.metadata,
|
||||
instruction_handler=self.stubs._instruction_handler,
|
||||
**self.caller_kwargs,
|
||||
)
|
||||
|
||||
yield from darks.direct()
|
||||
self.point_id = darks.point_id
|
||||
|
||||
|
||||
if self.num_flats:
|
||||
msg = f"Acquiring {self.num_flats} flat field images"
|
||||
logger.warning(msg)
|
||||
self.connector.send_client_info(
|
||||
msg,
|
||||
show_asap=True,
|
||||
rid=self.metadata.get("RID"),
|
||||
)
|
||||
logger.warning("Calling AcquireWhite")
|
||||
|
||||
flats = AcquireWhiteV2(
|
||||
motor=self.motor,
|
||||
exp_burst=self.num_flats,
|
||||
sample_position_out=self.sample_position_out,
|
||||
# sample_angle_out=self.sample_angle_out,
|
||||
device_manager=self.device_manager,
|
||||
metadata=self.metadata,
|
||||
instruction_handler=self.stubs._instruction_handler,
|
||||
**self.caller_kwargs,
|
||||
)
|
||||
|
||||
flats.point_id = self.point_id
|
||||
yield from flats.direct()
|
||||
self.point_id = flats.point_id
|
||||
## TODO move sample in beam and do not wait
|
||||
## TODO move rotation to angle and do not wait
|
||||
logger.warning("[AcquireRefsV2] Done with scan_core")
|
||||
|
||||
Reference in New Issue
Block a user