From 3c93a56c748e290dcb313fca1d1137a8f5cdbc47 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Mon, 12 May 2025 18:53:05 +0100 Subject: [PATCH 1/9] Updated json at end --- .../things/smart_scan.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 839d64b5..c569396f 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -197,6 +197,7 @@ class SmartScanThing(Thing): raise e finally: # However the scan finishes, unset all variables and release lock + self._update_scan_inputs_json() self._cancel = None self._scan_logger = None self._autofocus = None @@ -473,6 +474,35 @@ class SmartScanThing(Thing): with open(scan_inputs_fname, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) + @_scan_running + def _update_scan_inputs_json(self): + """ + Update scan inputs as a JSON file in the scan folder, with + data only known at the end of the scan. + """ + # This should be a method of the scan_data dataclass + + duration = datetime.now() - datetime.strptime(self._scan_data["start_time"], "%H_%M_%S-%d_%m_%Y") + hours, remainder = divmod(duration.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + outputs = { + 'image_count': self._scan_images_taken, + 'duration': f"{hours}:{minutes}:{seconds}", + } + + scan_inputs_fname = os.path.join( + self._ongoing_scan_images_dir, "scan_inputs.json" + ) + + with open(scan_inputs_fname, encoding="utf-8") as f: + data = json.load(f) + + data.update(outputs) + + with open(scan_inputs_fname, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + @_scan_running def _manage_stitching_threads(self): """ From 4234c92fed65c87e54f5f02be2af4cb5c60f6ba2 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 11:10:27 +0100 Subject: [PATCH 2/9] Better formatting of duration as datetime timedelta without microseconds --- .../things/smart_scan.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index c569396f..5eb3e2cb 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -481,14 +481,16 @@ class SmartScanThing(Thing): data only known at the end of the scan. """ # This should be a method of the scan_data dataclass + current_time = datetime.now().replace(microsecond=0) + start_time = datetime.strptime( + self._scan_data["start_time"], "%H_%M_%S-%d_%m_%Y" + ).replace(microsecond=0) - duration = datetime.now() - datetime.strptime(self._scan_data["start_time"], "%H_%M_%S-%d_%m_%Y") - hours, remainder = divmod(duration.seconds, 3600) - minutes, seconds = divmod(remainder, 60) + duration = current_time - start_time outputs = { - 'image_count': self._scan_images_taken, - 'duration': f"{hours}:{minutes}:{seconds}", + "image_count": self._scan_images_taken, + "duration": str(duration), } scan_inputs_fname = os.path.join( From 272f0509058c55d70a9fec77ccc5994d9bf967e0 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 11:32:03 +0100 Subject: [PATCH 3/9] Move update data outside of finally block --- src/openflexure_microscope_server/things/smart_scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 5eb3e2cb..9195b344 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -190,6 +190,7 @@ class SmartScanThing(Thing): # If _scan_data is set then scan started if self._scan_data is not None: self._return_to_starting_position() + self._update_scan_inputs_json() if not isinstance(e, NotEnoughFreeSpaceError): # Don't stich if drive is full (already logged) self._perform_final_stitch() @@ -197,7 +198,6 @@ class SmartScanThing(Thing): raise e finally: # However the scan finishes, unset all variables and release lock - self._update_scan_inputs_json() self._cancel = None self._scan_logger = None self._autofocus = None From 845c21c68a10845a6933998a4038ecd6544cb00a Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 11:32:03 +0100 Subject: [PATCH 4/9] Move update data outside of finally block --- src/openflexure_microscope_server/things/smart_scan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 9195b344..939abc1f 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -186,6 +186,7 @@ class SmartScanThing(Thing): # record starting position so we can return there self._starting_position = self._stage.position self._run_scan() + self._update_scan_inputs_json() except Exception as e: # If _scan_data is set then scan started if self._scan_data is not None: From 9734bba23b80c09c7b424f53ca2eaa14522e195f Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 12:45:55 +0100 Subject: [PATCH 5/9] Inputs renamed to data where appropriate --- .../things/smart_scan.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 939abc1f..d0685414 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -186,12 +186,12 @@ class SmartScanThing(Thing): # record starting position so we can return there self._starting_position = self._stage.position self._run_scan() - self._update_scan_inputs_json() + self._update_scan_data_json() except Exception as e: # If _scan_data is set then scan started if self._scan_data is not None: self._return_to_starting_position() - self._update_scan_inputs_json() + self._update_scan_data_json() if not isinstance(e, NotEnoughFreeSpaceError): # Don't stich if drive is full (already logged) self._perform_final_stitch() @@ -457,7 +457,7 @@ class SmartScanThing(Thing): Save scan inputs as a JSON file in the scan folder, to allow the user to review the settings used in the scan """ - # This should be a method of the scan_data dataclass + # Should this be a method of the scan_data dataclass? data = { "scan_name": self._ongoing_scan_name, @@ -470,18 +470,18 @@ class SmartScanThing(Thing): } scan_inputs_fname = os.path.join( - self._ongoing_scan_images_dir, "scan_inputs.json" + self._ongoing_scan_images_dir, "scan_data.json" ) with open(scan_inputs_fname, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) @_scan_running - def _update_scan_inputs_json(self): + def _update_scan_data_json(self): """ - Update scan inputs as a JSON file in the scan folder, with + Update scan data as a JSON file in the scan folder, with data only known at the end of the scan. """ - # This should be a method of the scan_data dataclass + # Should this be a method of the scan_data dataclass? current_time = datetime.now().replace(microsecond=0) start_time = datetime.strptime( self._scan_data["start_time"], "%H_%M_%S-%d_%m_%Y" @@ -494,16 +494,16 @@ class SmartScanThing(Thing): "duration": str(duration), } - scan_inputs_fname = os.path.join( - self._ongoing_scan_images_dir, "scan_inputs.json" + scan_data_fname = os.path.join( + self._ongoing_scan_images_dir, "scan_data.json" ) - with open(scan_inputs_fname, encoding="utf-8") as f: + with open(scan_data_fname, encoding="utf-8") as f: data = json.load(f) data.update(outputs) - with open(scan_inputs_fname, "w", encoding="utf-8") as f: + with open(scan_data_fname, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) @_scan_running @@ -1034,7 +1034,7 @@ class SmartScanThing(Thing): Note that as this is a thing_action it needs the logger passed as a variable if called from another thing action """ - json_fname = "scan_inputs.json" + json_fname = "scan_data.json" images_folder = self.images_dir_for_scan(scan_name=scan_name) json_fpath = os.path.join(images_folder, json_fname) From 1952e05284005a9ce2b421395c49834116155970 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 13:00:04 +0100 Subject: [PATCH 6/9] Scan result in data json --- .../things/smart_scan.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index d0685414..0fc84a49 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -186,12 +186,10 @@ class SmartScanThing(Thing): # record starting position so we can return there self._starting_position = self._stage.position self._run_scan() - self._update_scan_data_json() except Exception as e: # If _scan_data is set then scan started if self._scan_data is not None: self._return_to_starting_position() - self._update_scan_data_json() if not isinstance(e, NotEnoughFreeSpaceError): # Don't stich if drive is full (already logged) self._perform_final_stitch() @@ -476,10 +474,13 @@ class SmartScanThing(Thing): json.dump(data, f, ensure_ascii=False, indent=4) @_scan_running - def _update_scan_data_json(self): + def _update_scan_data_json(self, scan_result: str): """ Update scan data as a JSON file in the scan folder, with data only known at the end of the scan. + + Takes scan_result, a string that is either "success", + "cancelled by user", or the error that ended the scan. """ # Should this be a method of the scan_data dataclass? current_time = datetime.now().replace(microsecond=0) @@ -492,11 +493,10 @@ class SmartScanThing(Thing): outputs = { "image_count": self._scan_images_taken, "duration": str(duration), + "scan_result": scan_result, } - scan_data_fname = os.path.join( - self._ongoing_scan_images_dir, "scan_data.json" - ) + scan_data_fname = os.path.join(self._ongoing_scan_images_dir, "scan_data.json") with open(scan_data_fname, encoding="utf-8") as f: data = json.load(f) @@ -540,12 +540,15 @@ class SmartScanThing(Thing): # This is the main loop of the scan! self._main_scan_loop() + self._update_scan_data_json(scan_result="success") except InvocationCancelledError: scan_successful = False self._scan_logger.info("Stopping scan because it was cancelled.") + self._update_scan_data_json(scan_result="cancelled by user") except NotEnoughFreeSpaceError as e: scan_successful = False + self._update_scan_data_json(scan_result=str(e)) self._scan_logger.error( f"Stopping scan to avoid filling up the disk: {e}", exc_info=e, From 56edddd19504f537a7fcbfdaffe613a8747d6f72 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 13:03:34 +0100 Subject: [PATCH 7/9] Data filename as global --- src/openflexure_microscope_server/things/smart_scan.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 0fc84a49..38e5374a 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -86,6 +86,7 @@ DOWNLOADABLE_SCAN_FILES = ( JPEGBlob = blob_type("image/jpeg") ZipBlob = blob_type("application/zip") IMG_DIR_NAME = "images" +SCAN_DATA_FILENAME = "scan_data.json" SCAN_ZERO_PAD_DIGITS = 4 @@ -468,7 +469,7 @@ class SmartScanThing(Thing): } scan_inputs_fname = os.path.join( - self._ongoing_scan_images_dir, "scan_data.json" + self._ongoing_scan_images_dir, SCAN_DATA_FILENAME ) with open(scan_inputs_fname, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) @@ -496,7 +497,9 @@ class SmartScanThing(Thing): "scan_result": scan_result, } - scan_data_fname = os.path.join(self._ongoing_scan_images_dir, "scan_data.json") + scan_data_fname = os.path.join( + self._ongoing_scan_images_dir, SCAN_DATA_FILENAME + ) with open(scan_data_fname, encoding="utf-8") as f: data = json.load(f) From b5893bc29c60044817cdf39a493aadea40ec0b4b Mon Sep 17 00:00:00 2001 From: Joe Knapper Date: Tue, 13 May 2025 12:34:50 +0000 Subject: [PATCH 8/9] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Julian Stirling --- src/openflexure_microscope_server/things/smart_scan.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 38e5374a..4b9b3b81 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -1040,9 +1040,8 @@ class SmartScanThing(Thing): Note that as this is a thing_action it needs the logger passed as a variable if called from another thing action """ - json_fname = "scan_data.json" images_folder = self.images_dir_for_scan(scan_name=scan_name) - json_fpath = os.path.join(images_folder, json_fname) + json_fpath = os.path.join(images_folder, SCAN_DATA_FILENAME) if self.stitch_tiff: tiff_arg = "--stitch_tiff" From a77c0f6f9d9a2b3b39458430adc0777c09d9821a Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 13 May 2025 15:28:16 +0100 Subject: [PATCH 9/9] Updated wrong path to file --- src/openflexure_microscope_server/things/smart_scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 4b9b3b81..d180364c 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -1059,7 +1059,7 @@ class SmartScanThing(Thing): # the file not being there, it not being json in the file, # or the imported data not being indexable logger.warning( - f"Couldn't read scan data, is {json_fname} missing or corrupt? " + f"Couldn't read scan data, is {SCAN_DATA_FILENAME} missing or corrupt? " "Attempting stitch with overlap value of 0.1" ) overlap = 0.1