Download stitched image of scan (if present)

* Docstring for download stitch
* use global STITCH_SUFFIX
This commit is contained in:
jaknapper 2025-06-25 14:34:24 +01:00 committed by Julian Stirling
parent 62065dc385
commit ff43deae36
3 changed files with 91 additions and 25 deletions

View file

@ -5,7 +5,7 @@ Currently it handles scan getting information from a information from a scan dir
eventually is should handle all the file system operation for smart_scan.
"""
from typing import Optional
from typing import Optional, Literal
import os
import re
import shutil
@ -13,6 +13,7 @@ import zipfile
from pydantic import BaseModel
STITCH_SUFFIX = "_stitched.jpg"
IMG_DIR_NAME = "images"
IMAGE_REGEX = re.compile(r"-?[0-9]+_-?[0-9]+\.jpe?g$")
SCAN_ZERO_PAD_DIGITS = 4
@ -88,7 +89,7 @@ class ScanDirectoryManager:
"""Return the file path for the file within a scan directory
If check_exists is True, None is returned if the file does not exist. If False
then the path is returned anway
then the path is returned anyway
"""
file_path = os.path.join(self.img_dir_for(scan_name), filename)
if check_exists:
@ -96,6 +97,13 @@ class ScanDirectoryManager:
return None
return file_path
def get_final_stitch(self, scan_name: str) -> Optional[str]:
"""Return the file path for the final stitch.
If no final stitch is found, return None
"""
return ScanDirectory(scan_name, self.base_dir).get_final_stitch()
@property
def all_scans(self) -> list[str]:
"""Return a list of the scan names in the base directory"""
@ -242,25 +250,55 @@ class ScanDirectory:
return []
return os.listdir(self.images_dir)
def _find_files(
self, file_types=Literal["all", "scan_images", "stitches", "dzi"]
) -> tuple[list[str], list[str], list[str]] | list[str]:
folder_contents = self.get_scan_files()
# Return empty lists if no folder contents.
if not folder_contents:
if file_types == "all":
return [], [], []
return []
if file_types in ["all", "scan_images"]:
scan_images = [i for i in folder_contents if IMAGE_REGEX.search(i)]
if file_types == "scan_images":
return scan_images
if file_types in ["all", "stitches"]:
stitches = [i for i in folder_contents if i.endswith("_stitched.jpg")]
if file_types == "stitches":
return stitches
if file_types in ["all", "dzi"]:
dzi_files = [i for i in folder_contents if i.endswith("dzi")]
if file_types == "dzi":
return dzi_files
return scan_images, stitches, dzi_files
def get_final_stitch(self) -> Optional[str]:
"""Return the file path for the final stitch.
If no final stitch is found, return None
"""
stitches = self._find_files("stitches")
if not stitches:
return None
return stitches[0]
def get_modified_time(self) -> float:
"""Return the modified time of the directory"""
return max(os.stat(root).st_mtime for root, _, _ in os.walk(self.dir_path))
def scan_info(self):
"""Return the information for the scan directory as a ScanInfo object"""
folder_contents = self.get_scan_files()
if folder_contents:
scan_images = [i for i in folder_contents if IMAGE_REGEX.search(i)]
stitches = [i for i in folder_contents if i.endswith("_stitched.jpg")]
dzi_files = [i for i in folder_contents if i.endswith("dzi")]
number_of_images = len(scan_images)
stitch_available = len(stitches) > 0
dzi = None if not dzi_files else str(dzi_files[0])
else:
number_of_images = 0
stitch_available = False
dzi = None
scan_images, stitches, dzi_files = self._find_files("all")
number_of_images = len(scan_images)
stitch_available = len(stitches) > 0
dzi = None if not dzi_files else str(dzi_files[0])
return ScanInfo(
name=self.name,

View file

@ -666,6 +666,30 @@ class SmartScanThing(Thing):
"""
return self._scan_dir_manager.all_scans_info()
@fastapi_endpoint(
"get",
"get_stitch/{scan_name}",
responses={
200: {
"description": "Successfully downloading file",
"content": {"*/*": {}},
},
403: {"description": "Filename not permitted"},
404: {"description": "File not found"},
},
)
def get_stitch_file(self, scan_name: str) -> FileResponse:
"""Return the stitched image corresponding to a given scan name, if it exists.
Will only return a file ending in suffix STITCH_SUFFIX
Note: when downloading this, the default filename will be `scan_name`.jpeg"""
stitch_path = self._scan_dir_manager.get_final_stitch(scan_name)
if stitch_path is None:
raise HTTPException(404, "File not found")
return FileResponse(stitch_path)
@fastapi_endpoint(
"delete",
"scans/{scan_name}",

View file

@ -97,17 +97,13 @@
@error="modalError"
class="uk-button uk-width-1-2"
/>
<action-button
thing="smart_scan"
action="download_zip"
submit-label="Download Stitch"
:can-terminate="false"
:submit-data="{ scan_name: item.name }"
:button-primary="true"
@response="downloadZipFile"
@error="modalError"
class="uk-button uk-width-1-2"
/>
<a
class="uk-button uk-button-primary uk-width-1-2"
:class="item.stitch_available ? '' : 'disabled'"
:href="downloadStitchFile( item.name )"
download
>Download Stitch</a
>
<button
class="uk-button uk-button-default uk-width-1-1"
@click="deleteScan(item.name)"
@ -231,6 +227,9 @@ export default {
},
methods: {
downloadStitchFile: function(name) {
return `${this.$store.getters.baseUri}/smart_scan/get_stitch/${name}`;
},
thumbnailPath(scan_name) {
return (
`${this.$store.getters.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=` +
@ -393,4 +392,9 @@ ul {
text-align: center;
font-weight: bold;
}
a.disabled {
pointer-events: none;
color: #ccc;
}
</style>