Comment utiliser Async et Multithreading en C#
La migration de Mindeevers IronOCR déplace le traitement des documents des serveurs cloud externes vers votre propre infrastructure, éliminant ainsi la facturation par page, la transmission de données à des tiers et la disponibilité du réseau comme dépendance d'exécution. Les sections ci-dessous traitent du remplacement des packages, des mises à jour des espaces de noms et de quatre scénarios pratiques de code avant et après : extraction de la zone de facturation, traitement des reçus PDF multipages, prétraitement des documents financiers et archivage PDF consultable.
Pourquoi migrer depuis Mindee?
Mindee résout un problème réel avec élégance : soumettre un document, recevoir des champs JSON structurés. Pour les équipes chez qui la transmission de documents financiers à une API externe est acceptable et où le volume reste modéré, elle offre des résultats rapides. Le déclencheur de la migration survient généralement lorsqu'une des conditions suivantes change.
Les données financières franchissent une limite de conformité. Mindee's ParseAsync<InvoiceV4> télécharge le document complet sur des serveurs externes. Les codes IBAN, les numéros de routage, les numéros EIN des fournisseurs et les lignes de commande détaillées transitent par Internet et sont traités sur du matériel hors de votre contrôle. La certification SOC 2 Type II confirme que Mindeesuit les pratiques de sécurité ; Il ne conserve pas vos documents à l'intérieur de votre périmètre de sécurité. Lorsqu'un contrôle de conformité, un nouveau contrat client ou une réglementation sur la résidence des données impose le traitement par un tiers dans le cloud, l'architecture de Mindeedevient disqualifiante quelle que soit la précision de son extraction.
Le coût par page est dégressif. La formule Starter offre 1 000 pages par mois à 49 $/mois. Le forfait Pro offre 5 000 pages par mois à 499 $/mois. Les fichiers PDF multipages comptent chaque page. Les sessions de développement sont comptabilisées dans le quota. Une équipe traitant 4 000 factures par mois paie 499 $/mois — 5 988 $/an — ce chiffre étant réinitialisé chaque année et augmentant en fonction de la croissance du volume. À partir de 24 000 pages par mois, vous négociez les tarifs Enterprise . La licence perpétuelle d'IronOCR à 1 499 $ (niveau Professional ) couvre un volume de documents illimité et est amortie par rapport à MindeePro en moins de quatre mois.
L'interrogation asynchrone se propage à travers votre pile. Chaque opération Mindeeest asynchrone car chaque opération correspond à une requête réseau. Cette chaîne asynchrone obligatoire se répercute en cascade à travers les contrôleurs, les couches de service et les classes de travail. Lorsque le réseau est indisponible ou que le service de Mindeesubit une panne, la cascade entraîne une défaillance sans solution de repli locale. IronOCR traite de manière synchrone — travail en CPU local — et s'enveloppe dans Task.Run là où des interfaces asynchrones sont requises pour la cohérence architecturale.
Le catalogue API pré-construit a une limite maximale. Mindeecouvre InvoiceV4, ReceiptV5, PassportV1, et un petit catalogue d'autres types de documents. Tout type de document hors de cette liste nécessite une formation sur modèle personnalisé du plan Enterprise : collecte d'échantillons, étiquetage, temps de formation, et délai avant que l'API ne soit disponible. Chaque nouveau type de document dont votre entreprise a besoin nécessite une intervention distincte de la part d'un fournisseur. IronOCR traite tout type de document en utilisant le même appel IronTesseract().Read(); L'ajout d'un nouveau type de document implique la rédaction de modèles d'extraction, et non la négociation d'un contrat de formation.
La gestion des clés API augmente la surface d'opérations. Les identifiants Mindeedoivent être stockés de manière sécurisée côté serveur, renouvelés périodiquement et protégés contre toute divulgation. Les règles de sortie du réseau doivent autoriser le trafic HTTPS sortant vers les points de terminaison Mindee. La logique de nouvelle tentative doit gérer HttpRequestException et MindeeException des échecs de réseau inévitables. La suppression de Mindeesupprime toute cette surface opérationnelle : plus d'identifiants, plus de règles de sortie, plus de mécanismes de nouvelle tentative autour des E/S réseau.
Les environnements isolés et à accès restreint sont exclus. Les entreprises travaillant pour le gouvernement, les sous-traitants de la défense, les réseaux de santé soumis à des contrôles stricts des connexions Internet sortantes et les systèmes industriels déployés dans des installations sans connectivité Internet fiable ne peuvent pas utiliser Mindeeconformément à sa conception. IronOCR fonctionne de manière identique dans les conteneurs Docker sans accès sortant, dans Azure Functions avec restrictions de sortie et dans les environnements totalement isolés du réseau. Aucune dépendance au cloud n'est requise lors de l'exécution. Consultez la page de licence IronOCR pour plus de détails sur les niveaux de déploiement.
Le problème fondamental
Chaque document financier traité par Mindeefranchit une frontière de réseau que vous ne contrôlez pas :
// 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 : Comparaison des fonctionnalités
Le tableau suivant récapitule les différences architecturales et de fonctionnalités qui déterminent les décisions de migration.
| Fonction | Mindee | IronOCR |
|---|---|---|
| Lieu de traitement | Serveurs cloud Mindee | Machine locale / votre infrastructure |
| Internet requis lors de l'exécution | Toujours | Jamais |
| transmission de données | Document complet téléchargé par appel | None |
| Coût par document | Oui (par page, selon le forfait) | Non (licence perpétuelle) |
| Prix initial | 49 $/mois (1 000 pages) | $999 une seule fois (Lite) |
| Assistance hors ligne / isolée du réseau | Non pris en charge | Prise en charge complète |
| Déploiement sur site | Non disponible | Seule option |
| Analyse des factures | API structurée pré-construite (InvoiceV4) |
Extraction par reconnaissance optique de caractères (OCR) et par région ou par expression régulière |
| Analyse des reçus | API structurée pré-construite (ReceiptV5) |
Modèles d'extraction OCR + |
| Types de documents arbitraires | Non pris en charge sans formation personnalisée | Assistance complète, tout document |
| Formation personnalisée sur modèle | Plan Enterprise + délai de préavis | Non requis |
| Modèle d'appel API | Asynchrone obligatoire (E/S réseau) | Synchrone (asynchrone optionnel) |
| limitation de débit | En fonction du plan | None |
| Entrée PDF | Oui | Oui (natif, sans conversion) |
| Traitement de PDF multipages | Oui | Oui |
| Sortie PDF consultable | Non | Oui (result.SaveAsSearchablePdf()) |
| Prétraitement des images | Aucun exposé | Redresser, réduire le bruit, contraster, binariser, accentuer, mettre à l'échelle |
| Extraction basée sur la région | Coordonnées du champ en réponse | CropRectangle en entrée |
| Plus de 125 langues prises en charge | Limité | Oui (intégré, sans gestion de tessdata) |
| Lecture de codes-barres | Non | Oui (en parallèle avec la reconnaissance optique de caractères) |
| Coordonnées de mots structurées | Non | Oui (Pages, Paragraphes, Lignes, Mots) |
| Sécurité des threads | N/A (E/S réseau par appel) | Intégré |
| Multiplateforme | Cloud (indépendant de la plateforme) | Windows, Linux, macOS, Docker, Azure, AWS |
| Compatibilité .NET | .NET Standard 2.0+ | .NET Framework 4.6.2+, .NET 5/6/7/8/9 |
| soutien commercial | Oui | Oui |
Démarrage rapide : Migration de Mindeevers IronOCR
Étape 1 : Remplacer le package NuGet
Supprimez le paquet Mindee :
dotnet remove package Mindee
dotnet remove package Mindee
Installez IronOCR depuis NuGet :
dotnet add package IronOcr
Étape 2 : Mise à jour des espaces de noms
Remplacez le bloc d'espace de noms Mindeepar l'espace de noms 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
Étape 3 : initialisation de la licence
Ajoutez la clé de licence au démarrage de l'application (avant le premier appel OCR) :
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Exemples de migration de code
Extraction des champs de facture à l'aide des zones de documents
Mindee renvoie des champs pré-analysés à des emplacements connus car son modèle côté serveur sait où apparaissent généralement les numéros de facture, les dates et les totaux. L'équivalent IronOCR utilise CropRectangle pour lire des zones spécifiques du document, correspondant à la précision spatiale de l'extraction de Mindeesans transmettre le document.
Approche 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
Approche 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
L'OCR par zone correspond à l'objectif de la détection de champ spatial de Mindee : lire des zones spécifiques du document plutôt que d'appliquer une expression régulière à la page entière. CropRectangle(x, y, width, height) prend en compte les coordonnées pixel, donc l'ajustement nécessite de mesurer par rapport à une facture échantillon représentative. Le guide OCR basé sur les régions couvre la mesure des coordonnées et les stratégies de chevauchement de zone. Pour les factures avec des mises en page variables, combiner l'extraction de zones avec une regex pleine page sur result.Text produit les résultats les plus fiables.
Traitement des notes de frais multipages au format PDF
Mindee accepte un fichier PDF et traite chaque page comme une unité de document distincte, renvoyant un résultat structuré par page. IronOCR lit nativement les PDF multi-pages via OcrInput.LoadPdf(), traitant toutes les pages en un seul appel et retournant result.Pages avec le contenu par page et les coordonnées des mots.
Approche 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
Approche 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
Le traitement d'un rapport de dépenses PDF de 10 pages avec Mindeegénère 10 appels API et 10 pages facturées. IronOCR traite le même fichier en un seul appel local, sans coût par page et sans aller-retour réseau par page. La collection result.Pages fournit du texte par page, des boîtes de délimitation au niveau des mots, et des positions de ligne pour une extraction consciente de la mise en page. Consultez le guide d'entrée PDF pour le chargement de PDF protégés par mot de passe et la sélection de la plage de pages, ainsi que le guide des résultats de lecture structurés pour travailler avec les coordonnées des mots.
Pipeline de prétraitement des factures numérisées
Le traitement cloud de Mindeeapplique sa propre normalisation d'image avant d'effectuer l'extraction de champs. La qualité de cette normalisation est opaque — vous n'avez aucun contrôle dessus et aucune visibilité sur le prétraitement effectué. Lorsqu'une facture scannée présente une distorsion, une ombre ou un faible contraste, Mindeerenvoie des scores de confiance plus faibles sans aucun mécanisme vous permettant d'améliorer la numérisation avant la soumission. IronOCR expose explicitement le pipeline de prétraitement, vous permettant d'appliquer précisément les corrections nécessaires à un document donné avant l'exécution de la reconnaissance.
Approche 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
Approche 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
Le modèle en deux passes — lecture rapide d'abord, prétraitement sur les données à faible confiance — évite la surcharge liée à un prétraitement complet sur les analyses propres. Pour les scénarios de qualité de numérisation les plus courants dans les comptes fournisseurs (scans de plat légèrement inclinés, artefacts de fax, factures photocopiées), Deskew() et DeNoise() ensemble récupèrent la plupart de la précision de reconnaissance. Le guide de correction de la qualité d'image documente chaque filtre disponible et fournit des indications sur les combinaisons les plus adaptées aux différents défauts de numérisation. Le guide de correction de l'orientation de l'image couvre la rotation automatique des documents numérisés à l'envers.
Archivage PDF consultable pour les documents financiers
Mindee ne produit aucun fichier PDF. Son architecture est uniquement basée sur la saisie : soumettre un document, recevoir des champs JSON. Les équipes qui archivent les factures numérisées dans un format consultable doivent gérer ce processus séparément de l'extraction effectuée par Mindee. IronOCR génère des PDF consultables directement à partir de la même passe de reconnaissance utilisée pour l'extraction des champs — sans bibliothèque supplémentaire, sans deuxième étape de traitement.
Approche 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
Approche 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() intègre directement la couche de texte reconnue dans le PDF de sortie, produisant un fichier où chaque mot est sélectionnable et recherchable dans n'importe quel visualiseur PDF ou système de gestion de contenu d'entreprise. L'extraction de champs et l'archivage fonctionnent dans la même passe — un appel _ocr.Read(input) fournit à la fois result.Text pour la correspondance de modèles et result.SaveAsSearchablePdf() pour l'archive. Le guide PDF cherchable couvre les options de sortie y compris l'exportation HOCR et la page d'utilisation du cas OCR PDF couvre les modèles de déploiement en production pour les pipelines d'archivage de documents.
Suppression du point de terminaison personnalisé FinancialDocumentV1
L'API FinancialDocumentV1 de Mindeegère à la fois les factures et les reçus en détectant automatiquement le type de document. Les équipes qui l'utilisent éliminent la logique de branchement au prix d'une plus grande dépendance au cloud — chaque document, quel que soit son type, est téléchargé. Le remplacer par IronOCR utilise des données positionnelles au niveau des mots pour détecter la structure du document et la logique d'extraction des routes sans aucun appel au cloud.
Approche 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
Approche 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 logique de détection du type de document remplace le champ serveur DocumentType de Mindeepar un simple scan de mots-clés contre result.Text. Pour la grande majorité des documents financiers, la présence de la mention " Facture " ou " Facturer à " permet d'identifier sans ambiguïté les factures ; l'absence identifie les reçus. Les données de position au niveau des mots dans result.Pages permettent une détection basée sur la mise en page plus sophistiquée si votre ensemble de documents comprend des cas limites. Le guide des résultats de lecture explique le modèle d'objet complet y compris Words, Lines, et Paragraphs avec leurs coordonnées de délimitation.
Référence de mappage de l'API Mindeevers IronOCR
| Mindee | Équivalent d'IronOCR |
|---|---|
new MindeeClient(apiKey) |
new IronTesseract() — pas de clé API requise |
new LocalInputSource(filePath) |
new OcrInput() + input.LoadImage(filePath) ou ocr.Read(filePath) |
_client.ParseAsync<InvoiceV4>(input) |
_ocr.Read(filePath) + extraction regex sur result.Text |
_client.ParseAsync<ReceiptV5>(input) |
_ocr.Read(filePath) + modèles d'extraction de reçus |
_client.ParseAsync<PassportV1>(input) |
_ocr.Read(filePath) + extraction de la zone MRZ via CropRectangle |
_client.ParseAsync<FinancialDocumentV1>(input) |
_ocr.Read(filePath) + détection du type de document sur 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 |
Ligne du haut de la première page via result.Pages[0].Lines.First() |
prediction.TotalAmount?.Value |
Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})") |
prediction.LineItems |
result.Lines filtré par les modèles regex des articles |
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 (niveau du document) ou word.Confidence (par mot) |
catch (MindeeException ex) |
Aucun équivalent — aucune erreur de réseau au moment de la reconnaissance |
await Task.Delay(100) (limite de taux) |
Non requis — aucune limite de débit |
input.LoadPdf(path) |
ocrInput.LoadPdf(path) — même opération, entièrement locale |
| Clé API dans la configuration | Clé de licence via IronOcr.License.LicenseKey = "..." — pas de rotation nécessaire |
Problèmes de migration courants et solutions
Problème 1 : Les coordonnées du rectangle de recadrage ne correspondent pas à la mise en page du document.
Mindee : Les coordonnées spatiales sont gérées côté serveur. Le modèle Mindees'adapte aux différentes mises en page de factures sans aucune configuration de coordonnées de la part du développeur.
Solution : Mesurez les coordonnées de la zone par rapport à un échantillon représentatif de documents provenant de votre fournisseur. Ouvrez une facture échantillon dans n'importe quel éditeur d'image, lisez les dimensions en pixels et définissez CropRectangle(x, y, width, height) en conséquence. Pour les factures à mise en page variable, lisez d'abord le document complet et utilisez les données de position des mots pour localiser les zones de manière dynamique :
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
L' exemple de reconnaissance optique de caractères (OCR) basé sur les régions illustre ce modèle de zone dynamique dans un exemple fonctionnel complet.
Problème 2 : Les sites d'appels asynchrones cessent de fonctionner après la suppression de Mindee
Mindee : L'intégralité de l'interface API de Mindeeest asynchrone car il s'agit d'entrées/sorties réseau. Les contrôleurs et méthodes de service construits sur await _client.ParseAsync<t>() sont asynchrones tout au long de la chaîne d'appel.
Solution : La méthode Read() de IronOCR est synchrone. Les sites d'appel asynchrones existants fonctionnent immédiatement en enveloppant l'appel synchrone dans 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
Pour les scénarios ASP.NET à haut débit, consultez le guide OCR asynchrone avant de choisir entre les enveloppes Task.Run et le chemin asynchrone natif.
Problème n° 3 : La précision d'extraction diminue sur les numérisations de faible qualité
Mindee : Le prétraitement de Mindeeest interne et automatique. Les problèmes de qualité de numérisation réduisent les scores de confiance sur le terrain, sans possibilité d'intervention du développeur avant la nouvelle soumission.
Solution : Appliquer le pipeline de prétraitement d'IronOCR avant la reconnaissance. La combinaison de Deskew() et DeNoise() récupère la plupart de la précision des défauts de numérisation de plat typiques observés dans les flux de travail des comptes fournisseurs :
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
Pour les numérisations sévèrement dégradées, input.DeepCleanBackgroundNoise() applique un passage de suppression de fond plus lourd. Le guide de correction de la qualité d'image documente les seuils et l' assistant de filtrage aide à identifier les filtres qui améliorent la précision sur un échantillon de document donné.
Problème 4 : Suppression de la configuration de la clé API et du trafic réseau sortant
Mindee : La clé API stockée dans les paramètres de l'application, les règles de sortie réseau permettant les appels HTTPS sortants vers api.mindee.net, et la logique de nouvelle tentative enveloppant HttpRequestException sont toutes des infrastructures requises.
Solution : Les trois sont retirés simultanément. L'entrée de clé API Mindeea été supprimée de la configuration. La règle de sortie du réseau est révoquée. Le bloc try/catch (HttpRequestException) est supprimé. Le seul identifiant restant est la clé de licence IronOCR , définie une seule fois au démarrage :
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// Non 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"];
// Non rotation schedule, no secure storage beyond standard config secret management
' appsettings.json: remove "Mindee:ApiKey"
' Firewall: revoke egress rule for api.mindee.net
' Startup.vb or Program.vb:
IronOcr.License.LicenseKey = Configuration("IronOcr:LicenseKey")
' Non rotation schedule, no secure storage beyond standard config secret management
Problème n° 5 : Facturation du nombre de pages des PDF multipages
Mindee : une déclaration du fournisseur de 12 pages téléchargée sur Mindeeconsomme 12 pages par rapport au quota mensuel. Avec les tarifs de dépassement du niveau Pro, ce document unique entraîne un coût supplémentaire de 1,20 $ au-delà de l'allocation mensuelle si vous approchez de la limite.
Solution : IronOCR traite les PDF multipages sans coût par page. Chargez le document entier et parcourez les pages.
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
Problème 6 : La dépendance FinancialDocumentV1 ne peut pas être remplacée par un seul modèle
Mindee : FinancialDocumentV1 détecte automatiquement le type de document et extrait les champs sans que le code appelant ne doive se ramifier en fonction du type de document.
Solution : Créer un détecteur léger utilisant la présence de mots-clés. Les documents financiers sont clairement divisés en factures (contenant " Facture ", " Facturé à " ou " Date d'échéance ") et en reçus (contenant " Reçu ", " Merci " ou ne comportant pas les mentions de facture). Deux méthodes d'extraction et un détecteur à trois lignes remplacent l'appel à l'API Mindeesans sacrifier la précision sur les documents bien formés :
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))
Liste de contrôle pour la migration de Mindee
Pré-migration
Auditez l'ensemble des utilisations de Mindeedans le code source :
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" .
Résultats de l'inventaire :
- Comptez les sites d'appel uniques utilisant
ParseAsync<t> - Notez quels types de documents sont utilisés (
InvoiceV4,ReceiptV5,PassportV1,FinancialDocumentV1) - Identifier toutes les chaînes de méthodes asynchrones qui se propagent à partir des appels Mindee
- Localiser les références aux clés API dans les fichiers de configuration et les coffres-forts de secrets
- Identifier la logique de nouvelle tentative encapsulant les appels réseau Mindee
- Identifier les règles de sortie réseau autorisant le trafic sortant vers les points de terminaison de Mindee
Migration de code
- Supprimez le package NuGet
Mindeedu fichier du projet - Exécutez
dotnet add package IronOcrpour installer IronOCR - Ajoutez
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"au démarrage de l'application - Supprimez toutes les directives
using Mindee,using Mindee.Inputetusing Mindee.Product.* - Remplacez l'injection de constructeur
MindeeClientpar l'instanciationIronTesseractou l'enregistrement DI - Remplacez chaque appel
ParseAsync<InvoiceV4>par_ocr.Read(filePath)plus des méthodes d'extraction de factures - Remplacez chaque appel
ParseAsync<ReceiptV5>par_ocr.Read(filePath)plus des méthodes d'extraction de reçus - Remplacez chaque appel
ParseAsync<PassportV1>par de l'OCR basé sur des zones utilisantCropRectangledans la zone MRZ - Remplacez chaque appel
ParseAsync<FinancialDocumentV1>par le détecteur de type de document + modèle de routage - Supprimez toutes les lignes de conformité de limite de taux
await Task.Delay(...) - Supprimez tous les blocs
catch (MindeeException)etcatch (HttpRequestException)des chemins OCR - Ajoutez des appels de prétraitement
OcrInput(Deskew,DeNoise,Contrast) pour les chemins de documents numérisés - Ajoutez
result.SaveAsSearchablePdf(archivePath)à tous les chemins d'archivage de documents - Supprimez la clé API Mindeede tous les fichiers de configuration et coffres-forts de secrets.
- Mettre à jour les tests d'intégration pour qu'ils s'exécutent de manière synchrone sans simulation réseau.
Après la migration
- Vérifier la précision de l'extraction à l'aide d'un échantillon de 20 à 30 documents couvrant chaque type de document.
- Confirmez les valeurs
result.Confidencesupérieures à 80 sur des scans propres représentatifs; appliquer le prétraitement si nécessaire - Testez le pipeline de prétraitement (
Deskew,DeNoise) sur des échantillons de scans inclinés et dégradés - Vérifiez que les PDF multi-pages produisent un
result.Pages.Countcorrect et un contenu par page - Confirmez que la sortie
result.SaveAsSearchablePdf()est recherchable dans Adobe Acrobat et les visualiseurs PDF système - Testez toute l'extraction basée sur les zones (
CropRectangle) par rapport aux variations de mise en page des documents de votre ensemble de fournisseurs - Exécutez le traitement par lots sans appels
Task.Delayet vérifiez qu'il n'y a pas de problèmes de threading - Vérifier que l'application démarre et fonctionne correctement sans accès Internet sortant.
- Vérifiez que l'initialisation de la clé de licence s'exécute avant le premier appel
_ocr.Read()à chaque point d'entrée - Vérifiez qu'aucune référence à une clé API Mindeene subsiste dans la configuration, les variables d'environnement ou les secrets.
Principaux avantages de la migration vers IronOCR
Souveraineté totale des données sur les documents financiers. Après la migration, les codes IBAN des fournisseurs, les numéros de routage, les numéros d'identification fiscale des clients et les lignes de facturation détaillées restent dans votre infrastructure. Le périmètre de conformité ne concerne que votre organisation. Les accords de traitement conclus avec des fournisseurs d'IA tiers sont supprimés de votre journal d'audit. Les équipes opérant sous les normes GLBA, CMMC, FedRAMP ou les exigences de résidence des données peuvent traiter des documents financiers sans le contrôle de conformité qu'exige la transmission externe vers le cloud.
Coût prévisible indépendamment du volume. La licence IronOCR Lite à $999 couvre un développeur avec un traitement de documents illimité. Passer de 500 factures par mois à 50 000 factures par mois n'entraîne aucun coût supplémentaire. Les pics de traitement de fin d'année, les exécutions de correction par lots et les cycles de tests de développement n'incrémentent aucun compteur de facturation. Le coût total de possession sur trois ans pour une équipe traitant 5 000 pages par mois passe d'environ 17 964 $ (Mindee Pro) à 1 499 $–2 999 $ (licence perpétuelle IronOCR ). Consultez la page produit complète IronOCR pour plus de détails sur les différents niveaux de service.
Vous possédez définitivement la logique d'extraction. Les modèles d'extraction IronOCR sont du code C# simple dans votre dépôt. Elles sont versionnées, révisables, testables et déployables indépendamment du calendrier de publication ou des décisions de versionnage d'API de tout fournisseur externe. Lorsque Mindeepublie InvoiceV5 et déprécie InvoiceV4, c'est un délai de migration imposé par le fournisseur. Lorsque vous maintenez des modèles d'extraction, l'amélioration est un git commit.
Contrôle du prétraitement pour une qualité de numérisation optimale. Les services de comptabilité fournisseurs reçoivent des factures de dizaines, voire de centaines de fournisseurs, chacune numérisée avec un équipement différent et selon des paramètres différents. Le pipeline de prétraitement de IronOCR — Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — traite les défauts spécifiques de chaque catégorie de numérisation. Une photographie thermique de reçu présente des défauts différents d'une facture multipage scannée à plat ; vous appliquez les filtres appropriés à chaque cas. Le prétraitement de Mindeeest invisible et non configurable. Consultez la page relative aux fonctionnalités de prétraitement pour obtenir le catalogue complet des filtres.
Archivage PDF cherchable en une étape. Chaque facture traitée par IronOCR peut être archivée comme un PDF cherchable avec result.SaveAsSearchablePdf(). La couche de texte reconnue est intégrée au fichier de sortie, ce qui rend chaque mot consultable en texte intégral dans les systèmes de gestion de documents, les bibliothèques SharePoint et les référentiels de contenu Enterprise . Mindeene fournit aucune sortie PDF ; L'archivage consultable nécessitait auparavant une bibliothèque distincte, une étape de traitement distincte et une maintenance supplémentaire. Après la migration, l'extraction et l'archivage se regroupent en un seul appel _ocr.Read(input).
Déploiement universel sans modification d'architecture. IronOCR se déploie sur Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda et Google Cloud Run à l'aide du même package NuGet et de la même initialisation. Les environnements isolés, les systèmes de planchers d'usine et les installations gouvernementales sécurisées fonctionnent tous sans modification. Le guide de déploiement Docker , le guide Azure et le guide AWS couvrent la configuration spécifique à l'environnement pour chaque plateforme.
Questions Fréquemment Posées
Pourquoi devrais-je migrer de Mindee OCR API vers IronOCR ?
Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.
Quels sont les principaux changements de code lors de la migration de l'API OCR de Mindee vers IronOCR ?
Remplacer les séquences d'initialisation Mindee par l'instanciation IronTesseract, supprimer la gestion du cycle de vie COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.
Comment installer IronOCR pour commencer la migration ?
Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.
IronOCR offre-t-il la même précision d'OCR que Mindee OCR API pour les documents commerciaux standard ?
IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.
Comment IronOCR gère-t-il les données linguistiques que Mindee OCR API installe séparément ?
Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.
La migration de l'API OCR de Mindee vers IronOCR nécessite-t-elle des modifications de l'infrastructure de déploiement ?
IronOcr nécessite moins de changements d'infrastructure que l'API OCR de Mindee. Il n'y a pas de chemins binaires SDK, de placements de fichiers de licence ou de configurations de serveurs de licence. Le package NuGet contient le moteur d'OCR complet, et la clé de licence est une chaîne de caractères définie dans le code de l'application.
Comment configurer les licences IronOCR après la migration ?
Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.
IronOcr peut-il traiter les PDF de la même manière que Mindee ?
Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.
Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?
IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.
Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?
IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.
La tarification d'IronOCR est-elle plus prévisible que celle de l'API OCR de Mindee pour la mise à l'échelle des charges de travail ?
IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.
Qu'advient-il de mes tests existants après la migration de Mindee OCR API vers IronOCR ?
Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

