Automatic page splitting
This commit is contained in:
1 parent
d4404e4c27
commit
78f3e4b2f0
13 files changed
+1959
-40
No files matched your search
+107
-1
@@ -3,10 +3,116 @@ import unittest
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from copienator.ink_detection import detect_bounds, _paper_blur
|
||||
from copienator.ink_detection import detect_bounds, detect_page_content, _paper_blur, _blank_content_support
|
||||
|
||||
|
||||
class InkDetectionTests(unittest.TestCase):
|
||||
def test_lightweight_content_masks_match_full_diagnostics(self):
|
||||
for image,colour in [(self.scan(),False),(self.dark_grid(),False),
|
||||
(self.scan(),True),(np.full((300,220,3),255,np.uint8),False)]:
|
||||
with self.subTest(colour_cleanup=colour,shape=image.shape):
|
||||
full,light = {},{}
|
||||
expected=detect_bounds(image,dpi=150,diagnostics=full,colour_cleanup=colour)
|
||||
actual=detect_bounds(image,dpi=150,content_masks=light,colour_cleanup=colour)
|
||||
self.assertEqual(actual,expected)
|
||||
self.assertEqual(set(light),{'foreground','weak_candidates'})
|
||||
for name in light:
|
||||
np.testing.assert_array_equal(light[name],full['masks'][name])
|
||||
|
||||
def test_blank_detection_ignores_remote_specks(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
for x,y in ((150,200),(400,600),(700,1000)):
|
||||
cv2.rectangle(image,(x,y),(x+3,y+4),(30,30,30),-1)
|
||||
mask,result = detect_page_content(image,dpi=150)
|
||||
self.assertFalse(mask.any())
|
||||
self.assertGreater(result['content_unsupported_pixels'],0)
|
||||
|
||||
def test_isolated_question_numbers_remain_content(self):
|
||||
for text in ('1','7','16','x'):
|
||||
with self.subTest(text=text):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.putText(image,text,(300,600),cv2.FONT_HERSHEY_SIMPLEX,.7,(30,30,30),2)
|
||||
self.assertTrue(detect_page_content(image,dpi=150)[0].any())
|
||||
|
||||
def test_faint_edge_question_number_remains_content(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.putText(image,'1',(1,600),cv2.FONT_HERSHEY_SIMPLEX,.7,(190,190,190),2)
|
||||
self.assertTrue(detect_page_content(image,dpi=150)[0].any())
|
||||
|
||||
def test_isolated_circle_diagram_remains_content(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.circle(image,(400,600),20,(30,30,30),2)
|
||||
self.assertTrue(detect_page_content(image,dpi=150)[0].any())
|
||||
|
||||
def test_solid_ink_dots_do_not_establish_content_at_multiple_resolutions(self):
|
||||
for dpi in (150,200,300):
|
||||
px=dpi/25.4
|
||||
mask=np.zeros((round(100*px),round(70*px)),np.uint8)
|
||||
cv2.ellipse(mask,(round(30*px),round(45*px)),
|
||||
(round(.6*px),round(.8*px)),0,0,360,1,-1)
|
||||
self.assertFalse(_blank_content_support(mask>0,dpi)[0].any())
|
||||
|
||||
def test_compact_dot_is_kept_with_neighbouring_question_number(self):
|
||||
mask=np.zeros((400,300),np.uint8)
|
||||
cv2.putText(mask,'7',(100,200),cv2.FONT_HERSHEY_SIMPLEX,.7,1,2)
|
||||
mask[194:200,117:122]=1
|
||||
retained,_=_blank_content_support(mask>0,150)
|
||||
self.assertTrue(retained[194:200,117:122].all())
|
||||
|
||||
def test_large_isolated_numbers_and_diagram_near_bottom_remain_content(self):
|
||||
for text in ('1','7','x','?','2','16'):
|
||||
with self.subTest(text=text):
|
||||
image=np.full((1200,850,3),255,np.uint8)
|
||||
cv2.putText(image,text,(300,1150),cv2.FONT_HERSHEY_SIMPLEX,
|
||||
1.5,(30,30,30),2)
|
||||
self.assertTrue(detect_page_content(image,200)[0].any())
|
||||
image=np.full((1200,850,3),255,np.uint8)
|
||||
cv2.circle(image,(400,1140),20,(30,30,30),2)
|
||||
self.assertTrue(detect_page_content(image,200)[0].any())
|
||||
|
||||
def test_content_analysis_recognizes_blank_grid_without_changing_crop_fallback(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
for y in range(20,1200,20):
|
||||
cv2.line(image,(0,y),(849,y),(65,65,65),1)
|
||||
for x in range(10,850,20):
|
||||
cv2.line(image,(x,0),(x,1199),(65,65,65),1)
|
||||
for y in (110,370,630,890,1130):
|
||||
cv2.ellipse(image,(25,y),(12,20),0,0,300,(35,35,35),2)
|
||||
before = detect_bounds(image,dpi=150)
|
||||
mask,result = detect_page_content(image,dpi=150)
|
||||
self.assertTrue(result['content_ruling_confirmed'])
|
||||
self.assertFalse(mask.any())
|
||||
self.assertEqual(detect_bounds(image,dpi=150),before)
|
||||
|
||||
def test_content_analysis_preserves_pencil_only_writing(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.putText(image,'pencil',(100,600),cv2.FONT_HERSHEY_SIMPLEX,
|
||||
1,(195,195,195),2)
|
||||
mask,result = detect_page_content(image,dpi=150)
|
||||
self.assertTrue(mask[560:610,90:250].any())
|
||||
self.assertEqual(result['content_state'],'content')
|
||||
|
||||
def test_faint_scanner_border_does_not_count_as_pencil(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.rectangle(image,(1,1),(848,300),(195,195,195),1)
|
||||
mask,_ = detect_page_content(image,dpi=150)
|
||||
self.assertFalse(mask.any())
|
||||
|
||||
def test_faint_unruled_diagram_is_retained(self):
|
||||
image = np.full((1200,850,3),255,np.uint8)
|
||||
cv2.rectangle(image,(100,100),(750,1100),(195,195,195),2)
|
||||
mask,_ = detect_page_content(image,dpi=150)
|
||||
self.assertTrue(mask.any())
|
||||
|
||||
def test_content_analysis_preserves_faint_grid_note_and_fraction_bar(self):
|
||||
image = self.dark_grid()
|
||||
cv2.putText(image,'pencil',(100,1100),cv2.FONT_HERSHEY_SIMPLEX,
|
||||
.7,(190,190,190),2)
|
||||
cv2.line(image,(150,1020),(700,1020),(0,0,0),3)
|
||||
mask,_ = detect_page_content(image,dpi=150)
|
||||
self.assertTrue(mask[1070:1110,90:250].any())
|
||||
self.assertTrue(mask[1018:1023,150:700].any())
|
||||
|
||||
def test_fast_background_blur_matches_opencv_pixel_for_pixel(self):
|
||||
rng = np.random.default_rng(42)
|
||||
for shape in ((97,131), (241,319)):
|
||||
|
||||
Reference in new issue
Block a user