Compare commits
10 Commits
fixflomni-
...
fix/online
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c89086ba2 | |||
| 1eb2961b7f | |||
| 9d58dcfb83 | |||
| 541813a02e | |||
| 4b04e7a35d | |||
| 00c45b2bcf | |||
| 138b2668b3 | |||
| 31eb00bd97 | |||
|
|
53e7593b8e
|
||
|
|
8c7e1cf060 |
@@ -84,6 +84,15 @@ class FlomniInitStagesMixin:
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
sensor_voltage_target = dev.ftransy.user_parameter.get("sensor_voltage")
|
||||
sensor_voltage = float(dev.ftransy.controller.socket_put_and_receive("MG@AN[1]").strip())
|
||||
|
||||
if not np.isclose(sensor_voltage, sensor_voltage_target, 0.25):
|
||||
print(f"Sensor voltage of the gripper is {sensor_voltage}, while target from config is {sensor_voltage_target}")
|
||||
print("Verify that the value is acceptable and update config file. Reload config and start again.")
|
||||
return
|
||||
|
||||
print("Starting to drive ftransy to +y limit")
|
||||
self.drive_axis_to_limit(dev.ftransy, "forward")
|
||||
dev.ftransy.limits = [-100, 0]
|
||||
@@ -414,6 +423,7 @@ class FlomniSampleTransferMixin:
|
||||
raise FlomniError("Ftray is not at the 'IN' position. Aborting.")
|
||||
|
||||
def ftransfer_flomni_stage_in(self):
|
||||
time.sleep(1)
|
||||
sample_in_position = dev.flomni_samples.is_sample_slot_used(0)
|
||||
#bool(float(dev.flomni_samples.sample_placed.sample0.get()))
|
||||
if not sample_in_position:
|
||||
@@ -428,7 +438,7 @@ class FlomniSampleTransferMixin:
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
dev.fsamx.limits = [fsamx_in - 0.4, fsamx_in + 0.4]
|
||||
|
||||
self.flomnigui_idle()
|
||||
#self.flomnigui_idle()
|
||||
|
||||
def laser_tracker_show_all(self):
|
||||
dev.rtx.controller.laser_tracker_show_all()
|
||||
@@ -586,6 +596,8 @@ class FlomniSampleTransferMixin:
|
||||
if sample_in_position:
|
||||
raise FlomniError(f"The planned put position [{position}] already has a sample.")
|
||||
|
||||
self.flomnigui_show_cameras()
|
||||
|
||||
self.ftransfer_gripper_move(position)
|
||||
|
||||
self.ftransfer_controller_enable_mount_mode()
|
||||
@@ -611,7 +623,7 @@ class FlomniSampleTransferMixin:
|
||||
self.ftransfer_controller_disable_mount_mode()
|
||||
self.ensure_gripper_up()
|
||||
|
||||
sample_name = dev.flomni_samples.sample_in_gripper.get()
|
||||
sample_name = dev.flomni_samples.sample_in_gripper_name.get()
|
||||
self.flomni_modify_storage_non_interactive(100, 0, "-")
|
||||
self.flomni_modify_storage_non_interactive(position, 1, sample_name)
|
||||
|
||||
@@ -1844,6 +1856,9 @@ class Flomni(
|
||||
)
|
||||
|
||||
def tomo_scan_projection(self, angle: float):
|
||||
|
||||
dev.rtx.controller.laser_tracker_check_signalstrength()
|
||||
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
# additional_correction = self.align.compute_additional_correction(angle)
|
||||
|
||||
@@ -57,15 +57,22 @@ class flomniGuiTools:
|
||||
|
||||
def _flomnigui_check_attribute_not_exists(self, attribute_name):
|
||||
if hasattr(self.gui, "flomni"):
|
||||
if hasattr(self.gui.flomni, attribute_name):
|
||||
return False
|
||||
if attribute_name == "xeyegui":
|
||||
if hasattr(self.gui.flomni, "xrayeye"):
|
||||
return False
|
||||
if attribute_name == "progressbar":
|
||||
if hasattr(self.gui.flomni, "RingProgressBar"):
|
||||
return False
|
||||
if attribute_name == "cam_flomni_gripper" or attribute_name == "cam_flomni_overview":
|
||||
if hasattr(self.gui.flomni, "Image") or hasattr(self.gui.flomni, "Image_0"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def flomnigui_show_cameras(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_check_attribute_not_exists(
|
||||
"camera_gripper"
|
||||
) or self._flomnigui_check_attribute_not_exists("camera_overview"):
|
||||
"cam_flomni_gripper"
|
||||
) or self._flomnigui_check_attribute_not_exists("cam_flomni_overview"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
camera_gripper_image = self.gui.flomni.new("Image")
|
||||
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
|
||||
|
||||
@@ -49,6 +49,8 @@ class XrayEyeAlign:
|
||||
self._xray_fov_xy = [0, 0]
|
||||
|
||||
def update_frame(self, keep_shutter_open=False):
|
||||
if self.flomni._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||
self.flomni.flomnigui_show_xeyealign()
|
||||
|
||||
if not dev.cam_xeye.live_mode_enabled.get():
|
||||
dev.cam_xeye.live_mode_enabled.put(True)
|
||||
@@ -245,7 +247,13 @@ class XrayEyeAlign:
|
||||
)
|
||||
print("Check the fit in the GUI...")
|
||||
|
||||
print("Then LOAD ALIGNMENT PARAMETERS by running flomni.read_alignment_offset()\n")
|
||||
time.sleep(5)
|
||||
|
||||
print("Automatically loading new alignment parameters from xray eye alignment.\n")
|
||||
|
||||
self.flomni.read_alignment_offset()
|
||||
|
||||
print("You are ready to remove the xray eye and start ptychography scans.")
|
||||
|
||||
def write_output(self):
|
||||
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
||||
@@ -266,7 +274,7 @@ class XrayEyeAlign:
|
||||
fovx_x = (k - 1) * 45
|
||||
fovx_list.append([fovx_x, fovx_offset * 1000]) # Append the data to the list
|
||||
|
||||
print(f"Writing to file new alignment: number {k}, value x {fovx_offset}")
|
||||
print(f"Alignment number {k}, value x {fovx_offset}")
|
||||
alignment_values_file.write(f"{fovx_x}\t{fovx_offset * 1000}\n")
|
||||
|
||||
# Now build final numpy array:
|
||||
|
||||
@@ -25,7 +25,6 @@ from qtpy.QtWidgets import (
|
||||
QTextEdit,
|
||||
QTabWidget,
|
||||
)
|
||||
import time
|
||||
|
||||
logger = bec_logger.logger
|
||||
CAMERA = ("cam_xeye", "image")
|
||||
@@ -146,6 +145,7 @@ class XRayEye(BECWidget, QWidget):
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self._connected_motor = None
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
self._init_ui()
|
||||
@@ -357,13 +357,14 @@ class XRayEye(BECWidget, QWidget):
|
||||
self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor)
|
||||
)
|
||||
logger.info(f"Successfully connected to {motor}")
|
||||
self._connected_motor = motor
|
||||
|
||||
################################################################################
|
||||
# Properties ported from the original OmnyAlignment, can be adjusted as needed
|
||||
################################################################################
|
||||
@SafeProperty(str)
|
||||
def user_message(self):
|
||||
return self.message_line_edit.text()
|
||||
return self.message_line_edit.toPlainText()
|
||||
|
||||
@user_message.setter
|
||||
def user_message(self, message: str):
|
||||
@@ -520,44 +521,50 @@ class XRayEye(BECWidget, QWidget):
|
||||
@SafeSlot()
|
||||
def submit(self):
|
||||
"""Execute submit action by submit button."""
|
||||
print("submit pushed")
|
||||
self.submit_button.blockSignals(True)
|
||||
if self.roi_manager.single_active_roi is None:
|
||||
logger.warning("No active ROI")
|
||||
return
|
||||
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
|
||||
roi_center_x = roi_coordinates["center_x"]
|
||||
roi_center_y = roi_coordinates["center_y"]
|
||||
# Case of rectangular ROI
|
||||
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
|
||||
roi_width = roi_coordinates["width"]
|
||||
roi_height = roi_coordinates["height"]
|
||||
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
|
||||
roi_width = roi_coordinates["diameter"]
|
||||
roi_height = roi_coordinates["radius"]
|
||||
else:
|
||||
logger.warning("Unsupported ROI type for submit action.")
|
||||
return
|
||||
try:
|
||||
if self.roi_manager.single_active_roi is None:
|
||||
logger.warning("No active ROI")
|
||||
return
|
||||
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
|
||||
roi_center_x = roi_coordinates["center_x"]
|
||||
roi_center_y = roi_coordinates["center_y"]
|
||||
# Case of rectangular ROI
|
||||
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
|
||||
roi_width = roi_coordinates["width"]
|
||||
roi_height = roi_coordinates["height"]
|
||||
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
|
||||
roi_width = roi_coordinates["diameter"]
|
||||
roi_height = roi_coordinates["radius"]
|
||||
else:
|
||||
logger.warning("Unsupported ROI type for submit action.")
|
||||
return
|
||||
|
||||
print(
|
||||
f"current roi: x:{roi_center_x}, y:{roi_center_y}, w:{roi_width},h:{roi_height}"
|
||||
) # TODO remove when will be not needed for debugging
|
||||
# submit roi coordinates
|
||||
step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get("value"))
|
||||
# submit roi coordinates
|
||||
step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get("value"))
|
||||
|
||||
xval_x = getattr(self.dev.omny_xray_gui, f"xval_x_{step}").set(roi_center_x)
|
||||
xval_y = getattr(self.dev.omny_xray_gui, f"yval_y_{step}").set(roi_center_y)
|
||||
width_x = getattr(self.dev.omny_xray_gui, f"width_x_{step}").set(roi_width)
|
||||
width_y = getattr(self.dev.omny_xray_gui, f"width_y_{step}").set(roi_height)
|
||||
self.dev.omny_xray_gui.submit.set(1)
|
||||
print("submit done")
|
||||
self.submit_button.blockSignals(False)
|
||||
getattr(self.dev.omny_xray_gui, f"xval_x_{step}").set(roi_center_x)
|
||||
getattr(self.dev.omny_xray_gui, f"yval_y_{step}").set(roi_center_y)
|
||||
getattr(self.dev.omny_xray_gui, f"width_x_{step}").set(roi_width)
|
||||
getattr(self.dev.omny_xray_gui, f"width_y_{step}").set(roi_height)
|
||||
self.dev.omny_xray_gui.submit.set(1)
|
||||
finally:
|
||||
self.submit_button.blockSignals(False)
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup connections on widget close -> disconnect slots and stop live mode of camera."""
|
||||
if self._connected_motor is not None:
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_tomo_angle_readback, MessageEndpoints.device_readback(self._connected_motor)
|
||||
)
|
||||
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")
|
||||
self.getting_shutter_status, MessageEndpoints.device_readback("omnyfsh")
|
||||
)
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.getting_camera_status, MessageEndpoints.device_read_configuration(CAMERA[0])
|
||||
)
|
||||
|
||||
getattr(self.dev, CAMERA[0]).stop_live_mode()
|
||||
super().cleanup()
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("B", CHANNELREFERENCE.A),
|
||||
("C", CHANNELREFERENCE.T0), # T0
|
||||
("D", CHANNELREFERENCE.C),
|
||||
("E", CHANNELREFERENCE.D), # D One extra pulse once shutter closes for MCS
|
||||
("E", CHANNELREFERENCE.B), # B One extra pulse once shutter closes for MCS
|
||||
("F", CHANNELREFERENCE.E), # E + 1mu s
|
||||
("G", CHANNELREFERENCE.T0),
|
||||
("H", CHANNELREFERENCE.G),
|
||||
@@ -213,8 +213,23 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
|
||||
# NOTE Burst delay should be set to 0, don't remove as this will not be checked
|
||||
# Also set the burst count to 1 to only have a single pulse for DDG1.
|
||||
# As the IOC may be out of sync with the HW, we make sure that we set the default parameters
|
||||
# in the IOC to the expected values. In the past, we've experienced that IOC and HW can go out
|
||||
# of sync.
|
||||
self.burst_delay.put(1)
|
||||
time.sleep(0.02) # Give HW time to process
|
||||
self.burst_delay.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_count.put(2)
|
||||
time.sleep(0.02)
|
||||
self.burst_count.put(1)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_mode.put(1)
|
||||
time.sleep(0.02)
|
||||
self.burst_mode.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
def keep_shutter_open_during_scan(self, open: True) -> None:
|
||||
"""
|
||||
@@ -291,17 +306,24 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# Burst Period DDG1
|
||||
# Set burst_period to shutter width
|
||||
# c/t0 + self._shutter_to_open_delay + exp_time * burst_count
|
||||
shutter_width = (
|
||||
self._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
) # Shutter starts closing at end of exposure
|
||||
# SHUTTER WIDTH timing consists of the delay for the shutter to open
|
||||
# + the exposure time * frames per trigger
|
||||
shutter_width = self._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
# TOTAL EXPOSURE accounts for the shutter to open AND close. In addition, we add
|
||||
# a short additional delay of 3e-6 to allow for the extra trigger through 'ef'
|
||||
# (delay of 1e-6, width of 1e-6)
|
||||
total_exposure_time = 2 * self._shutter_to_open_delay + exp_time * frames_per_trigger + 3e-6
|
||||
if self.burst_period.get() != shutter_width:
|
||||
self.burst_period.put(shutter_width)
|
||||
# The burst_period has to be slightly longer
|
||||
self.burst_period.put(total_exposure_time)
|
||||
|
||||
# Trigger DDG2
|
||||
# a = t0 + 2ms, b = a + 1us
|
||||
# a has reference to t0, b has reference to a
|
||||
# Add delay of self._shutter_to_open_delay to allow shutter to open
|
||||
self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=1e-6)
|
||||
# AB is delayed by the shutter opening time, and the falling edge indicates the shutter has
|
||||
# fully closed, it has to be considered as the blocking signal for the next acquisition to start.
|
||||
# PS: + 3e-6
|
||||
self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=shutter_width)
|
||||
|
||||
# Trigger shutter
|
||||
# d = c/t0 + self._shutter_to_open_delay + exp_time * burst_count + 1ms
|
||||
@@ -321,7 +343,7 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=0)
|
||||
else:
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=1e-6)
|
||||
self.set_delay_pairs(channel="ef", delay=1e-6, width=1e-6)
|
||||
|
||||
# NOTE Add additional sleep to make sure that the IOC and DDG HW process the values properly
|
||||
# This value has been choosen empirically after testing with the HW. It's
|
||||
|
||||
@@ -29,6 +29,7 @@ from ophyd_devices import DeviceStatus, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
BURSTCONFIG,
|
||||
CHANNELREFERENCE,
|
||||
OUTPUTPOLARITY,
|
||||
STATUSBITS,
|
||||
@@ -37,7 +38,6 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
||||
ChannelConfig,
|
||||
DelayGeneratorCSAXS,
|
||||
LiteralChannels,
|
||||
BURSTCONFIG,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
@@ -138,6 +138,24 @@ class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# Set burst config
|
||||
self.burst_config.put(BURSTCONFIG.FIRST_CYCLE.value)
|
||||
|
||||
# TODO As the IOC may be out of sync with the HW, we make sure that we set the default parameters
|
||||
# in the IOC to the expected values. In the past, we've experienced that IOC and HW can go out
|
||||
# of sync.
|
||||
self.burst_delay.put(1)
|
||||
time.sleep(0.02) # Give HW time to process
|
||||
self.burst_delay.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_count.put(2)
|
||||
time.sleep(0.02)
|
||||
self.burst_count.put(1)
|
||||
time.sleep(0.02)
|
||||
|
||||
self.burst_mode.put(1)
|
||||
time.sleep(0.02)
|
||||
self.burst_mode.put(0)
|
||||
time.sleep(0.02)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Callable, Literal
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd.utils.errors import WaitTimeoutError
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, Kind
|
||||
from ophyd_devices import (
|
||||
@@ -513,7 +514,22 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
# that the acquisition finishes on the card and that data is emitted to BEC. If the acquisition
|
||||
# was already finished (i.e. normal step scan sends 1 extra pulse per burst cycle), this will
|
||||
# not have any effect as the card will already be in DONE state and signal.
|
||||
self.software_channel_advance.put(1)
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
expected_points = int(
|
||||
self.scan_info.msg.num_points
|
||||
* self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
)
|
||||
|
||||
status = CompareStatus(self.current_channel, expected_points-1, operation_success=">=")
|
||||
try:
|
||||
status.wait(timeout=5)
|
||||
except WaitTimeoutError:
|
||||
text = f"Device {self.name} received num points {self.current_channel.get()} / {expected_points}. Device timed out after 5s."
|
||||
logger.error(text)
|
||||
raise TimeoutError(text)
|
||||
|
||||
# Manually set the last advance
|
||||
self.software_channel_advance.put(1)
|
||||
|
||||
# Prepare and register status callback for the async monitoring loop
|
||||
status_async_data = StatusBase(obj=self)
|
||||
|
||||
@@ -92,7 +92,8 @@ class RtFlomniController(Controller):
|
||||
parent._min_scan_buffer_reached = False
|
||||
start_time = time.time()
|
||||
for pos_index, pos in enumerate(positions):
|
||||
parent.socket_put_and_receive(f"s{pos[0]:.05f},{pos[1]:.05f},{pos[2]:.05f}")
|
||||
cmd = f"s{pos[0]:.05f},{pos[1]:.05f},{pos[2]:.05f}"
|
||||
parent.socket_put_and_receive(cmd)
|
||||
if pos_index > 100:
|
||||
parent._min_scan_buffer_reached = True
|
||||
parent._min_scan_buffer_reached = True
|
||||
@@ -174,8 +175,12 @@ class RtFlomniController(Controller):
|
||||
self.set_device_read_write("foptx", False)
|
||||
self.set_device_read_write("fopty", False)
|
||||
|
||||
def move_samx_to_scan_region(self, fovx: float, cenx: float):
|
||||
def move_samx_to_scan_region(self, cenx: float, move_in_this_routine: bool = False):
|
||||
# attention. a movement will clear all positions in the rt trajectory generator!
|
||||
if move_in_this_routine == True:
|
||||
self.device_manager.devices.rtx.obj.move(cenx, wait=True)
|
||||
time.sleep(0.05)
|
||||
# at cenx we expect the PID to be close to zero for a good fsamx position
|
||||
if self.rt_pid_voltage is None:
|
||||
rtx = self.device_manager.devices.rtx
|
||||
self.rt_pid_voltage = rtx.user_parameter.get("rt_pid_voltage")
|
||||
@@ -184,31 +189,41 @@ class RtFlomniController(Controller):
|
||||
"rt_pid_voltage not set in rtx user parameters. Please run feedback_enable_with_reset first."
|
||||
)
|
||||
logger.info(f"Using PID voltage from rtx user parameter: {self.rt_pid_voltage}")
|
||||
expected_voltage = self.rt_pid_voltage + fovx / 2 * 7 / 100
|
||||
logger.info(f"Expected PID voltage: {expected_voltage}")
|
||||
expected_voltage = self.rt_pid_voltage
|
||||
# logger.info(f"Expected PID voltage: {expected_voltage}")
|
||||
logger.info(f"Current PID voltage: {self.get_pid_x()}")
|
||||
|
||||
wait_on_exit = False
|
||||
while True:
|
||||
if np.abs(self.get_pid_x() - expected_voltage) < 1:
|
||||
break
|
||||
wait_on_exit = True
|
||||
self.socket_put("v0")
|
||||
# we allow 2V range from center, this corresponds to 30 microns
|
||||
if np.abs(self.get_pid_x() - expected_voltage) < 2:
|
||||
logger.info("No correction of fsamx needed")
|
||||
else:
|
||||
fsamx = self.device_manager.devices.fsamx
|
||||
fsamx.read_only = False
|
||||
fsamx.obj.controller.socket_put_confirmed("axspeed[4]=0.1*stppermm[4]")
|
||||
fsamx.obj.pid_x_correction -= (self.get_pid_x() - expected_voltage) * 0.007
|
||||
logger.info(f"Correcting fsamx by {fsamx.obj.pid_x_correction}")
|
||||
fsamx_in = fsamx.user_parameter.get("in")
|
||||
fsamx.obj.move(fsamx_in + cenx / 1000 + fsamx.obj.pid_x_correction, wait=True)
|
||||
fsamx.read_only = True
|
||||
time.sleep(0.1)
|
||||
self.laser_tracker_on()
|
||||
time.sleep(0.01)
|
||||
while True:
|
||||
# when we correct, then to 1 V, within 15 microns
|
||||
if np.abs(self.get_pid_x() - expected_voltage) < 1:
|
||||
logger.info("No further correction needed")
|
||||
break
|
||||
wait_on_exit = True
|
||||
# disable FZP piezo feedback
|
||||
self.socket_put("v0")
|
||||
fsamx.read_only = False
|
||||
logger.info(f"Current PID voltage: {self.get_pid_x()}")
|
||||
# here we accumulate the correction
|
||||
fsamx.obj.pid_x_correction -= (self.get_pid_x() - expected_voltage) * 0.006
|
||||
fsamx_in = fsamx.user_parameter.get("in")
|
||||
logger.info(
|
||||
f"Moving fsamx to {cenx / 1000 * 0.7 + fsamx.obj.pid_x_correction}, PID portion of that {fsamx.obj.pid_x_correction}"
|
||||
)
|
||||
fsamx.obj.move(fsamx_in + cenx / 1000 * 0.7 + fsamx.obj.pid_x_correction, wait=True)
|
||||
fsamx.read_only = True
|
||||
time.sleep(0.1)
|
||||
self.laser_tracker_on()
|
||||
time.sleep(0.01)
|
||||
|
||||
if wait_on_exit:
|
||||
time.sleep(1)
|
||||
|
||||
# enable fast FZP feedback again
|
||||
self.socket_put("v1")
|
||||
|
||||
@threadlocked
|
||||
@@ -499,7 +514,7 @@ class RtFlomniController(Controller):
|
||||
# while scan is running
|
||||
while mode > 0:
|
||||
|
||||
#TODO here?: scan abortion if no progress in scan *raise error
|
||||
# TODO here?: scan abortion if no progress in scan *raise error
|
||||
|
||||
# logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}")
|
||||
mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status()
|
||||
|
||||
@@ -24,9 +24,11 @@ import time
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger, messages
|
||||
from bec_lib.alarm_handler import Alarms
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_server.scan_server.errors import ScanAbortion
|
||||
from bec_server.scan_server.scans import SyncFlyScanBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
|
||||
|
||||
logger = bec_logger.logger
|
||||
@@ -52,7 +54,7 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
angle: float = None,
|
||||
corridor_size: float = 3,
|
||||
parameter: dict = None,
|
||||
frames_per_trigger:int=1,
|
||||
frames_per_trigger: int = 1,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -76,7 +78,9 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
>>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)
|
||||
"""
|
||||
|
||||
super().__init__(parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs)
|
||||
super().__init__(
|
||||
parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs
|
||||
)
|
||||
self.show_live_table = False
|
||||
self.axis = []
|
||||
self.fovx = fovx
|
||||
@@ -153,13 +157,16 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
yield from self.stubs.send_rpc_and_wait("rty", "set", self.positions[0][1])
|
||||
|
||||
def _prepare_setup_part2(self):
|
||||
# Prepare DDG1 to use
|
||||
yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.EXT_RISING_EDGE.value)
|
||||
|
||||
# Prepare DDG1 to use
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"ddg1", "set_trigger", TRIGGERSOURCE.EXT_RISING_EDGE.value
|
||||
)
|
||||
|
||||
if self.flomni_rotation_status:
|
||||
self.flomni_rotation_status.wait()
|
||||
|
||||
rtx_status = yield from self.stubs.set(device="rtx", value=self.positions[0][0], wait=False)
|
||||
# rtx_status = yield from self.stubs.set(device="rtx", value=self.positions[0][0], wait=False)
|
||||
rtx_status = yield from self.stubs.set(device="rtx", value=self.cenx, wait=False)
|
||||
rtz_status = yield from self.stubs.set(device="rtz", value=self.positions[0][2], wait=False)
|
||||
|
||||
yield from self.stubs.send_rpc_and_wait("rtx", "controller.laser_tracker_on")
|
||||
@@ -167,22 +174,24 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
rtx_status.wait()
|
||||
rtz_status.wait()
|
||||
|
||||
# status = yield from self.stubs.send_rpc("rtx", "move", self.cenx)
|
||||
# status.wait()
|
||||
yield from self._transfer_positions_to_flomni()
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.move_samx_to_scan_region", self.fovx, self.cenx
|
||||
)
|
||||
tracker_signal_status = yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.laser_tracker_check_signalstrength"
|
||||
)
|
||||
#self.device_manager.connector.send_client_info(tracker_signal_status)
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"rtx", "controller.move_samx_to_scan_region", self.cenx
|
||||
)
|
||||
# self.device_manager.connector.send_client_info(tracker_signal_status)
|
||||
if tracker_signal_status == "low":
|
||||
self.device_manager.connector.raise_alarm(
|
||||
severity=0,
|
||||
alarm_type="LaserTrackerSignalStrength",
|
||||
source={"device": "rtx", "reason": "low signal strength", "method": "_prepare_setup_part2"},
|
||||
metadata={},
|
||||
msg="Signal strength of the laser tracker is low, sufficient to continue. Realignment recommended!",
|
||||
error_info = messages.ErrorInfo(
|
||||
error_message="Signal strength of the laser tracker is low, but sufficient to continue. Realignment recommended!",
|
||||
compact_error_message="Low signal strength of the laser tracker. Realignment recommended!",
|
||||
exception_type="LaserTrackerSignalStrengthLow",
|
||||
device="rtx",
|
||||
)
|
||||
self.device_manager.connector.raise_alarm(severity=Alarms.WARNING, info=error_info)
|
||||
elif tracker_signal_status == "toolow":
|
||||
raise ScanAbortion(
|
||||
"Signal strength of the laser tracker is too low for scanning. Realignment required!"
|
||||
@@ -307,13 +316,19 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
# in flomni, we need to move to the start position of the next scan, which is the end position of the current scan
|
||||
# this method is called in finalize and overwrites the default move_to_start()
|
||||
if isinstance(self.positions, np.ndarray) and len(self.positions[-1]) == 3:
|
||||
yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=self.positions[-1])
|
||||
# yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=self.positions[-1])
|
||||
# in x we move to cenx, then we avoid jumps in centering routine
|
||||
value = self.positions[-1]
|
||||
value[0] = self.cenx
|
||||
yield from self.stubs.set(device=["rtx", "rty", "rtz"], value=value)
|
||||
return
|
||||
|
||||
logger.warning("No positions found to return to start")
|
||||
|
||||
def cleanup(self):
|
||||
yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.SINGLE_SHOT.value)
|
||||
yield from self.stubs.send_rpc_and_wait(
|
||||
"ddg1", "set_trigger", TRIGGERSOURCE.SINGLE_SHOT.value
|
||||
)
|
||||
yield from super().cleanup()
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -287,19 +287,20 @@ def test_ddg1_stage(mock_ddg1: DDG1):
|
||||
mock_ddg1.stage()
|
||||
|
||||
shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
total_exposure = 2 * mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger + 3e-6
|
||||
|
||||
assert np.isclose(mock_ddg1.burst_mode.get(), 1) # burst mode is enabled
|
||||
assert np.isclose(mock_ddg1.burst_delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), shutter_width)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), total_exposure)
|
||||
|
||||
# Trigger DDG2 through EXT/EN
|
||||
assert np.isclose(mock_ddg1.ab.delay.get(), 2e-3)
|
||||
assert np.isclose(mock_ddg1.ab.width.get(), 1e-6)
|
||||
assert np.isclose(mock_ddg1.ab.width.get(), shutter_width)
|
||||
# Shutter channel cd
|
||||
assert np.isclose(mock_ddg1.cd.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.cd.width.get(), shutter_width)
|
||||
# MCS channel ef or gate
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 1e-6)
|
||||
assert np.isclose(mock_ddg1.ef.width.get(), 1e-6)
|
||||
|
||||
assert mock_ddg1.staged == ophyd.Staged.yes
|
||||
|
||||
Reference in New Issue
Block a user