arkindex-base-worker 0.3.7rc4__py3-none-any.whl → 0.5.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {arkindex_base_worker-0.3.7rc4.dist-info → arkindex_base_worker-0.5.0a1.dist-info}/METADATA +18 -19
  2. arkindex_base_worker-0.5.0a1.dist-info/RECORD +61 -0
  3. {arkindex_base_worker-0.3.7rc4.dist-info → arkindex_base_worker-0.5.0a1.dist-info}/WHEEL +1 -1
  4. {arkindex_base_worker-0.3.7rc4.dist-info → arkindex_base_worker-0.5.0a1.dist-info}/top_level.txt +2 -0
  5. arkindex_worker/cache.py +1 -1
  6. arkindex_worker/image.py +167 -2
  7. arkindex_worker/models.py +18 -0
  8. arkindex_worker/utils.py +98 -4
  9. arkindex_worker/worker/__init__.py +117 -218
  10. arkindex_worker/worker/base.py +39 -46
  11. arkindex_worker/worker/classification.py +45 -29
  12. arkindex_worker/worker/corpus.py +86 -0
  13. arkindex_worker/worker/dataset.py +89 -26
  14. arkindex_worker/worker/element.py +352 -91
  15. arkindex_worker/worker/entity.py +13 -11
  16. arkindex_worker/worker/image.py +21 -0
  17. arkindex_worker/worker/metadata.py +26 -16
  18. arkindex_worker/worker/process.py +92 -0
  19. arkindex_worker/worker/task.py +5 -4
  20. arkindex_worker/worker/training.py +25 -10
  21. arkindex_worker/worker/transcription.py +89 -68
  22. arkindex_worker/worker/version.py +3 -1
  23. hooks/pre_gen_project.py +3 -0
  24. tests/__init__.py +8 -0
  25. tests/conftest.py +47 -58
  26. tests/test_base_worker.py +212 -12
  27. tests/test_dataset_worker.py +294 -437
  28. tests/test_elements_worker/{test_classifications.py → test_classification.py} +313 -200
  29. tests/test_elements_worker/test_cli.py +3 -11
  30. tests/test_elements_worker/test_corpus.py +168 -0
  31. tests/test_elements_worker/test_dataset.py +106 -157
  32. tests/test_elements_worker/test_element.py +427 -0
  33. tests/test_elements_worker/test_element_create_multiple.py +715 -0
  34. tests/test_elements_worker/test_element_create_single.py +528 -0
  35. tests/test_elements_worker/test_element_list_children.py +969 -0
  36. tests/test_elements_worker/test_element_list_parents.py +530 -0
  37. tests/test_elements_worker/{test_entities.py → test_entity_create.py} +37 -195
  38. tests/test_elements_worker/test_entity_list_and_check.py +160 -0
  39. tests/test_elements_worker/test_image.py +66 -0
  40. tests/test_elements_worker/test_metadata.py +252 -161
  41. tests/test_elements_worker/test_process.py +89 -0
  42. tests/test_elements_worker/test_task.py +8 -18
  43. tests/test_elements_worker/test_training.py +17 -8
  44. tests/test_elements_worker/test_transcription_create.py +873 -0
  45. tests/test_elements_worker/test_transcription_create_with_elements.py +951 -0
  46. tests/test_elements_worker/test_transcription_list.py +450 -0
  47. tests/test_elements_worker/test_version.py +60 -0
  48. tests/test_elements_worker/test_worker.py +578 -293
  49. tests/test_image.py +542 -209
  50. tests/test_merge.py +1 -2
  51. tests/test_utils.py +89 -4
  52. worker-demo/tests/__init__.py +0 -0
  53. worker-demo/tests/conftest.py +32 -0
  54. worker-demo/tests/test_worker.py +12 -0
  55. worker-demo/worker_demo/__init__.py +6 -0
  56. worker-demo/worker_demo/worker.py +19 -0
  57. arkindex_base_worker-0.3.7rc4.dist-info/RECORD +0 -41
  58. tests/test_elements_worker/test_elements.py +0 -2713
  59. tests/test_elements_worker/test_transcriptions.py +0 -2119
  60. {arkindex_base_worker-0.3.7rc4.dist-info → arkindex_base_worker-0.5.0a1.dist-info}/LICENSE +0 -0
tests/test_image.py CHANGED
@@ -1,5 +1,5 @@
1
+ import logging
1
2
  import math
2
- import unittest
3
3
  import uuid
4
4
  from io import BytesIO
5
5
  from operator import attrgetter
@@ -9,6 +9,7 @@ import pytest
9
9
  from PIL import Image, ImageChops, ImageOps
10
10
  from requests import HTTPError
11
11
 
12
+ import arkindex_worker.image
12
13
  from arkindex_worker.cache import CachedElement, create_tables, init_cache_db
13
14
  from arkindex_worker.image import (
14
15
  IIIF_FULL,
@@ -18,21 +19,64 @@ from arkindex_worker.image import (
18
19
  download_tiles,
19
20
  open_image,
20
21
  polygon_bounding_box,
22
+ resized_images,
21
23
  revert_orientation,
22
24
  trim_polygon,
25
+ update_pillow_image_size_limit,
23
26
  upload_image,
24
27
  )
25
28
  from arkindex_worker.models import Element
29
+ from tests import FIXTURES_DIR
26
30
 
27
- FIXTURES = Path(__file__).absolute().parent / "data"
28
- TILE = FIXTURES / "test_image.jpg"
29
- FULL_IMAGE = FIXTURES / "tiled_image.jpg"
30
- ROTATED_IMAGE = FIXTURES / "rotated_image.jpg"
31
- MIRRORED_IMAGE = FIXTURES / "mirrored_image.jpg"
32
- ROTATED_MIRRORED_IMAGE = FIXTURES / "rotated_mirrored_image.jpg"
31
+ TILE = FIXTURES_DIR / "test_image.jpg"
32
+ FULL_IMAGE = FIXTURES_DIR / "tiled_image.jpg"
33
+ ROTATED_IMAGE = FIXTURES_DIR / "rotated_image.jpg"
34
+ MIRRORED_IMAGE = FIXTURES_DIR / "mirrored_image.jpg"
35
+ ROTATED_MIRRORED_IMAGE = FIXTURES_DIR / "rotated_mirrored_image.jpg"
33
36
  TEST_IMAGE = {"width": 800, "height": 300}
34
37
 
35
38
 
39
+ @pytest.fixture
40
+ def mock_page():
41
+ class Page(Element):
42
+ @property
43
+ def crop(self):
44
+ # Image from Socface (https://socface.site.ined.fr/) project (AD026)
45
+ image = Image.open(FIXTURES_DIR / "AD026_6M_00505_0001_0373.jpg")
46
+ x, y, element_width, element_height = polygon_bounding_box(
47
+ self.zone.polygon
48
+ )
49
+ return image.crop(box=(x, y, x + element_width, y + element_height))
50
+
51
+ def open_image(
52
+ self,
53
+ *args,
54
+ max_width: int | None = None,
55
+ max_height: int | None = None,
56
+ use_full_image: bool | None = False,
57
+ **kwargs,
58
+ ) -> Image.Image:
59
+ crop = self.crop.copy()
60
+ crop.thumbnail(
61
+ size=(
62
+ max_width or self.zone.image.width,
63
+ max_height or self.zone.image.height,
64
+ )
65
+ )
66
+ return crop
67
+
68
+ return Page(
69
+ id="page_id",
70
+ name="1",
71
+ zone={
72
+ "polygon": [[0, 0], [2000, 0], [2000, 3000], [0, 3000], [0, 0]],
73
+ "image": {"width": 2000, "height": 3000},
74
+ },
75
+ rotation_angle=0,
76
+ mirrored=False,
77
+ )
78
+
79
+
36
80
  def _root_mean_square(img_a, img_b):
37
81
  """
38
82
  Get the root-mean-square difference between two images for fuzzy matching
@@ -45,10 +89,33 @@ def _root_mean_square(img_a, img_b):
45
89
  )
46
90
 
47
91
 
92
+ @pytest.mark.parametrize(
93
+ ("max_image_pixels", "expected_image_pixels"),
94
+ [
95
+ # Pillow Image size limit not updated
96
+ (None, Image.MAX_IMAGE_PIXELS),
97
+ # Pillow Image size limit set to None
98
+ ("0", None),
99
+ (0, None),
100
+ # Update Pillow Image size limit
101
+ ("1", 1),
102
+ (1, 1),
103
+ ],
104
+ )
105
+ def test_update_pillow_image_size_limit(max_image_pixels, expected_image_pixels):
106
+ MAX_IMAGE_PIXELS = Image.MAX_IMAGE_PIXELS
107
+
108
+ @update_pillow_image_size_limit
109
+ def function() -> int | None:
110
+ return Image.MAX_IMAGE_PIXELS
111
+
112
+ assert function(max_image_pixels=max_image_pixels) == expected_image_pixels
113
+ assert Image.MAX_IMAGE_PIXELS == MAX_IMAGE_PIXELS
114
+
115
+
48
116
  def test_download_tiles(responses):
49
117
  expected = Image.open(FULL_IMAGE).convert("RGB")
50
- with TILE.open("rb") as tile:
51
- tile_bytes = tile.read()
118
+ tile_bytes = TILE.read_bytes()
52
119
 
53
120
  responses.add(
54
121
  responses.GET,
@@ -76,9 +143,8 @@ def test_download_tiles_crop(responses):
76
143
  """
77
144
  expected = Image.open(FULL_IMAGE).convert("RGB")
78
145
  tile_bytes = BytesIO()
79
- with TILE.open("rb") as tile:
80
- # Add one extra pixel to each tile to return slightly bigger tiles
81
- ImageOps.pad(Image.open(tile), (181, 241)).save(tile_bytes, format="jpeg")
146
+ # Add one extra pixel to each tile to return slightly bigger tiles
147
+ ImageOps.pad(Image.open(TILE), (181, 241)).save(tile_bytes, format="jpeg")
82
148
 
83
149
  tile_bytes = tile_bytes.getvalue()
84
150
 
@@ -165,74 +231,33 @@ def test_open_image_rotate_mirror(rotation_angle, mirrored, expected_path):
165
231
  assert _root_mean_square(expected, actual) <= 15.0
166
232
 
167
233
 
168
- class TestTrimPolygon(unittest.TestCase):
169
- def test_trim_polygon_partially_outside_image(self):
170
- bad_polygon = [
171
- [99, 200],
172
- [197, 224],
173
- [120, 251],
174
- [232, 350],
175
- [312, 364],
176
- [325, 310],
177
- [318, 295],
178
- [296, 260],
179
- [352, 259],
180
- [106, 210],
181
- [197, 206],
182
- [99, 200],
183
- ]
184
- expected_polygon = [
185
- [99, 200],
186
- [197, 224],
187
- [120, 251],
188
- [232, 300],
189
- [312, 300],
190
- [325, 300],
191
- [318, 295],
192
- [296, 260],
193
- [352, 259],
194
- [106, 210],
195
- [197, 206],
196
- [99, 200],
197
- ]
198
- assert (
199
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
200
- == expected_polygon
201
- )
202
-
203
- def test_trim_polygon_good_polygon(self):
204
- good_polygon = (
205
- (12, 56),
206
- (29, 60),
207
- (35, 61),
208
- (42, 59),
209
- (58, 57),
210
- (65, 61),
211
- (72, 57),
212
- (12, 56),
213
- )
214
- expected_polygon = [
215
- [12, 56],
216
- [29, 60],
217
- [35, 61],
218
- [42, 59],
219
- [58, 57],
220
- [65, 61],
221
- [72, 57],
222
- [12, 56],
223
- ]
224
- assert (
225
- trim_polygon(good_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
226
- == expected_polygon
227
- )
228
-
229
- def test_trim_polygon_invalid_polygon(self):
230
- """
231
- An assertion error is raised the polygon input isn't a list or tuple
232
- """
233
- bad_polygon = {
234
- "polygon": [
235
- [99, 200],
234
+ @pytest.mark.parametrize(
235
+ ("polygon", "error"),
236
+ [
237
+ # Polygon input isn't a list or tuple
238
+ (
239
+ {
240
+ "polygon": [
241
+ [99, 200],
242
+ [25, 224],
243
+ [0, 0],
244
+ [0, 300],
245
+ [102, 300],
246
+ [260, 300],
247
+ [288, 295],
248
+ [296, 260],
249
+ [352, 259],
250
+ [106, 210],
251
+ [197, 206],
252
+ [99, 208],
253
+ ]
254
+ },
255
+ "Input polygon must be a valid list or tuple of points.",
256
+ ),
257
+ # Point coordinates are not integers
258
+ (
259
+ [
260
+ [9.9, 200],
236
261
  [25, 224],
237
262
  [0, 0],
238
263
  [0, 300],
@@ -243,136 +268,161 @@ class TestTrimPolygon(unittest.TestCase):
243
268
  [352, 259],
244
269
  [106, 210],
245
270
  [197, 206],
246
- [99, 208],
247
- ]
248
- }
249
- with pytest.raises(
250
- AssertionError,
251
- match="Input polygon must be a valid list or tuple of points.",
252
- ):
253
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
254
-
255
- def test_trim_polygon_negative_coordinates(self):
256
- """
257
- Negative coordinates are ignored and replaced by 0 with no error being thrown
258
- """
259
- bad_polygon = [
260
- [99, 200],
261
- [25, 224],
262
- [-8, -52],
263
- [-12, 350],
264
- [102, 364],
265
- [260, 310],
266
- [288, 295],
267
- [296, 260],
268
- [352, 259],
269
- [106, 210],
270
- [197, 206],
271
- [99, 200],
272
- ]
273
- expected_polygon = [
274
- [99, 200],
275
- [25, 224],
276
- [0, 0],
277
- [0, 300],
278
- [102, 300],
279
- [260, 300],
280
- [288, 295],
281
- [296, 260],
282
- [352, 259],
283
- [106, 210],
284
- [197, 206],
285
- [99, 200],
286
- ]
287
- assert (
288
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
289
- == expected_polygon
290
- )
271
+ [99, 20.8],
272
+ ],
273
+ "Polygon point coordinates must be integers.",
274
+ ),
275
+ # Point coordinates are not lists or tuples
276
+ (
277
+ [
278
+ [12, 56],
279
+ [29, 60],
280
+ [35, 61],
281
+ "[42, 59]",
282
+ [58, 57],
283
+ [65, 61],
284
+ [72, 57],
285
+ [12, 56],
286
+ ],
287
+ "Polygon points must be tuples or lists.",
288
+ ),
289
+ # Point coordinates are not lists or tuples of length 2
290
+ (
291
+ [
292
+ [12, 56],
293
+ [29, 60, 3],
294
+ [35, 61],
295
+ [42, 59],
296
+ [58, 57],
297
+ [65, 61],
298
+ [72, 57],
299
+ [12, 56],
300
+ ],
301
+ "Polygon points must be tuples or lists of 2 elements.",
302
+ ),
303
+ # None of the polygon's points are inside the image
304
+ (
305
+ [
306
+ [999, 200],
307
+ [1097, 224],
308
+ [1020, 251],
309
+ [1232, 350],
310
+ [1312, 364],
311
+ [1325, 310],
312
+ [1318, 295],
313
+ [1296, 260],
314
+ [1352, 259],
315
+ [1006, 210],
316
+ [997, 206],
317
+ [999, 200],
318
+ ],
319
+ "This polygon is entirely outside the image's bounds.",
320
+ ),
321
+ ],
322
+ )
323
+ def test_trim_polygon_errors(polygon, error):
324
+ with pytest.raises(AssertionError, match=error):
325
+ trim_polygon(polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
326
+
327
+
328
+ def test_trim_polygon_negative_coordinates():
329
+ """
330
+ Negative coordinates are ignored and replaced by 0 with no error being thrown
331
+ """
332
+ polygon = [
333
+ [99, 200],
334
+ [25, 224],
335
+ [-8, -52],
336
+ [-12, 350],
337
+ [102, 364],
338
+ [260, 310],
339
+ [288, 295],
340
+ [296, 260],
341
+ [352, 259],
342
+ [106, 210],
343
+ [197, 206],
344
+ [99, 200],
345
+ ]
346
+ expected_polygon = [
347
+ [99, 200],
348
+ [25, 224],
349
+ [0, 0],
350
+ [0, 300],
351
+ [102, 300],
352
+ [260, 300],
353
+ [288, 295],
354
+ [296, 260],
355
+ [352, 259],
356
+ [106, 210],
357
+ [197, 206],
358
+ [99, 200],
359
+ ]
360
+ assert (
361
+ trim_polygon(polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
362
+ == expected_polygon
363
+ )
364
+
365
+
366
+ def test_trim_polygon_partially_outside_image():
367
+ polygon = [
368
+ [99, 200],
369
+ [197, 224],
370
+ [120, 251],
371
+ [232, 350],
372
+ [312, 364],
373
+ [325, 310],
374
+ [318, 295],
375
+ [296, 260],
376
+ [352, 259],
377
+ [106, 210],
378
+ [197, 206],
379
+ [99, 200],
380
+ ]
381
+ expected_polygon = [
382
+ [99, 200],
383
+ [197, 224],
384
+ [120, 251],
385
+ [232, 300],
386
+ [312, 300],
387
+ [325, 300],
388
+ [318, 295],
389
+ [296, 260],
390
+ [352, 259],
391
+ [106, 210],
392
+ [197, 206],
393
+ [99, 200],
394
+ ]
395
+ assert (
396
+ trim_polygon(polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
397
+ == expected_polygon
398
+ )
291
399
 
292
- def test_trim_polygon_outside_image_error(self):
293
- """
294
- An assertion error is raised when none of the polygon's points are inside the image
295
- """
296
- bad_polygon = [
297
- [999, 200],
298
- [1097, 224],
299
- [1020, 251],
300
- [1232, 350],
301
- [1312, 364],
302
- [1325, 310],
303
- [1318, 295],
304
- [1296, 260],
305
- [1352, 259],
306
- [1006, 210],
307
- [997, 206],
308
- [999, 200],
309
- ]
310
- with pytest.raises(
311
- AssertionError, match="This polygon is entirely outside the image's bounds."
312
- ):
313
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
314
-
315
- def test_trim_polygon_float_coordinates(self):
316
- """
317
- An assertion error is raised when point coordinates are not integers
318
- """
319
- bad_polygon = [
320
- [9.9, 200],
321
- [25, 224],
322
- [0, 0],
323
- [0, 300],
324
- [102, 300],
325
- [260, 300],
326
- [288, 295],
327
- [296, 260],
328
- [352, 259],
329
- [106, 210],
330
- [197, 206],
331
- [99, 20.8],
332
- ]
333
- with pytest.raises(
334
- AssertionError, match="Polygon point coordinates must be integers."
335
- ):
336
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
337
-
338
- def test_trim_polygon_invalid_points_1(self):
339
- """
340
- An assertion error is raised when point coordinates are not lists or tuples
341
- """
342
- bad_polygon = [
343
- [12, 56],
344
- [29, 60],
345
- [35, 61],
346
- "[42, 59]",
347
- [58, 57],
348
- [65, 61],
349
- [72, 57],
350
- [12, 56],
351
- ]
352
- with pytest.raises(
353
- AssertionError, match="Polygon points must be tuples or lists."
354
- ):
355
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
356
-
357
- def test_trim_polygon_invalid_points_2(self):
358
- """
359
- An assertion error is raised when point coordinates are not lists or tuples of length 2
360
- """
361
- bad_polygon = [
362
- [12, 56],
363
- [29, 60, 3],
364
- [35, 61],
365
- [42, 59],
366
- [58, 57],
367
- [65, 61],
368
- [72, 57],
369
- [12, 56],
370
- ]
371
- with pytest.raises(
372
- AssertionError,
373
- match="Polygon points must be tuples or lists of 2 elements.",
374
- ):
375
- trim_polygon(bad_polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
400
+
401
+ def test_trim_polygon():
402
+ polygon = (
403
+ (12, 56),
404
+ (29, 60),
405
+ (35, 61),
406
+ (42, 59),
407
+ (58, 57),
408
+ (65, 61),
409
+ (72, 57),
410
+ (12, 56),
411
+ )
412
+ expected_polygon = [
413
+ [12, 56],
414
+ [29, 60],
415
+ [35, 61],
416
+ [42, 59],
417
+ [58, 57],
418
+ [65, 61],
419
+ [72, 57],
420
+ [12, 56],
421
+ ]
422
+ assert (
423
+ trim_polygon(polygon, TEST_IMAGE["width"], TEST_IMAGE["height"])
424
+ == expected_polygon
425
+ )
376
426
 
377
427
 
378
428
  @pytest.mark.parametrize(
@@ -584,3 +634,286 @@ def test_upload_image(responses):
584
634
 
585
635
  assert len(responses.calls) == 1
586
636
  assert list(map(attrgetter("request.url"), responses.calls)) == [dest_url]
637
+
638
+
639
+ @pytest.mark.parametrize(
640
+ (
641
+ "max_pixels_short",
642
+ "max_pixels_long",
643
+ "max_bytes",
644
+ "expected_sizes",
645
+ "expected_logs",
646
+ ),
647
+ [
648
+ # No limits provided
649
+ (
650
+ None,
651
+ None,
652
+ None,
653
+ [(2000, 3000), (1000, 1500), (200, 300)],
654
+ [
655
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
656
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
657
+ (logging.WARNING, "The image was resized to (200 x 300)."),
658
+ ],
659
+ ),
660
+ # Image already under all three limits
661
+ (
662
+ 10000,
663
+ 10000,
664
+ 4000000, # 4MB
665
+ [(2000, 3000), (1000, 1500), (200, 300)],
666
+ [
667
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
668
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
669
+ (logging.WARNING, "The image was resized to (200 x 300)."),
670
+ ],
671
+ ),
672
+ # Image above the "short side in pixels" limit
673
+ (
674
+ 1000,
675
+ None,
676
+ None,
677
+ [(1000, 1500), (500, 750), (100, 150)],
678
+ [
679
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
680
+ (
681
+ logging.WARNING,
682
+ "Maximum image dimensions supported are (1000 x 3000).",
683
+ ),
684
+ (logging.WARNING, "The image will be resized."),
685
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
686
+ (logging.WARNING, "The image was resized to (500 x 750)."),
687
+ (logging.WARNING, "The image was resized to (100 x 150)."),
688
+ ],
689
+ ),
690
+ # Image above the "long side in pixels" limit
691
+ (
692
+ None,
693
+ 2000,
694
+ None,
695
+ [(1333, 2000), (667, 1000), (133, 200)],
696
+ [
697
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
698
+ (
699
+ logging.WARNING,
700
+ "Maximum image dimensions supported are (2000 x 2000).",
701
+ ),
702
+ (logging.WARNING, "The image will be resized."),
703
+ (logging.WARNING, "The image was resized to (1333 x 2000)."),
704
+ (logging.WARNING, "The image was resized to (667 x 1000)."),
705
+ (logging.WARNING, "The image was resized to (133 x 200)."),
706
+ ],
707
+ ),
708
+ # Image above the "size in bytes" limit
709
+ (
710
+ None,
711
+ None,
712
+ 100000, # 100kB
713
+ [(200, 300)],
714
+ [
715
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
716
+ (logging.WARNING, "The image size is 1.3 MB."),
717
+ (logging.WARNING, "Maximum image input size supported is 100.0 kB."),
718
+ (logging.WARNING, "The image will be resized."),
719
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
720
+ (logging.WARNING, "The image size is 214.2 kB."),
721
+ (logging.WARNING, "Maximum image input size supported is 100.0 kB."),
722
+ (logging.WARNING, "The image will be resized."),
723
+ (logging.WARNING, "The image was resized to (200 x 300)."),
724
+ ],
725
+ ),
726
+ # Image above all three limits
727
+ (
728
+ 1000,
729
+ 2000,
730
+ 100000, # 100kB
731
+ [(500, 750), (100, 150)],
732
+ [
733
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
734
+ (
735
+ logging.WARNING,
736
+ "Maximum image dimensions supported are (1000 x 2000).",
737
+ ),
738
+ (logging.WARNING, "The image will be resized."),
739
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
740
+ (logging.WARNING, "The image size is 214.2 kB."),
741
+ (logging.WARNING, "Maximum image input size supported is 100.0 kB."),
742
+ (logging.WARNING, "The image will be resized."),
743
+ (logging.WARNING, "The image was resized to (500 x 750)."),
744
+ (logging.WARNING, "The image was resized to (100 x 150)."),
745
+ ],
746
+ ),
747
+ # Image always above all three limits
748
+ (
749
+ 50,
750
+ 50,
751
+ 50, # 50B
752
+ [],
753
+ [
754
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
755
+ (logging.WARNING, "Maximum image dimensions supported are (50 x 50)."),
756
+ (logging.WARNING, "The image will be resized."),
757
+ (logging.WARNING, "The image was resized to (33 x 50)."),
758
+ (logging.WARNING, "The image size is 1.0 kB."),
759
+ (logging.WARNING, "Maximum image input size supported is 50 Bytes."),
760
+ (logging.WARNING, "The image will be resized."),
761
+ (logging.WARNING, "The image was resized to (17 x 25)."),
762
+ (logging.WARNING, "The image size is 785 Bytes."),
763
+ (logging.WARNING, "Maximum image input size supported is 50 Bytes."),
764
+ (logging.WARNING, "The image will be resized."),
765
+ (logging.WARNING, "The image was resized to (3 x 5)."),
766
+ (logging.WARNING, "The image size is 689 Bytes."),
767
+ (logging.WARNING, "Maximum image input size supported is 50 Bytes."),
768
+ (logging.WARNING, "The image will be resized."),
769
+ ],
770
+ ),
771
+ ],
772
+ )
773
+ def test_resized_images_portrait_format(
774
+ monkeypatch,
775
+ max_pixels_short,
776
+ max_pixels_long,
777
+ max_bytes,
778
+ expected_sizes,
779
+ expected_logs,
780
+ mock_page,
781
+ caplog,
782
+ ):
783
+ monkeypatch.setattr(arkindex_worker.image, "IMAGE_RATIOS", [1.0, 0.5, 0.1])
784
+
785
+ # Short side is the width, long side is the height
786
+ assert mock_page.zone.image.width < mock_page.zone.image.height
787
+
788
+ assert [
789
+ Image.open(image).size
790
+ for image in resized_images(
791
+ element=mock_page,
792
+ max_pixels_short=max_pixels_short,
793
+ max_pixels_long=max_pixels_long,
794
+ max_bytes=max_bytes,
795
+ )
796
+ ] == expected_sizes
797
+
798
+ assert [
799
+ (record.levelno, record.message) for record in caplog.records
800
+ ] == expected_logs
801
+
802
+
803
+ @pytest.mark.parametrize(
804
+ ("max_pixels_short", "max_pixels_long", "expected_sizes", "expected_logs"),
805
+ [
806
+ # Image above the "short side in pixels" limit
807
+ (
808
+ 1000,
809
+ None,
810
+ [(1500, 1000), (750, 500), (150, 100)],
811
+ [
812
+ (logging.INFO, "This element's image dimensions are (3000 x 2000)."),
813
+ (
814
+ logging.WARNING,
815
+ "Maximum image dimensions supported are (3000 x 1000).",
816
+ ),
817
+ (logging.WARNING, "The image will be resized."),
818
+ (logging.WARNING, "The image was resized to (1500 x 1000)."),
819
+ (logging.WARNING, "The image was resized to (750 x 500)."),
820
+ (logging.WARNING, "The image was resized to (150 x 100)."),
821
+ ],
822
+ ),
823
+ # Image above the "long side in pixels" limit
824
+ (
825
+ None,
826
+ 2000,
827
+ [(2000, 1333), (1000, 667), (200, 133)],
828
+ [
829
+ (logging.INFO, "This element's image dimensions are (3000 x 2000)."),
830
+ (
831
+ logging.WARNING,
832
+ "Maximum image dimensions supported are (2000 x 2000).",
833
+ ),
834
+ (logging.WARNING, "The image will be resized."),
835
+ (logging.WARNING, "The image was resized to (2000 x 1333)."),
836
+ (logging.WARNING, "The image was resized to (1000 x 667)."),
837
+ (logging.WARNING, "The image was resized to (200 x 133)."),
838
+ ],
839
+ ),
840
+ # Image above the two pixels limits
841
+ (
842
+ 1000,
843
+ 2000,
844
+ [(1500, 1000), (750, 500), (150, 100)],
845
+ [
846
+ (logging.INFO, "This element's image dimensions are (3000 x 2000)."),
847
+ (
848
+ logging.WARNING,
849
+ "Maximum image dimensions supported are (2000 x 1000).",
850
+ ),
851
+ (logging.WARNING, "The image will be resized."),
852
+ (logging.WARNING, "The image was resized to (1500 x 1000)."),
853
+ (logging.WARNING, "The image was resized to (750 x 500)."),
854
+ (logging.WARNING, "The image was resized to (150 x 100)."),
855
+ ],
856
+ ),
857
+ ],
858
+ )
859
+ def test_resized_images_landscape_format(
860
+ monkeypatch,
861
+ max_pixels_short,
862
+ max_pixels_long,
863
+ expected_sizes,
864
+ expected_logs,
865
+ mock_page,
866
+ caplog,
867
+ ):
868
+ monkeypatch.setattr(arkindex_worker.image, "IMAGE_RATIOS", [1.0, 0.5, 0.1])
869
+
870
+ # Short side is the height, long side is the width
871
+ mock_page.zone = {
872
+ "polygon": [[0, 0], [3000, 0], [3000, 2000], [0, 2000], [0, 0]],
873
+ "image": {"width": 3000, "height": 2000},
874
+ }
875
+ assert mock_page.zone.image.height < mock_page.zone.image.width
876
+
877
+ assert [
878
+ Image.open(image).size
879
+ for image in resized_images(
880
+ element=mock_page,
881
+ max_pixels_short=max_pixels_short,
882
+ max_pixels_long=max_pixels_long,
883
+ max_bytes=None,
884
+ )
885
+ ] == expected_sizes
886
+
887
+ assert [
888
+ (record.levelno, record.message) for record in caplog.records
889
+ ] == expected_logs
890
+
891
+
892
+ def test_resized_images_use_base64(monkeypatch, mock_page, caplog):
893
+ monkeypatch.setattr(arkindex_worker.image, "IMAGE_RATIOS", [1.0, 0.5, 0.25, 0.1])
894
+
895
+ assert list(
896
+ map(
897
+ len,
898
+ resized_images(
899
+ element=mock_page,
900
+ max_pixels_short=None,
901
+ max_pixels_long=None,
902
+ max_bytes=100000,
903
+ use_base64=True,
904
+ ),
905
+ )
906
+ ) == [65280, 11892]
907
+
908
+ assert [(record.levelno, record.message) for record in caplog.records] == [
909
+ (logging.INFO, "This element's image dimensions are (2000 x 3000)."),
910
+ (logging.WARNING, "The image size is 1.7 MB."),
911
+ (logging.WARNING, "Maximum image input size supported is 100.0 kB."),
912
+ (logging.WARNING, "The image will be resized."),
913
+ (logging.WARNING, "The image was resized to (1000 x 1500)."),
914
+ (logging.WARNING, "The image size is 285.6 kB."),
915
+ (logging.WARNING, "Maximum image input size supported is 100.0 kB."),
916
+ (logging.WARNING, "The image will be resized."),
917
+ (logging.WARNING, "The image was resized to (500 x 750)."),
918
+ (logging.WARNING, "The image was resized to (200 x 300)."),
919
+ ]