Skip to content
Snippets Groups Projects
Commit 9fbb705a authored by Manon Blanco's avatar Manon Blanco Committed by Yoann Schneider
Browse files

Allow a worker to specify the needed size of the image

parent b6a82f6c
No related branches found
Tags 0.3.1-beta1
1 merge request!395Allow a worker to specify the needed size of the image
Pipeline #131990 passed
...@@ -102,14 +102,26 @@ class CachedElement(Model): ...@@ -102,14 +102,26 @@ class CachedElement(Model):
database = db database = db
table_name = "elements" table_name = "elements"
def open_image(self, *args, max_size: Optional[int] = None, **kwargs) -> Image: def open_image(
self,
*args,
max_width: Optional[int] = None,
max_height: Optional[int] = None,
**kwargs,
) -> Image:
""" """
Open this element's image as a Pillow image. Open this element's image as a Pillow image.
This does not crop the image to the element's polygon. This does not crop the image to the element's polygon.
IIIF servers with maxWidth, maxHeight or maxArea restrictions on image size are not supported. IIIF servers with maxWidth, maxHeight or maxArea restrictions on image size are not supported.
Warns:
----
If both, ``max_width`` and ``max_height`` are set, the image ratio is not preserved.
:param *args: Positional arguments passed to [arkindex_worker.image.open_image][] :param *args: Positional arguments passed to [arkindex_worker.image.open_image][]
:param max_size: Subresolution of the image. :param max_width: The maximum width of the image.
:param max_height: The maximum height of the image.
:param **kwargs: Keyword arguments passed to [arkindex_worker.image.open_image][] :param **kwargs: Keyword arguments passed to [arkindex_worker.image.open_image][]
:raises ValueError: When this element does not have an image ID or a polygon. :raises ValueError: When this element does not have an image ID or a polygon.
:return: A Pillow image. :return: A Pillow image.
...@@ -129,7 +141,7 @@ class CachedElement(Model): ...@@ -129,7 +141,7 @@ class CachedElement(Model):
else: else:
box = "full" box = "full"
if max_size is None: if max_width is None and max_height is None:
resize = "full" resize = "full"
else: else:
# Do not resize for polygons that do not exactly match the images # Do not resize for polygons that do not exactly match the images
...@@ -141,14 +153,12 @@ class CachedElement(Model): ...@@ -141,14 +153,12 @@ class CachedElement(Model):
resize = "full" resize = "full"
# Do not resize when the image is below the maximum size # Do not resize when the image is below the maximum size
elif self.image.width <= max_size and self.image.height <= max_size: elif (max_width is None or self.image.width <= max_width) and (
max_height is None or self.image.height <= max_height
):
resize = "full" resize = "full"
else: else:
ratio = max_size / max(self.image.width, self.image.height) resize = f"{max_width or ''},{max_height or ''}"
new_width, new_height = int(self.image.width * ratio), int(
self.image.height * ratio
)
resize = f"{new_width},{new_height}"
url = self.image.url url = self.image.url
if not url.endswith("/"): if not url.endswith("/"):
......
...@@ -124,9 +124,10 @@ class Element(MagicDict): ...@@ -124,9 +124,10 @@ class Element(MagicDict):
def open_image( def open_image(
self, self,
*args, *args,
max_size: Optional[int] = None, max_width: Optional[int] = None,
max_height: Optional[int] = None,
use_full_image: Optional[bool] = False, use_full_image: Optional[bool] = False,
**kwargs **kwargs,
) -> Image: ) -> Image:
""" """
Open this element's image using Pillow, rotating and mirroring it according Open this element's image using Pillow, rotating and mirroring it according
...@@ -149,7 +150,13 @@ class Element(MagicDict): ...@@ -149,7 +150,13 @@ class Element(MagicDict):
``rotation_angle=0, mirrored=False`` as keyword arguments. ``rotation_angle=0, mirrored=False`` as keyword arguments.
:param max_size: The maximum size of the requested image. Warns:
----
If both, ``max_width`` and ``max_height`` are set, the image ratio is not preserved.
:param max_width: The maximum width of the image.
:param max_height: The maximum height of the image.
:param use_full_image: Ignore the ``zone.polygon`` and always :param use_full_image: Ignore the ``zone.polygon`` and always
retrieve the image without cropping. retrieve the image without cropping.
:param *args: Positional arguments passed to [arkindex_worker.image.open_image][]. :param *args: Positional arguments passed to [arkindex_worker.image.open_image][].
...@@ -172,12 +179,14 @@ class Element(MagicDict): ...@@ -172,12 +179,14 @@ class Element(MagicDict):
raise ValueError("Element {} has no zone".format(self.id)) raise ValueError("Element {} has no zone".format(self.id))
if self.requires_tiles: if self.requires_tiles:
if max_size is None: if max_width is None and max_height is None:
return download_tiles(self.zone.image.url) return download_tiles(self.zone.image.url)
else: else:
raise NotImplementedError raise NotImplementedError
if max_size is not None: if max_width is None and max_height is None:
resize = "full"
else:
bounding_box = polygon_bounding_box(self.zone.polygon) bounding_box = polygon_bounding_box(self.zone.polygon)
original_size = {"w": self.zone.image.width, "h": self.zone.image.height} original_size = {"w": self.zone.image.width, "h": self.zone.image.height}
# No resizing if the element is smaller than the image. # No resizing if the element is smaller than the image.
...@@ -191,15 +200,13 @@ class Element(MagicDict): ...@@ -191,15 +200,13 @@ class Element(MagicDict):
+ "downloading full size image." + "downloading full size image."
) )
# No resizing if the image is smaller than the wanted size. # No resizing if the image is smaller than the wanted size.
elif original_size["w"] <= max_size and original_size["h"] <= max_size: elif (max_width is None or original_size["w"] <= max_width) and (
max_height is None or original_size["h"] <= max_height
):
resize = "full" resize = "full"
# Resizing if the image is bigger than the wanted size. # Resizing if the image is bigger than the wanted size.
else: else:
ratio = max_size / max(original_size.values()) resize = f"{max_width or ''},{max_height or ''}"
new_width, new_height = [int(x * ratio) for x in original_size.values()]
resize = "{},{}".format(new_width, new_height)
else:
resize = "full"
if use_full_image: if use_full_image:
url = self.image_url(resize) url = self.image_url(resize)
...@@ -212,7 +219,7 @@ class Element(MagicDict): ...@@ -212,7 +219,7 @@ class Element(MagicDict):
*args, *args,
rotation_angle=self.rotation_angle, rotation_angle=self.rotation_angle,
mirrored=self.mirrored, mirrored=self.mirrored,
**kwargs **kwargs,
) )
except HTTPError as e: except HTTPError as e:
if ( if (
......
...@@ -148,10 +148,20 @@ def test_check_version_same_version(tmp_path): ...@@ -148,10 +148,20 @@ def test_check_version_same_version(tmp_path):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"image_width,image_height,polygon_x,polygon_y,polygon_width,polygon_height,max_size,expected_url", "image_width,image_height,polygon_x,polygon_y,polygon_width,polygon_height,max_width,max_height,expected_url",
[ [
# No max_size: no resize # No max_size: no resize
(400, 600, 0, 0, 400, 600, None, "http://something/full/full/0/default.jpg"), (
400,
600,
0,
0,
400,
600,
None,
None,
"http://something/full/full/0/default.jpg",
),
# No max_size: resize on bbox # No max_size: resize on bbox
( (
400, 400,
...@@ -161,6 +171,7 @@ def test_check_version_same_version(tmp_path): ...@@ -161,6 +171,7 @@ def test_check_version_same_version(tmp_path):
200, 200,
100, 100,
None, None,
None,
"http://something/0,0,200,100/full/0/default.jpg", "http://something/0,0,200,100/full/0/default.jpg",
), ),
( (
...@@ -171,12 +182,43 @@ def test_check_version_same_version(tmp_path): ...@@ -171,12 +182,43 @@ def test_check_version_same_version(tmp_path):
200, 200,
100, 100,
None, None,
None,
"http://something/50,50,200,100/full/0/default.jpg", "http://something/50,50,200,100/full/0/default.jpg",
), ),
# max_size equal to the image size, no resize # max_size equal to the image size, no resize
(400, 600, 0, 0, 400, 600, 600, "http://something/full/full/0/default.jpg"), (
(600, 400, 0, 0, 600, 400, 600, "http://something/full/full/0/default.jpg"), 400,
(400, 400, 0, 0, 400, 400, 400, "http://something/full/full/0/default.jpg"), 600,
0,
0,
400,
600,
400,
None,
"http://something/full/full/0/default.jpg",
),
(
600,
400,
0,
0,
600,
400,
None,
400,
"http://something/full/full/0/default.jpg",
),
(
400,
400,
0,
0,
400,
400,
400,
400,
"http://something/full/full/0/default.jpg",
),
( (
400, 400,
400, 400,
...@@ -185,11 +227,32 @@ def test_check_version_same_version(tmp_path): ...@@ -185,11 +227,32 @@ def test_check_version_same_version(tmp_path):
200, 200,
100, 100,
200, 200,
100,
"http://something/50,50,200,100/full/0/default.jpg", "http://something/50,50,200,100/full/0/default.jpg",
), ),
# max_size is smaller than the image, resize # max_size is smaller than the image, resize
(400, 600, 0, 0, 400, 600, 400, "http://something/full/266,400/0/default.jpg"), (
(600, 400, 0, 0, 600, 400, 400, "http://something/full/400,266/0/default.jpg"), 400,
600,
0,
0,
400,
600,
None,
400,
"http://something/full/,400/0/default.jpg",
),
(
600,
400,
0,
0,
600,
400,
400,
None,
"http://something/full/400,/0/default.jpg",
),
( (
400, 400,
600, 600,
...@@ -198,6 +261,7 @@ def test_check_version_same_version(tmp_path): ...@@ -198,6 +261,7 @@ def test_check_version_same_version(tmp_path):
200, 200,
600, 600,
400, 400,
600,
"http://something/0,0,200,600/full/0/default.jpg", "http://something/0,0,200,600/full/0/default.jpg",
), ),
( (
...@@ -208,13 +272,54 @@ def test_check_version_same_version(tmp_path): ...@@ -208,13 +272,54 @@ def test_check_version_same_version(tmp_path):
200, 200,
600, 600,
400, 400,
600,
"http://something/50,50,200,600/full/0/default.jpg", "http://something/50,50,200,600/full/0/default.jpg",
), ),
(400, 400, 0, 0, 400, 400, 200, "http://something/full/200,200/0/default.jpg"), (
400,
400,
0,
0,
400,
400,
200,
200,
"http://something/full/200,200/0/default.jpg",
),
# max_size above the image size, no resize # max_size above the image size, no resize
(400, 600, 0, 0, 400, 600, 800, "http://something/full/full/0/default.jpg"), (
(600, 400, 0, 0, 600, 400, 800, "http://something/full/full/0/default.jpg"), 400,
(400, 400, 0, 0, 400, 400, 800, "http://something/full/full/0/default.jpg"), 600,
0,
0,
400,
600,
800,
None,
"http://something/full/full/0/default.jpg",
),
(
600,
400,
0,
0,
600,
400,
None,
800,
"http://something/full/full/0/default.jpg",
),
(
400,
400,
0,
0,
400,
400,
800,
800,
"http://something/full/full/0/default.jpg",
),
( (
400, 400,
400, 400,
...@@ -223,6 +328,7 @@ def test_check_version_same_version(tmp_path): ...@@ -223,6 +328,7 @@ def test_check_version_same_version(tmp_path):
200, 200,
100, 100,
800, 800,
800,
"http://something/50,50,200,100/full/0/default.jpg", "http://something/50,50,200,100/full/0/default.jpg",
), ),
], ],
...@@ -235,7 +341,8 @@ def test_element_open_image( ...@@ -235,7 +341,8 @@ def test_element_open_image(
polygon_y, polygon_y,
polygon_width, polygon_width,
polygon_height, polygon_height,
max_size, max_width,
max_height,
expected_url, expected_url,
): ):
open_mock = mocker.patch( open_mock = mocker.patch(
...@@ -261,7 +368,7 @@ def test_element_open_image( ...@@ -261,7 +368,7 @@ def test_element_open_image(
], ],
) )
assert elt.open_image(max_size=max_size) == "an image!" assert elt.open_image(max_width=max_width, max_height=max_height) == "an image!"
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
expected_url, mirrored=False, rotation_angle=0 expected_url, mirrored=False, rotation_angle=0
......
...@@ -94,7 +94,7 @@ def test_open_image_resize_portrait(mocker): ...@@ -94,7 +94,7 @@ def test_open_image_resize_portrait(mocker):
} }
) )
# Resize = original size # Resize = original size
assert elt.open_image(max_size=600, use_full_image=True) == "an image!" assert elt.open_image(max_height=600, use_full_image=True) == "an image!"
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -102,15 +102,15 @@ def test_open_image_resize_portrait(mocker): ...@@ -102,15 +102,15 @@ def test_open_image_resize_portrait(mocker):
mirrored=False, mirrored=False,
) )
# Resize = smaller height # Resize = smaller height
assert elt.open_image(max_size=400, use_full_image=True) == "an image!" assert elt.open_image(max_height=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 2 assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/266,400/0/default.jpg", "http://something/full/,400/0/default.jpg",
rotation_angle=0, rotation_angle=0,
mirrored=False, mirrored=False,
) )
# Resize = bigger height # Resize = bigger height
assert elt.open_image(max_size=800, use_full_image=True) == "an image!" assert elt.open_image(max_height=800, use_full_image=True) == "an image!"
assert open_mock.call_count == 3 assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -138,7 +138,7 @@ def test_open_image_resize_partial_element(mocker): ...@@ -138,7 +138,7 @@ def test_open_image_resize_partial_element(mocker):
"mirrored": False, "mirrored": False,
} }
) )
assert elt.open_image(max_size=400, use_full_image=True) == "an image!" assert elt.open_image(max_height=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -167,7 +167,7 @@ def test_open_image_resize_landscape(mocker): ...@@ -167,7 +167,7 @@ def test_open_image_resize_landscape(mocker):
} }
) )
# Resize = original size # Resize = original size
assert elt.open_image(max_size=600, use_full_image=True) == "an image!" assert elt.open_image(max_width=600, use_full_image=True) == "an image!"
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -175,15 +175,15 @@ def test_open_image_resize_landscape(mocker): ...@@ -175,15 +175,15 @@ def test_open_image_resize_landscape(mocker):
mirrored=False, mirrored=False,
) )
# Resize = smaller width # Resize = smaller width
assert elt.open_image(max_size=400, use_full_image=True) == "an image!" assert elt.open_image(max_width=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 2 assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/400,266/0/default.jpg", "http://something/full/400,/0/default.jpg",
rotation_angle=0, rotation_angle=0,
mirrored=False, mirrored=False,
) )
# Resize = bigger width # Resize = bigger width
assert elt.open_image(max_size=800, use_full_image=True) == "an image!" assert elt.open_image(max_width=800, use_full_image=True) == "an image!"
assert open_mock.call_count == 3 assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -212,7 +212,14 @@ def test_open_image_resize_square(mocker): ...@@ -212,7 +212,14 @@ def test_open_image_resize_square(mocker):
} }
) )
# Resize = original size # Resize = original size
assert elt.open_image(max_size=400, use_full_image=True) == "an image!" assert (
elt.open_image(
max_width=400,
max_height=400,
use_full_image=True,
)
== "an image!"
)
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -220,7 +227,14 @@ def test_open_image_resize_square(mocker): ...@@ -220,7 +227,14 @@ def test_open_image_resize_square(mocker):
mirrored=False, mirrored=False,
) )
# Resize = smaller # Resize = smaller
assert elt.open_image(max_size=200, use_full_image=True) == "an image!" assert (
elt.open_image(
max_width=200,
max_height=200,
use_full_image=True,
)
== "an image!"
)
assert open_mock.call_count == 2 assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/200,200/0/default.jpg", "http://something/full/200,200/0/default.jpg",
...@@ -228,7 +242,14 @@ def test_open_image_resize_square(mocker): ...@@ -228,7 +242,14 @@ def test_open_image_resize_square(mocker):
mirrored=False, mirrored=False,
) )
# Resize = bigger # Resize = bigger
assert elt.open_image(max_size=800, use_full_image=True) == "an image!" assert (
elt.open_image(
max_width=800,
max_height=800,
use_full_image=True,
)
== "an image!"
)
assert open_mock.call_count == 3 assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg", "http://something/full/full/0/default.jpg",
...@@ -251,7 +272,7 @@ def test_open_image_resize_tiles(mocker): ...@@ -251,7 +272,7 @@ def test_open_image_resize_tiles(mocker):
} }
) )
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
elt.open_image(max_size=400) elt.open_image(max_width=400)
def test_open_image_requires_zone(): def test_open_image_requires_zone():
...@@ -364,10 +385,10 @@ def test_open_image_resize_use_full_image_false(mocker): ...@@ -364,10 +385,10 @@ def test_open_image_resize_use_full_image_false(mocker):
} }
) )
# Resize = smaller # Resize = smaller
assert elt.open_image(max_size=200, use_full_image=False) == "an image!" assert elt.open_image(max_height=200, use_full_image=False) == "an image!"
assert open_mock.call_count == 1 assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call( assert open_mock.call_args == mocker.call(
"http://zoneurl/0,0,400,600/133,200/0/default.jpg", "http://zoneurl/0,0,400,600/,200/0/default.jpg",
rotation_angle=0, rotation_angle=0,
mirrored=False, mirrored=False,
) )
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment