A newer version of the Gradio SDK is available:
6.1.0
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-tuneadoLABEL_LIST: Etiquetas de entidades soportadasID2LABEL/LABEL2ID: Mapeos de etiquetasLABEL2COLOR: Colores para visualizaciónMAX_LENGTH: Longitud máxima de secuenciaNORMALIZATION_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:
- LayoutLMv3 (Fine-tuned): Reconocimiento de entidades en documentos
- DocTR: Extracción de texto (OCR)
- Detector:
db_resnet50 - Reconocedor:
crnn_vgg16_bn
- Detector:
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
- config.py: Agregar a
LABEL_LIST - validator.py:
- Agregar a
REQUIRED_LABELS - Crear función
_validate_nueva_etiqueta() - Agregar validador al diccionario en
_validate_label()
- Agregar a
Cambiar Modelo
- config.py: Cambiar
HUGGINGFACE_MODEL - 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
- LayoutLMv3: Paper
- DocTR: Documentación
- Gradio: Documentación
- Pandas: Documentación
👥 Contribuciones
Para contribuir al proyecto:
- Mantener la estructura modular
- Documentar funciones con docstrings
- Seguir el estilo de código existente
- Probar cambios antes de commit
- 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