Spaces:
Running
Running
import spaces | |
import gradio as gr | |
import fitz # PyMuPDF | |
from PIL import Image | |
import pytesseract | |
import os | |
import numpy as np | |
import cv2 | |
def clean_ocr_text(text): | |
lines = text.splitlines() | |
cleaned_lines = [] | |
for line in lines: | |
line = line.strip() | |
if line and not line.isspace(): | |
cleaned_lines.append(line) | |
return "\n".join(cleaned_lines) | |
def extract_text_markdown(doc, image_paths, page_index, seen_xrefs): | |
markdown_output = f"\n## Página {page_index + 1}\n\n" | |
image_counter = 1 | |
elements = [] | |
page = doc[0] | |
blocks = page.get_text("dict")["blocks"] | |
for b in blocks: | |
y = b["bbox"][1] | |
if b["type"] == 0: | |
for line in b["lines"]: | |
line_y = line["bbox"][1] | |
line_text = " ".join([span["text"] for span in line["spans"]]).strip() | |
max_font_size = max([span.get("size", 10) for span in line["spans"]]) | |
if line_text: | |
elements.append((line_y, line_text, max_font_size)) | |
images_on_page = page.get_images(full=True) | |
for img_index, img in enumerate(images_on_page): | |
xref = img[0] | |
if xref in seen_xrefs: | |
continue | |
seen_xrefs.add(xref) | |
try: | |
base_image = page.parent.extract_image(xref) | |
image_bytes = base_image["image"] | |
ext = base_image["ext"] | |
image_path = f"/tmp/imagen_p{page_index + 1}_{img_index + 1}.{ext}" | |
with open(image_path, "wb") as f: | |
f.write(image_bytes) | |
image_paths.append(image_path) | |
elements.append((float("inf") - img_index, f"\n\n\n", 10)) | |
image_counter += 1 | |
except Exception as e: | |
elements.append((float("inf"), f"[Error imagen: {e}]", 10)) | |
elements.sort(key=lambda x: x[0]) | |
previous_y = None | |
for y, text, font_size in elements: | |
is_header = font_size >= 14 | |
if previous_y is not None and abs(y - previous_y) > 10: | |
markdown_output += "\n" | |
if is_header: | |
markdown_output += f"\n### {text.strip()}\n" | |
else: | |
markdown_output += text.strip() + "\n" | |
previous_y = y | |
markdown_output += "\n---\n\n" | |
return markdown_output.strip() | |
def convert(pdf_file): | |
doc = fitz.open(pdf_file) | |
markdown_output = "" | |
image_paths = [] | |
seen_xrefs = set() | |
for page_num in range(len(doc)): | |
page = doc[page_num] | |
text = page.get_text("text").strip() | |
if len(text) > 30: | |
markdown_output += extract_text_markdown([page], image_paths, page_num, seen_xrefs) + "\n" | |
else: | |
markdown_output += f"\n## Página {page_num + 1}\n\n" | |
pix = page.get_pixmap(dpi=300) | |
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) | |
image_path = f"/tmp/ocr_page_{page_num + 1}.jpg" | |
img.save(image_path) | |
image_paths.append(image_path) | |
markdown_output += f"\n" | |
try: | |
ocr_text = pytesseract.image_to_string(img) | |
except pytesseract.TesseractError: | |
ocr_text = "" | |
ocr_text = clean_ocr_text(ocr_text) | |
if ocr_text.strip(): | |
markdown_output += ocr_text + "\n" | |
try: | |
img_cv = np.array(img) | |
gray = cv2.cvtColor(img_cv, cv2.COLOR_RGB2GRAY) | |
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV) | |
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] | |
for i, cnt in enumerate(contours): | |
x, y, w, h = cv2.boundingRect(cnt) | |
area = w * h | |
if area > 5000: | |
region = img_cv[y:y+h, x:x+w] | |
detected_path = f"/tmp/img_detectada_p{page_num + 1}_{i + 1}.jpg" | |
Image.fromarray(region).save(detected_path) | |
image_paths.append(detected_path) | |
markdown_output += f"\n\n\n" | |
except Exception as e: | |
markdown_output += f"\n\n[Error al detectar imágenes embebidas: {e}]\n" | |
markdown_output += "\n---\n\n" | |
markdown_path = "/tmp/resultado.md" | |
with open(markdown_path, "w", encoding="utf-8") as f: | |
f.write(markdown_output) | |
return markdown_output.strip(), image_paths, markdown_path | |
# Interfaz Gradio compatible | |
with gr.Blocks() as demo: | |
with gr.Row(): | |
pdf_input = gr.File(label="Sube tu PDF", type="filepath") | |
submit_btn = gr.Button("Procesar PDF") | |
# 🔄 Botón refrescar eliminado | |
markdown_output = gr.Textbox(label="Markdown estructurado", lines=25, interactive=True) | |
gallery_output = gr.Gallery(label="Imágenes extraídas", type="file") | |
download_md = gr.File(label="Descargar .md") | |
submit_btn.click(fn=convert, inputs=[pdf_input], outputs=[markdown_output, gallery_output, download_md]) | |
demo.launch() | |