Further tidying of scan directory variables
This commit is contained in:
parent
90cfce146d
commit
a5d55c95b8
2 changed files with 65 additions and 53 deletions
|
|
@ -148,6 +148,7 @@ DOWNLOADABLE_SCAN_FILES = ("images.zip",)
|
||||||
|
|
||||||
JPEGBlob = blob_type("image/jpeg")
|
JPEGBlob = blob_type("image/jpeg")
|
||||||
ZipBlob = blob_type("application/zip")
|
ZipBlob = blob_type("application/zip")
|
||||||
|
IMG_DIR_NAME = "images"
|
||||||
|
|
||||||
|
|
||||||
class SmartScanThing(Thing):
|
class SmartScanThing(Thing):
|
||||||
|
|
@ -260,7 +261,7 @@ class SmartScanThing(Thing):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_scan_dir(self) -> str:
|
def base_scan_dir(self) -> str:
|
||||||
"""This folder will hold all the scans we do."""
|
"""This directory will hold all the scans we do."""
|
||||||
# TODO: This should be determined using sensible configuration.
|
# TODO: This should be determined using sensible configuration.
|
||||||
# If the working directory is `/var/openflexure` this will result
|
# If the working directory is `/var/openflexure` this will result
|
||||||
# in scans being saved at `/var/openflexure/scans/`
|
# in scans being saved at `/var/openflexure/scans/`
|
||||||
|
|
@ -271,19 +272,28 @@ class SmartScanThing(Thing):
|
||||||
"""The name of the last scan to be started."""
|
"""The name of the last scan to be started."""
|
||||||
return self._latest_scan_name
|
return self._latest_scan_name
|
||||||
|
|
||||||
def dir_for_scan_name(self, scan_name: Optional[str] = None):
|
def dir_for_scan(self, scan_name: str):
|
||||||
"""The path to the scan folder with a given name"""
|
"""The path to the scan directory for scan with input name"""
|
||||||
if not scan_name:
|
|
||||||
if not self.latest_scan_name:
|
|
||||||
raise IOError("There is no latest scan to return")
|
|
||||||
scan_name = self.latest_scan_name
|
|
||||||
return os.path.join(self.base_scan_dir, scan_name)
|
return os.path.join(self.base_scan_dir, scan_name)
|
||||||
|
|
||||||
|
def images_dir_for_scan(self, scan_name: str) -> str:
|
||||||
|
"""The path to theimages directory for scan with input name"""
|
||||||
|
scan_dir = self.dir_for_scan(scan_name=scan_name)
|
||||||
|
return os.path.join(scan_dir, IMG_DIR_NAME)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _ongoing_scan_folder(self):
|
def _ongoing_scan_dir(self):
|
||||||
|
"""For the ongoing scan this returns the scan directory"""
|
||||||
if not self._ongoing_scan_name:
|
if not self._ongoing_scan_name:
|
||||||
return None
|
raise RuntimeError("Cannot get ongoing scan name while no scan is running")
|
||||||
return self.dir_for_scan_name(self._ongoing_scan_name)
|
return self.dir_for_scan(self._ongoing_scan_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ongoing_scan_images_dir(self):
|
||||||
|
"""For the ongoing scan this returns the scan directory"""
|
||||||
|
if not self._ongoing_scan_name:
|
||||||
|
raise RuntimeError("Cannot get ongoing scan name while no scan is running")
|
||||||
|
return self.images_dir_for_scan(self._ongoing_scan_name)
|
||||||
|
|
||||||
def _get_uniuqe_scan_name_and_dir(self, scan_name: str) -> str:
|
def _get_uniuqe_scan_name_and_dir(self, scan_name: str) -> str:
|
||||||
"""Get a unique name for this scan and create a directory for it
|
"""Get a unique name for this scan and create a directory for it
|
||||||
|
|
@ -301,7 +311,7 @@ class SmartScanThing(Thing):
|
||||||
|
|
||||||
Returns the scan name.
|
Returns the scan name.
|
||||||
|
|
||||||
The directory can be accessed by self.dir_for_scan_name(unique_scan_name)
|
The directory can be accessed by self.dir_for_scan(unique_scan_name)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(self.base_scan_dir):
|
if not os.path.exists(self.base_scan_dir):
|
||||||
|
|
@ -314,7 +324,7 @@ class SmartScanThing(Thing):
|
||||||
|
|
||||||
for j in range(9999):
|
for j in range(9999):
|
||||||
trial_unique_scan_name = f"{scan_name}_{j:04}"
|
trial_unique_scan_name = f"{scan_name}_{j:04}"
|
||||||
trial_dir = self.dir_for_scan_name(trial_unique_scan_name)
|
trial_dir = self.dir_for_scan(trial_unique_scan_name)
|
||||||
if not os.path.exists(trial_dir):
|
if not os.path.exists(trial_dir):
|
||||||
os.makedirs(trial_dir)
|
os.makedirs(trial_dir)
|
||||||
# If we made the directory this is the scan name
|
# If we made the directory this is the scan name
|
||||||
|
|
@ -357,11 +367,9 @@ class SmartScanThing(Thing):
|
||||||
capture_thread = None
|
capture_thread = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
images_folder = os.path.join(self._ongoing_scan_folder, "images")
|
os.mkdir(self._ongoing_scan_images_dir)
|
||||||
os.mkdir(images_folder)
|
|
||||||
raw_images_folder = os.path.join(images_folder, "raw")
|
self._logger.info(f"Saving images to {self._ongoing_scan_images_dir}")
|
||||||
os.mkdir(raw_images_folder)
|
|
||||||
self._logger.info(f"Saving images to {images_folder}")
|
|
||||||
|
|
||||||
max_dist = self.max_range
|
max_dist = self.max_range
|
||||||
|
|
||||||
|
|
@ -423,14 +431,15 @@ class SmartScanThing(Thing):
|
||||||
"skipping background": self.skip_background,
|
"skipping background": self.skip_background,
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(
|
scan_inputs_fname = os.path.join(
|
||||||
os.path.join(images_folder, "scan_inputs.json"), "w", encoding="utf-8"
|
self._ongoing_scan_images_dir, "scan_inputs.json"
|
||||||
) as f:
|
)
|
||||||
|
with open(scan_inputs_fname, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
if self._scan_images_taken != 0:
|
if self._scan_images_taken != 0:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"_scan_images_taken fhould be zero before starting scanning"
|
"_scan_images_taken should be zero before starting scanning"
|
||||||
)
|
)
|
||||||
# At the start of the loop, we simultaneously capture an image and move to the next scan point.
|
# At the start of the loop, we simultaneously capture an image and move to the next scan point.
|
||||||
# We skip capturing on the first run, because we've not focused yet - and also we skip capturing if
|
# We skip capturing on the first run, because we've not focused yet - and also we skip capturing if
|
||||||
|
|
@ -438,12 +447,12 @@ class SmartScanThing(Thing):
|
||||||
while len(path) > 0:
|
while len(path) > 0:
|
||||||
loc = self.move_to_next_point(path=path, focused_path=focused_path)
|
loc = self.move_to_next_point(path=path, focused_path=focused_path)
|
||||||
if not self.preview_stitch_running():
|
if not self.preview_stitch_running():
|
||||||
self.preview_stitch_start(images_folder)
|
self.preview_stitch_start()
|
||||||
if self.stitch_automatically:
|
if self.stitch_automatically:
|
||||||
if not self.correlate_running():
|
if not self.correlate_running():
|
||||||
self.correlate_start(images_folder, overlap=overlap)
|
self.correlate_start(overlap=overlap)
|
||||||
|
|
||||||
ensure_free_disk_space(self._ongoing_scan_folder)
|
ensure_free_disk_space(self._ongoing_scan_dir)
|
||||||
|
|
||||||
# Check if the image is background
|
# Check if the image is background
|
||||||
if self.skip_background:
|
if self.skip_background:
|
||||||
|
|
@ -544,7 +553,7 @@ class SmartScanThing(Thing):
|
||||||
self._scan_images_taken += 1
|
self._scan_images_taken += 1
|
||||||
acquired = Event()
|
acquired = Event()
|
||||||
name = f"image_{loc[0]}_{loc[1]}.jpg"
|
name = f"image_{loc[0]}_{loc[1]}.jpg"
|
||||||
jpeg_path = os.path.join(images_folder, name)
|
jpeg_path = os.path.join(self._ongoing_scan_images_dir, name)
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# Use ErrorCapturingThread intead of Thread. This will raise errors in the calling
|
# Use ErrorCapturingThread intead of Thread. This will raise errors in the calling
|
||||||
|
|
@ -650,11 +659,9 @@ class SmartScanThing(Thing):
|
||||||
self.preview_stitch_wait()
|
self.preview_stitch_wait()
|
||||||
self.correlate_wait()
|
self.correlate_wait()
|
||||||
try:
|
try:
|
||||||
if self._ongoing_scan_folder and self.stitch_automatically:
|
if self.stitch_automatically:
|
||||||
self._logger.info("Stitching final image (may take some time)...")
|
self._logger.info("Stitching final image (may take some time)...")
|
||||||
self.stitch_scan(
|
self.stitch_scan(self._ongoing_scan_name, overlap=overlap)
|
||||||
os.path.basename(self._ongoing_scan_folder), overlap=overlap
|
|
||||||
)
|
|
||||||
except SubprocessError as e:
|
except SubprocessError as e:
|
||||||
self._logger.error(f"Stitching failed: {e}", exc_info=e)
|
self._logger.error(f"Stitching failed: {e}", exc_info=e)
|
||||||
|
|
||||||
|
|
@ -795,7 +802,7 @@ class SmartScanThing(Thing):
|
||||||
for f in os.listdir(self.base_scan_dir):
|
for f in os.listdir(self.base_scan_dir):
|
||||||
path = os.path.join(self.base_scan_dir, f)
|
path = os.path.join(self.base_scan_dir, f)
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
images_folder = os.path.join(path, "images")
|
images_folder = os.path.join(path, IMG_DIR_NAME)
|
||||||
if os.path.isdir(images_folder):
|
if os.path.isdir(images_folder):
|
||||||
number_of_images = len(os.listdir(images_folder))
|
number_of_images = len(os.listdir(images_folder))
|
||||||
else:
|
else:
|
||||||
|
|
@ -871,14 +878,22 @@ class SmartScanThing(Thing):
|
||||||
for scan in self.scans:
|
for scan in self.scans:
|
||||||
self.delete_scan(scan.name)
|
self.delete_scan(scan.name)
|
||||||
|
|
||||||
def images_folder(self, scan_name: Optional[str] = None) -> str:
|
|
||||||
scan_folder = self.dir_for_scan_name(scan_name=scan_name)
|
|
||||||
return os.path.join(scan_folder, "images")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def latest_preview_stitch_path(self):
|
def latest_preview_stitch_path(self):
|
||||||
"""The path of the latest preview stitched image"""
|
"""Return the path of the latest preview stitched image
|
||||||
return os.path.join(self.images_folder(), "stitched_from_stage.jpg")
|
|
||||||
|
If the image cannot be found (because there is no latest
|
||||||
|
scan, or because the latest scan has no preview stitch) then
|
||||||
|
raise FileNotFoundError
|
||||||
|
"""
|
||||||
|
if not self.latest_scan_name:
|
||||||
|
raise FileNotFoundError("No latest scan found")
|
||||||
|
|
||||||
|
images_dir = self.images_dir_for_scan(self.latest_scan_name)
|
||||||
|
stitch_path = os.path.join(images_dir, "stitched_from_stage.jpg")
|
||||||
|
if not os.path.isfile(stitch_path):
|
||||||
|
raise FileNotFoundError("Latest scan has no preview stitch")
|
||||||
|
return stitch_path
|
||||||
|
|
||||||
@thing_property
|
@thing_property
|
||||||
def latest_preview_stitch_time(self) -> Optional[datetime]:
|
def latest_preview_stitch_time(self) -> Optional[datetime]:
|
||||||
|
|
@ -887,12 +902,9 @@ class SmartScanThing(Thing):
|
||||||
This will return `null` if there is no preview image to return.
|
This will return `null` if there is no preview image to return.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
fpath = self.latest_preview_stitch_path
|
return os.path.getmtime(self.latest_preview_stitch_path)
|
||||||
if os.path.exists(fpath):
|
except FileNotFoundError:
|
||||||
return os.path.getmtime(fpath)
|
|
||||||
except IOError:
|
|
||||||
return None
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
@fastapi_endpoint(
|
@fastapi_endpoint(
|
||||||
"get",
|
"get",
|
||||||
|
|
@ -907,12 +919,12 @@ class SmartScanThing(Thing):
|
||||||
)
|
)
|
||||||
def get_latest_preview(self) -> FileResponse:
|
def get_latest_preview(self) -> FileResponse:
|
||||||
"""Retrieve the latest preview image."""
|
"""Retrieve the latest preview image."""
|
||||||
path = self.latest_preview_stitch_path
|
try:
|
||||||
if not os.path.isfile(path):
|
return FileResponse(self.latest_preview_stitch_path)
|
||||||
|
except FileNotFoundError:
|
||||||
raise HTTPException(404, "File not found")
|
raise HTTPException(404, "File not found")
|
||||||
return FileResponse(path)
|
|
||||||
|
|
||||||
def preview_stitch_start(self, images_folder: str) -> None:
|
def preview_stitch_start(self) -> None:
|
||||||
"""Start stitching a preview of the scan in a backgroun subprocess
|
"""Start stitching a preview of the scan in a backgroun subprocess
|
||||||
|
|
||||||
This uses popen and returns immediatly
|
This uses popen and returns immediatly
|
||||||
|
|
@ -929,7 +941,7 @@ class SmartScanThing(Thing):
|
||||||
self._stitching_script,
|
self._stitching_script,
|
||||||
"--stitching_mode",
|
"--stitching_mode",
|
||||||
"only_stage_stitch",
|
"only_stage_stitch",
|
||||||
images_folder,
|
self._ongoing_scan_images_dir,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -947,7 +959,7 @@ class SmartScanThing(Thing):
|
||||||
with self._preview_stitch_popen_lock:
|
with self._preview_stitch_popen_lock:
|
||||||
self._preview_stitch_popen.wait()
|
self._preview_stitch_popen.wait()
|
||||||
|
|
||||||
def correlate_start(self, images_folder: str, overlap: float = 0.1) -> None:
|
def correlate_start(self, overlap: float = 0.1) -> None:
|
||||||
"""Start stitching a preview of the scan in a subprocess"""
|
"""Start stitching a preview of the scan in a subprocess"""
|
||||||
if self.correlate_running():
|
if self.correlate_running():
|
||||||
raise RuntimeError("Only one subprocess is allowed at a time")
|
raise RuntimeError("Only one subprocess is allowed at a time")
|
||||||
|
|
@ -959,7 +971,7 @@ class SmartScanThing(Thing):
|
||||||
"only_correlate",
|
"only_correlate",
|
||||||
"--minimum_overlap",
|
"--minimum_overlap",
|
||||||
f"{round(overlap * 0.9, 2)}",
|
f"{round(overlap * 0.9, 2)}",
|
||||||
images_folder,
|
self._ongoing_scan_images_dir,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1011,11 +1023,11 @@ class SmartScanThing(Thing):
|
||||||
@thing_action
|
@thing_action
|
||||||
def stitch_scan(
|
def stitch_scan(
|
||||||
self,
|
self,
|
||||||
scan_name: Optional[str] = None,
|
scan_name: str,
|
||||||
overlap: float = 0.0,
|
overlap: float = 0.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Generate a stitched image based on stage position metadata"""
|
"""Generate a stitched image based on stage position metadata"""
|
||||||
images_folder = self.images_folder(scan_name=scan_name)
|
images_folder = self.images_dir_for_scan(scan_name=scan_name)
|
||||||
|
|
||||||
if self.stitch_tiff:
|
if self.stitch_tiff:
|
||||||
tiff_arg = "--stitch_tiff"
|
tiff_arg = "--stitch_tiff"
|
||||||
|
|
@ -1049,8 +1061,8 @@ class SmartScanThing(Thing):
|
||||||
download_zip=True,
|
download_zip=True,
|
||||||
) -> ZipBlob:
|
) -> ZipBlob:
|
||||||
"""Generate a zip file that can be downloaded, with all the scan files in it."""
|
"""Generate a zip file that can be downloaded, with all the scan files in it."""
|
||||||
images_folder = self.images_folder(scan_name=scan_name)
|
images_folder = self.images_dir_for_scan(scan_name=scan_name)
|
||||||
scan_folder = self.dir_for_scan_name(scan_name=scan_name)
|
scan_folder = self.dir_for_scan(scan_name=scan_name)
|
||||||
|
|
||||||
if not os.path.isdir(images_folder):
|
if not os.path.isdir(images_folder):
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class Stitcher(Thing):
|
||||||
def images_folder(
|
def images_folder(
|
||||||
self, smart_scan: SmartScanDep, scan_name: Optional[str] = None
|
self, smart_scan: SmartScanDep, scan_name: Optional[str] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
scan_folder = smart_scan.dir_for_scan_name(scan_name=scan_name)
|
scan_folder = smart_scan.dir_for_scan(scan_name=scan_name)
|
||||||
return os.path.join(scan_folder, "images")
|
return os.path.join(scan_folder, "images")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue