Cómo utilizar Async y Multithreading en C#
La migración de Mindeea IronOCR traslada el procesamiento de documentos de los servidores externos en la nube a su propia infraestructura, eliminando la facturación por página, la transmisión de datos de terceros y la disponibilidad de la red como dependencia en tiempo de ejecución. Las secciones siguientes abarcan la sustitución de paquetes, las actualizaciones de espacios de nombres y cuatro escenarios prácticos de código antes y después: extracción de zonas de facturas, procesamiento de recibos en formato PDF de varias páginas, preprocesamiento de documentos financieros y archivado de PDF con capacidad de búsqueda.
¿Por qué migrar desde Mindee?
Mindee resuelve un problema real de forma elegante: enviar un documento y recibir campos JSON estructurados. Para los equipos en los que la transmisión de documentos financieros a una API externa es aceptable y el volumen se mantiene moderado, ofrece resultados rápidos. El desencadenante de la migración suele producirse cuando cambia alguna de varias condiciones.
Los datos financieros cruzan un límite de cumplimiento. ParseAsync<InvoiceV4> de Mindeesube el documento completo a servidores externos. Los códigos IBAN, los números de ruta, los números EIN de los proveedores y los detalles de las partidas se transmiten a través de Internet y se procesan en hardware que está fuera de su control. La certificación SOC 2 Tipo II confirma que Mindeecumple con las prácticas de seguridad. No mantiene sus documentos dentro de su perímetro de seguridad. Cuando una revisión de cumplimiento, un nuevo contrato con un cliente o una normativa sobre la residencia de datos establecen límites al procesamiento en la nube por parte de terceros, la arquitectura de Mindeedeja de ser válida, independientemente de la precisión de su extracción.
El costo por página aumenta según tus necesidades. El plan Básico ofrece 1000 páginas al mes por $49/mes. El plan Pro ofrece 5.000 páginas al mes por 499 dólares al mes. Los archivos PDF de varias páginas cuentan todas las páginas. Las pruebas de desarrollo se contabilizan dentro de la cuota. Un equipo que procesa 4.000 facturas al mes paga 499 dólares al mes (5.988 dólares al año), cifra que se reinicia cada año y aumenta en función del volumen de transacciones. Con 24.000 páginas al mes, estás negociando precios Enterprise . La licencia perpetua de IronOCR, con un precio de 1499 dólares (nivel Professional ), cubre un volumen ilimitado de documentos y recupera su coste frente a MindeePro en menos de cuatro meses.
El sondeo asíncrono se propaga a través de la pila. Cada operación de Mindeees asíncrona porque cada operación es una solicitud de red. Esta cadena asíncrona obligatoria se propaga en cascada a través de los controladores, las capas de servicio y las clases de trabajadores. Cuando la red no está disponible o el servicio de Mindeesufre una interrupción, la cascada de fallos produce una avería sin posibilidad de recuperación local.IronOCR procesa sincrónicamente — trabajo de CPU local — y envuelve en Task.Run donde se requieren interfaces asíncronas para mantener la consistencia arquitectónica.
El catálogo de API pre-construido tiene un límite duro. Mindeecubre InvoiceV4, ReceiptV5, PassportV1, y un pequeño catálogo de otros tipos de documentos. Cualquier tipo de documento fuera de esta lista requiere entrenamiento de modelo personalizado del plan Enterprise: recolección de muestras, etiquetado, tiempo de entrenamiento y tiempo de entrega antes de que la API esté disponible. Cada nuevo tipo de documento que su empresa necesite implica la contratación de un proveedor independiente.IronOCR procesa cualquier tipo de documento usando el mismo llamado IronTesseract().Read(); Agregar un nuevo tipo de documento implica escribir patrones de extracción, no negociar un contrato de capacitación.
La gestión de claves API amplía la superficie operativa. Las credenciales de Mindeedeben almacenarse de forma segura en el servidor, rotarse periódicamente y protegerse de cualquier exposición. Las reglas de salida de red deben permitir el tráfico HTTPS saliente hacia los puntos finales de Mindee. La lógica de reintento debe manejar HttpRequestException y MindeeException de las inevitables fallas de red. Eliminar Mindeeelimina toda esa superficie operativa: sin credenciales, sin reglas de salida, sin mecanismos de reintento para las operaciones de entrada/salida de red.
Se excluyen los entornos aislados y con acceso restringido. Los contratistas gubernamentales, los subcontratistas de defensa, las redes sanitarias con estrictos controles de internet saliente y los sistemas industriales implementados en instalaciones sin conectividad a internet fiable no pueden utilizar Mindeesegún lo previsto.IronOCR funciona de forma idéntica en contenedores Docker sin acceso saliente, en Azure Functions con restricciones de salida y en entornos totalmente aislados de la red. No existe dependencia de la nube en tiempo de ejecución. Consulte la página de licencias de IronOCR para obtener detalles sobre los niveles de implementación.
El problema fundamental
Cada documento financiero que procesa Mindeecruza un límite de red que usted no controla:
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
Imports System.Threading.Tasks
' Mindee: invoice with bank details leaves your infrastructure on this line
Dim response = Await _client.ParseAsync(Of InvoiceV4)(New LocalInputSource("invoice.pdf"))
' IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
' IronOCR: same invoice, same result, zero data transmission
Dim result = New IronTesseract().Read("invoice.pdf")
' All processing local — bank details never leave your machine
IronOCR vs Mindee: Comparación de características
La siguiente tabla resume las diferencias arquitectónicas y de capacidades que influyen en las decisiones de migración.
| Característica | Mindee | IronOCR |
|---|---|---|
| Lugar de procesamiento | Servidores en la nube de Mindee | Máquina local / su infraestructura |
| Se requiere conexión a Internet en tiempo de ejecución. | Siempre | Nunca |
| Transmisión de datos | Documento completo subido por llamada | None |
| Coste por documento | Sí (por página, según el plan) | No (licencia perpetua) |
| Precio inicial | $49/mes (1.000 páginas) | $999 único (Lite) |
| Soporte sin conexión / aislado de la red | No soportado | Soporte completo |
| Implementación en las instalaciones | No disponible | Única opción |
| Análisis de facturas | API estructurada pre-construida (InvoiceV4) |
OCR + extracción basada en regiones o expresiones regulares |
| Análisis de recibos | API estructurada pre-construida (ReceiptV5) |
Patrones de extracción y OCR |
| Tipos de documentos arbitrarios | No se admite sin formación personalizada. | Soporte completo, cualquier documento |
| Formación en modelos personalizados | Plan Enterprise + plazo de entrega | No es necesario |
| Patrón de llamada a la API | Asíncrono obligatorio (E/S de red) | Síncrono (asíncrono opcional) |
| limitación de velocidad | Plan-dependent | None |
| Entrada de PDF | Sí | Sí (nativo, sin conversión) |
| Procesamiento de archivos PDF de varias páginas | Sí | Sí |
| Salida en PDF con capacidad de búsqueda | No | Sí (result.SaveAsSearchablePdf()) |
| Preprocesamiento de imágenes | Ninguno expuesto | Corregir inclinación, Reducir ruido, Contraste, Binarizar, Enfocar, Escalar |
| Extracción basada en regiones | Coordenadas de campo en respuesta | CropRectangle en la entrada |
| Soporte para más de 125 idiomas | Limitado | Sí (incluido, sin gestión de datos de prueba) |
| Lectura de BarCodes | No | Sí (simultáneamente con el OCR) |
| Coordenadas de palabras estructuradas | No | Sí (Páginas, Párrafos, Líneas, Palabras) |
| Seguridad de los hilos | N/D (E/S de red por llamada) | Incorporado en |
| Multiplataforma | Nube (independiente de la plataforma) | Windows, Linux, macOS, Docker, Azure, AWS |
| Compatibilidad con .NET | .NET Standard 2.0 o superior | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| Soporte comercial | Sí | Sí |
Inicio rápido: Migración de Mindeea IronOCR
Paso 1: Sustituir el paquete NuGet
Elimine el paquete Mindee:
dotnet remove package Mindee
dotnet remove package Mindee
Instala IronOCR desde NuGet :
dotnet add package IronOcr
Paso 2: Actualizar los espacios de nombres
Reemplace el bloque de espacio de nombres Mindeecon el espacio de nombres IronOCR:
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;
// After (IronOCR)
using IronOcr;
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports Mindee.Product.Receipt
Imports Mindee.Product.FinancialDocument
Imports IronOcr
Paso 3: Inicializar licencia
Agregue la clave de licencia al iniciar la aplicación (antes de la primera llamada de OCR):
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Ejemplos de migración de código
Extracción de campos de factura mediante zonas de documento
Mindee devuelve los campos preprocesados en ubicaciones conocidas porque su modelo del lado del servidor sabe dónde suelen aparecer los números de factura, las fechas y los totales. El equivalente de IronOCR usa CropRectangle para leer zonas específicas del documento, igualando la conciencia espacial de la extracción de Mindeesin transmitir el documento.
Enfoque de Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted to Mindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeZoneExtractor
{
private readonly MindeeClient _client;
public MindeeZoneExtractor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<(string invoiceNumber, string supplierName, decimal? total)>
ExtractKeyFieldsAsync(string filePath)
{
// Document transmitted to Mindee— spatial field detection happens server-side
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Fields arrive pre-parsed; no zone configuration required on your end
return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
);
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeZoneExtractor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ExtractKeyFieldsAsync(filePath As String) As Task(Of (invoiceNumber As String, supplierName As String, total As Decimal?))
' Document transmitted to Mindee— spatial field detection happens server-side
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Fields arrive pre-parsed; no zone configuration required on your end
Return (
prediction.InvoiceNumber?.Value,
prediction.SupplierName?.Value,
prediction.TotalAmount?.Value
)
End Function
End Class
Enfoque IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrZoneExtractor
{
private readonly IronTesseract _ocr = new IronTesseract();
public (string invoiceNumber, string supplierName, string total)
ExtractKeyFields(string filePath)
{
// Zone 1: supplier name — top 12% of document, full width
var headerRegion = new CropRectangle(0, 0, 850, 120);
using var headerInput = new OcrInput();
headerInput.LoadImage(filePath, headerRegion);
var supplierResult = _ocr.Read(headerInput);
// Zone 2: invoice number and date — upper-right quadrant
var metaRegion = new CropRectangle(450, 80, 400, 100);
using var metaInput = new OcrInput();
metaInput.LoadImage(filePath, metaRegion);
var metaResult = _ocr.Read(metaInput);
// Zone 3: totals block — bottom-right 20%
var totalsRegion = new CropRectangle(500, 800, 350, 200);
using var totalsInput = new OcrInput();
totalsInput.LoadImage(filePath, totalsRegion);
var totalsResult = _ocr.Read(totalsInput);
var invoiceNumber = Regex.Match(
metaResult.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
totalsResult.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
RegexOptions.IgnoreCase).Groups[1].Value;
// Supplier name is the first substantial text line in the header zone
var supplierName = supplierResult.Text
.Split('\n')
.FirstOrDefault(l => l.Trim().Length > 4)
?.Trim();
return (invoiceNumber, supplierName, total);
}
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrZoneExtractor
Private ReadOnly _ocr As New IronTesseract()
Public Function ExtractKeyFields(filePath As String) As (invoiceNumber As String, supplierName As String, total As String)
' Zone 1: supplier name — top 12% of document, full width
Dim headerRegion As New CropRectangle(0, 0, 850, 120)
Using headerInput As New OcrInput()
headerInput.LoadImage(filePath, headerRegion)
Dim supplierResult = _ocr.Read(headerInput)
' Zone 2: invoice number and date — upper-right quadrant
Dim metaRegion As New CropRectangle(450, 80, 400, 100)
Using metaInput As New OcrInput()
metaInput.LoadImage(filePath, metaRegion)
Dim metaResult = _ocr.Read(metaInput)
' Zone 3: totals block — bottom-right 20%
Dim totalsRegion As New CropRectangle(500, 800, 350, 200)
Using totalsInput As New OcrInput()
totalsInput.LoadImage(filePath, totalsRegion)
Dim totalsResult = _ocr.Read(totalsInput)
Dim invoiceNumber = Regex.Match(metaResult.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(totalsResult.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)", RegexOptions.IgnoreCase).Groups(1).Value
' Supplier name is the first substantial text line in the header zone
Dim supplierName = supplierResult.Text.Split(vbLf).FirstOrDefault(Function(l) l.Trim().Length > 4)?.Trim()
Return (invoiceNumber, supplierName, total)
End Using
End Using
End Using
End Function
End Class
El OCR basado en zonas coincide con la intención de la detección de campos espaciales de Mindee: leer áreas específicas del documento en lugar de aplicar expresiones regulares a toda la página. CropRectangle(x, y, width, height) toma coordenadas de píxeles, por lo que la afinación requiere medir contra una factura de muestra representativa. La guía de OCR basada en región cubre la medición de coordenadas y las estrategias de superposición de zonas. Para facturas con diseños variables, combinar la extracción de zonas con regex de página completa en result.Text produce los resultados más confiables.
Procesamiento de informes de gastos de varias páginas en formato PDF
Mindee acepta un archivo PDF y procesa cada página como una unidad de documento independiente, devolviendo un resultado estructurado por página.IronOCR lee PDFs de múltiples páginas de manera nativa a través de OcrInput.LoadPdf(), procesando todas las páginas en una sola llamada y devolviendo result.Pages con contenido por página y coordenadas de palabras.
Enfoque de Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
// Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;
public class MindeeExpenseReportProcessor
{
private readonly MindeeClient _client;
public MindeeExpenseReportProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
{
var summaries = new List<ExpenseSummary>();
// Mindeeprocesses per-document; each page of a PDF is a separate upload
// For a 10-page expense report: 10 API calls, 10 pages billed
var inputSource = new LocalInputSource(pdfPath);
var response = await _client.ParseAsync<ReceiptV5>(inputSource);
var prediction = response.Document.Inference.Prediction;
summaries.Add(new ExpenseSummary
{
MerchantName = prediction.SupplierName?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value ?? 0m,
Category = prediction.Category?.Value
});
return summaries;
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Receipt
Imports System.Threading.Tasks
Public Class MindeeExpenseReportProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessExpenseReportAsync(pdfPath As String) As Task(Of List(Of ExpenseSummary))
Dim summaries As New List(Of ExpenseSummary)()
' Mindee processes per-document; each page of a PDF is a separate upload
' For a 10-page expense report: 10 API calls, 10 pages billed
Dim inputSource As New LocalInputSource(pdfPath)
Dim response = Await _client.ParseAsync(Of ReceiptV5)(inputSource)
Dim prediction = response.Document.Inference.Prediction
summaries.Add(New ExpenseSummary With {
.MerchantName = prediction.SupplierName?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = If(prediction.TotalAmount?.Value, 0D),
.Category = prediction.Category?.Value
})
Return summaries
End Function
End Class
Enfoque IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrExpenseReportProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
{
// Load entire multi-page PDF in one call — no per-page upload
using var input = new OcrInput();
input.LoadPdf(pdfPath); // all pages loaded locally
var result = _ocr.Read(input);
var summaries = new List<ExpenseSummary>();
// Each page maps to one receipt in the expense report
foreach (var page in result.Pages)
{
var pageText = page.Text;
// Skip pages without receipt content
if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
continue;
var merchantName = page.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
pageText,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
var dateMatch = Regex.Match(
pageText,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");
var categoryMatch = Regex.Match(
pageText,
@"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase);
summaries.Add(new ExpenseSummary
{
PageNumber = page.PageNumber,
MerchantName = merchantName,
Date = dateMatch.Success ? dateMatch.Value : null,
TotalAmount = totalMatch.Success
? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
: 0m,
Category = categoryMatch.Success
? categoryMatch.Groups[1].Value.Trim()
: null,
Confidence = page.Words.Any()
? page.Words.Average(w => (double)w.Confidence)
: 0
});
}
return summaries;
}
}
public class ExpenseSummary
{
public int PageNumber { get; set; }
public string MerchantName { get; set; }
public string Date { get; set; }
public decimal TotalAmount { get; set; }
public string Category { get; set; }
public double Confidence { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrExpenseReportProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessExpenseReport(pdfPath As String) As List(Of ExpenseSummary)
' Load entire multi-page PDF in one call — no per-page upload
Using input As New OcrInput()
input.LoadPdf(pdfPath) ' all pages loaded locally
Dim result = _ocr.Read(input)
Dim summaries As New List(Of ExpenseSummary)()
' Each page maps to one receipt in the expense report
For Each page In result.Pages
Dim pageText = page.Text
' Skip pages without receipt content
If Not pageText.Contains("Total", StringComparison.OrdinalIgnoreCase) Then
Continue For
End If
Dim merchantName = page.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
Dim totalMatch = Regex.Match(
pageText,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase)
Dim dateMatch = Regex.Match(
pageText,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b")
Dim categoryMatch = Regex.Match(
pageText,
"(?:Category|Dept|Department)\s*:?\s*(.+)",
RegexOptions.IgnoreCase)
summaries.Add(New ExpenseSummary With {
.PageNumber = page.PageNumber,
.MerchantName = merchantName,
.Date = If(dateMatch.Success, dateMatch.Value, Nothing),
.TotalAmount = If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), 0D),
.Category = If(categoryMatch.Success, categoryMatch.Groups(1).Value.Trim(), Nothing),
.Confidence = If(page.Words.Any(), page.Words.Average(Function(w) CDbl(w.Confidence)), 0)
})
Next
Return summaries
End Using
End Function
End Class
Public Class ExpenseSummary
Public Property PageNumber As Integer
Public Property MerchantName As String
Public Property Date As String
Public Property TotalAmount As Decimal
Public Property Category As String
Public Property Confidence As Double
End Class
El procesamiento de un informe de gastos en formato PDF de 10 páginas con Mindeegenera 10 llamadas a la API y 10 páginas facturadas.IronOCR procesa el mismo archivo en una sola llamada local, sin coste por página y sin viajes de ida y vuelta a la red por página. La colección result.Pages proporciona texto por página, cuadros delimitadores a nivel de palabra y posiciones de líneas para extracción consciente del diseño. Consulte la guía de entrada de PDF para la carga de PDF protegidos con contraseña y la selección de rangos de páginas, y la guía de resultados de lectura estructurados para trabajar con coordenadas de palabras.
Proceso de preprocesamiento de facturas escaneadas
El procesamiento en la nube de Mindeeaplica su propia normalización de imagen antes de realizar la extracción de campos. La calidad de esa normalización es opaca: no tienes control sobre ella ni visibilidad sobre el preprocesamiento que se ejecutó. Cuando una factura escaneada presenta distorsión, sombras o bajo contraste, Mindeedevuelve puntuaciones de confianza más bajas sin ofrecer ningún mecanismo para mejorar el escaneo antes de enviarlo.IronOCR expone explícitamente el proceso de preprocesamiento, lo que permite aplicar exactamente las correcciones que necesita un documento determinado antes de que se ejecute el reconocimiento.
Enfoque de Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
// Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeScanProcessor
{
private readonly MindeeClient _client;
public MindeeScanProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
{
// Mindeeapplies internal normalization — no developer control over preprocessing
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new ScanResult
{
InvoiceNumber = prediction.InvoiceNumber?.Value,
Total = prediction.TotalAmount?.Value,
// Per-field confidence: only proxy for scan quality
InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
TotalConfidence = prediction.TotalAmount?.Confidence
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeScanProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessScannedInvoiceAsync(scanPath As String) As Task(Of ScanResult)
' Mindee applies internal normalization — no developer control over preprocessing
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New ScanResult With {
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Total = prediction.TotalAmount?.Value,
' Per-field confidence: only proxy for scan quality
.InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
.TotalConfidence = prediction.TotalAmount?.Confidence
}
End Function
End Class
Enfoque IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrScanProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ScanResult ProcessScannedInvoice(string scanPath)
{
// First pass: read without preprocessing
var quickResult = _ocr.Read(scanPath);
OcrResult result;
if (quickResult.Confidence < 75)
{
// Low confidence — apply full preprocessing pipeline
using var input = new OcrInput();
input.LoadImage(scanPath);
input.Deskew(); // correct page rotation up to ±45 degrees
input.DeNoise(); // remove scanner artifacts and speckle
input.Contrast(); // normalize contrast for faded or overexposed scans
input.Sharpen(); // sharpen blurred text edges
result = _ocr.Read(input);
}
else
{
result = quickResult;
}
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var total = Regex.Match(
result.Text,
@"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups[1].Value;
return new ScanResult
{
InvoiceNumber = invoiceNumber,
Total = total,
DocumentConfidence = result.Confidence,
PreprocessingApplied = quickResult.Confidence < 75
};
}
}
public class ScanResult
{
public string InvoiceNumber { get; set; }
public string Total { get; set; }
public double DocumentConfidence { get; set; }
public bool PreprocessingApplied { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrScanProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessScannedInvoice(scanPath As String) As ScanResult
' First pass: read without preprocessing
Dim quickResult = _ocr.Read(scanPath)
Dim result As OcrResult
If quickResult.Confidence < 75 Then
' Low confidence — apply full preprocessing pipeline
Using input As New OcrInput()
input.LoadImage(scanPath)
input.Deskew() ' correct page rotation up to ±45 degrees
input.DeNoise() ' remove scanner artifacts and speckle
input.Contrast() ' normalize contrast for faded or overexposed scans
input.Sharpen() ' sharpen blurred text edges
result = _ocr.Read(input)
End Using
Else
result = quickResult
End If
Dim invoiceNumber = Regex.Match(
result.Text,
"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
Dim total = Regex.Match(
result.Text,
"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase).Groups(1).Value
Return New ScanResult With {
.InvoiceNumber = invoiceNumber,
.Total = total,
.DocumentConfidence = result.Confidence,
.PreprocessingApplied = quickResult.Confidence < 75
}
End Function
End Class
Public Class ScanResult
Public Property InvoiceNumber As String
Public Property Total As String
Public Property DocumentConfidence As Double
Public Property PreprocessingApplied As Boolean
End Class
El método de dos pasadas —primero una lectura rápida y luego el preprocesamiento en caso de baja confianza— evita la sobrecarga que supone el preprocesamiento completo en escaneos limpios. Para los escenarios de calidad de escaneo más comunes en cuentas por pagar (escaneos ligeramente inclinados en cama plana, artefactos de fax, facturas fotocopiadas), Deskew() y DeNoise() juntos recuperan la mayor parte de la precisión de reconocimiento. La guía de corrección de la calidad de imagen documenta todos los filtros disponibles e incluye indicaciones sobre qué combinaciones funcionan mejor para los diferentes defectos de escaneo. La guía de corrección de la orientación de la imagen abarca la rotación automática para documentos escaneados al revés.
Archivo PDF con capacidad de búsqueda para documentos financieros
Mindee no genera ningún archivo PDF. Su arquitectura es de solo entrada: se envía un documento y se reciben campos JSON. Los equipos que archivan facturas escaneadas en un formato que permita realizar búsquedas deben mantener ese proceso por separado de la extracción que realiza Mindee.IronOCR genera archivos PDF con capacidad de búsqueda directamente a partir del mismo proceso de reconocimiento utilizado para la extracción de campos, sin necesidad de bibliotecas adicionales ni de un segundo paso de procesamiento.
Enfoque de Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields via Mindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
public class MindeeArchiveProcessor
{
private readonly MindeeClient _client;
public MindeeArchiveProcessor(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
{
// Extract fields via Mindee(document transmitted to cloud)
var inputSource = new LocalInputSource(scanPath);
var response = await _client.ParseAsync<InvoiceV4>(inputSource);
var prediction = response.Document.Inference.Prediction;
// Mindeereturns no PDF output — searchable PDF requires a separate library
// (e.g., iTextSharp, PdfSharp, or a separate OCR library)
// The scan at scanPath is the only PDF available for archiving;
// it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks
Public Class MindeeArchiveProcessor
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ArchiveInvoiceAsync(scanPath As String, archivePath As String) As Task
' Extract fields via Mindee(document transmitted to cloud)
Dim inputSource = New LocalInputSource(scanPath)
Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
Dim prediction = response.Document.Inference.Prediction
' Mindeereturns no PDF output — searchable PDF requires a separate library
' (e.g., iTextSharp, PdfSharp, or a separate OCR library)
' The scan at scanPath is the only PDF available for archiving;
' it remains image-only with no embedded text layer
Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.")
Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}")
End Function
End Class
Enfoque IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrArchiveProcessor
{
private readonly IronTesseract _ocr = new IronTesseract();
public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
{
// Apply preprocessing before recognition and archiving
using var input = new OcrInput();
input.LoadPdf(scanPath); // works with both PDF scans and image files
input.Deskew();
input.DeNoise();
var result = _ocr.Read(input);
// Extract fields from the same recognition pass
var invoiceNumber = Regex.Match(
result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
var vendor = result.Pages.FirstOrDefault()?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
var totalMatch = Regex.Match(
result.Text,
@"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
RegexOptions.IgnoreCase);
// Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath);
return new ArchiveResult
{
InvoiceNumber = invoiceNumber,
VendorName = vendor,
Total = totalMatch.Success ? totalMatch.Groups[1].Value : null,
ArchivePath = archiveOutputPath,
DocumentConfidence = result.Confidence,
PageCount = result.Pages.Count()
};
}
}
public class ArchiveResult
{
public string InvoiceNumber { get; set; }
public string VendorName { get; set; }
public string Total { get; set; }
public string ArchivePath { get; set; }
public double DocumentConfidence { get; set; }
public int PageCount { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Linq
Public Class IronOcrArchiveProcessor
Private ReadOnly _ocr As New IronTesseract()
Public Function ArchiveInvoice(scanPath As String, archiveOutputPath As String) As ArchiveResult
' Apply preprocessing before recognition and archiving
Using input As New OcrInput()
input.LoadPdf(scanPath) ' works with both PDF scans and image files
input.Deskew()
input.DeNoise()
Dim result = _ocr.Read(input)
' Extract fields from the same recognition pass
Dim invoiceNumber = Regex.Match(result.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
Dim vendor = result.Pages.FirstOrDefault()?.Lines.FirstOrDefault(Function(l) l.Text.Trim().Length > 4)?.Text.Trim()
Dim totalMatch = Regex.Match(result.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})", RegexOptions.IgnoreCase)
' Write searchable PDF to archive in one call — no separate library needed
result.SaveAsSearchablePdf(archiveOutputPath)
Return New ArchiveResult With {
.InvoiceNumber = invoiceNumber,
.VendorName = vendor,
.Total = If(totalMatch.Success, totalMatch.Groups(1).Value, Nothing),
.ArchivePath = archiveOutputPath,
.DocumentConfidence = result.Confidence,
.PageCount = result.Pages.Count()
}
End Using
End Function
End Class
Public Class ArchiveResult
Public Property InvoiceNumber As String
Public Property VendorName As String
Public Property Total As String
Public Property ArchivePath As String
Public Property DocumentConfidence As Double
Public Property PageCount As Integer
End Class
result.SaveAsSearchablePdf() incrusta directamente la capa de texto reconocido en el PDF de salida, produciendo un archivo donde cada palabra es seleccionable y buscable en cualquier visor de PDF o sistema de gestión de contenido empresarial. La extracción de campos y el archivo se ejecutan en la misma pasada — una llamada _ocr.Read(input) proporciona tanto result.Text para la coincidencia de patrones como result.SaveAsSearchablePdf() para el archivo. La guía de PDF buscable cubre las opciones de salida incluyendo la exportación de HOCR y la página de casos de uso de PDF OCR cubre patrones de despliegue de producción para tuberías de archivo de documentos.
Eliminación del punto final personalizado FinancialDocumentV1
La API FinancialDocumentV1 de Mindeemaneja tanto facturas como recibos al detectar automáticamente el tipo de documento. Los equipos que lo utilizan eliminan la lógica de ramificación a costa de una mayor dependencia de la nube: se cargan todos los documentos, independientemente de su tipo. Sustituirlo por IronOCR utiliza datos posicionales a nivel de palabra para detectar la estructura del documento y la lógica de extracción de rutas sin necesidad de realizar ninguna llamada a la nube.
Enfoque de Mindee:
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;
public class MindeeFinancialRouter
{
private readonly MindeeClient _client;
public MindeeFinancialRouter(string apiKey)
{
_client = new MindeeClient(apiKey);
}
public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
{
// All financial documents uploaded for auto-detection and parsing
var inputSource = new LocalInputSource(filePath);
var response = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
var prediction = response.Document.Inference.Prediction;
return new FinancialSummary
{
DocumentType = prediction.DocumentType?.Value, // "INVOICE" or "RECEIPT"
InvoiceNumber = prediction.InvoiceNumber?.Value,
Date = prediction.Date?.Value?.ToString(),
TotalAmount = prediction.TotalAmount?.Value,
SupplierName = prediction.SupplierName?.Value,
CustomerName = prediction.CustomerName?.Value
};
}
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.FinancialDocument
Imports System.Threading.Tasks
Public Class MindeeFinancialRouter
Private ReadOnly _client As MindeeClient
Public Sub New(apiKey As String)
_client = New MindeeClient(apiKey)
End Sub
Public Async Function ProcessFinancialDocumentAsync(filePath As String) As Task(Of FinancialSummary)
' All financial documents uploaded for auto-detection and parsing
Dim inputSource = New LocalInputSource(filePath)
Dim response = Await _client.ParseAsync(Of FinancialDocumentV1)(inputSource)
Dim prediction = response.Document.Inference.Prediction
Return New FinancialSummary With {
.DocumentType = prediction.DocumentType?.Value, ' "INVOICE" or "RECEIPT"
.InvoiceNumber = prediction.InvoiceNumber?.Value,
.Date = prediction.Date?.Value?.ToString(),
.TotalAmount = prediction.TotalAmount?.Value,
.SupplierName = prediction.SupplierName?.Value,
.CustomerName = prediction.CustomerName?.Value
}
End Function
End Class
Enfoque IronOCR:
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;
public class IronOcrFinancialRouter
{
private readonly IronTesseract _ocr = new IronTesseract();
public FinancialSummary ProcessFinancialDocument(string filePath)
{
var result = _ocr.Read(filePath);
var text = result.Text;
// Detect document type using structural keywords from word data
var isInvoice = DetectInvoice(result);
if (isInvoice)
{
return new FinancialSummary
{
DocumentType = "INVOICE",
InvoiceNumber = Regex.Match(text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value,
Date = Regex.Match(text,
@"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups[1].Value,
TotalAmount = ParseAmount(text,
@"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result),
CustomerName = Regex.Match(text,
@"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups[1].Value.Trim()
};
}
else
{
// Receipt structure: merchant at top, no "Bill To" section
return new FinancialSummary
{
DocumentType = "RECEIPT",
Date = Regex.Match(text,
@"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
TotalAmount = ParseAmount(text,
@"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
SupplierName = ExtractTopLine(result)
};
}
}
private bool DetectInvoice(OcrResult result)
{
// Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
var text = result.Text;
var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
return invoiceSignals.Any(s =>
text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
}
private string ExtractTopLine(OcrResult result)
{
return result.Pages
.FirstOrDefault()
?.Lines
.FirstOrDefault(l => l.Text.Trim().Length > 4)
?.Text.Trim();
}
private decimal? ParseAmount(string text, string pattern)
{
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
if (match.Success &&
decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
return val;
return null;
}
}
public class FinancialSummary
{
public string DocumentType { get; set; }
public string InvoiceNumber { get; set; }
public string Date { get; set; }
public decimal? TotalAmount { get; set; }
public string SupplierName { get; set; }
public string CustomerName { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Public Class IronOcrFinancialRouter
Private ReadOnly _ocr As New IronTesseract()
Public Function ProcessFinancialDocument(filePath As String) As FinancialSummary
Dim result = _ocr.Read(filePath)
Dim text = result.Text
' Detect document type using structural keywords from word data
Dim isInvoice = DetectInvoice(result)
If isInvoice Then
Return New FinancialSummary With {
.DocumentType = "INVOICE",
.InvoiceNumber = Regex.Match(text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value,
.Date = Regex.Match(text,
"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
RegexOptions.IgnoreCase).Groups(1).Value,
.TotalAmount = ParseAmount(text,
"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result),
.CustomerName = Regex.Match(text,
"Bill\s+To\s*:?\s*\n?(.+)",
RegexOptions.IgnoreCase).Groups(1).Value.Trim()
}
Else
' Receipt structure: merchant at top, no "Bill To" section
Return New FinancialSummary With {
.DocumentType = "RECEIPT",
.Date = Regex.Match(text,
"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
.TotalAmount = ParseAmount(text,
"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
.SupplierName = ExtractTopLine(result)
}
End If
End Function
Private Function DetectInvoice(result As OcrResult) As Boolean
' Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
Dim text = result.Text
Dim invoiceSignals = {"Invoice", "Bill To", "Due Date", "Purchase Order"}
Return invoiceSignals.Any(Function(s) text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
End Function
Private Function ExtractTopLine(result As OcrResult) As String
Return result.Pages _
.FirstOrDefault() _
?.Lines _
.FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
?.Text.Trim()
End Function
Private Function ParseAmount(text As String, pattern As String) As Decimal?
Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
If match.Success AndAlso
Decimal.TryParse(match.Groups(1).Value.Replace(",", ""), val) Then
Return val
End If
Return Nothing
End Function
End Class
Public Class FinancialSummary
Public Property DocumentType As String
Public Property InvoiceNumber As String
Public Property Date As String
Public Property TotalAmount As Decimal?
Public Property SupplierName As String
Public Property CustomerName As String
End Class
La lógica de detección de tipo de documento reemplaza el campo DocumentType del servidor de Mindeecon un escaneo de palabras clave simple contra result.Text. En la gran mayoría de los documentos financieros, la presencia de "Factura" o "Facturar a" identifica inequívocamente las facturas; la ausencia identifica recibos. Los datos de posición a nivel de palabra en result.Pages permiten una detección más sofisticada basada en el diseño si su conjunto de documentos incluye casos extremos. La guía de lectura de resultados explica el modelo completo de objetos incluyendo Words, Lines, y Paragraphs con sus coordenadas delimitantes.
Referencia de mapeo de la API de Mindeea IronOCR
| Mindee | Equivalente a IronOCR |
|---|---|
new MindeeClient(apiKey) |
new IronTesseract() — no se requiere clave de API |
new LocalInputSource(filePath) |
new OcrInput() + input.LoadImage(filePath) o ocr.Read(filePath) |
_client.ParseAsync<InvoiceV4>(input) |
_ocr.Read(filePath) + extracción de regex en result.Text |
_client.ParseAsync<ReceiptV5>(input) |
_ocr.Read(filePath) + patrones de extracción de recibos |
_client.ParseAsync<PassportV1>(input) |
_ocr.Read(filePath) + extracción de zona MRZ vía CropRectangle |
_client.ParseAsync<FinancialDocumentV1>(input) |
_ocr.Read(filePath) + detección de tipo de documento en result.Text |
response.Document.Inference.Prediction |
result.Text, result.Pages, result.Lines, result.Words |
prediction.InvoiceNumber?.Value |
Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)") |
prediction.SupplierName?.Value |
Línea superior de la primera página vía result.Pages[0].Lines.First() |
prediction.TotalAmount?.Value |
Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})") |
prediction.LineItems |
result.Lines filtrado por patrones de regex de línea de artículos |
prediction.SupplierPaymentDetails[].Iban |
Regex.Match(result.Text, @"IBAN\s*:?\s*([\w\s]+)") |
prediction.InvoiceDate?.Value |
Regex.Match(result.Text, @"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{4})") |
field.Confidence |
result.Confidence (a nivel de documento) o word.Confidence (por palabra) |
catch (MindeeException ex) |
Sin equivalente: no hay errores de red en el momento del reconocimiento. |
await Task.Delay(100) (límite de tasa) |
No es obligatorio; no hay límites de tarifa. |
input.LoadPdf(path) |
ocrInput.LoadPdf(path) — misma operación, completamente local |
| Clave API en la configuración | Clave de licencia vía IronOcr.License.LicenseKey = "..." — no se necesita rotación |
Problemas comunes de migración y soluciones
Problema 1: Las coordenadas del rectángulo de recorte no coinciden con el diseño del documento.
Mindee: Las coordenadas espaciales se gestionan en el servidor. El modelo de Mindeese adapta a diferentes diseños de facturas sin necesidad de que el desarrollador configure las coordenadas.
Solución: Mida las coordenadas de la zona comparándolas con una muestra representativa de documentos de su conjunto de proveedores. Abra una factura de muestra en cualquier editor de imágenes, lea las dimensiones en píxeles, y defina CropRectangle(x, y, width, height) en consecuencia. Para facturas con diseños variables, lea primero el documento completo y utilice los datos de posición de la palabra para ubicar las zonas dinámicamente:
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
var result = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;
// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));
if (totalLabel != null)
{
var valueRegion = new CropRectangle(
totalLabel.X + totalLabel.Width + 5,
totalLabel.Y - 5,
200,
totalLabel.Height + 10);
using var valueInput = new OcrInput();
valueInput.LoadImage("invoice.jpg", valueRegion);
var valueResult = _ocr.Read(valueInput);
Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
Imports System
Imports System.Linq
Dim result = _ocr.Read("invoice.jpg")
Dim allWords = result.Pages.First().Words
' Find the word "Total" and read the region 50px to its right
Dim totalLabel = allWords.FirstOrDefault(Function(w) w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase))
If totalLabel IsNot Nothing Then
Dim valueRegion = New CropRectangle(totalLabel.X + totalLabel.Width + 5, totalLabel.Y - 5, 200, totalLabel.Height + 10)
Using valueInput As New OcrInput()
valueInput.LoadImage("invoice.jpg", valueRegion)
Dim valueResult = _ocr.Read(valueInput)
Console.WriteLine($"Total: {valueResult.Text.Trim()}")
End Using
End If
El ejemplo de OCR basado en regiones muestra este patrón de zona dinámico en un ejemplo completamente funcional.
Problema 2: Los sitios de llamadas asíncronas fallan después de eliminar Mindee.
Mindee: Toda la interfaz API de Mindeees asíncrona porque se basa en E/S de red. Los controladores y métodos de servicio construidos sobre await _client.ParseAsync<t>() son asíncronos a lo largo de la cadena de llamadas.
Solución: el método Read() de IronOCR es sincrónico. Las llamadas asíncronas existentes funcionan inmediatamente envolviendo la llamada sincrónica en Task.Run:
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
return await Task.Run(() =>
{
var result = new IronTesseract().Read(filePath);
return Regex.Match(result.Text,
@"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups[1].Value;
});
}
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks
' Existing async signature preserved for callers
Public Async Function ExtractInvoiceNumberAsync(filePath As String) As Task(Of String)
Return Await Task.Run(Function()
Dim result = New IronTesseract().Read(filePath)
Return Regex.Match(result.Text,
"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
RegexOptions.IgnoreCase).Groups(1).Value
End Function)
End Function
Para los escenarios de alto rendimiento de ASP.NET, revise la guía de OCR asíncrono antes de decidir entre envolturas Task.Run y el camino asíncrono nativo.
Problema 3: La precisión de la extracción disminuye en escaneos de baja calidad.
Mindee: El preprocesamiento de Mindeees interno y automático. Los problemas de calidad del escaneo reducen las puntuaciones de confianza del campo sin que el desarrollador pueda intervenir antes de volver a enviarlo.
Solución: Aplicar el proceso de preprocesamiento de IronOCR antes del reconocimiento. La combinación de Deskew() y DeNoise() recupera la mayor parte de la precisión en los defectos típicos de escaneo en cama plana vistos en los flujos de trabajo de cuentas por pagar:
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast(); // for faded or low-contrast thermal paper receipts
input.Binarize(); // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
Imports IronOcr
Using input As New OcrInput()
input.LoadImage("faded-invoice.jpg")
input.Deskew()
input.DeNoise()
input.Contrast() ' for faded or low-contrast thermal paper receipts
input.Binarize() ' convert to clean black-and-white before recognition
Dim result = New IronTesseract().Read(input)
End Using
Para escaneos gravemente degradados, input.DeepCleanBackgroundNoise() aplica una pasada de eliminación de fondo más intensiva. La guía de corrección de la calidad de imagen documenta los umbrales y el asistente de filtros ayuda a identificar qué filtros mejoran la precisión en una muestra de documento determinada.
Problema 4: Se eliminó la clave API y la configuración de salida de red.
Mindee: La clave de API almacenada en la configuración de la aplicación, las reglas de salida de la red que permiten HTTPS hacia api.mindee.net, y la lógica de reintento envolviendo HttpRequestException son toda la infraestructura necesaria.
Solución: Se eliminan los tres elementos a la vez. La entrada de la clave API de Mindeese ha eliminado de la configuración. Se revoca la regla de salida de red. El bloque try/catch (HttpRequestException) se elimina. La única credencial restante es la clave de licencia de IronOCR, que se establece una sola vez al iniciar el sistema:
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// No rotation schedule, no secure storage beyond standard config secret management
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// No rotation schedule, no secure storage beyond standard config secret management
Número 5: Facturación mediante recuento de páginas en PDF de varias páginas
Mindee: un extracto de proveedor de 12 páginas cargado en Mindeeconsume 12 páginas de la cuota mensual. Con las tarifas de exceso de consumo del nivel Pro, ese único documento genera un costo adicional de $1.20 más allá de la asignación mensual si está cerca del límite.
Solución:IronOCR procesa archivos PDF de varias páginas sin coste por página. Cargue el documento completo e itere las páginas:
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf"); // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);
foreach (var page in result.Pages)
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
Imports IronOcr
Using input As New OcrInput()
input.LoadPdf("vendor-statement.pdf") ' 12 pages — no billing unit increment
Dim result = New IronTesseract().Read(input)
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized")
Next
End Using
Problema 6: La dependencia FinancialDocumentV1 no se puede sustituir con un solo patrón.
Mindee: FinancialDocumentV1 detecta automáticamente el tipo de documento y extrae campos sin que el código llamado deba ramificarse por tipo de documento.
Solución: Crear un detector ligero que utilice la presencia de palabras clave. Los documentos financieros se dividen claramente en facturas (que contienen "Factura", "Facturar a" o "Fecha de vencimiento") y recibos (que contienen "Recibo", "Gracias" o carecen de los marcadores de factura). Dos métodos de extracción, Plus con un detector de tres líneas, reemplazan la llamada a la API de Mindeesin sacrificar la precisión en documentos bien formados:
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
var result = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
? ExtractInvoiceFields(result)
: ExtractReceiptFields(result);
Dim result = _ocr.Read(filePath)
Dim summary = If(result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0, ExtractInvoiceFields(result), ExtractReceiptFields(result))
Lista de verificación de migración de Mindee
Pre-Migración
Auditar todo el uso de Mindeeen todo el código fuente:
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
Resultados del inventario:
- Cuenta sitios de llamada únicos usando
ParseAsync<t> - Nota qué tipos de documentos se usan (
InvoiceV4,ReceiptV5,PassportV1,FinancialDocumentV1) - Identificar todas las cadenas de métodos asíncronos que se propagan desde las llamadas a Mindee.
- Localice las referencias a las claves API en los archivos de configuración y en los almacenes de secretos.
- Identificar la lógica de reintento que encapsula las llamadas de red de Mindee
- Identificar las reglas de salida de red que permiten el tráfico saliente hacia los puntos finales de Mindee.
Migración de código
- Elimina el paquete NuGet
Mindeedel archivo del proyecto - Ejecuta
dotnet add package IronOcrpara instalar IronOCR - Agrega
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"al inicio de la aplicación - Elimina todas las directivas
using Mindee,using Mindee.Input, yusing Mindee.Product.* - Reemplaza la inyección de constructor
MindeeClientcon la instanciación o registro DIIronTesseract - Reemplaza cada llamada
ParseAsync<InvoiceV4>con_ocr.Read(filePath)más métodos de extracción de facturas - Reemplaza cada llamada
ParseAsync<ReceiptV5>con_ocr.Read(filePath)más métodos de extracción de recibos - Reemplaza cada llamada
ParseAsync<PassportV1>con OCR basado en zonas usandoCropRectangleen el área MRZ - Reemplaza cada llamada
ParseAsync<FinancialDocumentV1>con el detector de tipo de documento + patrón de enrutamiento - Elimina todas las líneas de cumplimiento de límite de tasa
await Task.Delay(...) - Elimina todos los bloques
catch (MindeeException)ycatch (HttpRequestException)de los caminos de OCR - Añade llamadas de preprocesamiento
OcrInput(Deskew,DeNoise,Contrast) para caminos de documentos escaneados - Añade
result.SaveAsSearchablePdf(archivePath)a cualquier camino de archivo de documentos - Elimine la clave API de Mindeede todos los archivos de configuración y bóvedas de secretos.
- Actualizar las pruebas de integración para que se ejecuten de forma síncrona sin simulación de red.
Posmigración
- Verificar la precisión de la extracción comparándola con un conjunto de muestra de 20 a 30 documentos que abarque cada tipo de documento.
- Confirma valores
result.Confidencepor encima de 80 en escaneos limpios representativos; aplicar preprocesamiento si se indica a continuación - Prueba la tubería de preprocesamiento (
Deskew,DeNoise) en muestras de escaneos sesgados y degradados - Verifica que PDFs de varias páginas produzcan
result.Pages.Countcorrectos y contenido por página - Confirma que la salida
result.SaveAsSearchablePdf()sea buscable en Adobe Acrobat y visores de PDF del sistema - Prueba toda la extracción basada en zonas (
CropRectangle) contra las variaciones de diseño de documentos en tu conjunto de proveedores - Ejecuta procesamiento por lotes sin llamadas
Task.Delayy verifica que no haya problemas de subprocesos - Confirme que la aplicación se inicia y funciona correctamente sin acceso a Internet saliente.
- Verifica que la inicialización de la clave de licencia se ejecute antes de la primera llamada
_ocr.Read()en cada punto de entrada - Compruebe que no queden referencias a claves API de Mindeeen la configuración, variables de entorno o secretos.
Principales ventajas de migrar a IronOCR
Control total de los datos de los documentos financieros. Tras la migración, los códigos IBAN de los proveedores, los números de ruta, los números EIN de los clientes y los detalles de las partidas permanecen siempre dentro de su infraestructura. El alcance del cumplimiento normativo abarca únicamente a su organización. Los acuerdos de procesamiento con proveedores externos de IA se eliminan de su registro de auditoría. Los equipos que operan bajo los requisitos de GLBA, CMMC, FedRAMP o residencia de datos pueden procesar documentos financieros sin la revisión de cumplimiento que requiere la transmisión a la nube externa.
Costo predecible sin importar el volumen. La licencia Lite de IronOCR en $999 cubre un desarrollador con procesamiento de documentos ilimitado. Pasar de 500 facturas al mes a 50.000 facturas al mes no supone ningún coste adicional. Los picos de procesamiento de fin de año, las ejecuciones de corrección por lotes y los ciclos de pruebas de desarrollo no incrementan ningún contador de facturación. El coste total de propiedad durante tres años para un equipo que procesa 5.000 páginas al mes se reduce de aproximadamente 17.964 dólares (Mindee Pro) a entre 1.499 y 2.999 dólares (licencia perpetua de IronOCR). Consulte la página completa del producto IronOCR para obtener detalles sobre los diferentes niveles.
La lógica de extracción es de su propiedad de forma permanente. Los patrones de extracción de IronOCR son código C# simple en su repositorio. Son sistemas con control de versiones, revisables, comprobables e implementables independientemente del calendario de lanzamientos o las decisiones de versionado de la API de cualquier proveedor externo. Cuando Mindeelance InvoiceV5 y descontinúe InvoiceV4, eso es una fecha límite de migración impuesta por el proveedor. Cuando mantienes patrones de extracción, la mejora es git commit.
Control de preprocesamiento para una calidad de escaneo en condiciones reales. Las operaciones de cuentas por pagar reciben facturas de docenas o cientos de proveedores, cada una escaneada con equipos diferentes y configuraciones distintas. La tubería de preprocesamiento de IronOCR— Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — aborda los defectos específicos en cada categoría de escaneo. Una fotografía de un recibo tomada con impresora térmica presenta defectos diferentes a los de una factura de varias páginas escaneada con escáner plano; se aplican los filtros adecuados para cada caso. El preprocesamiento de Mindeees invisible y no configurable. Consulte la página de funciones de preprocesamiento para ver el catálogo completo de filtros.
Archivado de PDF buscable en un solo paso. Cada factura procesada a través de IronOCR puede archivarse como un PDF buscable con result.SaveAsSearchablePdf(). La capa de texto reconocida está integrada en el archivo de salida, lo que permite realizar búsquedas de texto completo de cada palabra en sistemas de gestión documental, bibliotecas de SharePoint y repositorios de contenido Enterprise . Mindeeno proporciona ningún tipo de salida en formato PDF; Anteriormente, el archivado con capacidad de búsqueda requería una biblioteca independiente, una fase de procesamiento independiente y un mantenimiento adicional. Después de la migración, la extracción y el archivado se colapsan en una sola llamada _ocr.Read(input).
Implementación en cualquier entorno sin cambios de arquitectura.IronOCR se implementa en Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda y Google Cloud Run utilizando el mismo paquete NuGet y la misma inicialización. Los entornos aislados, los sistemas de las plantas de producción y las instalaciones gubernamentales de alta seguridad funcionan sin necesidad de modificaciones. La guía de implementación de Docker , la guía de Azure y la guía de AWS cubren la configuración específica del entorno para cada plataforma.
Preguntas Frecuentes
¿Por qué debería migrar de Mindee OCR API a IronOCR?
Los impulsores comunes incluyen la eliminación de la complejidad de la interoperabilidad COM, la sustitución de la gestión de licencias basada en archivos, la evitación de la facturación por página, la habilitación de la implementación de Docker/contenedores y la adopción de un flujo de trabajo nativo de NuGet que se integre con las herramientas .NET estándar.
¿Cuáles son los principales cambios de código al migrar de Mindee OCR API a IronOCR?
Sustituya las secuencias de inicialización de Mindee por la instanciación de IronTesseract, elimine la gestión del ciclo de vida COM (patrones explícitos Create/Load/Close) y actualice los nombres de las propiedades de los resultados. El resultado es un número significativamente menor de líneas repetitivas.
¿Cómo instalo IronOCR para comenzar la migración?
Ejecute 'Install-Package IronOcr' en la consola del gestor de paquetes o 'dotnet add package IronOcr' en la CLI. Los paquetes de idiomas son paquetes independientes: 'dotnet add package IronOcr.Languages.French' para el francés, por ejemplo.
¿IronOCR alcanza la precisión de OCR de la API de OCR de Mindee para documentos comerciales estándar?
IronOCR consigue una gran precisión para contenido empresarial estándar, como facturas, contratos, recibos y formularios mecanografiados. Los filtros de preprocesamiento de imágenes (eliminación de distorsiones, eliminación de ruido, mejora del contraste) mejoran aún más el reconocimiento en entradas degradadas.
¿Cómo maneja IronOCR los datos de idioma que la API Mindee OCR instala por separado?
Los datos de idiomas en IronOCR se distribuyen como paquetes NuGet. dotnet add package IronOcr.Languages.German' instala la compatibilidad con el alemán. No es necesario colocar manualmente los archivos ni las rutas de los directorios.
¿La migración de Mindee OCR API a IronOCR requiere cambios en la infraestructura de implementación?
IronOCR requiere menos cambios de infraestructura que Mindee OCR API. No hay rutas binarias del SDK, ubicaciones de archivos de licencia ni configuraciones del servidor de licencias. El paquete NuGet contiene el motor OCR completo, y la clave de licencia es una cadena establecida en el código de la aplicación.
¿Cómo configuro las licencias de IronOCR después de la migración?
Asigne IronOcr.License.LicenseKey = "YOUR-KEY" en el código de inicio de la aplicación. En Docker o Kubernetes, almacene la clave como una variable de entorno y léala en el inicio. Utilice License.IsValidLicense para validar antes de aceptar tráfico.
¿Puede IronOCR procesar archivos PDF del mismo modo que Mindee?
Sí, IronOCR lee PDF nativos y escaneados. Instancie IronTesseract, llame a ocr.Read(input) donde input es una ruta PDF u OcrPdfInput, e itere las páginas OcrResult. No es necesario un proceso de renderizado de PDF independiente.
¿Cómo gestiona IronOCR los hilos en el procesamiento de grandes volúmenes?
IronTesseract puede instanciarse de forma segura por subproceso. Gire una instancia por hilo en un Parallel.ForEach o Task pool, ejecute OCR concurrentemente, y disponga de cada instancia cuando termine. No se requiere estado global o bloqueo.
¿Qué formatos de salida admite IronOCR tras la extracción de texto?
IronOCR devuelve resultados estructurados que incluyen texto, coordenadas de palabras, puntuaciones de confianza y estructura de páginas. Las opciones de exportación incluyen texto sin formato, PDF con opción de búsqueda y objetos de resultados estructurados para su procesamiento posterior.
¿Es el precio de IronOCR más predecible que el de Mindee OCR API para escalar cargas de trabajo?
IronOCR utiliza licencias perpetuas de tarifa plana sin cargos por página o volumen. Tanto si procesa 10.000 como 10 millones de páginas, el coste de la licencia permanece constante. Las opciones de licencias por volumen y por equipo se encuentran en la página de precios de IronOCR.
¿Qué sucede con mis pruebas existentes después de migrar de Mindee OCR API a IronOCR?
Las pruebas que validan el contenido de texto extraído deben seguir superándose tras la migración. Las pruebas que validan patrones de llamada a API o el ciclo de vida de objetos COM deberán actualizarse para reflejar el modelo de inicialización y resultados más sencillo de IronOCR.

