ParthKulshreshtha's picture
Deploy personalized ad environment
526cf2e verified
Raw
History Blame Contribute Delete
2.15 kB
"""OCR helper for part B. Wraps the local `tesseract` binary via pytesseract.
Deterministic on identical pixels; no network. We drew the overlay text
ourselves (clean, high-contrast), so tesseract reads it back reliably.
"""
from __future__ import annotations
import re
import pytesseract
from PIL import Image
def read_text(image: Image.Image) -> str:
"""Return the raw OCR text of ``image``.
Convert to luminance ("L") first: tesseract binarizes internally, and on the
raw RGB composite (colored background + light-on-dark text bands) its global
threshold drops every glyph. A single-channel luminance image gives a clean
histogram it reads reliably.
"""
return pytesseract.image_to_string(image.convert("L"))
def read_band(image: Image.Image, band: tuple[float, float]) -> str:
"""OCR ONE horizontal band with single-line page segmentation.
Whole-image OCR with Tesseract's DEFAULT page segmentation silently drops text regions when
they are widely separated — measured at 151/1088 of our combos. Cropping to the band and
forcing --psm 7 (treat the image as a single text line) reads them reliably.
"""
width, height = image.size
y0, y1 = band
crop = image.crop((0, int(height * y0), width, int(height * y1)))
return pytesseract.image_to_string(crop.convert("L"), config="--psm 7")
def _normalize(s: str) -> str:
"""Lowercase; collapse every run of non-alphanumerics to a single space."""
return re.sub(r"[^a-z0-9]+", " ", s.lower()).strip()
def text_present(ocr_text: str, phrase: str, cutoff: float = 0.8) -> bool:
"""True if ``phrase`` appears in ``ocr_text`` (normalized).
Exact normalized substring wins; otherwise fall back to token overlap
(fraction of the phrase's tokens found) against ``cutoff``.
"""
norm_ocr = _normalize(ocr_text)
norm_phrase = _normalize(phrase)
if not norm_phrase:
return True
if norm_phrase in norm_ocr:
return True
tokens = norm_phrase.split()
if not tokens:
return True
hits = sum(1 for t in tokens if t in norm_ocr)
return hits / len(tokens) >= cutoff