OCR-NER-Facturas / bbox_adder.py
Lucas Gagneten
DocTR
a3dc003
import gradio as gr
from PIL import Image
from typing import List, Dict, Any, Optional
# ----------------------------------------------------------------------
# FUNCIONES DE ADICIÓN DE BBOX MANUAL
# ----------------------------------------------------------------------
def add_new_bbox_mode(
tokens_data: List[Dict[str, Any]],
image_orig: Optional[Image.Image],
select_data: gr.SelectData
):
"""
Captura las coordenadas de un Bounding Box dibujado en la interfaz
mediante el evento .select(type="select") en gr.Image.
Args:
tokens_data: Estado actual de los tokens.
image_orig: Imagen original (no usada directamente, pero necesaria para la firma).
select_data: Objeto gr.SelectData devuelto por el evento .select(type="select").
Returns:
Tupla de actualizaciones de Gradio:
(tb_token_editor (Ocultar), dd_tag_selector (Mostrar),
tb_new_token_text (Mostrar), btn_add_new_token (Mostrar),
STATE_NEW_BBOX (Coordenadas [x1, y1, x2, y2]))
"""
print("-> Evento de selección (type='select') detectado.")
# Valores de retorno por defecto en caso de fallo (UI ocultos, BBox limpio)
FAIL_RETURN = (
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False, value=""),
gr.update(visible=False),
None
)
# 1. Verificar y extraer coordenadas del BBox arrastrado
new_bbox_data = None
# Gradio 4+ con type="select" devuelve un diccionario de coords
if select_data and hasattr(select_data, 'coords') and select_data.coords:
coords = select_data.coords
try:
x1 = int(coords['x_min'])
y1 = int(coords['y_min'])
x2 = int(coords['x_max'])
y2 = int(coords['y_max'])
new_bbox_data = [x1, y1, x2, y2]
except (ValueError, TypeError, KeyError) as e:
print(f"Error al parsear coordenadas de select_data.coords: {e}")
return FAIL_RETURN
# Alternativa (si la versión de Gradio devuelve atributos directamente)
elif select_data and hasattr(select_data, 'x_min') and select_data.x_min is not None:
try:
x1 = int(select_data.x_min)
y1 = int(select_data.y_min)
x2 = int(select_data.x_max)
y2 = int(select_data.y_max)
new_bbox_data = [x1, y1, x2, y2]
except (ValueError, TypeError) as e:
print(f"Error al parsear coordenadas de select_data: {e}")
return FAIL_RETURN
if new_bbox_data is None:
print("Error: Los datos de selección no contienen las coordenadas (bbox).")
return FAIL_RETURN
print(f"-> BBox extraído: {new_bbox_data}")
# 2. Retornar actualizaciones para mostrar la UI de entrada
# Nota: tb_token_editor debe ocultarse porque solo se usa para editar tokens existentes.
return (
gr.update(visible=False), # Ocultar tb_token_editor
gr.update(visible=True), # Mostrar dd_tag_selector
gr.update(visible=True, value=""), # Mostrar tb_new_token_text (limpio)
gr.update(visible=True), # Mostrar btn_add_new_token
new_bbox_data # Guardar STATE_NEW_BBOX
)
def append_new_token(
tokens_data: List[Dict[str, Any]],
image_orig: Optional[Image.Image],
bbox: List[int],
text: str,
tag: str
):
"""
Agrega el nuevo token con el BBox dibujado al estado de tokens_data.
Args:
tokens_data: Lista de tokens existente.
image_orig: Imagen original (no usada directamente).
bbox: Coordenadas del BBox [x1, y1, x2, y2].
text: Texto del nuevo token.
tag: Etiqueta NER del nuevo token.
Returns:
Tupla de actualizaciones de Gradio:
(tokens_data actualizada, df_label_input (para forzar refresh),
tb_token_editor, tb_new_token_text, btn_add_new_token, STATE_NEW_BBOX)
"""
if not bbox or not text:
print("Error: BBox o Texto están vacíos. No se puede añadir el token.")
# Retornar el estado actual sin cambios, pero limpia los campos de entrada
return (
tokens_data,
gr.update(), # No forzamos actualización de DF
gr.update(visible=False),
gr.update(visible=False, value=""),
gr.update(visible=False),
None
)
# Crear el nuevo token
new_token = {
'token': text.strip(),
'ner_tag': tag,
'bbox': bbox,
'id': len(tokens_data), # Asignar un ID simple
'page': 0 # Asumimos página 0 si no hay lógica de multipágina
}
# Añadir el token al final de la lista
tokens_data.append(new_token)
print(f"-> Nuevo token añadido: {new_token}")
# Preparar la nueva tabla para Gradio (actualizar df_label_input)
# Nota: El dataframe de Gradio espera una lista de listas [[token, tag], [token, tag], ...]
df_rows = [[t['token'], t['ner_tag']] for t in tokens_data]
# Retornar los estados actualizados y limpiar la UI de dibujo
return (
tokens_data,
gr.update(value=df_rows), # Actualiza el DataFrame
gr.update(visible=False), # Ocultar tb_token_editor
gr.update(visible=False, value=""), # Limpiar y Ocultar tb_new_token_text
gr.update(visible=False), # Ocultar btn_add_new_token
None # Limpiar STATE_NEW_BBOX
)