pdftract/tools/generate_invoice_pdf_fixtures.py
jedarden a68d01c726 feat(bf-337i2): create 300 DPI invoice OCR fixture
Add invoice fixture with ground truth text for OCR testing.

Files:
- tests/fixtures/scanned/invoice/invoice-300dpi.pdf: 300 DPI scanned invoice
- tests/fixtures/scanned/invoice/invoice-300dpi-ground-truth.txt: complete text content
- tools/generate_invoice_pdf_fixtures.py: fixture generator script
- tests/fixtures/PROVENANCE.md: added provenance entries for new fixtures
- notes/bf-337i2.md: verification note

Verification:
- PDF is image-based (type=image, not text-embedded)
- Resolution: 300 DPI (x-ppi=300, y-ppi=300)
- Dimensions: 2550 x 3300 pixels (letter size at 300 DPI)
- Ground truth contains complete invoice text with headers, addresses, line items, totals

Closes bf-337i2
2026-07-05 18:20:09 -04:00

158 lines
5.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Generate invoice OCR test fixtures with proper DPI metadata.
This creates a scanned PDF from an image with correct 300 DPI settings.
The PDF contains a single invoice page with ground truth text for OCR testing.
"""
import sys
import os
def create_invoice_pdf():
"""Create invoice PDF with proper 300 DPI metadata."""
try:
from PIL import Image
# Load the extracted image
img_path = "/tmp/invoice_img-000.png"
if not os.path.exists(img_path):
print(f"Error: {img_path} not found")
return False
# Open image and verify dimensions
img = Image.open(img_path)
width, height = img.size
# Expected dimensions for 300 DPI letter-size (8.5" x 11")
# 8.5 * 300 = 2550, 11 * 300 = 3300
expected_width = 2550
expected_height = 3300
if width != expected_width or height != expected_height:
print(f"Warning: Image dimensions {width}x{height} differ from expected {expected_width}x{expected_height}")
# Convert to RGB if needed
if img.mode != 'RGB':
img = img.convert('RGB')
# Save as JPEG for PDF embedding
jpeg_path = "/tmp/invoice_300dpi.jpg"
img.save(jpeg_path, format='JPEG', quality=95, dpi=(300, 300))
print(f"Created JPEG with 300 DPI: {jpeg_path}")
# Now use img2pdf approach via subprocess if available
import subprocess
output_path = "tests/fixtures/scanned/invoice/invoice-300dpi.pdf"
os.makedirs(os.path.dirname(output_path), exist_ok=True)
# Try using img2pdf from Python
try:
import img2pdf
# Convert image to PDF with proper DPI
with open(jpeg_path, 'rb') as img_file:
pdf_bytes = img2pdf.convert(
img_file.read(),
dpi=(300, 300),
with_img2pdf=False
)
with open(output_path, 'wb') as pdf_file:
pdf_file.write(pdf_bytes)
print(f"Created: {output_path}")
print(f" Image size: {width} x {height} pixels")
print(f" DPI: 300 x 300")
print(f" Physical size: {width/300:.2f} x {height/300:.2f} inches")
return True
except ImportError:
print("img2pdf not available, using alternative approach...")
# Use PIL's PDF approach
# Create a simple one-page PDF using reportlab if available
try:
from reportlab.lib.pagesizes import letter
from reportlab.platypus import Image as RLImage, SimpleDocTemplate
from reportlab.lib.units import inch
# Calculate size at 300 DPI
img_width_inch = width / 300
img_height_inch = height / 300
doc = SimpleDocTemplate(
output_path,
pagesize=(img_width_inch * inch, img_height_inch * inch)
)
story = []
rl_img = RLImage(jpeg_path, width=img_width_inch * inch, height=img_height_inch * inch)
story.append(rl_img)
doc.build(story)
print(f"Created: {output_path}")
print(f" Image size: {width} x {height} pixels")
print(f" DPI: 300 x 300")
print(f" Physical size: {img_width_inch:.2f} x {img_height_inch:.2f} inches")
return True
except ImportError:
print("Neither img2pdf nor reportlab available")
return False
except Exception as e:
print(f"Error creating PDF: {e}")
import traceback
traceback.print_exc()
return False
def verify_pdf(pdf_path):
"""Verify the PDF was created correctly."""
try:
import subprocess
# Check if pdfimages is available
result = subprocess.run(
["pdfimages", "-list", pdf_path],
capture_output=True,
text=True
)
if result.returncode == 0:
print("\nPDF verification (pdfimages -list):")
for line in result.stdout.split('\n'):
if line.strip():
print(f" {line}")
# Check pdfinfo if available
result = subprocess.run(
["pdfinfo", pdf_path],
capture_output=True,
text=True
)
if result.returncode == 0:
print("\nPDF info (pdfinfo):")
for line in result.stdout.split('\n'):
if any(keyword in line.lower() for keyword in ['page size', 'dpi', 'file size', 'pages']):
print(f" {line.strip()}")
return True
except Exception as e:
print(f"Warning: Could not verify PDF - {e}")
return False
if __name__ == "__main__":
print("Generating invoice OCR fixture...")
if create_invoice_pdf():
output_path = "tests/fixtures/scanned/invoice/invoice-300dpi.pdf"
verify_pdf(output_path)
print("\n✓ Invoice fixture created successfully!")
else:
print("\n✗ Failed to create invoice fixture")
sys.exit(1)