Merge branch 'cleanup_empty_scans' into 'v3'

Added purge_empty_scans() method

Closes #370

See merge request openflexure/openflexure-microscope-server!257
This commit is contained in:
Julian Stirling 2025-05-20 18:26:00 +00:00
commit c793620fcd
2 changed files with 58 additions and 9 deletions

View file

@ -588,6 +588,9 @@ class SmartScanThing(Thing):
self._return_to_starting_position() self._return_to_starting_position()
self._perform_final_stitch() self._perform_final_stitch()
# Remove any scan folders containing zero images
self.purge_empty_scans(logger=self._scan_logger)
@_scan_running @_scan_running
def _main_scan_loop(self): def _main_scan_loop(self):
""" """
@ -868,22 +871,26 @@ class SmartScanThing(Thing):
404: {"description": "Scan not found"}, 404: {"description": "Scan not found"},
}, },
) )
def delete_scan(self, scan_name: str) -> None: def delete_scan(self, scan_name: str, logger: InvocationLogger) -> None:
"""Delete all files from a scan. """Delete the folder for the specified scan.
This endpoint allows scans to be deleted from disk. This endpoint allows scans to be deleted from disk.
Takes the scan name to delete, and the Invocation Logger
""" """
path = os.path.join(self.base_scan_dir, scan_name) path = os.path.join(self.base_scan_dir, scan_name)
if not os.path.isdir(path): if not os.path.isdir(path):
print(f"can't find {path}") logger.info(f"can't find {path}")
raise HTTPException(404, "Scan not found") raise HTTPException(400, "Scan not found")
shutil.rmtree(path) deleted_scan_success = self._delete_scan(path, logger)
if not deleted_scan_success:
raise HTTPException(400, "Couldn't delete scan, check log for details")
@fastapi_endpoint( @fastapi_endpoint(
"delete", "delete",
"scans", "scans",
) )
def delete_all_scans(self) -> None: def delete_all_scans(self, logger: InvocationLogger) -> None:
"""Delete all the scans on the microscope """Delete all the scans on the microscope
**This will irreversibly remove all scanned data from the **This will irreversibly remove all scanned data from the
@ -891,7 +898,20 @@ class SmartScanThing(Thing):
Use with extreme caution. Use with extreme caution.
""" """
for scan in self.scans: for scan in self.scans:
self.delete_scan(scan.name) path = os.path.join(self.base_scan_dir, scan.name)
self._delete_scan(path, logger)
@thing_action
def _delete_scan(self, scan_path, logger: InvocationLogger) -> bool:
try:
shutil.rmtree(scan_path)
return True
except Exception as e:
logger.warning(
"Attempted to delete scan " + scan_path + ", which failed."
" Server sent response" + str(e)
)
return False
@property @property
def latest_preview_stitch_path(self): def latest_preview_stitch_path(self):
@ -1179,3 +1199,26 @@ class SmartScanThing(Thing):
"""List the relative paths of all files and folders in the zip folder specified""" """List the relative paths of all files and folders in the zip folder specified"""
scan_zip = zipfile.ZipFile(zip_path) scan_zip = zipfile.ZipFile(zip_path)
return [os.path.normpath(i) for i in scan_zip.namelist()] return [os.path.normpath(i) for i in scan_zip.namelist()]
@thing_action
def purge_empty_scans(self, logger: InvocationLogger) -> None:
"""
Delete all scan folders containing no images at the top level
"""
scan_list = self.scans
# Filter out scans with no top level files, ignoring JSON files
# JSON is ignored as it's created before any images are captured
for scan in scan_list:
scan_folder = os.path.join(self.base_scan_dir, scan.name, IMG_DIR_NAME)
images_found = False
for fname in os.listdir(scan_folder):
fpath = os.path.join(scan_folder, fname)
if os.path.isfile(fpath) and IMAGE_REGEX.search(fname):
images_found = True
# break as soon as an image is found.
break
if not images_found:
path = os.path.join(self.base_scan_dir, scan.name)
self._delete_scan(path, logger)

View file

@ -144,10 +144,16 @@ Vue.mixin({
if (error.response) { if (error.response) {
// If the response is a nicely formatted JSON response from the server // If the response is a nicely formatted JSON response from the server
if (error.response.data.message) { if (error.response.data.message) {
return `${error.response.status}: ${error.response.data.message}`; return `${error.response.data.message}`;
}
if (error.response.data.detail) {
return `${error.response.data.detail}`;
} }
// If the response is just some generic error response // If the response is just some generic error response
return `${error.response.status}: ${error.response.data}`; if (error.response.data){
return `${error.response.data}`;
}
return `${error.response}`;
} }
// If we have an error object with a message, use that // If we have an error object with a message, use that
if (error.message) return `${error.message}`; if (error.message) return `${error.message}`;