Lucas Gagneten
missing data configuration
afaa8ff

A newer version of the Gradio SDK is available: 6.1.0

Upgrade
metadata
title: Layoutlmv3 Facturas Extractor
emoji: 🏃
colorFrom: blue
colorTo: indigo
sdk: gradio
sdk_version: 5.49.1
app_file: app.py
pinned: false
license: mit
short_description: LayoutLMv3 fine-tuned - Ner Facturas Extractor

🇦🇷 Extractor de Datos de Facturas Argentinas

Sistema automático de extracción, validación y exportación de datos de facturas argentinas usando LayoutLMv3 y DocTR.


📁 Estructura del Proyecto

layoutlmv3-facturas-extractor/
│
├── app.py                      # Punto de entrada principal
├── config.py                   # Configuración y constantes
├── model_loader.py             # Carga de modelos ML
├── invoice_processor.py        # Procesamiento de facturas individuales
├── batch_processor.py          # Procesamiento por lotes
├── validator.py                # Validación y corrección de datos
├── interface.py                # Interfaz de usuario (Gradio)
├── requirements.txt            # Dependencias del proyecto
├── README.md                   # Este archivo
└── INSTRUCCIONES.md           # Guía de usuario

🏗️ Arquitectura del Sistema

Componentes Principales

┌─────────────┐
│   app.py    │  Inicializa y orquesta todos los componentes
└──────┬──────┘
       │
       ├──────────────────┬──────────────────┬──────────────────┐
       │                  │                  │                  │
┌──────▼──────┐  ┌────────▼────────┐  ┌─────▼──────┐  ┌───────▼────────┐
│model_loader │  │invoice_processor│  │ validator  │  │batch_processor │
└─────────────┘  └─────────────────┘  └────────────┘  └────────────────┘
       │                  │                  │                  │
       └──────────────────┴──────────────────┴──────────────────┘
                                     │
                              ┌──────▼──────┐
                              │ interface   │
                              │  (Gradio)   │
                              └─────────────┘

📦 Descripción de Módulos

1. config.py

Centraliza toda la configuración del proyecto.

Contenido:

  • HUGGINGFACE_MODEL: Ruta al modelo LayoutLMv3 fine-tuneado
  • LABEL_LIST: Etiquetas de entidades soportadas
  • ID2LABEL / LABEL2ID: Mapeos de etiquetas
  • LABEL2COLOR: Colores para visualización
  • MAX_LENGTH: Longitud máxima de secuencia
  • NORMALIZATION_FACTOR: Factor de normalización de coordenadas

Etiquetas Soportadas:

REQUIRED_LABELS = [
    'PROVEEDOR_RAZON_SOCIAL',
    'PROVEEDOR_CUIT',
    'COMPROBANTE_NUMERO',
    'FECHA',
    'JURISDICCION_GASTO',
    'TIPO',
    'CONCEPTO_GASTO',
    'ALICUOTA',
    'IVA',
    'NETO',
    'TOTAL'
]

2. model_loader.py

Gestiona la carga de modelos de Machine Learning.

Clase: ModelManager

class ModelManager:
    def __init__(self, force_cpu=True):
        # Carga LayoutLMv3 para NER
        # Carga DocTR para OCR
    
    def get_processor():  # Procesador de LayoutLMv3
    def get_model():      # Modelo de LayoutLMv3
    def get_ocr_model():  # Modelo OCR de DocTR
    def get_device():     # CPU o CUDA

Modelos Utilizados:

  1. LayoutLMv3 (Fine-tuned): Reconocimiento de entidades en documentos
  2. DocTR: Extracción de texto (OCR)
    • Detector: db_resnet50
    • Reconocedor: crnn_vgg16_bn

3. invoice_processor.py

Procesa facturas individuales: OCR → NER → Validación → Visualización.

Clase: InvoiceProcessor

Pipeline de Procesamiento:

Imagen de Factura
       ↓
[extract_ocr_data]        # DocTR extrae texto + coordenadas
       ↓
[perform_ner]             # LayoutLMv3 predice entidades
       ↓
[group_entities]          # Agrupa tokens según esquema BIO
       ↓
[Validación]              # Valida y corrige datos
       ↓
[draw_annotations]        # Dibuja bounding boxes
       ↓
Resultados + Imagen Anotada

Métodos Principales:

def extract_ocr_data(image):
    """
    Extrae texto y coordenadas usando DocTR.
    Returns: (words_data, image_width, image_height)
    """

def perform_ner(image, words_data):
    """
    Predice etiquetas NER con LayoutLMv3.
    Returns: predictions_final
    """

def group_entities(words_data, predictions):
    """
    Agrupa tokens usando esquema BIO.
    Desduplicación: selecciona el valor más largo.
    Returns: final_ner_results
    """

def draw_annotations(image, entities):
    """
    Dibuja bounding boxes en la imagen.
    Returns: annotated_image
    """

def process_invoice(image, filename):
    """
    Pipeline completo.
    Returns: (filename, annotated_image, table_data, json_data)
    """

4. validator.py

Valida y corrige datos extraídos según reglas de negocio.

Clase: InvoiceValidator

Reglas de Validación:

Etiqueta Regla Valor por Defecto
ALICUOTA 21.00 o 10.5 21.00
COMPROBANTE_NUMERO Formato ####-######## 00000-00000000
CONCEPTO_GASTO Texto libre (vacío)
FECHA dd/mm/yyyy Fecha actual
TOTAL Número sin $ Máximo del OCR
IVA Número sin $ 21% de TOTAL
NETO Número sin $ 79% de TOTAL
JURISDICCION_GASTO Texto libre (vacío)
PROVEEDOR_CUIT ##-########-# 00-00000000-0
PROVEEDOR_RAZON_SOCIAL Texto libre (vacío)
TIPO A, B, C, M, E, T A

Métodos Principales:

def validate_and_correct(ner_results, ocr_text):
    """
    Valida todas las etiquetas.
    Returns: (corrected_table, validation_errors)
    """

def _validate_label(label, value, all_values, ocr_text):
    """
    Valida una etiqueta específica.
    Returns: (corrected_value, is_valid)
    """

# Validadores específicos:
def _validate_alicuota(...)
def _validate_comprobante(...)
def _validate_cuit(...)
def _validate_total(...)
# ... etc

Características:

  • ✅ Auto-completado de campos faltantes
  • ✅ Limpieza de símbolos ($, texto extra)
  • ✅ Normalización de formatos (decimales, fechas)
  • ✅ Búsqueda inteligente en texto OCR
  • ✅ Cálculos automáticos (IVA/NETO desde TOTAL)

5. batch_processor.py

Gestiona procesamiento por lotes y navegación de resultados.

Clase: BatchProcessor

class BatchProcessor:
    def __init__(self, invoice_processor):
        self.invoice_processor = invoice_processor
        self.images_dict = {}  # Almacena imágenes anotadas
    
    def process_batch(file_list):
        """
        Procesa múltiples facturas.
        Genera progreso en tiempo real con yield.
        Returns: (master_df, initial_index, status, first_image, first_filename)
        """

Clase: ResultNavigator

class ResultNavigator:
    @staticmethod
    def get_row_as_table(df, index):
        """Convierte fila del DataFrame a tabla Gradio"""
    
    @staticmethod
    def update_df_from_table(df, index, table_data):
        """Actualiza DataFrame con ediciones del usuario"""
    
    @staticmethod
    def go_next(df, current_index, batch_processor):
        """Navega a siguiente factura"""
    
    @staticmethod
    def go_prev(df, current_index, batch_processor):
        """Navega a factura anterior"""

Estructura de Datos:

# DataFrame Maestro
master_df = pd.DataFrame({
    'Nombre_Archivo': ['factura1.pdf', 'factura2.pdf', ...],
    'PROVEEDOR_RAZON_SOCIAL': ['EMPRESA SA', 'COMERCIO SRL', ...],
    'PROVEEDOR_CUIT': ['30-54808315-6', '27-12345678-9', ...],
    # ... resto de columnas
})

# Diccionario de Imágenes
images_dict = {
    'factura1.pdf': <PIL.Image>,
    'factura2.pdf': <PIL.Image>,
    # ...
}

6. interface.py

Interfaz de usuario con Gradio.

Clase: GradioInterface

Componentes de la Interfaz:

┌─────────────────────────────────────────────────────┐
│  🇦🇷 Extracción de Datos de Facturas Argentinas     │
├─────────────────────────────────────────────────────┤
│                                                      │
│  📂 Cargar Facturas    │  🚀 Procesar  📥 Descargar │
│                        │  📊 Estado                  │
├────────────────────────┴─────────────────────────────┤
│                                                      │
│  🖼️ Imagen            │  📄 Nombre                  │
│   Anotada             │  ⬅️ 💾 ➡️ Navegación       │
│                        │  📋 Tabla Editable          │
│                        │     [Etiqueta | Valor]      │
└────────────────────────┴─────────────────────────────┘

Estados de Gradio:

master_dataframe_state = gr.State(value=None)  # DataFrame con todas las facturas
current_index_state = gr.State(value=0)        # Índice de factura actual

Eventos Principales:

# Procesamiento
process_button.click(
    fn=batch_processor.process_batch,
    outputs=[master_dataframe_state, ...]
)

# Guardar ediciones
save_button.click(
    fn=update_dataframe_with_edits,
    inputs=[master_dataframe_state, current_index, table],
    outputs=[master_dataframe_state]
)

# Navegación
next_button.click(
    fn=navigator.go_next,
    inputs=[master_dataframe_state, current_index],
    outputs=[current_index, image, table, ...]
)

# Descarga
download_button.click(
    fn=verify_and_generate_excel,
    inputs=[master_dataframe_state],
    outputs=[excel_file, status]
)

7. app.py

Punto de entrada que inicializa y orquesta todos los componentes.

def main():
    # 1. Cargar modelos
    model_manager = ModelManager(force_cpu=True)
    
    # 2. Inicializar procesadores
    invoice_processor = InvoiceProcessor(model_manager)
    batch_processor = BatchProcessor(invoice_processor)
    
    # 3. Construir interfaz
    gradio_interface = GradioInterface(batch_processor, ResultNavigator)
    demo = gradio_interface.build_interface()
    
    # 4. Lanzar aplicación
    demo.launch()

🔄 Flujo de Datos Completo

1. Procesamiento de Lote

Usuario carga archivos
        ↓
[BatchProcessor.process_batch]
        ↓
Para cada archivo:
    ├─ [InvoiceProcessor.process_invoice]
    │   ├─ [extract_ocr_data]        # DocTR
    │   ├─ [perform_ner]             # LayoutLMv3
    │   ├─ [group_entities]          # BIO Schema
    │   └─ [Validator.validate_and_correct]
    │
    └─ Agregar al DataFrame
        ↓
DataFrame Maestro creado
        ↓
Estado: master_dataframe_state

2. Visualización y Edición

master_dataframe_state[índice]
        ↓
[ResultNavigator.get_row_as_table]
        ↓
Tabla Gradio (editable)
        ↓
Usuario edita valores
        ↓
Click "💾 Guardar"
        ↓
[ResultNavigator.update_df_from_table]
        ↓
master_dataframe_state actualizado

3. Exportación

Click "📥 Descargar XLSX"
        ↓
master_dataframe_state
        ↓
[verify_and_generate_excel]
        ↓
pd.DataFrame.to_excel()
        ↓
Archivo .xlsx generado

🧠 Algoritmos Clave

Esquema BIO (Begin-Inside-Outside)

El sistema usa el esquema BIO para agrupar tokens:

Input OCR:  ["EMPRESA", "S.A.", "CUIT:", "30", "-", "54808315", "-", "6"]
Predicciones: ["B-RAZON", "I-RAZON", "O", "B-CUIT", "I-CUIT", "I-CUIT", "I-CUIT", "I-CUIT"]

Agrupación:
- PROVEEDOR_RAZON_SOCIAL: "EMPRESA S.A."
- PROVEEDOR_CUIT: "30 - 54808315 - 6"

Desduplicación

Si el modelo detecta la misma entidad varias veces, se selecciona el valor más largo:

# Ejemplo: TOTAL aparece 2 veces
candidatos = [
    {"valor": "$10000", "bbox": [...]},
    {"valor": "$10000.00", "bbox": [...]}
]

# Se selecciona el más largo
resultado = "$10000.00"

Normalización de Coordenadas

DocTR retorna coordenadas normalizadas [0-1]. Se multiplican por NORMALIZATION_FACTOR=1000:

# Coordenadas DocTR: (0.1, 0.2) → (0.9, 0.3)
normalized = np.array(word.geometry) * 1000
# Resultado: (100, 200) → (900, 300)

🎯 Decisiones de Diseño

¿Por qué Gradio?

  • ✅ Interfaz web automática
  • ✅ Componentes interactivos out-of-the-box
  • ✅ Sistema de estados integrado
  • ✅ Fácil deployment

¿Por qué un DataFrame como estado central?

  • ✅ Estructura tabular natural para facturas
  • ✅ Fácil manipulación con pandas
  • ✅ Exportación directa a Excel
  • ✅ Una sola fuente de verdad

¿Por qué validación post-NER?

  • ✅ Los modelos no son perfectos
  • ✅ Reglas de negocio específicas
  • ✅ Auto-completado de campos faltantes
  • ✅ Mejora la experiencia del usuario

🔧 Extensibilidad

Agregar Nueva Etiqueta

  1. config.py: Agregar a LABEL_LIST
  2. validator.py:
    • Agregar a REQUIRED_LABELS
    • Crear función _validate_nueva_etiqueta()
    • Agregar validador al diccionario en _validate_label()

Cambiar Modelo

  1. config.py: Cambiar HUGGINGFACE_MODEL
  2. model_loader.py: Ajustar _load_layoutlmv3() si es necesario

Agregar Nueva Validación

# validator.py
def _validate_nueva_etiqueta(self, value, all_values, ocr_text):
    # Lógica de validación
    if condicion:
        return valor_corregido, True
    return valor_por_defecto, False

📊 Métricas y Performance

Tiempos Aproximados (CPU)

  • Carga de modelos: ~10-15 segundos
  • Procesamiento por factura: ~3-5 segundos
  • Validación: <1 segundo
  • Exportación a Excel: <1 segundo

Uso de Memoria

  • Modelos cargados: ~2-3 GB
  • Procesamiento: ~500 MB adicionales
  • DataFrame (100 facturas): ~1 MB

🐛 Debugging

Logs Importantes

El sistema está configurado con logs mínimos para producción. Para debug, activar logs temporalmente:

# En cualquier módulo
DEBUG = True

if DEBUG:
    print(f"Debug info: {variable}")

Verificar Estado del DataFrame

# En consola Python
from batch_processor import BatchProcessor

# Ver contenido
df = BatchProcessor.get_master_dataframe()
print(df)

📚 Referencias


👥 Contribuciones

Para contribuir al proyecto:

  1. Mantener la estructura modular
  2. Documentar funciones con docstrings
  3. Seguir el estilo de código existente
  4. Probar cambios antes de commit
  5. Actualizar documentación según sea necesario

📄 Licencia

Este proyecto utiliza modelos pre-entrenados sujetos a sus respectivas licencias.


Desarrollado con ❤️ para automatizar el procesamiento de facturas argentinas