refactor(pilatus): add live mode to pilatus
This commit is contained in:
@@ -37,6 +37,7 @@ PILATUS_READOUT_TIME = 0.1 # in s
|
||||
# )
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
# pylint: disable=raise-missing-from
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -108,6 +109,8 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
device_manager (DeviceManager | None) : DeviceManager object passed through the device by the device_manager
|
||||
"""
|
||||
|
||||
# USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
||||
|
||||
cam = Cpt(PilatusDetectorCam, "cam1:")
|
||||
hdf = Cpt(HDF5Plugin, "HDF1:")
|
||||
image1 = Cpt(ImagePlugin, "image1:")
|
||||
@@ -173,8 +176,15 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
self._poll_thread = threading.Thread(
|
||||
target=self._poll_array_data, daemon=True, name=f"{self.name}_poll_thread"
|
||||
)
|
||||
self._poll_thread_stop_event = threading.Event()
|
||||
self._poll_thread_kill_event = threading.Event()
|
||||
self._poll_rate = 1 # Poll rate in Hz
|
||||
# self._live_mode_thread = threading.Thread(
|
||||
# target=self._live_mode_loop, daemon=True, name=f"{self.name}_live_mode_thread"
|
||||
# )
|
||||
# self._live_mode_kill_event = threading.Event()
|
||||
# self._live_mode_run_event = threading.Event()
|
||||
# self._live_mode_stopped_event = threading.Event()
|
||||
# self._live_mode_stopped_event.set() # Initial state is stopped
|
||||
|
||||
########################################
|
||||
# Custom Beamline Methods #
|
||||
@@ -182,8 +192,9 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
|
||||
def _poll_array_data(self):
|
||||
"""Poll the array data for preview updates."""
|
||||
while not self._poll_thread_stop_event.wait(1 / self._poll_rate):
|
||||
while not self._poll_thread_kill_event.wait(1 / self._poll_rate):
|
||||
try:
|
||||
logger.info(f"Running poll loop for {self.name}..")
|
||||
value = self.image1.array_data.get()
|
||||
if value is None:
|
||||
continue
|
||||
@@ -192,7 +203,7 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
# Geometry correction for the image
|
||||
data = np.reshape(value, (height, width))
|
||||
last_image: DevicePreviewMessage = self.preview.get()
|
||||
|
||||
logger.info(f"Preview image for {self.name} has shape {data.shape}")
|
||||
if last_image is not None:
|
||||
if np.array_equal(data, last_image.data):
|
||||
# No update if image is the same, ~2.5ms on 2400x2400 image (6M)
|
||||
@@ -209,6 +220,90 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
f"Error while polling array data for preview of {self.name}: {content}"
|
||||
)
|
||||
|
||||
# def start_live_mode(self, exp_time: float, n_images_max: int = 50000):
|
||||
# """
|
||||
# Start live mode with given exposure time.
|
||||
|
||||
# Args:
|
||||
# exp_time (float) : Exposure time in seconds
|
||||
# n_images_max (int): Maximum number of images to capture during live mode.
|
||||
# Default is 5000. Only reset if needed.
|
||||
# """
|
||||
# if (
|
||||
# self.cam.acquire.get() != ACQUIREMODE.DONE.value
|
||||
# or self.hdf.capture.get() != ACQUIREMODE.DONE.value
|
||||
# ):
|
||||
# logger.warning(f"Can't start live mode, acquisition running on detector {self.name}.")
|
||||
# return
|
||||
# if self._live_mode_run_event.is_set():
|
||||
# logger.warning(f"Live mode is already running on detector {self.name}.")
|
||||
# return
|
||||
|
||||
# # Set relevant PVs
|
||||
# self.cam.array_counter.set(0).wait(5) # Reset array counter
|
||||
# self.cam.num_images.set(n_images_max).wait(5)
|
||||
# logger.info(
|
||||
# f"Setting exposure time to {exp_time} s for live mode on {self.name} with {n_images_max} images."
|
||||
# )
|
||||
# self.cam.acquire_time.set(exp_time - self._readout_time).wait(5)
|
||||
# self.cam.acquire_period.set(exp_time).wait(5)
|
||||
|
||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
# # It should suffice to make sure that self.hdf.capture is not set..
|
||||
# self.cam.acquire.put(1) # Start measurement
|
||||
# try:
|
||||
# status.wait(10)
|
||||
# except WaitTimeoutError:
|
||||
# content = traceback.format_exc()
|
||||
# raise RuntimeError(
|
||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
||||
# )
|
||||
# self._live_mode_run_event.set()
|
||||
|
||||
# def _live_mode_loop(self, exp_time: float):
|
||||
# while not self._live_mode_kill_event.is_set():
|
||||
# self._live_mode_run_event.wait()
|
||||
# self._live_mode_stopped_event.clear() # Clear stopped event
|
||||
# time.sleep(self._readout_time) # make sure to wait for the readout_time
|
||||
# n_images = self.cam.array_counter.get()
|
||||
# status = CompareStatus(self.cam.array_counter, n_images + 1)
|
||||
# self.trigger_shot.put(1)
|
||||
# try:
|
||||
# status.wait(60)
|
||||
# except WaitTimeoutError:
|
||||
# logger.warning(
|
||||
# f"Live mode timeout exceeded for {self.name}. Continuing in live_mode_loop"
|
||||
# )
|
||||
# if self._live_mode_run_event.is_set():
|
||||
# self._live_mode_stopped_event.set() # Set stopped event to indicate that live mode loop is stopped
|
||||
|
||||
# def stop_live_mode(self):
|
||||
# """Stop live mode."""
|
||||
# if self._live_mode_stopped_event.is_set():
|
||||
# return
|
||||
# status = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
# self.cam.acquire.put(0)
|
||||
# self._live_mode_run_event.clear()
|
||||
# if not self._live_mode_stopped_event.wait(10): # Wait until live mode loop is stopped
|
||||
# logger.warning(f"Live mode did not stop in time for {self.name}.")
|
||||
# try:
|
||||
# status.wait(10)
|
||||
# except WaitTimeoutError:
|
||||
# content = traceback.format_exc()
|
||||
# raise RuntimeError(
|
||||
# f"Live Mode on detector {self.name} did not stop: {content} after 10s."
|
||||
# )
|
||||
|
||||
def check_detector_stop_running_acquisition(self) -> AndStatusWithList:
|
||||
"""Check if the detector is still running an acquisition."""
|
||||
status_acquire = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_writing = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
||||
status_cam_server = CompareStatus(self.cam.armed, DETECTORSTATE.UNARMED.value)
|
||||
status = AndStatusWithList(
|
||||
device=self, status_list=[status_acquire, status_writing, status_cam_server]
|
||||
)
|
||||
return status
|
||||
|
||||
########################################
|
||||
# Beamline Specific Implementations #
|
||||
########################################
|
||||
@@ -226,6 +321,7 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
|
||||
status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value)
|
||||
status_hdf = CompareStatus(self.hdf.capture, ACQUIREMODE.DONE.value)
|
||||
try:
|
||||
@@ -247,6 +343,8 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
self.hdf.compression.set(COMPRESSIONALGORITHM.NONE.value).wait(5) # To test which to use
|
||||
# Start polling thread...
|
||||
self._poll_thread.start()
|
||||
# Start live mode thread...
|
||||
# self._live_mode_thread.start()
|
||||
|
||||
def on_stage(self) -> DeviceStatus | None:
|
||||
"""
|
||||
@@ -255,6 +353,7 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
Information about the upcoming scan can be accessed from the scan_info
|
||||
(self.scan_info.msg) object.
|
||||
"""
|
||||
# self.stop_live_mode() # Make sure that live mode is stopped if scan runs
|
||||
scan_msg: ScanStatusMessage = self.scan_info.msg
|
||||
if scan_msg.scan_name.startswith("xas"):
|
||||
return None
|
||||
@@ -287,9 +386,11 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
self.hdf.num_capture.set(n_images).wait(5)
|
||||
self.cam.array_counter.set(0).wait(5) # Reset array counter
|
||||
self.file_event.put(
|
||||
file_path=self._full_path, done=False, successful=False
|
||||
) # TODO add h5_entry dict
|
||||
return None
|
||||
file_path=self._full_path,
|
||||
done=False,
|
||||
successful=False,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
|
||||
def on_unstage(self) -> None:
|
||||
"""Called while unstaging the device."""
|
||||
@@ -330,12 +431,18 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
"""Callback for when the device completes a scan."""
|
||||
if status.success:
|
||||
status.device.file_event.put(
|
||||
file_path=status.device._full_path, done=True, successful=True
|
||||
) # pylint: disable:protected-access
|
||||
file_path=status.device._full_path, # pylint: disable:protected-access
|
||||
done=True,
|
||||
successful=True,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
else:
|
||||
status.device.file_event.put(
|
||||
file_path=status.device._full_path, done=True, successful=False
|
||||
) # pylint: disable:protected-access
|
||||
file_path=status.device._full_path, # pylint: disable:protected-access
|
||||
done=True,
|
||||
successful=False,
|
||||
hinted_h5_entries={"data": "/entry/data/data"},
|
||||
)
|
||||
|
||||
def on_complete(self) -> DeviceStatus | None:
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
@@ -366,7 +473,7 @@ class Pilatus(PSIDeviceBase, ADBase):
|
||||
|
||||
def on_destroy(self) -> None:
|
||||
"""Called when the device is destroyed. Cleanup resources here."""
|
||||
self._poll_thread_stop_event.set()
|
||||
self._poll_thread_kill_event.set()
|
||||
# TODO do we need to clean the poll thread ourselves?
|
||||
self.on_stop()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user