diff --git a/debye_bec/devices/pilatus/pilatus.py b/debye_bec/devices/pilatus/pilatus.py index e287da7..ba307cc 100644 --- a/debye_bec/devices/pilatus/pilatus.py +++ b/debye_bec/devices/pilatus/pilatus.py @@ -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()