Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • workers/base-worker
1 result
Show changes
Commits on Source (5)
......@@ -55,6 +55,8 @@ class CachedElement(Model):
type = CharField(max_length=50)
image = ForeignKeyField(CachedImage, backref="elements", null=True)
polygon = JSONField(null=True)
rotation_angle = IntegerField(default=0)
mirrored = BooleanField(default=False)
initial = BooleanField(default=False)
worker_version_id = UUIDField(null=True)
......@@ -79,7 +81,7 @@ class CachedElement(Model):
bounding_box.width != self.image.width
or bounding_box.height != self.image.height
):
box = f"{bounding_box.x},{bounding_box.y},{bounding_box.x + bounding_box.width},{bounding_box.y + bounding_box.height}"
box = f"{bounding_box.x},{bounding_box.y},{bounding_box.width},{bounding_box.height}"
else:
box = "full"
......@@ -108,7 +110,13 @@ class CachedElement(Model):
if not url.endswith("/"):
url += "/"
return open_image(f"{url}{box}/{resize}/0/default.jpg", *args, **kwargs)
return open_image(
f"{url}{box}/{resize}/0/default.jpg",
*args,
rotation_angle=self.rotation_angle,
mirrored=self.mirrored,
**kwargs,
)
class CachedTranscription(Model):
......
......@@ -21,7 +21,7 @@ DOWNLOAD_TIMEOUT = (30, 60)
BoundingBox = namedtuple("BoundingBox", ["x", "y", "width", "height"])
def open_image(path, mode="RGB"):
def open_image(path, mode="RGB", rotation_angle=0, mirrored=False):
"""
Open an image from a path or a URL
"""
......@@ -40,6 +40,12 @@ def open_image(path, mode="RGB"):
if image.mode != mode:
image = image.convert(mode)
if mirrored:
image = image.transpose(Image.FLIP_LEFT_RIGHT)
if rotation_angle:
image = image.rotate(-rotation_angle, expand=True)
return image
......
......@@ -135,11 +135,19 @@ class Element(MagicDict):
else:
resize = "full"
if use_full_image:
url = self.image_url(resize)
else:
url = self.resize_zone_url(resize)
try:
if use_full_image:
return open_image(self.image_url(resize), *args, **kwargs)
else:
return open_image(self.resize_zone_url(resize), *args, **kwargs)
return open_image(
url,
*args,
rotation_angle=self.rotation_angle,
mirrored=self.mirrored,
**kwargs
)
except HTTPError as e:
if (
self.zone.image.get("s3_url") is not None
......
......@@ -62,7 +62,7 @@ class BaseWorker(object):
logger.info(f"Worker will use {self.work_dir} as working directory")
self.process_information = None
self.user_configuration = None
self.user_configuration = {}
self.support_cache = support_cache
# use_cache will be updated in configure() if the cache is supported and if there
# is at least one available sqlite database either given or in the parent tasks
......
arkindex-client==1.0.6
peewee==3.14.4
Pillow==8.3.1
Pillow==8.3.2
python-gitlab==2.7.1
python-gnupg==0.4.7
sh==1.14.2
......
pytest==6.2.4
pytest==6.2.5
pytest-mock==3.6.1
pytest-responses==0.5.0
tests/data/mirrored_image.jpg

14.4 KiB

tests/data/rotated_image.jpg

21.9 KiB

tests/data/rotated_mirrored_image.jpg

21.8 KiB

......@@ -136,7 +136,7 @@ def test_configure_dev_mode(
assert worker.process_information is None
assert worker.worker_version_id == "12341234-1234-1234-1234-123412341234"
assert worker.is_read_only is True
assert worker.user_configuration is None
assert worker.user_configuration == {}
def test_configure_worker_run(mocker, monkeypatch, responses, mock_config_api):
......
......@@ -55,7 +55,7 @@ def test_create_tables(tmp_path):
create_tables()
expected_schema = """CREATE TABLE "classifications" ("id" TEXT NOT NULL PRIMARY KEY, "element_id" TEXT NOT NULL, "class_name" TEXT NOT NULL, "confidence" REAL NOT NULL, "state" VARCHAR(10) NOT NULL, "worker_version_id" TEXT NOT NULL, FOREIGN KEY ("element_id") REFERENCES "elements" ("id"))
CREATE TABLE "elements" ("id" TEXT NOT NULL PRIMARY KEY, "parent_id" TEXT, "type" VARCHAR(50) NOT NULL, "image_id" TEXT, "polygon" text, "initial" INTEGER NOT NULL, "worker_version_id" TEXT, FOREIGN KEY ("image_id") REFERENCES "images" ("id"))
CREATE TABLE "elements" ("id" TEXT NOT NULL PRIMARY KEY, "parent_id" TEXT, "type" VARCHAR(50) NOT NULL, "image_id" TEXT, "polygon" text, "rotation_angle" INTEGER NOT NULL, "mirrored" INTEGER NOT NULL, "initial" INTEGER NOT NULL, "worker_version_id" TEXT, FOREIGN KEY ("image_id") REFERENCES "images" ("id"))
CREATE TABLE "entities" ("id" TEXT NOT NULL PRIMARY KEY, "type" VARCHAR(50) NOT NULL, "name" TEXT NOT NULL, "validated" INTEGER NOT NULL, "metas" text, "worker_version_id" TEXT NOT NULL)
CREATE TABLE "images" ("id" TEXT NOT NULL PRIMARY KEY, "width" INTEGER NOT NULL, "height" INTEGER NOT NULL, "url" TEXT NOT NULL)
CREATE TABLE "transcription_entities" ("transcription_id" TEXT NOT NULL, "entity_id" TEXT NOT NULL, "offset" INTEGER NOT NULL CHECK (offset >= 0), "length" INTEGER NOT NULL CHECK (length > 0), "worker_version_id" TEXT NOT NULL, PRIMARY KEY ("transcription_id", "entity_id"), FOREIGN KEY ("transcription_id") REFERENCES "transcriptions" ("id"), FOREIGN KEY ("entity_id") REFERENCES "entities" ("id"))
......@@ -74,31 +74,91 @@ CREATE TABLE "transcriptions" ("id" TEXT NOT NULL PRIMARY KEY, "element_id" TEXT
@pytest.mark.parametrize(
"image_width,image_height,polygon_width,polygon_height,max_size,expected_url",
"image_width,image_height,polygon_x,polygon_y,polygon_width,polygon_height,max_size,expected_url",
[
# No max_size: no resize
(400, 600, 400, 600, None, "http://something/full/full/0/default.jpg"),
(400, 600, 0, 0, 400, 600, None, "http://something/full/full/0/default.jpg"),
# No max_size: resize on bbox
(400, 600, 200, 100, None, "http://something/0,0,200,100/full/0/default.jpg"),
(
400,
600,
0,
0,
200,
100,
None,
"http://something/0,0,200,100/full/0/default.jpg",
),
(
400,
600,
50,
50,
200,
100,
None,
"http://something/50,50,200,100/full/0/default.jpg",
),
# max_size equal to the image size, no resize
(400, 600, 400, 600, 600, "http://something/full/full/0/default.jpg"),
(600, 400, 600, 400, 600, "http://something/full/full/0/default.jpg"),
(400, 400, 400, 400, 400, "http://something/full/full/0/default.jpg"),
(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, 0, 0, 400, 400, 400, "http://something/full/full/0/default.jpg"),
(
400,
400,
50,
50,
200,
100,
200,
"http://something/50,50,200,100/full/0/default.jpg",
),
# max_size is smaller than the image, resize
(400, 600, 400, 600, 400, "http://something/full/266,400/0/default.jpg"),
(400, 600, 200, 600, 400, "http://something/0,0,200,600/full/0/default.jpg"),
(600, 400, 600, 400, 400, "http://something/full/400,266/0/default.jpg"),
(400, 400, 400, 400, 200, "http://something/full/200,200/0/default.jpg"),
(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,
200,
600,
400,
"http://something/0,0,200,600/full/0/default.jpg",
),
(
400,
600,
50,
50,
200,
600,
400,
"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"),
# max_size above the image size, no resize
(400, 600, 400, 600, 800, "http://something/full/full/0/default.jpg"),
(600, 400, 600, 400, 800, "http://something/full/full/0/default.jpg"),
(400, 400, 400, 400, 800, "http://something/full/full/0/default.jpg"),
(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, 0, 0, 400, 400, 800, "http://something/full/full/0/default.jpg"),
(
400,
400,
50,
50,
200,
100,
800,
"http://something/50,50,200,100/full/0/default.jpg",
),
],
)
def test_element_open_image(
mocker,
image_width,
image_height,
polygon_x,
polygon_y,
polygon_width,
polygon_height,
max_size,
......@@ -119,17 +179,19 @@ def test_element_open_image(
type="element",
image=image,
polygon=[
[0, 0],
[polygon_width, 0],
[polygon_width, polygon_height],
[0, polygon_height],
[0, 0],
[polygon_x, polygon_y],
[polygon_x + polygon_width, polygon_y],
[polygon_x + polygon_width, polygon_y + polygon_height],
[polygon_x, polygon_y + polygon_height],
[polygon_x, polygon_y],
],
)
assert elt.open_image(max_size=max_size) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(expected_url)
assert open_mock.call_args == mocker.call(
expected_url, mirrored=False, rotation_angle=0
)
def test_element_open_image_requires_image():
......
......@@ -59,13 +59,17 @@ def test_open_image(mocker):
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]],
}
}
},
"rotation_angle": 0,
"mirrored": False,
},
)
assert elt.open_image(use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -83,26 +87,34 @@ def test_open_image_resize_portrait(mocker):
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [400, 0], [400, 600], [0, 600], [0, 0]],
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
# Resize = original size
assert elt.open_image(max_size=600, use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = smaller height
assert elt.open_image(max_size=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call(
"http://something/full/266,400/0/default.jpg"
"http://something/full/266,400/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = bigger height
assert elt.open_image(max_size=800, use_full_image=True) == "an image!"
assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -120,13 +132,17 @@ def test_open_image_resize_partial_element(mocker):
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [200, 0], [200, 600], [0, 600], [0, 0]],
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
assert elt.open_image(max_size=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -144,26 +160,34 @@ def test_open_image_resize_landscape(mocker):
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [600, 0], [600, 400], [0, 400], [0, 0]],
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
# Resize = original size
assert elt.open_image(max_size=600, use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = smaller width
assert elt.open_image(max_size=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call(
"http://something/full/400,266/0/default.jpg"
"http://something/full/400,266/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = bigger width
assert elt.open_image(max_size=800, use_full_image=True) == "an image!"
assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -181,26 +205,34 @@ def test_open_image_resize_square(mocker):
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [400, 0], [400, 400], [0, 400], [0, 0]],
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
# Resize = original size
assert elt.open_image(max_size=400, use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = smaller
assert elt.open_image(max_size=200, use_full_image=True) == "an image!"
assert open_mock.call_count == 2
assert open_mock.call_args == mocker.call(
"http://something/full/200,200/0/default.jpg"
"http://something/full/200,200/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
# Resize = bigger
assert elt.open_image(max_size=800, use_full_image=True) == "an image!"
assert open_mock.call_count == 3
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg"
"http://something/full/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -232,11 +264,17 @@ def test_open_image_s3(mocker):
"arkindex_worker.models.open_image", return_value="an image!"
)
elt = Element(
{"zone": {"image": {"url": "http://something", "s3_url": "http://s3url"}}}
{
"zone": {"image": {"url": "http://something", "s3_url": "http://s3url"}},
"rotation_angle": 0,
"mirrored": False,
}
)
assert elt.open_image(use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call("http://s3url")
assert open_mock.call_args == mocker.call(
"http://s3url", rotation_angle=0, mirrored=False
)
def test_open_image_s3_retry(mocker):
......@@ -252,6 +290,8 @@ def test_open_image_s3_retry(mocker):
{
"id": "cafe",
"zone": {"image": {"url": "http://something", "s3_url": "http://oldurl"}},
"rotation_angle": 0,
"mirrored": False,
}
)
......@@ -270,6 +310,8 @@ def test_open_image_s3_retry_once(mocker):
{
"id": "cafe",
"zone": {"image": {"url": "http://something", "s3_url": "http://oldurl"}},
"rotation_angle": 0,
"mirrored": False,
}
)
......@@ -286,13 +328,17 @@ def test_open_image_use_full_image_false(mocker):
"zone": {
"image": {"url": "http://something", "s3_url": "http://s3url"},
"url": "http://zoneurl/0,0,400,600/full/0/default.jpg",
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
assert elt.open_image(use_full_image=False) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://zoneurl/0,0,400,600/full/0/default.jpg"
"http://zoneurl/0,0,400,600/full/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
......@@ -311,14 +357,44 @@ def test_open_image_resize_use_full_image_false(mocker):
},
"polygon": [[0, 0], [400, 0], [400, 600], [0, 600], [0, 0]],
"url": "http://zoneurl/0,0,400,600/full/0/default.jpg",
}
},
"rotation_angle": 0,
"mirrored": False,
}
)
# Resize = smaller
assert elt.open_image(max_size=200, use_full_image=False) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://zoneurl/0,0,400,600/133,200/0/default.jpg"
"http://zoneurl/0,0,400,600/133,200/0/default.jpg",
rotation_angle=0,
mirrored=False,
)
def test_open_image_rotation_mirror(mocker):
open_mock = mocker.patch(
"arkindex_worker.models.open_image", return_value="an image!"
)
elt = Element(
{
"zone": {
"image": {
"url": "http://something",
"server": {"max_width": None, "max_height": None},
},
"polygon": [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]],
},
"rotation_angle": 42,
"mirrored": True,
},
)
assert elt.open_image(use_full_image=True) == "an image!"
assert open_mock.call_count == 1
assert open_mock.call_args == mocker.call(
"http://something/full/full/0/default.jpg",
rotation_angle=42,
mirrored=True,
)
......
......@@ -11,6 +11,9 @@ from arkindex_worker.image import download_tiles, open_image
FIXTURES = Path(__file__).absolute().parent / "data"
TILE = FIXTURES / "test_image.jpg"
FULL_IMAGE = FIXTURES / "tiled_image.jpg"
ROTATED_IMAGE = FIXTURES / "rotated_image.jpg"
MIRRORED_IMAGE = FIXTURES / "mirrored_image.jpg"
ROTATED_MIRRORED_IMAGE = FIXTURES / "rotated_mirrored_image.jpg"
def _root_mean_square(img_a, img_b):
......@@ -127,3 +130,19 @@ def test_open_image(path, is_local, monkeypatch):
assert image.size == (1, 10)
else:
assert image.size == (10, 1)
@pytest.mark.parametrize(
"rotation_angle,mirrored,expected_path",
(
(0, False, TILE),
(45, False, ROTATED_IMAGE),
(0, True, MIRRORED_IMAGE),
(45, True, ROTATED_MIRRORED_IMAGE),
),
)
def test_open_image_rotate_mirror(rotation_angle, mirrored, expected_path):
expected = Image.open(expected_path).convert("RGB")
actual = open_image(str(TILE), rotation_angle=rotation_angle, mirrored=mirrored)
actual.save(f"/tmp/{rotation_angle}_{mirrored}.jpg")
assert _root_mean_square(expected, actual) <= 15.0