From 83d2930d1f7bbd2fde1dafbfeee7e384efba7b38 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 9 Apr 2025 23:52:47 +0100 Subject: [PATCH] Fix capture error handing The error handling for capture needs to still send the aquired event or it will lock forever. On getting the errors to work there were some other minor issues in how the errors were called. --- .../things/smart_scan.py | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 070eb7d1..b4150d95 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -152,14 +152,13 @@ ZipBlob = blob_type("application/zip") class SmartScanThing(Thing): def __init__(self, path_to_openflexure_stitch: str): - self._script = path_to_openflexure_stitch + self._stitching_script = path_to_openflexure_stitch self._preview_stitch_popen = None self._preview_stitch_popen_lock = threading.Lock() self._correlate_popen = None self._correlate_popen_lock = threading.Lock() self._scan_lock = threading.Lock() - # Variables set by the scan self._latest_scan_name = None self._cancel = None @@ -217,9 +216,11 @@ class SmartScanThing(Thing): # record starting position so we can return there self._starting_position = self._stage.position self._run_scan(scan_name, overlap) - except: + except Exception as e: self._return_to_starting_position() self._perform_final_stitch(overlap) + # Error must be raised so UI gives correct output + raise e finally: # However the scan finnishes unset all variables and release lock self._cancel = None @@ -550,6 +551,7 @@ class SmartScanThing(Thing): ) capture_thread.start() acquired.wait() # wait until the image is acquired + positions.append(loc[:2]) names.append(name) @@ -637,6 +639,7 @@ class SmartScanThing(Thing): download_zip=False, ) self._logger.info("Waiting for background processes to finish...") + self.preview_stitch_wait() self.correlate_wait() try: @@ -658,9 +661,12 @@ class SmartScanThing(Thing): This will set the event `acquired` once the image has been acquired, so that the stage may be moved while it's saved. """ - capture_start = time.time() - image, metadata = self.capture_image() - acquired.set() + try: + capture_start = time.time() + image, metadata = self.capture_image() + finally: + # Ensure aquired is set even if capture fails or program will hang forever. + acquired.set() acquisition_time = time.time() self.save_capture(jpeg_path, image, metadata) save_time = time.time() @@ -681,9 +687,7 @@ class SmartScanThing(Thing): metadata = self._metadata_getter() image = self._cam.capture_array()[..., :3] except Exception as e: - raise CaptureError( - "An error occurred while capturing: {}".format(e), exc_info=e - ) + raise CaptureError("An error occurred while capturing") from e return image, metadata def save_capture( @@ -709,10 +713,7 @@ class SmartScanThing(Thing): except: self._logger.warning(f"Failed to add metadata to {jpeg_path}") except Exception as e: - raise IOError( - f"An error occurred while saving {jpeg_path}: {e}", - exc_info=e, - ) + raise IOError(f"An error occurred while saving {jpeg_path}") from e @thing_property def max_range(self) -> int: @@ -905,12 +906,24 @@ class SmartScanThing(Thing): return FileResponse(path) def preview_stitch_start(self, images_folder: str) -> None: - """Start stitching a preview of the scan in a subprocess""" + """Start stitching a preview of the scan in a backgroun subprocess + + This uses popen and returns immediatly + + - self._preview_stitch_popen holds the popen for polling + - self._preview_stitch_popen_lock is a lock aquired while interacting + with Popen + """ if self.preview_stitch_running(): raise RuntimeError("Only one subprocess is allowed at a time") with self._preview_stitch_popen_lock: self._preview_stitch_popen = Popen( - [self._script, "--stitching_mode", "only_stage_stitch", images_folder] + [ + self._stitching_script, + "--stitching_mode", + "only_stage_stitch", + images_folder, + ] ) def preview_stitch_running(self) -> bool: @@ -934,7 +947,7 @@ class SmartScanThing(Thing): with self._correlate_popen_lock: self._correlate_popen = Popen( [ - self._script, + self._stitching_script, "--stitching_mode", "only_correlate", "--minimum_overlap", @@ -1012,7 +1025,7 @@ class SmartScanThing(Thing): overlap = 0.1 self.run_subprocess( [ - self._script, + self._stitching_script, "--stitching_mode", "all", f"{tiff_arg}",