Passer au contenu du pied de page
VIDéOS

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
$vbLabelText   $csharpLabel
// 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
$vbLabelText   $csharpLabel

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
SHELL

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
$vbLabelText   $csharpLabel

É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"
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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))
$vbLabelText   $csharpLabel

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" .
SHELL

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

  1. Supprimez le package NuGet Mindee du fichier du projet
  2. Exécutez dotnet add package IronOcr pour installer IronOCR
  3. Ajoutez IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" au démarrage de l'application
  4. Supprimez toutes les directives using Mindee, using Mindee.Input et using Mindee.Product.*
  5. Remplacez l'injection de constructeur MindeeClient par l'instanciation IronTesseract ou l'enregistrement DI
  6. Remplacez chaque appel ParseAsync<InvoiceV4> par _ocr.Read(filePath) plus des méthodes d'extraction de factures
  7. Remplacez chaque appel ParseAsync<ReceiptV5> par _ocr.Read(filePath) plus des méthodes d'extraction de reçus
  8. Remplacez chaque appel ParseAsync<PassportV1> par de l'OCR basé sur des zones utilisant CropRectangle dans la zone MRZ
  9. Remplacez chaque appel ParseAsync<FinancialDocumentV1> par le détecteur de type de document + modèle de routage
  10. Supprimez toutes les lignes de conformité de limite de taux await Task.Delay(...)
  11. Supprimez tous les blocs catch (MindeeException) et catch (HttpRequestException) des chemins OCR
  12. Ajoutez des appels de prétraitement OcrInput (Deskew, DeNoise, Contrast) pour les chemins de documents numérisés
  13. Ajoutez result.SaveAsSearchablePdf(archivePath) à tous les chemins d'archivage de documents
  14. Supprimez la clé API Mindeede tous les fichiers de configuration et coffres-forts de secrets.
  15. 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.Confidence supé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.Count correct 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.Delay et 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.

Veuillez noterAdobe Acrobat, Mindee, PDFSharp, Tesseract et iText sont des marques déposées de leurs propriétaires respectifs. Ce site n'est affilié à, endossé par ou sponsorisé par Adobe Inc., Google, Mindee, empira Software GmbH, ou iText Group. Tous les noms de produits, logos et marques sont la propriété de leurs propriétaires respectifs. Les comparaisons sont à titre informatif uniquement et reflètent les informations publiquement disponibles au moment de l'écriture.

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.

Kannaopat Udonpant
Ingénieur logiciel
Avant de devenir ingénieur logiciel, Kannapat a obtenu un doctorat en ressources environnementales à l'université d'Hokkaido au Japon. Pendant qu'il poursuivait son diplôme, Kannapat est également devenu membre du laboratoire de robotique de véhicules, qui fait partie du département de bioproduction. En 2022, il a utilisé ses compé...
Lire la suite

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi