Spaces:
Sleeping
Sleeping
File size: 11,717 Bytes
b26ec1d a3dc003 f35ce5e a3dc003 f35ce5e cd12bfc a3dc003 cd12bfc b26ec1d f35ce5e b26ec1d f35ce5e b26ec1d f35ce5e a3dc003 f35ce5e 2c6746f fe821af f35ce5e cd12bfc 2c6746f fe821af f35ce5e 2c6746f fe821af f35ce5e a3dc003 2c6746f f35ce5e 2c6746f f35ce5e fe821af f35ce5e fe821af f35ce5e a3dc003 cd12bfc fe821af f35ce5e fe821af f35ce5e fe821af f35ce5e fe821af f35ce5e fe821af f35ce5e fe821af a3dc003 b26ec1d cd12bfc b26ec1d f35ce5e cd12bfc b26ec1d a3dc003 fe821af cd12bfc a3dc003 f35ce5e b26ec1d fe821af f35ce5e 2c6746f b26ec1d fe821af b26ec1d f35ce5e a3dc003 f35ce5e a3dc003 b26ec1d cd12bfc fe821af cd12bfc f35ce5e a3dc003 fe821af cd12bfc f35ce5e cd12bfc a3dc003 cd12bfc fe821af 2c6746f f35ce5e 2c6746f f35ce5e 2c6746f fe821af cd12bfc 2c6746f b26ec1d 2c6746f f35ce5e b26ec1d 2c6746f b26ec1d 2c6746f b26ec1d fe821af b26ec1d 2c6746f b26ec1d 2c6746f f35ce5e fe821af b26ec1d 2c6746f b26ec1d fe821af a3dc003 fe821af a3dc003 f35ce5e cd12bfc b26ec1d cd12bfc fe821af 2c6746f b26ec1d f35ce5e 2c6746f f35ce5e 2c6746f a3dc003 f35ce5e a3dc003 f35ce5e 2c6746f b26ec1d ced724a 53ab502 ced724a |
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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
import gradio as gr
from PIL import Image
import pandas as pd
# ----------------------------------------------------------------------
# 💡 1. IMPORTACIONES
# ----------------------------------------------------------------------
# ocr_processor: Funciones de OCR (Doctr) y guardado de imagen
from ocr_processor import process_and_setup
# label_editor: Funciones de setup, edición y persistencia (JSON/ZIP)
from label_editor import (
setup_label_components,
update_ui,
save_current_annotation_to_json,
export_and_zip_dataset,
update_dataframe_and_state,
display_selected_row,
)
# image_loader: Define la UI para la carga de imagen y la API Key
from image_loader import setup_image_components
# bbox_adder: Funciones para añadir manualmente tokens no detectados por OCR
from bbox_adder import add_new_bbox_mode, append_new_token
# --- Función de Limpieza/Reset ---
def clear_ui_and_reset_states(api_key_input, tb_new_token_text, btn_add_new_token, state_new_bbox):
"""Limpia los componentes de la interfaz y resetea los estados a su valor inicial."""
print("Reiniciando la interfaz y los estados...")
# Valores de reseteo para los estados de Gradio
reset_image_orig_state = None
reset_tokens_data_state = []
reset_highlight_index_state = -1
reset_image_filename_state = None
# Actualizaciones para los componentes de la interfaz
api_key_update = gr.update(value="", visible=True)
image_input_update = gr.update(value=None, visible=True)
image_output_update = gr.update(value=None, visible=False)
df_update = gr.update(value=[])
# Componentes de edición (ocultar)
tb_update = gr.update(value="", visible=False)
dd_update = gr.update(value="O", visible=False)
# Componentes de Adición (ocultar y resetear)
tb_new_token_update = gr.update(value="", visible=False)
btn_add_token_update = gr.update(visible=False)
reset_new_bbox_state = None
status_update = "Sube una imagen para comenzar..."
return (
reset_image_orig_state, # 0. image_orig_state
reset_tokens_data_state, # 1. tokens_data_state
reset_highlight_index_state, # 2. highlight_index_state
reset_image_filename_state, # 3. image_filename_state
api_key_update, # 4. api_key_input 💡
image_input_update, # 5. image_input_file
image_output_update, # 6. image_output_display
df_update, # 7. df_label_input
tb_update, # 8. tb_token_editor
dd_update, # 9. dd_tag_selector
tb_new_token_update, # 10. tb_new_token_text
btn_add_token_update, # 11. btn_add_new_token
reset_new_bbox_state, # 12. state_new_bbox
status_update # 13. status_output
)
# --- FUNCIÓN AUXILIAR DE FLUJO: OCR y Gemini (MODIFICADA) ---
def process_image(image, api_key: str):
"""
Ejecuta el OCR, la inferencia de Gemini (si hay API Key) y el preprocesamiento
inicial, guardando la imagen.
"""
# 💡 La función ahora requiere api_key
if image is None:
# Nota: 8 outputs para la CONEXIÓN 1
return None, [], None, [], "Sube una imagen para comenzar...", gr.update(visible=True), gr.update(visible=False, value=None), None
try:
# process_and_setup requiere image y api_key (la lógica de consulta condicional está dentro de ocr_processor)
# Retorna: image_orig, tokens_data, highlighted_image, df_data, status, image_filename
result = process_and_setup(image, api_key)
if result[0] is None:
# Nota: 8 outputs para la CONEXIÓN 1
return None, [], None, [], "Error en el procesamiento del OCR. Verifica logs.", gr.update(visible=True), gr.update(visible=False, value=None), None
image_orig, tokens_data, highlighted_image, df_data, status, image_filename = result
# Convertir datos para el DataFrame de Gradio (lista de listas)
df_rows = []
if df_data and isinstance(df_data, dict):
for t, n in zip(df_data['token'], df_data['ner_tag']):
df_rows.append([t, n])
# Nota: 8 outputs para la CONEXIÓN 1
return (
image_orig,
tokens_data,
highlighted_image,
df_rows,
status,
gr.update(visible=False), # Ocultar image_input_file
gr.update(visible=True), # Mostrar image_output_display
image_filename # Nombre de archivo único
)
except Exception as e:
print(f"Error en process_image: {str(e)}")
# Nota: 8 outputs para la CONEXIÓN 1
return None, [], None, [], f"Error: {str(e)}", gr.update(visible=True), gr.update(visible=False, value=None), None
def capture_highlight_index(evt: gr.SelectData):
"""Captura el índice de fila (0-index) seleccionado en el DataFrame."""
if evt and evt.index is not None and evt.index[0] is not None:
return evt.index[0]
return gr.State(-1)
# --- INTERFAZ GRADIO (GR.BLOCKS) ---
with gr.Blocks(title="Anotador NER de Facturas (Doctr/LayoutXLM)") as app:
gr.Markdown(
"""
# 🧾 Anotador NER para Facturas (LayoutXLM)
**Instrucciones:** 1. **Sube** una imagen (y opcionalmente la Clave API de Gemini para asistencia de etiquetado). La imagen se guarda automáticamente en `dataset/imagenes`.
2. **Edita** los tokens o etiquetas. Los cambios se aplican automáticamente.
3. Haz clic en **'Guardar Anotación Actual (JSON)'** para confirmar los datos de la factura actual en `dataset/anotacion_factura.json`.
4. Haz clic en **'Descargar Dataset Completo (.zip)'** para obtener todas las imágenes y el JSON consolidado.
"""
)
# --- 1. Definición de Estados Globales ---
image_orig_state = gr.State(None)
tokens_data_state = gr.State([])
highlight_index_state = gr.State(-1)
image_filename_state = gr.State(None)
STATE_NEW_BBOX = gr.State(value=None) # Estado para BBox Manual
with gr.Row():
with gr.Column(scale=1):
# Columna Izquierda: Carga y Visualización
# 💡 setup_image_components retorna: api_key_input, image_input_file, image_output_display
api_key_input, image_input_file, image_output_display = setup_image_components()
# Se muestran explícitamente los componentes de entrada
api_key_input
image_input_file
status_output = gr.Markdown("Sube una imagen para comenzar...")
btn_clear = gr.Button("🗑️ Quitar Imagen / Nuevo Documento", visible=True)
with gr.Column(scale=2):
# Columna Derecha: Edición de Etiquetas
gr.Markdown("### 2. Edición de Etiquetas NER")
# 💡 setup_label_components ya retorna los componentes de adición manual
(
df_label_input, tb_token_editor, dd_tag_selector,
btn_save_annotation, btn_export, file_output,
tb_new_token_text, btn_add_new_token, temp_state_dummy
) = setup_label_components()
# Dataframe
df_label_input
# Contenedor para los editores (Token y Tag)
with gr.Row(visible=True) as editor_row:
with gr.Column(scale=2):
tb_token_editor
# Campo de texto para el BBox manual (se muestra condicionalmente)
tb_new_token_text
with gr.Column(scale=1):
dd_tag_selector
# Contenedor para los botones de Guardar/Descargar/Agregar
with gr.Row(visible=True):
btn_save_annotation
btn_export
btn_add_new_token
file_output
# --- CONEXIONES DE EVENTOS ---
# CONEXIÓN 1: EJECUTAR OCR y Gemini (MODIFICADA)
image_input_file.change(
fn=process_image,
inputs=[image_input_file, api_key_input], # 💡 AÑADIR api_key_input
outputs=[
image_orig_state, tokens_data_state, image_output_display, df_label_input, status_output,
image_input_file, image_output_display, image_filename_state
],
api_name=False
)
# CONEXIÓN 2: Selección de FILA
df_label_input.select(
fn=capture_highlight_index,
inputs=None,
outputs=[highlight_index_state],
queue=False
).then(
fn=display_selected_row,
inputs=[tokens_data_state, highlight_index_state],
outputs=[tb_token_editor, dd_tag_selector, highlight_index_state],
).then(
fn=update_ui,
inputs=[image_orig_state, tokens_data_state, df_label_input, highlight_index_state],
outputs=[tokens_data_state, image_output_display],
api_name=False
)
# CONEXIÓN 3: Edición de Tag o Token (Actualiza estado y UI)
dd_tag_selector.change(
fn=lambda t, d, i, new_tag_val: update_dataframe_and_state(t, d, new_tag_val, None, i, 'tag'),
inputs=[tokens_data_state, df_label_input, highlight_index_state, dd_tag_selector],
outputs=[tokens_data_state, df_label_input],
).then(
fn=update_ui,
inputs=[image_orig_state, tokens_data_state, df_label_input, highlight_index_state],
outputs=[tokens_data_state, image_output_display],
api_name=False
)
token_update_events = [tb_token_editor.blur, tb_token_editor.submit]
for event in token_update_events:
event(
fn=lambda t, d, i, new_token_val: update_dataframe_and_state(t, d, None, new_token_val, i, 'token'),
inputs=[tokens_data_state, df_label_input, highlight_index_state, tb_token_editor],
outputs=[tokens_data_state, df_label_input],
).then(
fn=update_ui,
inputs=[image_orig_state, tokens_data_state, df_label_input, highlight_index_state],
outputs=[tokens_data_state, image_output_display],
api_name=False
)
# CONEXIÓN 4: Guardar y Exportar
btn_save_annotation.click(
fn=save_current_annotation_to_json,
inputs=[image_orig_state, tokens_data_state, image_filename_state],
outputs=[file_output, status_output],
api_name=False
)
btn_export.click(
fn=export_and_zip_dataset,
inputs=[image_orig_state, tokens_data_state, image_filename_state],
outputs=[file_output, status_output],
api_name=False
)
# CONEXIÓN 5: Limpiar y Reiniciar (MODIFICADA)
btn_clear.click(
fn=clear_ui_and_reset_states,
inputs=[api_key_input, tb_new_token_text, btn_add_new_token, STATE_NEW_BBOX], # 💡 AÑADIR api_key_input
outputs=[
image_orig_state, tokens_data_state, highlight_index_state,
image_filename_state, api_key_input, image_input_file, image_output_display, # 💡 AÑADIR api_key_input
df_label_input, tb_token_editor, dd_tag_selector,
tb_new_token_text, btn_add_new_token, STATE_NEW_BBOX,
status_output
],
api_name=False
)
if __name__ == "__main__":
try:
app.launch()
except Exception as e:
print(f"Error crítico durante la ejecución de la aplicación: {str(e)}")
raise |