Biifruu commited on
Commit
da6ca5a
·
verified ·
1 Parent(s): 2b30e40

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -66
app.py CHANGED
@@ -1,83 +1,208 @@
1
- import pytesseract
 
2
  from PIL import Image
3
- import re
 
 
 
 
4
  import unicodedata
5
 
6
- # Configuración avanzada de Tesseract para OCR en español
7
- OCR_CONFIG = r'--oem 3 --psm 6'
8
-
9
- # Corrección básica de errores comunes de OCR
10
- def clean_text(text):
11
- # Normaliza caracteres Unicode (acentos, símbolos)
12
- text = unicodedata.normalize('NFKC', text)
13
-
14
- # Diccionario de correcciones frecuentes
15
- replacements = {
16
- "martphone": "smartphone",
17
- "desinstatar": "desinstalar",
18
- "Desconectas": "desconectadas",
19
- "cuestiónn": "cuestión",
20
- "el coche sólo cargando": "el coche solo carga cuando",
21
- "Modo online": "Modo en línea",
22
- "Modo Online": "Modo en línea",
23
- "Modo sin conexión": "modo sin conexión",
24
- "desconectas": "desconectadas",
25
- "cuestión no resolverá": "cuestión no solucionará",
26
- "El símbolo del globo": "\nEl símbolo del globo",
27
- }
28
-
29
- for wrong, correct in replacements.items():
30
- text = text.replace(wrong, correct)
31
 
32
- return text
 
 
 
33
 
34
- # Formatea el texto limpio en Markdown estructurado
35
- def format_text_to_markdown(text):
36
- text = clean_text(text)
37
  lines = text.splitlines()
38
- markdown = []
 
39
 
40
- for line in lines:
41
- line = line.strip()
42
- if not line:
43
- continue
 
 
 
 
 
 
 
 
 
 
44
 
45
- # Encabezados comunes detectados por OCR
46
- if re.search(r"^posible causa", line, re.IGNORECASE):
47
- markdown.append("### 🛑 Posible causa\n")
48
- continue
49
- elif re.search(r"^posible solución", line, re.IGNORECASE):
50
- markdown.append("### ✅ Posible solución\n")
51
- continue
52
- elif re.search(r"^descripción del problema", line, re.IGNORECASE):
53
- markdown.append("### 📝 Descripción del problema\n")
54
- continue
55
 
56
- # Listas con viñetas o números
57
- elif re.match(r"^\d+\.", line):
58
- markdown.append(f"- {line}")
59
- continue
60
- elif re.match(r"^[•*-]", line):
61
- markdown.append(f"- {line[1:].strip()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- # Texto normal
65
- markdown.append(line)
 
 
 
 
66
 
67
- return "\n\n".join(markdown)
 
 
68
 
69
- # Función principal: OCR → Limpieza → Markdown
70
- def ocr_to_markdown(image_path):
71
- image = Image.open(image_path)
72
- raw_text = pytesseract.image_to_string(image, lang='spa', config=OCR_CONFIG)
73
- return format_text_to_markdown(raw_text)
 
 
 
 
 
 
 
 
74
 
75
- if __name__ == "__main__":
76
- image_path = "pagina1_ocr.png" # Cambia esto por tu nombre real
77
- markdown_output = ocr_to_markdown(image_path)
 
 
 
 
78
 
79
- with open("resultado.md", "w", encoding="utf-8") as f:
80
- f.write("## Página 1\n\n")
81
- f.write(f"![imagen_pagina_1]({image_path})\n\n")
 
 
 
 
 
 
 
 
82
  f.write(markdown_output)
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import fitz # PyMuPDF
3
  from PIL import Image
4
+ import numpy as np
5
+ import cv2
6
+ import pytesseract
7
+ import base64
8
+ import os
9
  import unicodedata
10
 
11
+ # NUEVO: Traducción
12
+ from transformers import pipeline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # Inicializa el pipeline de traducción EN->ES una sola vez
15
+ translator = pipeline("translation_en_to_es", model="Helsinki-NLP/opus-mt-en-es")
16
+
17
+ # ---------- OCR y limpieza de texto ----------
18
 
19
+ def clean_ocr_text(text):
20
+ text = unicodedata.normalize("NFC", text)
 
21
  lines = text.splitlines()
22
+ cleaned_lines = [line.strip() for line in lines if line.strip()]
23
+ return "\n".join(cleaned_lines)
24
 
25
+ def translate_text(text):
26
+ """
27
+ Traduce texto del inglés al español si está en inglés (siempre lo traduce para simplificar)
28
+ """
29
+ # Para hacerlo robusto podrías agregar detección de idioma (langdetect),
30
+ # pero para este ejemplo traducimos siempre
31
+ if len(text.strip()) < 5:
32
+ return text
33
+ chunks = [text[i:i+500] for i in range(0, len(text), 500)]
34
+ translated = []
35
+ for chunk in chunks:
36
+ result = translator(chunk)
37
+ translated.append(result[0]["translation_text"])
38
+ return "\n".join(translated)
39
 
40
+ # ---------- Funciones de imagen ----------
 
 
 
 
 
 
 
 
 
41
 
42
+ def text_area_ratio(image):
43
+ np_img = np.array(image.convert("L"))
44
+ _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY_INV)
45
+ contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
46
+ text_area = sum(w * h for x, y, w, h in [cv2.boundingRect(c) for c in contours if 8 < cv2.boundingRect(c)[3] < 40 and 5 < cv2.boundingRect(c)[2] < 100])
47
+ total_area = np_img.shape[0] * np_img.shape[1]
48
+ return text_area / total_area if total_area > 0 else 0
49
+
50
+ def has_significant_text(image):
51
+ return text_area_ratio(image) > 0.25
52
+
53
+ def is_primarily_text(image, ocr_threshold=30):
54
+ if has_significant_text(image):
55
+ ocr_result = pytesseract.image_to_string(image, lang="eng+spa")
56
+ return len(ocr_result.strip()) > ocr_threshold
57
+ return False
58
+
59
+ def is_likely_photo(crop):
60
+ np_crop = np.array(crop)
61
+ gray = cv2.cvtColor(np_crop, cv2.COLOR_RGB2GRAY)
62
+ return np.std(gray) > 25 and len(np.unique(gray)) > 50
63
+
64
+ def extract_visual_regions(image):
65
+ np_img = np.array(image.convert("RGB"))
66
+ gray = cv2.cvtColor(np_img, cv2.COLOR_RGB2GRAY)
67
+ _, binary = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)
68
+ closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15)))
69
+
70
+ num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(closed, connectivity=8)
71
+ results = []
72
+ for i in range(1, num_labels):
73
+ x, y, w, h, area = stats[i]
74
+ if area > 2000 and 0.3 < (w / float(h)) < 3.5:
75
+ bbox = (x, y, x + w, y + h)
76
+ crop = image.crop(bbox)
77
+ if is_likely_photo(crop) and text_area_ratio(crop) < 0.25 and not is_primarily_text(crop):
78
+ results.append(crop)
79
+ return results
80
+
81
+ # ---------- Extracción de texto + imágenes ----------
82
+
83
+ def clean_bullet_line(text):
84
+ text = unicodedata.normalize("NFKC", text)
85
+ text = text.replace("e@", "-")
86
+ text = text.replace("@", "-")
87
+ text = text.replace("•", "-")
88
+ text = text.replace("*", "-")
89
+ text = text.replace("·", "-")
90
+ text = text.replace("–", "-")
91
+ text = " ".join(text.split())
92
+ return text
93
+
94
+ def extract_text_markdown(doc, image_paths, page_index, seen_xrefs):
95
+ markdown_output = f"\n## Página {page_index + 1}\n\n"
96
+ image_counter = 1
97
+ elements = []
98
+ page = doc[0]
99
+ blocks = page.get_text("dict")["blocks"]
100
+
101
+ for b in blocks:
102
+ y = b["bbox"][1]
103
+ if b["type"] == 0:
104
+ for line in b["lines"]:
105
+ line_y = line["bbox"][1]
106
+ line_text = " ".join([span["text"] for span in line["spans"]]).strip()
107
+ line_text = clean_bullet_line(line_text)
108
+ max_font_size = max([span.get("size", 10) for span in line["spans"]])
109
+ if line_text:
110
+ elements.append((line_y, line_text, max_font_size))
111
+
112
+ images_on_page = page.get_images(full=True)
113
+ for img_index, img in enumerate(images_on_page):
114
+ xref = img[0]
115
+ if xref in seen_xrefs:
116
  continue
117
+ seen_xrefs.add(xref)
118
+ try:
119
+ base_image = page.parent.extract_image(xref)
120
+ image_bytes = base_image["image"]
121
+ ext = base_image["ext"]
122
+ image_path = f"/tmp/imagen_p{page_index + 1}_{img_index + 1}.{ext}"
123
+ with open(image_path, "wb") as f:
124
+ f.write(image_bytes)
125
+ image_paths.append(image_path)
126
+ elements.append((float("inf") - img_index, f"\n\n![imagen_{image_counter}]({image_path})\n", 10))
127
+ image_counter += 1
128
+ except Exception as e:
129
+ elements.append((float("inf"), f"[Error imagen: {e}]", 10))
130
+
131
+ elements.sort(key=lambda x: x[0])
132
+ previous_y = None
133
+
134
+ for y, text, font_size in elements:
135
+ is_header = font_size >= 14
136
+ if previous_y is not None and abs(y - previous_y) > 10:
137
+ markdown_output += "\n"
138
+ translated = translate_text(text.strip())
139
+ markdown_output += f"\n### {translated}\n" if is_header else translated + "\n"
140
+ previous_y = y
141
+
142
+ markdown_output += "\n---\n\n"
143
+ return markdown_output.strip()
144
+
145
+ # ---------- Función principal ----------
146
 
147
+ def convert(pdf_file):
148
+ temp_pdf_path = pdf_file.name
149
+ doc = fitz.open(temp_pdf_path)
150
+ markdown_output = ""
151
+ image_paths = []
152
+ seen_xrefs = set()
153
 
154
+ for page_num in range(len(doc)):
155
+ page = doc[page_num]
156
+ text = page.get_text("text").strip()
157
 
158
+ if len(text) > 30:
159
+ # Texto nativo del PDF
160
+ extracted = extract_text_markdown([page], image_paths, page_num, seen_xrefs)
161
+ markdown_output += extracted + "\n"
162
+ else:
163
+ # Página "escaneada" -> OCR
164
+ markdown_output += f"\n## Página {page_num + 1}\n\n"
165
+ pix = page.get_pixmap(dpi=300)
166
+ img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
167
+ image_path = f"/tmp/ocr_page_{page_num + 1}.jpg"
168
+ img.save(image_path)
169
+ image_paths.append(image_path)
170
+ markdown_output += f"![imagen_pagina_{page_num + 1}]({image_path})\n"
171
 
172
+ try:
173
+ ocr_text = pytesseract.image_to_string(img, lang="eng+spa")
174
+ except pytesseract.TesseractError:
175
+ ocr_text = ""
176
+ ocr_text_clean = clean_ocr_text(ocr_text)
177
+ translated_ocr = translate_text(ocr_text_clean)
178
+ markdown_output += translated_ocr + "\n"
179
 
180
+ crops = extract_visual_regions(img)
181
+ for i, crop in enumerate(crops):
182
+ crop_path = f"/tmp/recorte_p{page_num + 1}_{i + 1}.jpg"
183
+ crop.save(crop_path)
184
+ image_paths.append(crop_path)
185
+ markdown_output += f"\n\n![imagen_detectada]({crop_path})\n"
186
+
187
+ markdown_output += "\n---\n\n"
188
+
189
+ markdown_path = "/tmp/resultado.md"
190
+ with open(markdown_path, "w", encoding="utf-8") as f:
191
  f.write(markdown_output)
192
 
193
+ return markdown_output.strip(), image_paths, markdown_path
194
+
195
+ # ---------- Gradio Interface ----------
196
+
197
+ with gr.Blocks() as demo:
198
+ with gr.Row():
199
+ pdf_input = gr.File(label="Upload your PDF", type="filepath", file_types=[".pdf"])
200
+ submit_btn = gr.Button("Process PDF")
201
+
202
+ markdown_output = gr.Textbox(label="Generated Markdown", lines=25, interactive=True)
203
+ gallery_output = gr.Gallery(label="Extracted and Detected Images", type="file")
204
+ download_md = gr.File(label="Download Markdown File")
205
+
206
+ submit_btn.click(fn=convert, inputs=[pdf_input], outputs=[markdown_output, gallery_output, download_md])
207
+
208
+ demo.launch()