Files
unredact/tests/test_font_detect_real.py

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}"