Repository: comic/grand-challenge.org

Issue:
Images converted to tiff can get wrong image spacing.
Images that are converted to tif files can get incorrect image spacing during conversion. Problem was found with certain svs files, where openslide found certain mpp propreties (micron per pixel) but these were not used to set the tiff tags ResolutionX and ResolutionY, resulting in these properties having a value of 1.

When used in algorithms that expect a certain image spacing, these images are then rejected.



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/cases/image_builders/tiff.py b/app/grandchallenge/cases/image_builders/tiff.py
--- a/app/grandchallenge/cases/image_builders/tiff.py
+++ b/app/grandchallenge/cases/image_builders/tiff.py
@@ -327,8 +327,14 @@ def _convert_to_tiff(*, path: Path, pk: UUID, converter) -> Path:
         str(path.absolute()), access="sequential"
     )
 
-    converter.Image.write_to_file(
-        image,
+    # correct xres and yres if they have default value of 1
+    # can be removed once updated to VIPS 8.10
+    if image.get("xres") == 1 and "openslide.mpp-x" in image.get_fields():
+        x_res = 1000.0 / float(image.get("openslide.mpp-x"))
+        y_res = 1000.0 / float(image.get("openslide.mpp-y"))
+        image = image.copy(xres=x_res, yres=y_res)
+
+    image.write_to_file(
         str(new_file_name.absolute()),
         tile=True,
         pyramid=True,


Test patch:
diff --git a/app/tests/cases_tests/test_tiff.py b/app/tests/cases_tests/test_tiff.py
--- a/app/tests/cases_tests/test_tiff.py
+++ b/app/tests/cases_tests/test_tiff.py
@@ -1,6 +1,7 @@
 import os
 import shutil
 from pathlib import Path
+from unittest.mock import MagicMock, Mock
 from uuid import uuid4
 
 import pytest
@@ -10,6 +11,7 @@
 from pytest import approx
 from tifffile import tifffile
 
+
 from grandchallenge.cases.image_builders.tiff import (
     GrandChallengeTiffFile,
     _convert_to_tiff,
@@ -26,28 +28,6 @@
 from tests.cases_tests import RESOURCE_PATH
 
 
-class MockConverter:
-    def __init__(self):
-        pass
-
-    class Image:
-        def __init__(self):
-            pass
-
-        def new_from_file(*args, access):  # noqa B902
-            return None
-
-        def write_to_file(
-            *args,  # noqa B902
-            tile,  # noqa N805
-            pyramid,
-            bigtiff,
-            compression,
-            Q,  # noqa N803
-        ):
-            return None
-
-
 @pytest.mark.parametrize(
     "color_space_string, expected",
     [
@@ -346,7 +326,27 @@ def test_handle_complex_files(tmpdir_factory):
     temp_dir = Path(tmpdir_factory.mktemp("temp") / "resources")
     shutil.copytree(RESOURCE_PATH / "complex_tiff", temp_dir)
     files = [Path(d[0]).joinpath(f) for d in os.walk(temp_dir) for f in d[2]]
-    gc_list, errors = _load_gc_files(files=files, converter=MockConverter)
+
+    # set up mock object to mock pyvips
+    properties = {
+        "xres": 1,
+        "yres": 1,
+        "openslide.mpp-x": 0.2525,
+        "openslide.mpp-y": 0.2525,
+    }
+
+    mock_converter = MagicMock(pyvips)
+    mock_image = mock_converter.Image.new_from_file.return_value
+    mock_image.get = Mock(return_value=1)
+    mock_image.get_fields = Mock(return_value=properties)
+
+    gc_list, errors = _load_gc_files(files=files, converter=mock_converter)
+    mock_image.copy.assert_called()
+    assert "xres" in mock_image.copy.call_args[1]
+    assert (
+        pyvips.base.version(0) == 8 and pyvips.base.version(1) < 10
+    ), "Remove work-around calculation of xres and yres in _convert_to_tiff function."
+
     assert len(gc_list) == 2
     all_associated_files = []
     for gc in gc_list: