Spaces:
Sleeping
Sleeping
File size: 8,694 Bytes
597d9c3 fe821af a3dc003 f35ce5e 2c6746f a3dc003 597d9c3 a3dc003 fe821af a3dc003 2c6746f a3dc003 2c6746f a3dc003 2c6746f a3dc003 0c2f23e a3dc003 2c6746f a3dc003 597d9c3 2c6746f a3dc003 597d9c3 a3dc003 597d9c3 a3dc003 597d9c3 a3dc003 597d9c3 a3dc003 2c6746f a3dc003 2c6746f a3dc003 597d9c3 a3dc003 597d9c3 2c6746f a3dc003 597d9c3 a3dc003 597d9c3 2c6746f fe821af a3dc003 fe821af a3dc003 fe821af a3dc003 f35ce5e fe821af f35ce5e a3dc003 fe821af 597d9c3 a3dc003 2c6746f a3dc003 dca2819 2c6746f a3dc003 f35ce5e a3dc003 fe821af 597d9c3 2c6746f a3dc003 597d9c3 f35ce5e 2c6746f fe821af |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
import os
import uuid
from PIL import Image, ImageDraw
import numpy as np
import cv2
from gemini_ner_client import get_gemini_ner_tags
# Módulos de Doctr
from doctr.models import ocr_predictor
# Módulos locales
# Asegúrate de tener este archivo 'error_handler.py' en tu entorno
# from error_handler import ErrorHandler
# --- Configuración de Directorios ---
DATASET_BASE_DIR = "dataset"
IMAGES_DIR = os.path.join(DATASET_BASE_DIR, "imagenes")
# --- Inicialización del Modelo Doctr ---
# Se carga el modelo una sola vez al inicio del módulo
try:
# Usaremos el modelo por defecto, que es robusto.
OCR_MODEL = ocr_predictor(pretrained=True)
print("✅ Modelo Doctr cargado con éxito.")
except Exception as e:
print(f"FATAL: Error al cargar el modelo Doctr. Asegúrate de instalar doctr[full]. Error: {e}")
OCR_MODEL = None
# --- Función Dummy para ErrorHandler ---
# Se asume que ErrorHandler.show_error existe. Si no, usa print o logging.
class ErrorHandler:
@staticmethod
def show_error(title, message=""):
print(f"[{title}] ERROR: {message}")
# --- Preprocesamiento de Imagen (Activado) ---
def preprocess_image_for_ocr(image: Image.Image) -> Image.Image:
"""
Aplica preprocesamiento con OpenCV (conversión a escala de grises y umbral adaptativo)
para mejorar la calidad del OCR.
"""
"""
try:
# Convertir PIL Image a array NumPy RGB
img_np = np.array(image.convert('RGB'))
# Convertir a escala de grises
gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
# Aplicar umbral adaptativo (útil para imágenes con iluminación variable)
thresh = cv2.adaptiveThreshold(
gray,
255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
11, # Tamaño del bloque (debe ser impar y > 1)
2 # Constante a restar de la media
)
# Devolver el array umbralizado como una imagen PIL
return Image.fromarray(thresh).convert('RGB')
except Exception as e:
ErrorHandler.show_error("Error de Preprocesamiento", f"Fallo al aplicar OpenCV: {e}")
# En caso de fallo, devolvemos la imagen original
return image
"""
return image
# --- Función Principal de OCR con Doctr ---
def get_ocr_data_doctr(image: Image.Image):
"""
Ejecuta Doctr en la imagen, opcionalmente preprocesada, y devuelve
tokens y bboxes normalizados para LayoutXLM.
"""
if image is None:
raise ValueError("No se proporcionó ninguna imagen.")
if OCR_MODEL is None:
ErrorHandler.show_error("Error de Modelo",
"El motor Doctr no está disponible. Revise el log de inicio.")
return None, [], None
W, H = image.size
tokens_data = []
# 💡 PASO CLAVE: Aplicar Preprocesamiento
# Almacenamos la imagen original ANTES del preprocesamiento
image_orig_unprocessed = image.copy()
processed_image = preprocess_image_for_ocr(image)
try:
# Doctr espera la imagen como un array numpy RGB
img_np = np.array(processed_image.convert('RGB'))
# 1. Ejecutar la predicción
result = OCR_MODEL([img_np]) # El modelo espera una lista de imágenes
# 2. Parsear el resultado a nivel de 'Word'
for page in result.pages:
for block in page.blocks:
for line in block.lines:
for word in line.words:
token_text = word.value.strip()
if not token_text:
continue
# Las coordenadas de Doctr son normalizadas a 0-1 (fracciones)
# word.geometry: [[x_min, y_min], [x_max, y_max]]
bbox_frac = word.geometry
# BBox original (en píxeles de la imagen original)
x_min, y_min, x_max, y_max = [
int(bbox_frac[0][0] * W),
int(bbox_frac[0][1] * H),
int(bbox_frac[1][0] * W),
int(bbox_frac[1][1] * H)
]
bbox_original = [x_min, y_min, x_max, y_max]
# BBox normalizado a 0-1000 (para LayoutXLM)
# Nota: Se corrigen los denominadores. LayoutXLM normaliza X e Y
# con respecto al tamaño completo (W o H).
bbox_normalized = [
int(x_min * 1000 / W),
int(y_min * 1000 / H),
int(x_max * 1000 / W),
int(y_max * 1000 / H)
]
tokens_data.append({
'token': token_text,
'bbox_norm': bbox_normalized,
'bbox_orig': bbox_original,
'ner_tag': 'O'
})
# 💡 Importante: Devolvemos la imagen original sin procesar, ya que
# esta es la que se guarda y se usa para el dibujo en la interfaz.
return image_orig_unprocessed, tokens_data, None
except Exception as e:
ErrorHandler.show_error("Error de Procesamiento OCR", e)
return None, [], None
# --- Funciones de Visualización y Guardado ---
def draw_boxes(image: Image.Image, tokens_data: list, highlight_index: int = -1):
"""Dibuja un resaltado en la imagen para el bounding box seleccionado."""
if image is None or not tokens_data:
return None
img_copy = image.copy()
draw = ImageDraw.Draw(img_copy)
if highlight_index >= 0 and highlight_index < len(tokens_data):
# Usamos bbox_orig para dibujar en la imagen a tamaño completo
bbox = tokens_data[highlight_index]['bbox_orig']
x1, y1, x2, y2 = bbox
outline_color = (255, 0, 0)
draw.rectangle([x1, y1, x2, y2], outline=outline_color, width=4)
return img_copy
def save_image_to_dataset(image: Image.Image) -> str:
"""
Genera un nombre único y guarda la imagen en el directorio de dataset.
Retorna el nombre único del archivo.
"""
# 1. Crear el nombre único (UUID + extensión)
unique_filename = f"{uuid.uuid4()}.jpeg"
save_path = os.path.join(IMAGES_DIR, unique_filename)
# 2. Asegurar que el directorio exista (es importante en un entorno efímero)
os.makedirs(IMAGES_DIR, exist_ok=True)
# 3. Guardar la imagen
image.save(save_path, format="JPEG")
print(f"Imagen guardada: {save_path}")
return unique_filename
# --- Función de Flujo Principal ---
def process_and_setup(image_file, api_key: str): # 💡 ACEPTA LA LLAVE API
"""
Función inicial: OCR con Doctr, **NER asistido por Gemini**,
configuración del estado y guardar la imagen.
"""
if image_file is None:
empty_df = {'token': [], 'ner_tag': []}
return None, [], None, empty_df, None, None
# 💡 Llama a la función de OCR basada en Doctr
image_orig, tokens_data, _ = get_ocr_data_doctr(image_file)
if image_orig is None:
empty_df = {'token': [], 'ner_tag': []}
return None, [], None, empty_df, "Error fatal al procesar el OCR con Doctr. Revise el log.", None
# 💡 PASO NUEVO: NER Asistido por Gemini (CONDICIONAL)
msg_ner_assist = ""
if api_key:
# Llama a la función que SÓLO se ejecuta si api_key no es None/vacío
tokens_data = get_gemini_ner_tags(api_key, tokens_data)
msg_ner_assist = " (NER asistido)"
# --- Guardar la Imagen Original ---
image_filename = save_image_to_dataset(image_orig)
if not tokens_data:
empty_df = {'token': [], 'ner_tag': []}
msg = "OCR completado. No se detectaron tokens válidos."
ErrorHandler.show_error(msg)
return image_orig, [], None, empty_df, msg, image_filename
# Crear el DataFrame inicial para la edición en Gradio
df_data = {
'token': [item['token'] for item in tokens_data],
'ner_tag': [item['ner_tag'] for item in tokens_data]
}
# La imagen inicial no tiene resaltado
highlighted_image = image_orig.copy()
msg = f"OCR de Doctr completado. Tokens detectados: {len(tokens_data)}.{msg_ner_assist}"
print(msg)
return image_orig, tokens_data, highlighted_image, df_data, msg, image_filename |