scan, split, OCR, prepare for LLM
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

test_slicer.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. from __future__ import annotations
  2. import tempfile
  3. from pathlib import Path
  4. import cv2
  5. import numpy as np
  6. import pytest
  7. from src.split.slicer import (
  8. SUPPORTED_EXTENSIONS,
  9. VALID_ROTATIONS,
  10. add_border,
  11. generate_output_paths,
  12. load_image,
  13. parse_slice,
  14. process_image,
  15. rotate_image,
  16. save_page,
  17. slice_grid,
  18. )
  19. from tests.helpers import create_test_image, image_to_path
  20. class TestParseSlice:
  21. def test_simple_2x2(self) -> None:
  22. assert parse_slice("2:2") == (2, 2)
  23. def test_2x4(self) -> None:
  24. assert parse_slice("2:4") == (2, 4)
  25. def test_1x1(self) -> None:
  26. assert parse_slice("1:1") == (1, 1)
  27. def test_large(self) -> None:
  28. assert parse_slice("10:10") == (10, 10)
  29. def test_invalid_format_missing_colon(self) -> None:
  30. with pytest.raises(ValueError, match="Неверный формат --slice"):
  31. parse_slice("22")
  32. def test_invalid_format_extra_colon(self) -> None:
  33. with pytest.raises(ValueError, match="Неверный формат --slice"):
  34. parse_slice("2:3:4")
  35. def test_non_integer_value(self) -> None:
  36. with pytest.raises(ValueError, match="Неверный формат --slice"):
  37. parse_slice("a:b")
  38. def test_non_integer_col(self) -> None:
  39. with pytest.raises(ValueError, match="Неверный формат --slice"):
  40. parse_slice("x:2")
  41. def test_zero_cols(self) -> None:
  42. with pytest.raises(ValueError, match="Неверное значение --slice"):
  43. parse_slice("0:2")
  44. def test_zero_rows(self) -> None:
  45. with pytest.raises(ValueError, match="Неверное значение --slice"):
  46. parse_slice("2:0")
  47. def test_negative_cols(self) -> None:
  48. with pytest.raises(ValueError, match="Неверное значение --slice"):
  49. parse_slice("-1:2")
  50. def test_blank_string(self) -> None:
  51. with pytest.raises(ValueError):
  52. parse_slice("")
  53. class TestSliceGrid:
  54. def test_2x2_exact(self) -> None:
  55. image = create_test_image(400, 300)
  56. pages = slice_grid(image, 2, 2)
  57. assert len(pages) == 4
  58. assert pages[0].shape == (150, 200, 3)
  59. assert pages[1].shape == (150, 200, 3)
  60. assert pages[2].shape == (150, 200, 3)
  61. assert pages[3].shape == (150, 200, 3)
  62. def test_1x1(self) -> None:
  63. image = create_test_image(100, 100)
  64. pages = slice_grid(image, 1, 1)
  65. assert len(pages) == 1
  66. assert pages[0].shape == (100, 100, 3)
  67. def test_2x3(self) -> None:
  68. image = create_test_image(300, 200)
  69. pages = slice_grid(image, 2, 3)
  70. assert len(pages) == 6
  71. for page in pages:
  72. assert page.shape == (100, 100, 3)
  73. def test_uneven_division_width(self) -> None:
  74. image = create_test_image(width=403, height=300)
  75. pages = slice_grid(image, 1, 2)
  76. assert len(pages) == 2
  77. assert pages[0].shape == (300, 201, 3)
  78. assert pages[1].shape == (300, 202, 3)
  79. def test_uneven_division_height(self) -> None:
  80. image = create_test_image(width=400, height=301)
  81. pages = slice_grid(image, 2, 1)
  82. assert len(pages) == 2
  83. assert pages[0].shape == (150, 400, 3)
  84. assert pages[1].shape == (151, 400, 3)
  85. def test_uneven_both(self) -> None:
  86. image = create_test_image(width=401, height=301)
  87. pages = slice_grid(image, 2, 2)
  88. assert len(pages) == 4
  89. assert pages[0].shape == (150, 200, 3)
  90. assert pages[1].shape == (150, 201, 3)
  91. assert pages[2].shape == (151, 200, 3)
  92. assert pages[3].shape == (151, 201, 3)
  93. def test_pixel_content_preserved(self) -> None:
  94. image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
  95. pages = slice_grid(image, 2, 2)
  96. assert np.array_equal(image[0:50, 0:50], pages[0])
  97. assert np.array_equal(image[0:50, 50:100], pages[1])
  98. assert np.array_equal(image[50:100, 0:50], pages[2])
  99. assert np.array_equal(image[50:100, 50:100], pages[3])
  100. def test_total_pixels_preserved(self) -> None:
  101. image = create_test_image(width=401, height=301)
  102. pages = slice_grid(image, 3, 3)
  103. total = sum(page.shape[0] * page.shape[1] for page in pages)
  104. assert total == 401 * 301
  105. def test_order_2x2(self) -> None:
  106. image = create_test_image(200, 200)
  107. image[0:100, 0:100] = (255, 0, 0)
  108. image[0:100, 100:200] = (0, 255, 0)
  109. image[100:200, 0:100] = (0, 0, 255)
  110. image[100:200, 100:200] = (255, 255, 0)
  111. pages = slice_grid(image, 2, 2)
  112. assert pages[0][0, 0].tolist() == [255, 0, 0]
  113. assert pages[1][0, 0].tolist() == [0, 255, 0]
  114. assert pages[2][0, 0].tolist() == [0, 0, 255]
  115. assert pages[3][0, 0].tolist() == [255, 255, 0]
  116. def test_order_2x4(self) -> None:
  117. image = create_test_image(400, 200)
  118. pages = slice_grid(image, 2, 4)
  119. assert len(pages) == 8
  120. assert pages[0].shape == (100, 100, 3)
  121. assert pages[7].shape == (100, 100, 3)
  122. def test_4x4(self) -> None:
  123. image = create_test_image(400, 400)
  124. pages = slice_grid(image, 4, 4)
  125. assert len(pages) == 16
  126. for page in pages:
  127. assert page.shape == (100, 100, 3)
  128. class TestAddBorder:
  129. def test_default_border(self) -> None:
  130. image = create_test_image(100, 100)
  131. result = add_border(image, 20)
  132. assert result.shape == (140, 140, 3)
  133. def test_zero_border(self) -> None:
  134. image = create_test_image(100, 100)
  135. result = add_border(image, 0)
  136. assert np.array_equal(result, image)
  137. def test_border_is_white(self) -> None:
  138. image = create_test_image(100, 100)
  139. border = 10
  140. result = add_border(image, border)
  141. assert np.all(result[0:border, :] == 255)
  142. assert np.all(result[-border:, :] == 255)
  143. assert np.all(result[:, 0:border] == 255)
  144. assert np.all(result[:, -border:] == 255)
  145. def test_large_border(self) -> None:
  146. image = create_test_image(10, 10)
  147. result = add_border(image, 50)
  148. assert result.shape == (110, 110, 3)
  149. class TestRotateImage:
  150. def test_rotate_90(self) -> None:
  151. image = create_test_image(200, 100)
  152. result = rotate_image(image, 90)
  153. assert result.shape == (200, 100, 3)
  154. def test_rotate_180(self) -> None:
  155. image = create_test_image(200, 100)
  156. result = rotate_image(image, 180)
  157. assert result.shape == (100, 200, 3)
  158. def test_rotate_270(self) -> None:
  159. image = create_test_image(200, 100)
  160. result = rotate_image(image, 270)
  161. assert result.shape == (200, 100, 3)
  162. def test_invalid_angle(self) -> None:
  163. image = create_test_image()
  164. with pytest.raises(ValueError, match="Недопустимый угол поворота"):
  165. rotate_image(image, 45)
  166. def test_invalid_angle_zero(self) -> None:
  167. image = create_test_image()
  168. with pytest.raises(ValueError):
  169. rotate_image(image, 0)
  170. def test_valid_rotations_set(self) -> None:
  171. assert VALID_ROTATIONS == frozenset({90, 180, 270})
  172. class TestGenerateOutputPaths:
  173. def test_simple(self) -> None:
  174. paths = generate_output_paths(Path("scan001.jpg"), 2, Path("/tmp"))
  175. assert paths == [Path("/tmp/scan001_01.jpg"), Path("/tmp/scan001_02.jpg")]
  176. def test_tiff_extension(self) -> None:
  177. paths = generate_output_paths(Path("doc.tiff"), 3, Path("out"))
  178. assert paths == [
  179. Path("out/doc_01.tiff"),
  180. Path("out/doc_02.tiff"),
  181. Path("out/doc_03.tiff"),
  182. ]
  183. def test_png_extension(self) -> None:
  184. paths = generate_output_paths(Path("img.PNG"), 1, Path("out"))
  185. assert paths == [Path("out/img_01.png")]
  186. def test_zero_padding(self) -> None:
  187. paths = generate_output_paths(Path("scan.jpg"), 12, Path("out"))
  188. assert paths[0] == Path("out/scan_01.jpg")
  189. assert paths[9] == Path("out/scan_10.jpg")
  190. assert paths[11] == Path("out/scan_12.jpg")
  191. def test_many_pages(self) -> None:
  192. paths = generate_output_paths(Path("scan.jpg"), 100, Path("out"))
  193. assert paths[0] == Path("out/scan_001.jpg")
  194. assert paths[99] == Path("out/scan_100.jpg")
  195. def test_dot_in_filename(self) -> None:
  196. paths = generate_output_paths(Path("scan.001.jpg"), 2, Path("out"))
  197. assert paths == [Path("out/scan.001_01.jpg"), Path("out/scan.001_02.jpg")]
  198. class TestLoadImage:
  199. def test_valid_jpeg(self) -> None:
  200. image = create_test_image()
  201. path = image_to_path(image, ".jpg")
  202. loaded = load_image(path)
  203. assert loaded.shape == (300, 400, 3)
  204. def test_valid_png(self) -> None:
  205. image = create_test_image()
  206. path = image_to_path(image, ".png")
  207. loaded = load_image(path)
  208. assert loaded.shape == (300, 400, 3)
  209. def test_valid_tiff(self) -> None:
  210. image = create_test_image()
  211. path = image_to_path(image, ".tiff")
  212. loaded = load_image(path)
  213. assert loaded.shape == (300, 400, 3)
  214. def test_file_not_found(self) -> None:
  215. with pytest.raises(FileNotFoundError, match="Файл не найден"):
  216. load_image(Path("/tmp/nonexistent_scan2html_test.jpg"))
  217. def test_unsupported_extension(self) -> None:
  218. path = Path("/tmp/test.bmp")
  219. with pytest.raises(ValueError, match="Неподдерживаемый формат"):
  220. load_image(path)
  221. def test_unsupported_extension_no_dot(self) -> None:
  222. path = Path("/tmp/test")
  223. with pytest.raises(ValueError, match="Неподдерживаемый формат"):
  224. load_image(path)
  225. def test_supported_extensions_set(self) -> None:
  226. assert SUPPORTED_EXTENSIONS == frozenset({".jpg", ".jpeg", ".png", ".tiff", ".tif"})
  227. class TestSavePage:
  228. def test_saves_file(self) -> None:
  229. image = create_test_image()
  230. with tempfile.TemporaryDirectory() as tmp:
  231. path = Path(tmp) / "test.png"
  232. save_page(image, path)
  233. assert path.exists()
  234. loaded = cv2.imread(str(path))
  235. assert loaded is not None
  236. assert loaded.shape == (300, 400, 3)
  237. def test_creates_parent_dir(self) -> None:
  238. image = create_test_image()
  239. with tempfile.TemporaryDirectory() as tmp:
  240. path = Path(tmp) / "subdir" / "test.png"
  241. save_page(image, path)
  242. assert path.exists()
  243. class TestProcessImage:
  244. def test_end_to_end_2x2(self) -> None:
  245. image = create_test_image(400, 300)
  246. path = image_to_path(image, ".jpg")
  247. with tempfile.TemporaryDirectory() as tmp:
  248. result = process_image(Path(path), 2, 2, Path(tmp))
  249. assert len(result) == 4
  250. for p in result:
  251. assert p.exists()
  252. loaded = cv2.imread(str(p))
  253. assert loaded.shape == (250, 300, 3)
  254. def test_end_to_end_with_border(self) -> None:
  255. image = create_test_image(200, 200)
  256. path = image_to_path(image, ".png")
  257. with tempfile.TemporaryDirectory() as tmp:
  258. result = process_image(Path(path), 2, 2, Path(tmp), border_px=10)
  259. assert len(result) == 4
  260. for p in result:
  261. assert p.exists()
  262. loaded = cv2.imread(str(p))
  263. assert loaded.shape == (120, 120, 3)
  264. def test_end_to_end_tiff(self) -> None:
  265. image = create_test_image(400, 400)
  266. path = image_to_path(image, ".tiff")
  267. with tempfile.TemporaryDirectory() as tmp:
  268. result = process_image(Path(path), 2, 2, Path(tmp))
  269. assert len(result) == 4
  270. for p in result:
  271. assert p.suffix.lower() == ".tiff"
  272. assert p.exists()
  273. def test_end_to_end_with_rotation(self) -> None:
  274. image = create_test_image(200, 400)
  275. path = image_to_path(image, ".png")
  276. with tempfile.TemporaryDirectory() as tmp:
  277. result = process_image(Path(path), 2, 2, Path(tmp), pre_rotate=90)
  278. assert len(result) == 4
  279. for p in result:
  280. assert p.exists()
  281. loaded = cv2.imread(str(result[0]))
  282. assert loaded.shape[0] > 0