scan, split, OCR, prepare for LLM
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

test_post_crop.py 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from __future__ import annotations
  2. import tempfile
  3. from pathlib import Path
  4. import cv2
  5. from src.split.slicer import (
  6. crop_to_content,
  7. find_content_bounds,
  8. is_empty,
  9. process_image,
  10. )
  11. from tests.helpers import create_test_image, image_to_path
  12. class TestFindContentBounds:
  13. def test_uniform_image(self) -> None:
  14. image = create_test_image(200, 150)
  15. image[:, :] = (255, 255, 255)
  16. x1, y1, x2, y2 = find_content_bounds(image)
  17. assert (x1, y1, x2, y2) == (0, 0, 200, 150)
  18. def test_single_dot_center(self) -> None:
  19. image = create_test_image(200, 150)
  20. image[:, :] = (255, 255, 255)
  21. image[75, 100] = (0, 0, 0)
  22. x1, y1, x2, y2 = find_content_bounds(image)
  23. assert abs(x1 - 100) <= 1 and abs(x2 - 100) <= 1
  24. assert abs(y1 - 75) <= 1 and abs(y2 - 75) <= 1
  25. def test_rectangle_content(self) -> None:
  26. image = create_test_image(200, 150)
  27. image[:, :] = (255, 255, 255)
  28. image[30:100, 50:140] = (0, 0, 0)
  29. x1, y1, x2, y2 = find_content_bounds(image)
  30. assert x1 == 50
  31. assert y1 == 30
  32. assert x2 == 139
  33. assert y2 == 99
  34. def test_content_at_edges(self) -> None:
  35. image = create_test_image(200, 150)
  36. image[:, :] = (255, 255, 255)
  37. image[0:10, :] = (0, 0, 0)
  38. image[:, 0:10] = (0, 0, 0)
  39. x1, y1, _x2, _y2 = find_content_bounds(image)
  40. assert y1 == 0
  41. assert x1 == 0
  42. def test_grayscale_content(self) -> None:
  43. image = create_test_image(100, 100)
  44. image[:, :] = (255, 255, 255)
  45. image[20:30, 20:30] = (128, 128, 128)
  46. x1, y1, x2, y2 = find_content_bounds(image)
  47. assert x1 <= 29 and x2 >= 20
  48. assert y1 <= 29 and y2 >= 20
  49. class TestCropToContent:
  50. def test_crop_centered_content(self) -> None:
  51. image = create_test_image(200, 150)
  52. image[:, :] = (255, 255, 255)
  53. image[30:50, 40:60] = (0, 0, 0)
  54. result = crop_to_content(image, border_px=10)
  55. assert result.shape[0] >= 20
  56. assert result.shape[1] >= 20
  57. def test_crop_uniform_image_no_change(self) -> None:
  58. image = create_test_image(100, 100)
  59. image[:, :] = (255, 255, 255)
  60. result = crop_to_content(image, border_px=10)
  61. assert result.shape == (100, 100, 3)
  62. def test_border_capped_by_image_edge(self) -> None:
  63. image = create_test_image(100, 100)
  64. image[:, :] = (255, 255, 255)
  65. image[10:30, 10:30] = (0, 0, 0)
  66. result = crop_to_content(image, border_px=200)
  67. assert result.shape == (100, 100, 3)
  68. def test_border_equals_distance_to_edge(self) -> None:
  69. image = create_test_image(100, 100)
  70. image[:, :] = (255, 255, 255)
  71. image[40:60, 30:70] = (0, 0, 0)
  72. result = crop_to_content(image, border_px=50)
  73. assert result.shape[1] <= 100
  74. assert result.shape[0] <= 100
  75. def test_dark_bg_no_crop_uniform(self) -> None:
  76. image = create_test_image(100, 100)
  77. image[:, :] = (0, 0, 0)
  78. result = crop_to_content(image, border_px=0)
  79. assert result.shape == (100, 100, 3)
  80. def test_zero_border_exact_crop(self) -> None:
  81. image = create_test_image(200, 150)
  82. image[:, :] = (255, 255, 255)
  83. image[20:100, 30:170] = (0, 0, 0)
  84. result = crop_to_content(image, border_px=0)
  85. assert result.shape == (80, 140, 3)
  86. class TestProcessImagePostCrop:
  87. def test_post_crop_reduces_size(self) -> None:
  88. image = create_test_image(400, 400)
  89. image[:, :] = (255, 255, 255)
  90. image[100:200, 100:200] = (0, 0, 0)
  91. path = image_to_path(image, ".png")
  92. with tempfile.TemporaryDirectory() as tmp:
  93. result = process_image(Path(path), 1, 1, Path(tmp), border_px=30, post_crop=True)
  94. assert len(result) == 1
  95. loaded = cv2.imread(str(result[0]))
  96. assert loaded.shape[0] < 400 or loaded.shape[1] < 400
  97. def test_post_crop_off_no_crop(self) -> None:
  98. image = create_test_image(400, 300)
  99. image[10:290, 10:390] = (0, 0, 0)
  100. path = image_to_path(image, ".png")
  101. with tempfile.TemporaryDirectory() as tmp:
  102. result = process_image(Path(path), 1, 1, Path(tmp), post_crop=False)
  103. assert len(result) == 1
  104. loaded = cv2.imread(str(result[0]))
  105. assert loaded.shape == (310, 410, 3)
  106. def test_post_crop_e2e_2x2(self) -> None:
  107. image = create_test_image(400, 400)
  108. image[:, :] = (255, 255, 255)
  109. image[50:150, 50:150] = (0, 0, 0)
  110. image[50:150, 250:350] = (0, 0, 0)
  111. image[250:350, 50:150] = (0, 0, 0)
  112. image[250:350, 250:350] = (0, 0, 0)
  113. path = image_to_path(image, ".png")
  114. with tempfile.TemporaryDirectory() as tmp:
  115. result_crop = process_image(Path(path), 2, 2, Path(tmp) / "crop", border_px=30, post_crop=True)
  116. result_no = process_image(Path(path), 2, 2, Path(tmp) / "no", border_px=30, post_crop=False)
  117. assert len(result_crop) == 4
  118. for cp, np in zip(result_crop, result_no):
  119. c = cv2.imread(str(cp))
  120. n = cv2.imread(str(np))
  121. assert c.shape[0] < n.shape[0]
  122. assert c.shape[1] < n.shape[1]
  123. class TestIsEmpty:
  124. def test_white_image_is_empty(self) -> None:
  125. image = create_test_image(100, 100)
  126. assert is_empty(image)
  127. def test_dark_content_not_empty(self) -> None:
  128. image = create_test_image(100, 100)
  129. image[10:20, 10:20] = (0, 0, 0)
  130. assert not is_empty(image)
  131. def test_threshold_boundary_not_empty(self) -> None:
  132. image = create_test_image(100, 100)
  133. image[:, :] = (99, 99, 99)
  134. assert not is_empty(image)
  135. def test_threshold_boundary_empty(self) -> None:
  136. image = create_test_image(100, 100)
  137. image[:, :] = (100, 100, 100)
  138. assert is_empty(image)
  139. class TestSkipEmptyPages:
  140. def test_skip_empty_page_renumbering(self) -> None:
  141. image = create_test_image(400, 400)
  142. image[10:390, 200:390] = (0, 0, 0)
  143. path = image_to_path(image, ".png")
  144. with tempfile.TemporaryDirectory() as tmp:
  145. result = process_image(Path(path), 1, 2, Path(tmp), border_px=5)
  146. assert len(result) == 1
  147. assert result[0].name == Path(path).stem + "_01.png"
  148. def test_all_empty_returns_empty_list(self) -> None:
  149. image = create_test_image(200, 200)
  150. path = image_to_path(image, ".png")
  151. with tempfile.TemporaryDirectory() as tmp:
  152. result = process_image(Path(path), 2, 2, Path(tmp), border_px=5)
  153. assert len(result) == 0