From cd311f08bf972ee773bd26face8f037035bf1a9d Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 6 Aug 2025 17:47:33 +0100 Subject: [PATCH] Update stiching to differentiate bwtween stitchers that use a thread and those that block --- src/openflexure_microscope_server/stitching.py | 18 +++++++----------- .../things/smart_scan.py | 2 +- tests/test_stitching.py | 9 +++------ 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/openflexure_microscope_server/stitching.py b/src/openflexure_microscope_server/stitching.py index ba423941..54b367f9 100644 --- a/src/openflexure_microscope_server/stitching.py +++ b/src/openflexure_microscope_server/stitching.py @@ -44,7 +44,12 @@ def validate_command(cmd: list[str]): class BaseStitcher: - """A base stitching class for all stitchers. Don't initialise this directly.""" + """A base stitching class for all stitchers. Don't initialise this directly. + + The base class has no way to run the command. Child classes should either implement + a ``start``, ``running``, and ``wait`` methods if they stitch in a thread, or ``run`` + if they stitch in this thread and return once complete. + """ def __init__(self, images_dir: str, *, overlap: float, correlation_resize: float): """Initialise a stitcher. @@ -104,15 +109,6 @@ class BaseStitcher: "Invalid directory path: Contains unsafe characters." ) - def start(self) -> None: - """Start stitching a stitching process. - - This should be overridden by any child class. - """ - raise NotImplementedError( - "Child stitchers should implement their own ``start`` method." - ) - class PreviewStitcher(BaseStitcher): """A stitcher for stitching an ongoing scan in preview mode. @@ -262,7 +258,7 @@ class FinalStitcher(BaseStitcher): ) return overlap, correlation_resize - def start( + def run( self, cancel: lt.deps.CancelHook, ) -> None: diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 2af113ac..c4206bcc 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -715,7 +715,7 @@ class SmartScanThing(lt.Thing): ) try: # start the final stitch, providing the cancel hook to allow aborting - final_stitcher.start(cancel) + final_stitcher.run(cancel) except lt.exceptions.InvocationCancelledError: # Sleep for 1 second just to allow invocation logs to pass to user. time.sleep(1) diff --git a/tests/test_stitching.py b/tests/test_stitching.py index 13d1f6a8..75d5ad98 100644 --- a/tests/test_stitching.py +++ b/tests/test_stitching.py @@ -54,9 +54,6 @@ def test_base_stitcher(): ] stitcher = BaseStitcher(FAKE_DIR, overlap=overlap, correlation_resize=0.5) assert stitcher.command == expected_command - # check that a BaseStitcher can't start - with pytest.raises(NotImplementedError): - stitcher.start() def test_preview_stitcher_command(): @@ -227,7 +224,7 @@ def test_final_stitching_command(caplog, mocker): FAKE_DIR, logger=LOGGER, overlap=0.1, correlation_resize=0.5 ) # For the final stitcher it will always complete before returning. - stitcher.start(cancel=lt.deps.CancelHook(id=uuid.uuid4())) + stitcher.run(cancel=lt.deps.CancelHook(id=uuid.uuid4())) # The mock command logs the inputs (but not the initial command) and the # stitcher logs # "Stitching complete" when it ends. assert len(caplog.records) == len(FINAL_EXPECTED_COMMAND) @@ -250,7 +247,7 @@ def test_final_stitching_command_cancelled(caplog, mocker): cancel_hook = lt.deps.CancelHook(id=uuid.uuid4()) # Start stitching in a thread. - thread = threading.Thread(target=stitcher.start, kwargs={"cancel": cancel_hook}) + thread = threading.Thread(target=stitcher.run, kwargs={"cancel": cancel_hook}) thread.start() # Sleep long enough for at least 1 log. sleep(0.5) @@ -273,4 +270,4 @@ def test_final_stitching_command_error(caplog, mocker): # than echo. stitcher._extra_args = ["ERROR"] with pytest.raises(ChildProcessError): - stitcher.start(cancel=lt.deps.CancelHook(id=uuid.uuid4())) + stitcher.run(cancel=lt.deps.CancelHook(id=uuid.uuid4()))