OCR-Demo / utils /pdf_utils.py
thangvckeygen's picture
Redesign UI/UX Pro Max with Light Theme and memory-safe PDF pipeline
bd7234d
Raw
History Blame Contribute Delete
4.09 kB
"""
PDF and Image handling utilities for OCR space.
Supports multi-page PDF rendering from paths or in-memory bytes with guaranteed file handle closure.
"""
import os
from typing import List, Union, Tuple, Optional
from PIL import Image
import io
import logging
logger = logging.getLogger("PDFUtils")
def is_pdf_file(file_input: Union[str, bytes, None]) -> bool:
"""Check if the input represents a PDF."""
if isinstance(file_input, str):
return file_input.lower().endswith(".pdf")
if isinstance(file_input, bytes):
return file_input.startswith(b"%PDF")
return False
def convert_pdf_bytes_to_images(pdf_bytes: bytes, max_pages: int = 10, dpi: int = 200) -> List[Image.Image]:
"""Converts in-memory PDF bytes into a list of PIL Images (one per page)."""
images = []
# 1. Try pypdfium2 (in-memory, no temp file)
try:
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(pdf_bytes)
try:
total_pages = min(len(pdf), max_pages)
scale = dpi / 72.0
for i in range(total_pages):
page = pdf[i]
image = page.render(scale=scale).to_pil()
images.append(image.convert("RGB"))
if images:
return images
finally:
pdf.close()
except Exception as e:
logger.debug(f"pypdfium2 byte render fallback: {e}")
# 2. Try fitz (PyMuPDF in-memory)
try:
import fitz
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
try:
total_pages = min(len(doc), max_pages)
for i in range(total_pages):
page = doc[i]
pix = page.get_pixmap(dpi=dpi)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
images.append(img)
if images:
return images
finally:
doc.close()
except Exception as e:
logger.debug(f"fitz byte render fallback: {e}")
if not images:
raise RuntimeError("Could not decode in-memory PDF with pypdfium2 or fitz.")
return images
def convert_pdf_to_images(pdf_path: str, max_pages: int = 10, dpi: int = 200) -> List[Image.Image]:
"""Converts a PDF file from a path into a list of PIL Images."""
if not os.path.exists(pdf_path):
raise FileNotFoundError(f"PDF not found: {pdf_path}")
with open(pdf_path, "rb") as f:
pdf_bytes = f.read()
return convert_pdf_bytes_to_images(pdf_bytes, max_pages=max_pages, dpi=dpi)
def load_input_image_or_pdf(
file_input: Union[str, bytes, Image.Image, None], page_index: int = 0
) -> Tuple[Optional[Image.Image], int, List[Image.Image]]:
"""
Helper to process upload file (image, PDF path, or PDF bytes) and return:
- (selected_image, total_pages, all_page_images)
"""
if file_input is None:
return None, 0, []
if isinstance(file_input, Image.Image):
return file_input.convert("RGB"), 1, [file_input.convert("RGB")]
if isinstance(file_input, bytes):
try:
img = Image.open(io.BytesIO(file_input)).convert("RGB")
return img, 1, [img]
except Exception:
pages = convert_pdf_bytes_to_images(file_input)
total = len(pages)
if total == 0:
raise ValueError("PDF contains no readable pages.")
idx = max(0, min(page_index, total - 1))
return pages[idx], total, pages
if isinstance(file_input, str):
if not os.path.exists(file_input):
raise FileNotFoundError(f"File not found: {file_input}")
if is_pdf_file(file_input):
pages = convert_pdf_to_images(file_input)
total = len(pages)
if total == 0:
raise ValueError("PDF contains no readable pages.")
idx = max(0, min(page_index, total - 1))
return pages[idx], total, pages
else:
img = Image.open(file_input).convert("RGB")
return img, 1, [img]
return None, 0, []