Cropping investigation

This commit is contained in:
sebastien committed 2026-10-11 10:12:59 +02:00
1 parent 48602c24f1
commit bd1964ffcd
10 files changed
+814 -24

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
import json
import tempfile
import unittest
from pathlib import Path
import cv2
import numpy as np
from PIL import Image
from copienator.crop_diagnostics import check_protected_regions, save_diagnostics
from copienator.ink_detection import detect_bounds
class CropDiagnosticsTests(unittest.TestCase):
def scan(self, *, dark=False, skew=False):
rgb = np.full((900,650,3),255,np.uint8)
if dark:
for y in range(15,900,20):
cv2.line(rgb,(0,y),(649,y),(65,65,65),1)
for x in range(10,650,20):
cv2.line(rgb,(x,0),(x,899),(65,65,65),1)
cv2.putText(rgb,'answer',(100,400),cv2.FONT_HERSHEY_SIMPLEX,1,(20,50,180),2)
cv2.putText(rgb,'more',(100,560),cv2.FONT_HERSHEY_SIMPLEX,.8,(30,30,30),2)
if skew:
rgb = cv2.warpAffine(rgb,cv2.getRotationMatrix2D((325,450),2,1),
(650,900),borderValue=(255,255,255))
return rgb
def test_diagnostics_leave_decisions_and_json_contract_unchanged(self):
for dark,skew in [(False,False),(True,False),(True,True)]:
with self.subTest(dark=dark,skew=skew):
rgb = self.scan(dark=dark,skew=skew)
before = rgb.copy()
normal = detect_bounds(rgb,dpi=150)
debug = {'stale': 'discard this'}
observed = detect_bounds(rgb,dpi=150,diagnostics=debug)
self.assertEqual(normal,observed)
json.dumps(observed)
np.testing.assert_array_equal(rgb,before)
self.assertNotIn('stale',debug)
for name,mask in debug['masks'].items():
self.assertEqual(mask.shape,rgb.shape[:2],name)
self.assertEqual(mask.dtype,np.bool_,name)
masks = debug['masks']
self.assertFalse(np.any(masks['strong_seeds'] & ~masks['foreground']))
self.assertFalse(np.any(masks['recovered_weak'] & masks['strong_seeds']))
for item in debug['thresholds']:
for edge in ['top','bottom']:
for component in item['limiting_components'][edge]:
box = component['bbox_px']
self.assertEqual(box[1] if edge=='top' else box[3],
item['extent'][0 if edge=='top' else 1])
self.assertGreater(component['seed_pixels'],0)
def test_blank_fallback_has_empty_foreground_and_explanation(self):
rgb = np.full((400,300,3),255,np.uint8)
debug = {}
result = detect_bounds(rgb,diagnostics=debug)
self.assertEqual((result['top_px'],result['bottom_px']),(0,400))
self.assertFalse(debug['masks']['foreground'].any())
self.assertEqual(debug['edges']['bottom']['reason'],'no-reliable-ink-seeds')
def test_faint_unseeded_candidate_is_distinguished_from_recovered_ink(self):
rgb = self.scan()
cv2.putText(rgb,'weak',(100,800),cv2.FONT_HERSHEY_SIMPLEX,1,(130,160,205),2)
debug = {}
detect_bounds(rgb,dpi=150,diagnostics=debug)
self.assertTrue(debug['masks']['unretained_weak'][770:810,100:220].any())
self.assertFalse(debug['masks']['foreground'][770:810,100:220].any())
def test_exported_masks_match_actual_pixels_and_report_is_serializable(self):
rgb = self.scan(dark=True,skew=True)
debug = {}
result = detect_bounds(rgb,dpi=150,diagnostics=debug)
with tempfile.TemporaryDirectory() as tmp:
files = save_diagnostics(rgb,result,debug,Path(tmp),width_mm=110,height_mm=152)
for name,path in files['masks'].items():
with Image.open(Path(tmp)/path) as im:
np.testing.assert_array_equal(np.array(im).astype(bool),debug['masks'][name])
report = json.loads((Path(tmp)/files['report']).read_text())
self.assertEqual(report['detector'],result)
self.assertEqual(report['shape'],list(rgb.shape[:2]))
for path in files['overlays'].values():
self.assertTrue((Path(tmp)/path).is_file())
class ProtectedContentTests(unittest.TestCase):
def check(self,top,bottom,box):
return check_protected_regions([dict(id='ink',label='Real ink',bbox_mm=box)],
dict(top_px=top,bottom_px=bottom),width_mm=80,height_mm=100,shape=(1000,800))[0]
def test_both_boundaries_can_clip_a_protected_region(self):
top = self.check(210,800,[10,20,50,30])
self.assertFalse(top['passed'])
self.assertEqual(top['clipped_edges'],['top'])
self.assertAlmostEqual(top['clipped_area_fraction'],.1)
bottom = self.check(100,790,[10,70,50,80])
self.assertFalse(bottom['passed'])
self.assertEqual(bottom['clipped_edges'],['bottom'])
self.assertAlmostEqual(bottom['clipped_area_fraction'],.1)
def test_contact_at_boundary_and_short_page_physical_height(self):
self.assertTrue(self.check(200,300,[10,20,50,30])['passed'])
row = check_protected_regions([dict(id='end',label='End',bbox_mm=[2,17,20,19])],
dict(top_px=0,bottom_px=180),width_mm=40,height_mm=20,shape=(200,400))[0]
self.assertFalse(row['passed'])
self.assertAlmostEqual(row['clipped_area_fraction'],.5)
def test_full_removal_and_invalid_ground_truth(self):
self.assertEqual(self.check(400,800,[10,20,50,30])['clipped_area_fraction'],1)
with self.assertRaises(ValueError):
self.check(0,1000,[10,20,90,30])
with self.assertRaises(ValueError):
self.check(0,1000,[10,30,50,20])
if __name__ == '__main__':
unittest.main()