Remove all cancel hooks from smart scan and stitching
This commit is contained in:
parent
150e69d235
commit
510a44dd17
2 changed files with 19 additions and 32 deletions
|
|
@ -6,6 +6,7 @@ CPU intensity of stitching causing scanning problems due to the Python Global
|
||||||
Interpreter Lock (GIL). May be possible to shift to multiprocessing in the future.
|
Interpreter Lock (GIL). May be possible to shift to multiprocessing in the future.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
|
|
@ -167,14 +168,17 @@ class PreviewStitcher(BaseStitcher):
|
||||||
return False
|
return False
|
||||||
return self._popen_obj.poll() is None
|
return self._popen_obj.poll() is None
|
||||||
|
|
||||||
def wait(self, cancel: lt.deps.CancelHook) -> None:
|
def wait(self) -> None:
|
||||||
"""Wait for this preview stitch to return.
|
"""Wait for this preview stitch to return.
|
||||||
|
|
||||||
:raises InvocationCancelledError: if the action is cancelled.
|
:raises InvocationCancelledError: if the action is cancelled.
|
||||||
"""
|
"""
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
cancel.sleep(0.1)
|
# This should act exactly like sleep if not started in a LabThings
|
||||||
|
# action thread. In an action thread it will raise
|
||||||
|
# InvocationCancelledError if the action is cancelled.
|
||||||
|
lt.cancellable_sleep(0.2)
|
||||||
except lt.exceptions.InvocationCancelledError as e:
|
except lt.exceptions.InvocationCancelledError as e:
|
||||||
with self._popen_lock:
|
with self._popen_lock:
|
||||||
if self._popen_obj is not None:
|
if self._popen_obj is not None:
|
||||||
|
|
@ -189,7 +193,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
self,
|
self,
|
||||||
images_dir: str,
|
images_dir: str,
|
||||||
*,
|
*,
|
||||||
logger: lt.deps.InvocationLogger,
|
logger: logging.Logger,
|
||||||
overlap: Optional[float] = None,
|
overlap: Optional[float] = None,
|
||||||
correlation_resize: Optional[float] = None,
|
correlation_resize: Optional[float] = None,
|
||||||
stitch_tiff: bool = False,
|
stitch_tiff: bool = False,
|
||||||
|
|
@ -200,7 +204,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
All args except images_dir are positional only.
|
All args except images_dir are positional only.
|
||||||
|
|
||||||
:param images_dir: The images directory of the scan to stitch.
|
:param images_dir: The images directory of the scan to stitch.
|
||||||
:param logger: The invocation logger for this stitch process.
|
:param logger: The logger from the Thing that created this stitcher.
|
||||||
:param overlap: The scan overlap, if not known enter None. A value will be
|
:param overlap: The scan overlap, if not known enter None. A value will be
|
||||||
chosen from the scan_data_dict, or set to a default value if no value is
|
chosen from the scan_data_dict, or set to a default value if no value is
|
||||||
available in the scan_data.
|
available in the scan_data.
|
||||||
|
|
@ -281,10 +285,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
)
|
)
|
||||||
return overlap, correlation_resize
|
return overlap, correlation_resize
|
||||||
|
|
||||||
def run(
|
def run(self) -> None:
|
||||||
self,
|
|
||||||
cancel: lt.deps.CancelHook,
|
|
||||||
) -> None:
|
|
||||||
"""Run the final stitch logging any output.
|
"""Run the final stitch logging any output.
|
||||||
|
|
||||||
:raises ChildProcessError: if exit code is not zero
|
:raises ChildProcessError: if exit code is not zero
|
||||||
|
|
@ -305,7 +306,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
# Stop opening pipe blocking writing to it
|
# Stop opening pipe blocking writing to it
|
||||||
os.set_blocking(process.stdout.fileno(), False)
|
os.set_blocking(process.stdout.fileno(), False)
|
||||||
|
|
||||||
self._log_ongoing(process, cancel=cancel)
|
self._log_ongoing(process)
|
||||||
|
|
||||||
if process.poll() == 0:
|
if process.poll() == 0:
|
||||||
self.logger.info("Stitching complete")
|
self.logger.info("Stitching complete")
|
||||||
|
|
@ -319,11 +320,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
f"Subprocess {STITCHING_CMD} errored with exit code {process.poll()}."
|
f"Subprocess {STITCHING_CMD} errored with exit code {process.poll()}."
|
||||||
)
|
)
|
||||||
|
|
||||||
def _log_ongoing(
|
def _log_ongoing(self, process: subprocess.Popen) -> None:
|
||||||
self,
|
|
||||||
process: subprocess.Popen,
|
|
||||||
cancel: lt.deps.CancelHook,
|
|
||||||
) -> None:
|
|
||||||
"""Log the ongoing process unless it is cancelled."""
|
"""Log the ongoing process unless it is cancelled."""
|
||||||
# Poll returns None while running, will return the error code when finished
|
# Poll returns None while running, will return the error code when finished
|
||||||
while process.poll() is None:
|
while process.poll() is None:
|
||||||
|
|
@ -331,9 +328,10 @@ class FinalStitcher(BaseStitcher):
|
||||||
# Once buffer is clear sleep for 0.2s before trying again.
|
# Once buffer is clear sleep for 0.2s before trying again.
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Note that using cancel.sleep allows the InvocationCancelledError to
|
# This should act exactly like sleep if not started in a LabThings
|
||||||
# be thrown
|
# action thread. In an action thread it will raise
|
||||||
cancel.sleep(0.2)
|
# InvocationCancelledError if the action is cancelled.
|
||||||
|
lt.cancellable_sleep(0.2)
|
||||||
except lt.exceptions.InvocationCancelledError as e:
|
except lt.exceptions.InvocationCancelledError as e:
|
||||||
self.logger.info("Stitching cancelled by user")
|
self.logger.info("Stitching cancelled by user")
|
||||||
process.kill()
|
process.kill()
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ class SmartScanThing(lt.Thing):
|
||||||
# any method that calls these should be decorated with
|
# any method that calls these should be decorated with
|
||||||
# @_scan_running
|
# @_scan_running
|
||||||
self._scan_logger: Optional[lt.deps.InvocationLogger] = None
|
self._scan_logger: Optional[lt.deps.InvocationLogger] = None
|
||||||
self._cancel: Optional[lt.deps.CancelHook] = None
|
|
||||||
self._autofocus: Optional[AutofocusDep] = None
|
self._autofocus: Optional[AutofocusDep] = None
|
||||||
self._stage: Optional[StageDep] = None
|
self._stage: Optional[StageDep] = None
|
||||||
self._cam: Optional[CameraClient] = None
|
self._cam: Optional[CameraClient] = None
|
||||||
|
|
@ -139,7 +138,6 @@ class SmartScanThing(lt.Thing):
|
||||||
@lt.action
|
@lt.action
|
||||||
def sample_scan(
|
def sample_scan(
|
||||||
self,
|
self,
|
||||||
cancel: lt.deps.CancelHook,
|
|
||||||
logger: lt.deps.InvocationLogger,
|
logger: lt.deps.InvocationLogger,
|
||||||
autofocus: AutofocusDep,
|
autofocus: AutofocusDep,
|
||||||
stage: StageDep,
|
stage: StageDep,
|
||||||
|
|
@ -158,7 +156,6 @@ class SmartScanThing(lt.Thing):
|
||||||
raise RuntimeError("Trying to run scan while scan is already running!")
|
raise RuntimeError("Trying to run scan while scan is already running!")
|
||||||
|
|
||||||
# Set private variables for this scan
|
# Set private variables for this scan
|
||||||
self._cancel = cancel
|
|
||||||
self._scan_logger = logger
|
self._scan_logger = logger
|
||||||
self._autofocus = autofocus
|
self._autofocus = autofocus
|
||||||
self._stage = stage
|
self._stage = stage
|
||||||
|
|
@ -188,7 +185,6 @@ class SmartScanThing(lt.Thing):
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
# However the scan finishes, unset all variables and release lock
|
# However the scan finishes, unset all variables and release lock
|
||||||
self._cancel = None
|
|
||||||
self._scan_logger = None
|
self._scan_logger = None
|
||||||
self._autofocus = None
|
self._autofocus = None
|
||||||
self._stage = None
|
self._stage = None
|
||||||
|
|
@ -390,8 +386,6 @@ class SmartScanThing(lt.Thing):
|
||||||
self._save_final_scan_data(scan_result="success")
|
self._save_final_scan_data(scan_result="success")
|
||||||
|
|
||||||
except lt.exceptions.InvocationCancelledError:
|
except lt.exceptions.InvocationCancelledError:
|
||||||
# Reset the cancel event so it can be thrown again
|
|
||||||
self._cancel.clear()
|
|
||||||
self._scan_logger.info("Stopping scan because it was cancelled.")
|
self._scan_logger.info("Stopping scan because it was cancelled.")
|
||||||
self._save_final_scan_data(scan_result="cancelled by user")
|
self._save_final_scan_data(scan_result="cancelled by user")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -513,13 +507,12 @@ class SmartScanThing(lt.Thing):
|
||||||
self._scan_logger.info("Waiting for background processes to finish...")
|
self._scan_logger.info("Waiting for background processes to finish...")
|
||||||
|
|
||||||
if self._preview_stitcher is not None:
|
if self._preview_stitcher is not None:
|
||||||
self._preview_stitcher.wait(self._cancel)
|
self._preview_stitcher.wait()
|
||||||
|
|
||||||
if self._scan_data.stitch_automatically:
|
if self._scan_data.stitch_automatically:
|
||||||
self._scan_logger.info("Stitching final image (may take some time)...")
|
self._scan_logger.info("Stitching final image (may take some time)...")
|
||||||
self.stitch_scan(
|
self.stitch_scan(
|
||||||
logger=self._scan_logger,
|
logger=self._scan_logger,
|
||||||
cancel=self._cancel,
|
|
||||||
scan_name=self._ongoing_scan.name,
|
scan_name=self._ongoing_scan.name,
|
||||||
correlation_resize=self._scan_data.correlation_resize,
|
correlation_resize=self._scan_data.correlation_resize,
|
||||||
overlap=self._scan_data.overlap,
|
overlap=self._scan_data.overlap,
|
||||||
|
|
@ -731,7 +724,6 @@ class SmartScanThing(lt.Thing):
|
||||||
def stitch_scan(
|
def stitch_scan(
|
||||||
self,
|
self,
|
||||||
logger: lt.deps.InvocationLogger,
|
logger: lt.deps.InvocationLogger,
|
||||||
cancel: lt.deps.CancelHook,
|
|
||||||
scan_name: str,
|
scan_name: str,
|
||||||
correlation_resize: Optional[float] = None,
|
correlation_resize: Optional[float] = None,
|
||||||
overlap: Optional[float] = None,
|
overlap: Optional[float] = None,
|
||||||
|
|
@ -753,8 +745,7 @@ class SmartScanThing(lt.Thing):
|
||||||
scan_data_dict=scan_data_dict,
|
scan_data_dict=scan_data_dict,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
# start the final stitch, providing the cancel hook to allow aborting
|
final_stitcher.run()
|
||||||
final_stitcher.run(cancel)
|
|
||||||
except lt.exceptions.InvocationCancelledError:
|
except lt.exceptions.InvocationCancelledError:
|
||||||
# Sleep for 1 second just to allow invocation logs to pass to user.
|
# Sleep for 1 second just to allow invocation logs to pass to user.
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
@ -774,9 +765,7 @@ class SmartScanThing(lt.Thing):
|
||||||
return ZipBlob.from_file(zip_fname)
|
return ZipBlob.from_file(zip_fname)
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def stitch_all_scans(
|
def stitch_all_scans(self, logger: lt.deps.InvocationLogger) -> None:
|
||||||
self, logger: lt.deps.InvocationLogger, cancel: lt.deps.CancelHook
|
|
||||||
) -> None:
|
|
||||||
"""Check the list of scans, and stitch any that don't have a DZI associated with it.
|
"""Check the list of scans, and stitch any that don't have a DZI associated with it.
|
||||||
|
|
||||||
:raises RuntimeError: if the microscope is currently running a scan
|
:raises RuntimeError: if the microscope is currently running a scan
|
||||||
|
|
@ -786,4 +775,4 @@ class SmartScanThing(lt.Thing):
|
||||||
raise RuntimeError("Can't stitch previous scans while a scan is ongoing")
|
raise RuntimeError("Can't stitch previous scans while a scan is ongoing")
|
||||||
for scan in self._get_all_scan_info():
|
for scan in self._get_all_scan_info():
|
||||||
if scan.dzi is None:
|
if scan.dzi is None:
|
||||||
self.stitch_scan(logger=logger, cancel=cancel, scan_name=scan.name)
|
self.stitch_scan(logger=logger, scan_name=scan.name)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue