Biifruu commited on
Commit
162382b
·
verified ·
1 Parent(s): 52b9ecf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -219
app.py CHANGED
@@ -1,245 +1,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
  import re
11
- from transformers import pipeline
12
-
13
- # Inicializa el pipeline de traducción EN->ES una sola vez
14
- translator = pipeline("translation_en_to_es", model="Helsinki-NLP/opus-mt-en-es")
15
-
16
- # ---------- OCR y limpieza de texto ----------
17
 
18
- def clean_ocr_text(text):
19
- text = unicodedata.normalize("NFC", text)
20
- lines = text.splitlines()
21
- cleaned_lines = [line.strip() for line in lines if line.strip()]
22
- return "\n".join(cleaned_lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- def fix_common_ocr_errors(text):
25
- text = text.replace(" e ", " • ") # cuando OCR confunde viñetas con "e"
26
- text = re.sub(r'\bposibl\b', 'posible', text, flags=re.IGNORECASE)
27
- text = re.sub(r'\binstatar\b', 'instalar', text)
28
- text = re.sub(r'\bfuncionación\b', 'función taller', text)
29
- text = re.sub(r'\boptar\b', 'opta', text)
30
- text = re.sub(r'ICACIONES\b', 'APLICACIONES', text)
31
- text = re.sub(r'Lar\b', 'la', text)
32
- text = re.sub(r'([a-zA-Z])-\n([a-zA-Z])', r'\1\2', text) # une palabras partidas por salto
33
  return text
34
 
 
35
  def format_text_to_markdown(text):
36
- # Corrige errores comunes
37
- text = fix_common_ocr_errors(text)
38
-
39
- # Línea por línea
40
  lines = text.splitlines()
41
- markdown_lines = []
42
-
43
  for line in lines:
44
  line = line.strip()
45
  if not line:
46
  continue
47
 
48
- # Encabezados conocidos
49
- if re.search(r"\b(posible causa)\b", line, re.IGNORECASE):
50
- markdown_lines.append("### 🛑 Posible causa\n")
51
  continue
52
- elif re.search(r"\b(posible solución)\b", line, re.IGNORECASE):
53
- markdown_lines.append("### ✅ Posible solución\n")
54
  continue
55
- elif re.search(r"\b(descripción del problema)\b", line, re.IGNORECASE):
56
- markdown_lines.append("### 📝 Descripción del problema\n")
57
  continue
 
 
58
  elif re.match(r"^\d+\.", line):
59
- markdown_lines.append(f"- {line}")
60
  continue
61
  elif re.match(r"^[•*-]", line):
62
- markdown_lines.append(f"- {line[1:].strip()}")
63
- continue
64
- else:
65
- # Detecta si la línea es basura (letras sin sentido, símbolos)
66
- if len(line) < 5:
67
- continue
68
- if re.search(r"[^\w\s.,:;¡!¿?\-()]", line):
69
- symbols = re.findall(r"[^\w\s.,:;¡!¿?\-()]", line)
70
- if len(symbols) > 3:
71
- continue # basura
72
- markdown_lines.append(line)
73
-
74
- # Une líneas y separa párrafos correctamente
75
- formatted_text = "\n\n".join(markdown_lines)
76
- return formatted_text
77
-
78
- def translate_text(text):
79
- if len(text.strip()) < 5:
80
- return text
81
- chunks = [text[i:i+500] for i in range(0, len(text), 500)]
82
- translated = []
83
- for chunk in chunks:
84
- result = translator(chunk)
85
- translated.append(result[0]["translation_text"])
86
- return "\n".join(translated)
87
-
88
- # ---------- Funciones de imagen ----------
89
-
90
- def text_area_ratio(image):
91
- np_img = np.array(image.convert("L"))
92
- _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY_INV)
93
- contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
94
- 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])
95
- total_area = np_img.shape[0] * np_img.shape[1]
96
- return text_area / total_area if total_area > 0 else 0
97
-
98
- def has_significant_text(image):
99
- return text_area_ratio(image) > 0.25
100
-
101
- def is_primarily_text(image, ocr_threshold=30):
102
- if has_significant_text(image):
103
- ocr_result = pytesseract.image_to_string(image, lang="eng+spa")
104
- return len(ocr_result.strip()) > ocr_threshold
105
- return False
106
-
107
- def is_likely_photo(crop):
108
- np_crop = np.array(crop)
109
- gray = cv2.cvtColor(np_crop, cv2.COLOR_RGB2GRAY)
110
- return np.std(gray) > 25 and len(np.unique(gray)) > 50
111
-
112
- def extract_visual_regions(image):
113
- np_img = np.array(image.convert("RGB"))
114
- gray = cv2.cvtColor(np_img, cv2.COLOR_RGB2GRAY)
115
- _, binary = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV)
116
- closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15)))
117
- num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(closed, connectivity=8)
118
- results = []
119
- for i in range(1, num_labels):
120
- x, y, w, h, area = stats[i]
121
- if area > 2000 and 0.3 < (w / float(h)) < 3.5:
122
- bbox = (x, y, x + w, y + h)
123
- crop = image.crop(bbox)
124
- if is_likely_photo(crop) and text_area_ratio(crop) < 0.25 and not is_primarily_text(crop):
125
- results.append(crop)
126
- return results
127
-
128
- # ---------- Extracción de texto + imágenes ----------
129
-
130
- def extract_text_markdown(doc, image_paths, page_index, seen_xrefs):
131
- markdown_output = f"\n## Página {page_index + 1}\n\n"
132
- image_counter = 1
133
- elements = []
134
- page = doc[0]
135
- blocks = page.get_text("dict")["blocks"]
136
-
137
- for b in blocks:
138
- y = b["bbox"][1]
139
- if b["type"] == 0:
140
- for line in b["lines"]:
141
- line_y = line["bbox"][1]
142
- line_text = " ".join([span["text"] for span in line["spans"]]).strip()
143
- line_text = clean_ocr_text(line_text)
144
- max_font_size = max([span.get("size", 10) for span in line["spans"]])
145
- if line_text:
146
- elements.append((line_y, line_text, max_font_size))
147
-
148
- images_on_page = page.get_images(full=True)
149
- for img_index, img in enumerate(images_on_page):
150
- xref = img[0]
151
- if xref in seen_xrefs:
152
  continue
153
- seen_xrefs.add(xref)
154
- try:
155
- base_image = page.parent.extract_image(xref)
156
- image_bytes = base_image["image"]
157
- ext = base_image["ext"]
158
- image_path = f"/tmp/imagen_p{page_index + 1}_{img_index + 1}.{ext}"
159
- with open(image_path, "wb") as f:
160
- f.write(image_bytes)
161
- image_paths.append(image_path)
162
- elements.append((float("inf") - img_index, f"\n\n![imagen_{image_counter}]({image_path})\n", 10))
163
- image_counter += 1
164
- except Exception as e:
165
- elements.append((float("inf"), f"[Error imagen: {e}]", 10))
166
-
167
- elements.sort(key=lambda x: x[0])
168
- previous_y = None
169
-
170
- for y, text, font_size in elements:
171
- is_header = font_size >= 14
172
- if previous_y is not None and abs(y - previous_y) > 10:
173
- markdown_output += "\n"
174
- fixed = fix_common_ocr_errors(text.strip())
175
- formatted = format_text_to_markdown(fixed)
176
- translated = translate_text(formatted)
177
- markdown_output += f"\n### {translated}\n" if is_header else translated + "\n"
178
- previous_y = y
179
-
180
- markdown_output += "\n---\n\n"
181
- return markdown_output.strip()
182
-
183
- # ---------- Función principal ----------
184
 
185
- def convert(pdf_file):
186
- temp_pdf_path = pdf_file.name
187
- doc = fitz.open(temp_pdf_path)
188
- markdown_output = ""
189
- image_paths = []
190
- seen_xrefs = set()
191
 
192
- for page_num in range(len(doc)):
193
- page = doc[page_num]
194
- text = page.get_text("text").strip()
195
 
196
- if len(text) > 30:
197
- extracted = extract_text_markdown([page], image_paths, page_num, seen_xrefs)
198
- markdown_output += extracted + "\n"
199
- else:
200
- markdown_output += f"\n## Página {page_num + 1}\n\n"
201
- pix = page.get_pixmap(dpi=300)
202
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
203
- image_path = f"/tmp/ocr_page_{page_num + 1}.jpg"
204
- img.save(image_path)
205
- image_paths.append(image_path)
206
- markdown_output += f"![imagen_pagina_{page_num + 1}]({image_path})\n"
207
 
208
- try:
209
- ocr_text = pytesseract.image_to_string(img, lang="spa", config="--oem 3 --psm 6")
210
- except pytesseract.TesseractError:
211
- ocr_text = ""
212
- ocr_text_clean = clean_ocr_text(fix_common_ocr_errors(ocr_text))
213
- formatted = format_text_to_markdown(ocr_text_clean)
214
- translated = translate_text(formatted)
215
- markdown_output += translated + "\n"
216
 
217
- crops = extract_visual_regions(img)
218
- for i, crop in enumerate(crops):
219
- crop_path = f"/tmp/recorte_p{page_num + 1}_{i + 1}.jpg"
220
- crop.save(crop_path)
221
- image_paths.append(crop_path)
222
- markdown_output += f"\n\n![imagen_detectada]({crop_path})\n"
223
-
224
- markdown_output += "\n---\n\n"
225
-
226
- markdown_path = "/tmp/resultado.md"
227
- with open(markdown_path, "w", encoding="utf-8") as f:
228
  f.write(markdown_output)
229
-
230
- return markdown_output.strip(), image_paths, markdown_path
231
-
232
- # ---------- Gradio Interface ----------
233
-
234
- with gr.Blocks() as demo:
235
- with gr.Row():
236
- pdf_input = gr.File(label="Upload your PDF", type="filepath", file_types=[".pdf"])
237
- submit_btn = gr.Button("Process PDF")
238
-
239
- markdown_output = gr.Textbox(label="Generated Markdown", lines=25, interactive=True)
240
- gallery_output = gr.Gallery(label="Extracted and Detected Images", type="file")
241
- download_md = gr.File(label="Download Markdown File")
242
-
243
- submit_btn.click(fn=convert, inputs=[pdf_input], outputs=[markdown_output, gallery_output, download_md])
244
-
245
- demo.launch()
 
 
 
 
 
 
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
+ # Ejecutar y exportar resultado como archivo .md
76
+ if __name__ == "__main__":
77
+ image_path = "tu_imagen.png" # <-- Cambia este nombre al de tu archivo
78
+ markdown_output = ocr_to_markdown(image_path)
 
 
 
 
79
 
80
+ with open("resultado.md", "w", encoding="utf-8") as f:
81
+ f.write("## Página 1\n\n")
82
+ f.write(f"![imagen_pagina_1]({image_path})\n\n")
 
 
 
 
 
 
 
 
83
  f.write(markdown_output)