-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathtest_rotation.py
More file actions
222 lines (184 loc) · 9.1 KB
/
Copy pathtest_rotation.py
File metadata and controls
222 lines (184 loc) · 9.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
"""
Tests for the unified rotation module.
"""
import unittest
from bigocrpdf.services.rapidocr_service.rotation import (
PageRotation,
apply_editor_modifications,
)
class TestPageRotation(unittest.TestCase):
"""Tests for PageRotation dataclass."""
def test_effective_rotation_no_editor(self):
"""Original rotation only."""
rot = PageRotation(page_number=1, original_pdf_rotation=90)
self.assertEqual(rot.effective_rotation, 90)
def test_effective_rotation_with_editor(self):
"""Combined original + editor rotation."""
rot = PageRotation(page_number=1, original_pdf_rotation=90, editor_rotation=90)
self.assertEqual(rot.effective_rotation, 180)
def test_effective_rotation_wraps(self):
"""Rotation wraps at 360 degrees."""
rot = PageRotation(page_number=1, original_pdf_rotation=270, editor_rotation=180)
self.assertEqual(rot.effective_rotation, 90) # 270 + 180 = 450 % 360 = 90
def test_ocr_image_rotation_equals_original(self):
"""OCR rotation should match original PDF rotation."""
rot = PageRotation(page_number=1, original_pdf_rotation=180)
self.assertEqual(rot.ocr_image_rotation, 180)
def test_pdf_dimensions_from_mediabox(self):
"""Dimensions from mediabox."""
rot = PageRotation(page_number=1, mediabox=[0.0, 0.0, 612.0, 792.0])
self.assertEqual(rot.pdf_dimensions, (612.0, 792.0))
def test_pdf_dimensions_default_a4(self):
"""Default A4 when no mediabox."""
rot = PageRotation(page_number=1)
self.assertEqual(rot.pdf_dimensions, (595.0, 842.0))
class TestApplyEditorModifications(unittest.TestCase):
"""Tests for apply_editor_modifications function."""
def test_no_modifications(self):
"""No editor modifications - returns unchanged."""
rotations = [
PageRotation(page_number=1, original_pdf_rotation=0),
PageRotation(page_number=2, original_pdf_rotation=90),
]
result = apply_editor_modifications(rotations, None)
self.assertEqual(result[0].editor_rotation, 0)
self.assertEqual(result[1].editor_rotation, 0)
def test_apply_rotation(self):
"""Editor rotation is applied."""
rotations = [
PageRotation(page_number=1, original_pdf_rotation=0),
PageRotation(page_number=2, original_pdf_rotation=0),
]
mods = [{"page_number": 2, "rotation": 90, "deleted": False}]
result = apply_editor_modifications(rotations, mods)
self.assertEqual(result[0].editor_rotation, 0)
self.assertEqual(result[1].editor_rotation, 90)
def test_apply_deletion(self):
"""Deletion flag is applied."""
rotations = [PageRotation(page_number=1), PageRotation(page_number=2)]
mods = [{"page_number": 1, "deleted": True}]
result = apply_editor_modifications(rotations, mods)
self.assertTrue(result[0].deleted)
self.assertFalse(result[1].deleted)
def test_multiple_modifications(self):
"""Multiple modifications applied correctly."""
rotations = [
PageRotation(page_number=1),
PageRotation(page_number=2),
PageRotation(page_number=3),
]
mods = [
{"page_number": 1, "rotation": 180},
{"page_number": 3, "rotation": 270, "deleted": True},
]
result = apply_editor_modifications(rotations, mods)
self.assertEqual(result[0].editor_rotation, 180)
self.assertEqual(result[0].deleted, False)
self.assertEqual(result[1].editor_rotation, 0)
self.assertEqual(result[2].editor_rotation, 270)
self.assertTrue(result[2].deleted)
if __name__ == "__main__":
unittest.main()
class TestTransformOcrCoordsForRotation(unittest.TestCase):
"""Tests for transform_ocr_coords_for_rotation (pdf_extractor.py).
This function transforms OCR coordinates from the upright image space
to the PDF's native coordinate space, accounting for /Rotate metadata.
"""
def setUp(self):
"""Import the function under test."""
from bigocrpdf.services.rapidocr_service.config import OCRResult
self.OCRResult = OCRResult
from bigocrpdf.services.rapidocr_service.pdf_extractor import (
transform_ocr_coords_for_rotation,
)
self.transform = transform_ocr_coords_for_rotation
def _make_result(self, box):
"""Create an OCRResult with given box coordinates."""
return self.OCRResult(text="test", box=box, confidence=0.95)
def test_rotation_0_identity(self):
"""No rotation — coordinates are just scaled."""
result = self._make_result([[0, 0], [100, 0], [100, 50], [0, 50]])
transformed = self.transform(
[result],
ocr_img_size=(200, 100), # OCR image W×H
pdf_page_size=(400, 200), # PDF W×H (2x scale)
rotation=0,
)
self.assertEqual(len(transformed), 1)
# 2x scale in both axes
self.assertAlmostEqual(transformed[0].box[0][0], 0.0)
self.assertAlmostEqual(transformed[0].box[0][1], 0.0)
self.assertAlmostEqual(transformed[0].box[1][0], 200.0)
self.assertAlmostEqual(transformed[0].box[2][1], 100.0)
def test_rotation_90_contrato_like(self):
"""Rotation=90 (contrato.pdf case): landscape MediaBox, portrait OCR image.
Original: MediaBox [0,0,3864,2814] (landscape), /Rotate=90
Image extracted landscape → rotated to portrait for OCR.
OCR coords are in portrait space and must map to landscape PDF space.
"""
# Point at top-left of portrait image (after 90° CW rotation of landscape)
result = self._make_result([[0, 0], [100, 0], [100, 100], [0, 100]])
transformed = self.transform(
[result],
ocr_img_size=(2814, 3864), # Portrait OCR image (W, H)
pdf_page_size=(3864, 2814), # Landscape PDF MediaBox (W, H)
rotation=90,
)
self.assertEqual(len(transformed), 1)
# scale_x = 3864/3864 = 1.0, scale_y = 2814/2814 = 1.0
# new_x = p[1] * 1.0, new_y = (2814 - p[0]) * 1.0
self.assertAlmostEqual(transformed[0].box[0][0], 0.0)
self.assertAlmostEqual(transformed[0].box[0][1], 2814.0)
def test_rotation_180(self):
"""Rotation=180 — coordinates are mirrored in both axes."""
result = self._make_result([[10, 20], [110, 20], [110, 70], [10, 70]])
transformed = self.transform(
[result],
ocr_img_size=(200, 100),
pdf_page_size=(200, 100), # Same size, no scaling
rotation=180,
)
self.assertEqual(len(transformed), 1)
# new_x = (200 - p[0]), new_y = (100 - p[1])
self.assertAlmostEqual(transformed[0].box[0][0], 190.0)
self.assertAlmostEqual(transformed[0].box[0][1], 80.0)
def test_rotation_270(self):
"""Rotation=270 — coordinates transform correctly with correct scale_y."""
# Simulate: landscape MediaBox, image rotated 270° to portrait for OCR
result = self._make_result([[0, 0], [100, 0], [100, 50], [0, 50]])
transformed = self.transform(
[result],
ocr_img_size=(2814, 3864), # Portrait OCR (W, H)
pdf_page_size=(3864, 2814), # Landscape PDF (W, H)
rotation=270,
)
self.assertEqual(len(transformed), 1)
# scale_x = 3864/3864 = 1.0 (pdf_w / ocr_h)
# scale_y = 2814/2814 = 1.0 (pdf_h / ocr_w)
# new_x = (3864 - 0) * 1.0 = 3864
# new_y = 0 * 1.0 = 0
self.assertAlmostEqual(transformed[0].box[0][0], 3864.0)
self.assertAlmostEqual(transformed[0].box[0][1], 0.0)
class TestGeometryChangeThreshold(unittest.TestCase):
"""Test that geometry change detection uses a significance threshold."""
@staticmethod
def _is_significant(orig_h, orig_w, proc_h, proc_w, orientation_angle=0):
"""Replicate the threshold logic from _create_text_layer_pdf."""
total_size = orig_h + orig_w
dim_change = abs(orig_h - proc_h) + abs(orig_w - proc_w)
change_ratio = dim_change / total_size if total_size > 0 else 0
return change_ratio > 0.05 or orientation_angle != 0
def test_deskew_minor_change_not_significant(self):
"""Deskew adding small borders (~3%) should NOT trigger geometry mode."""
# contrato.pdf: 2814x3864 → 2939x3954
self.assertFalse(self._is_significant(3864, 2814, 3954, 2939))
def test_perspective_correction_is_significant(self):
"""Perspective correction (~19%) SHOULD trigger geometry mode."""
# example-need-fix-perspective1.pdf: 1920x2560 → 1495x2114
self.assertTrue(self._is_significant(2560, 1920, 2114, 1495))
def test_no_change_not_significant(self):
"""Identical dimensions should NOT trigger geometry mode."""
self.assertFalse(self._is_significant(3864, 2814, 3864, 2814))
def test_orientation_angle_is_significant(self):
"""Non-zero orientation angle should always trigger geometry mode."""
self.assertTrue(self._is_significant(3864, 2814, 3864, 2814, orientation_angle=90))