paperhawk / ingest /pdf_loader.py
Nándorfi Vince
Initial paperhawk push to HF Space (LFS for binaries)
7ff7119
raw
history blame
4.61 kB
"""PDF loader 3-szintű fallback-kel.
Réteg 1: PyMuPDF (fitz) — natív szövegkinyerés. Ha az oldal text >= SCANNED_THRESHOLD
karakter, ennyi elég, oldal "digitális".
Réteg 2: Tesseract OCR — ha az oldal natív szöveg < SCANNED_THRESHOLD, az oldalt
renderelt képpé alakítjuk és átadjuk az `ocr_image_bytes`-nak. Ha az
eredmény >= SCANNED_THRESHOLD, az oldalt is_scanned=False-ra állítjuk
és az OCR szöveget használjuk.
Réteg 3: Vision fallback — ha az OCR is < SCANNED_THRESHOLD, az oldal `is_scanned=True`
marad és a `image_bytes` mezőben tartjuk a renderelt képet. A downstream
extract subgraph `vision_extract_node` közvetlenül a képből (LLM vision)
nyer ki strukturált adatot.
Mindezt szinkronban csináljuk (PyMuPDF blocking C-binding), de a subgraph-ban
`asyncio.to_thread()` wrapper-rel hívjuk.
"""
from __future__ import annotations
from io import BytesIO
from graph.states.pipeline_state import IngestedDocument, PageContent
from ingest.ocr import SCANNED_THRESHOLD, ocr_image_bytes, tesseract_available
from ingest.tables import extract_tables_markdown
# Render DPI a vision-fallback-hez (200 DPI elég jó minőség Claude vision-nek)
RENDER_DPI = 200
def load_pdf(file_name: str, file_bytes: bytes) -> IngestedDocument:
"""Egy PDF betöltése IngestedDocument-té.
Args:
file_name: a fájl neve (a metadata-hoz)
file_bytes: a PDF bináris tartalma
Raises:
Az alsóbb rétegek hibái fel vannak fogva (try/except), de ha a PyMuPDF
kifejezetten nem tudja megnyitni a fájlt, akkor RuntimeError-ral fail-fast.
"""
import fitz # PyMuPDF
try:
pdf_doc = fitz.open(stream=file_bytes, filetype="pdf")
except Exception as e:
raise RuntimeError(f"Nem sikerult megnyitni a PDF-et: {file_name}: {e}") from e
try:
pages: list[PageContent] = []
any_scanned = False
ocr_enabled = tesseract_available()
for page_idx, page in enumerate(pdf_doc, start=1):
# 1. réteg: PyMuPDF natív
native_text = (page.get_text() or "").strip()
if len(native_text) >= SCANNED_THRESHOLD:
# Digitális oldal — natív szöveg elég
pages.append(PageContent(
page_number=page_idx,
text=native_text,
is_scanned=False,
image_bytes=None,
))
continue
# 2-3. réteg: oldalt képpé renderelünk
try:
pix = page.get_pixmap(dpi=RENDER_DPI)
image_bytes = pix.tobytes("png")
except Exception:
# Render fail — natív szöveggel megyünk tovább, mégha kevés is
pages.append(PageContent(
page_number=page_idx,
text=native_text,
is_scanned=True, # gyenge minőségű
image_bytes=None,
))
any_scanned = True
continue
# 2. réteg: Tesseract OCR (ha telepítve van)
ocr_text = ocr_image_bytes(image_bytes) if ocr_enabled else ""
if len(ocr_text) >= SCANNED_THRESHOLD:
# OCR sikerült — a natív szöveg helyett ezt használjuk
pages.append(PageContent(
page_number=page_idx,
text=ocr_text,
is_scanned=False,
image_bytes=image_bytes, # vision-extract opcionálisan használhatja
))
continue
# 3. réteg: vision fallback — a downstream extract LLM-vision-nel nyer ki
pages.append(PageContent(
page_number=page_idx,
text=native_text or ocr_text, # ami van (gyengébb), full_text-be megy RAG-hoz
is_scanned=True,
image_bytes=image_bytes,
))
any_scanned = True
# Aggregált full_text RAG-hoz
full_text = "\n\n".join(p.text for p in pages if p.text)
# Táblázatok kinyerése pdfplumber-rel (ha van)
tables_md, table_count = extract_tables_markdown(file_bytes)
return IngestedDocument(
file_name=file_name,
file_type="pdf",
pages=pages,
full_text=full_text,
tables_markdown=tables_md,
table_count=table_count,
is_scanned=any_scanned,
)
finally:
pdf_doc.close()