diff --git a/src/openflexure_microscope_server/stitching.py b/src/openflexure_microscope_server/stitching.py index 311a00a4..bafeeb18 100644 --- a/src/openflexure_microscope_server/stitching.py +++ b/src/openflexure_microscope_server/stitching.py @@ -23,7 +23,10 @@ from openflexure_microscope_server.utilities import is_path_safe IS_WINDOWS = os.name == "nt" STITCHING_CMD = "openflexure-stitch" -STITCHING_RESOLUTION = (820, 616) + +# The target width and height to correlate images. Used to choose +# a suitable correlation_resize factor in scan_workflows +TARGET_STITCHING_DIMENSION = 700 STITCH_TILE_SIZE = 8192 DEFAULT_OVERLAP = 0.1 diff --git a/src/openflexure_microscope_server/things/scan_workflows.py b/src/openflexure_microscope_server/things/scan_workflows.py index 29d64841..c92900c2 100644 --- a/src/openflexure_microscope_server/things/scan_workflows.py +++ b/src/openflexure_microscope_server/things/scan_workflows.py @@ -24,7 +24,7 @@ from openflexure_microscope_server.scan_planners import ( SmartSpiral, ) from openflexure_microscope_server.stitching import ( - STITCHING_RESOLUTION, + TARGET_STITCHING_DIMENSION, StitchingSettings, ) from openflexure_microscope_server.things.autofocus import ( @@ -261,9 +261,19 @@ class RectGridWorkflow( def _get_stitching_settings_model(self) -> StitchingSettings: """Return a stitching settings model based on current settings.""" + # Use the save resolution and target stitch resolution to choose a unit fraction, + # which makes correlating faster + width, height = self.save_resolution + # Target area in pixels + target_area = TARGET_STITCHING_DIMENSION**2 + # Find N so that (width/N) * (height/N) ~ target_area + # N^2 ~ (width * height) / target_area + downsample_factor = max(1, round((width * height / target_area) ** 0.5)) + correlation_resize = 1 / downsample_factor + return StitchingSettings( overlap=self.overlap, - correlation_resize=STITCHING_RESOLUTION[0] / self.save_resolution[0], + correlation_resize=correlation_resize, ) def _build_scan_settings(self, base_kwargs: dict) -> RectGridSettingModelType: diff --git a/tests/unit_tests/test_scan_workflows.py b/tests/unit_tests/test_scan_workflows.py index 0ffa4a00..0bb7b13c 100644 --- a/tests/unit_tests/test_scan_workflows.py +++ b/tests/unit_tests/test_scan_workflows.py @@ -431,3 +431,34 @@ def test_histo_workflow_settings_ui(histo_workflow, mocker): "equal_distances", ] assert names == expected_names + + +@pytest.mark.parametrize( + ("save_res", "expected_resize"), + [ + ((1400, 1400), 1 / 2), + ((1640, 1232), 1 / 2), + ((760, 750), 1 / 1), + ((1499, 1000), 1 / 2), + ((2250, 1800), 1 / 3), + ((700, 700), 1 / 1), + ], +) +def test_correlation_resize(histo_workflow, save_res, expected_resize): + """Test that scan_workflows chooses a suitable correlation_resize factor. + + correlation_resize is always a unit fraction (1/N, N integer) so that the + downsampled image has an area roughly equal to TARGET_STITCHING_DIMENSION**2. + This ensures stitching correlations are fast, robust, and avoid artefacts. + + The value is found by taking the image area (width * height), dividing by the + target area, taking the square root, and rounding to the nearest integer N. Then + correlation_resize = 1 / N. + """ + histo_workflow.save_resolution = save_res + histo_workflow.overlap = 0.1 + settings = histo_workflow._get_stitching_settings_model() + + assert isinstance(settings, StitchingSettings) + assert settings.correlation_resize == expected_resize + assert 0 < settings.correlation_resize <= 1