mirror of
https://github.com/PR0M3TH3AN/unredact.git
synced 2026-10-27 08:40:29 +00:00
148 lines
5.7 KiB
Python
148 lines
5.7 KiB
Python
"""Font detection tests using real document data.
|
|
|
|
Uses captured OCR + LLM fixtures from EFTA00554620.pdf to verify that
|
|
font detection returns Times New Roman (or Liberation Serif) at ~50px
|
|
for the body text.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from unredact.pipeline.font_detect import (
|
|
detect_font_for_line,
|
|
detect_font_masked,
|
|
_find_font_path,
|
|
)
|
|
from unredact.pipeline.ocr import OcrChar, OcrLine
|
|
|
|
FIXTURES = Path(__file__).parent / "fixtures"
|
|
PAGE1_JSON = FIXTURES / "EFTA00554620_page1.json"
|
|
PAGE1_PNG = FIXTURES / "EFTA00554620_page1.png"
|
|
|
|
|
|
def _load_fixture():
|
|
"""Load OCR lines from the JSON fixture as OcrLine objects."""
|
|
data = json.loads(PAGE1_JSON.read_text())
|
|
lines = []
|
|
for ld in data["ocr_lines"]:
|
|
chars = [
|
|
OcrChar(text=c["text"], x=c["x"], y=c["y"], w=c["w"], h=c["h"], conf=c["conf"])
|
|
for c in ld["chars"]
|
|
]
|
|
lines.append(OcrLine(chars=chars, x=ld["x"], y=ld["y"], w=ld["w"], h=ld["h"]))
|
|
return data, lines
|
|
|
|
|
|
def _load_page_image():
|
|
return Image.open(PAGE1_PNG)
|
|
|
|
|
|
def _is_serif(name: str) -> bool:
|
|
"""Check if the font name is a serif font."""
|
|
return any(s in name for s in ("Times", "Liberation Serif", "Georgia", "DejaVu Serif"))
|
|
|
|
|
|
# Skip all tests if fixtures missing
|
|
pytestmark = pytest.mark.skipif(
|
|
not PAGE1_JSON.exists() or not PAGE1_PNG.exists(),
|
|
reason="Real document fixtures not available",
|
|
)
|
|
|
|
|
|
class TestCleanLines:
|
|
"""Font detection on lines without redactions."""
|
|
|
|
def test_date_line(self):
|
|
"""Line 3: 'Date: Mon, 27 Aug 2012 18:57:08 +0000'"""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[3]
|
|
result = detect_font_for_line(line, page_img)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
def test_body_text_line(self):
|
|
"""Line 10: 'I need to know if he's out of jerky...'"""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[10]
|
|
result = detect_font_for_line(line, page_img)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
def test_short_clean_line(self):
|
|
"""Line 5: 'through it like crazy'"""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[5]
|
|
result = detect_font_for_line(line, page_img)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
|
|
class TestMaskedLines:
|
|
"""Font detection on lines with redactions, using LLM output."""
|
|
|
|
def _get_redaction_boxes_for_line(self, data, line_index):
|
|
"""Get approximate redaction boxes from LLM output for a given line."""
|
|
boxes = []
|
|
for r in data["llm_redactions"]:
|
|
if r["line_index"] == line_index:
|
|
line_data = data["ocr_lines"][line_index]
|
|
# Use LLM's x range as the redaction box
|
|
x = r["left_x"]
|
|
w = r["right_x"] - r["left_x"]
|
|
y = line_data["y"]
|
|
h = line_data["h"]
|
|
if w > 0:
|
|
boxes.append((x, y, w, h))
|
|
return boxes
|
|
|
|
def test_got_it_sent_line(self):
|
|
"""Line 7: 'Got it. Sent[ an email.' — the screenshot case."""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[7]
|
|
boxes = self._get_redaction_boxes_for_line(data, 7)
|
|
assert len(boxes) > 0, "Expected redaction boxes for line 7"
|
|
result = detect_font_masked(line, page_img, boxes)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
def test_from_line_with_redaction(self):
|
|
"""Line 0: 'From: |-' — redacted email address."""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[0]
|
|
boxes = self._get_redaction_boxes_for_line(data, 0)
|
|
assert len(boxes) > 0, "Expected redaction boxes for line 0"
|
|
result = detect_font_masked(line, page_img, boxes)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
def test_on_aug_line(self):
|
|
"""Line 6: 'On Aug 27, 2012, at 2:02 PM, — wrote:' — redacted name."""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[6]
|
|
boxes = self._get_redaction_boxes_for_line(data, 6)
|
|
assert len(boxes) > 0, "Expected redaction boxes for line 6"
|
|
result = detect_font_masked(line, page_img, boxes)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|
|
|
|
def test_let_or_know_line(self):
|
|
"""Line 19: multiple redactions — 'let ]l or ] know what to do...'"""
|
|
data, lines = _load_fixture()
|
|
page_img = _load_page_image()
|
|
line = lines[19]
|
|
boxes = self._get_redaction_boxes_for_line(data, 19)
|
|
assert len(boxes) >= 2, f"Expected multiple redaction boxes, got {len(boxes)}"
|
|
result = detect_font_masked(line, page_img, boxes)
|
|
assert _is_serif(result.font_name), f"Expected serif, got {result.font_name}"
|
|
assert 45 <= result.font_size <= 55, f"Expected ~50px, got {result.font_size}"
|