Zum Fußzeileninhalt springen
VIDEOS

Umstellung von AWS Textract auf IronOCR

Dieser Leitfaden führt Sie durch eine vollständige Migration von AWS Textractzu IronOCR for .NET Entwickler, die eine lokale Dokumentenverarbeitung ohne Cloud-Abhängigkeiten benötigen. Es umfasst die Entfernung der Anmeldeinformationen, die Beseitigung der asynchronen Pipeline, die Eliminierung von S3 und die spezifischen Codeersetzungen, die erforderlich sind, um jede Textract-Operation in ihr IronOCR Äquivalent zu überführen.

Warum von AWS Textractmigrieren?

AWS Textract ist ein leistungsfähiger Managed Service, aber seine Architektur verursacht Kosten – finanzieller und betrieblicher Art –, die mit dem Wachstum Ihres Dokumenten-Workloads steigen. Teams, die professionelle Dokumentenverarbeitungssysteme entwickeln, stoßen früher oder später auf all diese Reibungspunkte.

Die AWS-Zugangsdateninfrastruktur erschwert jede Bereitstellung. Jede Umgebung, in der Textract-Code ausgeführt wird, benötigt AWS-Zugangsdaten: IAM-Benutzer oder -Rolle, Zugriffsschlüssel und -geheimnis, Regionskonfiguration und – für die PDF-Verarbeitung – zusätzlich S3-Bucket-Berechtigungen. Das sind fünf separate Konfigurationsschritte, bevor die erste Seite verarbeitet wird. Produktionsbereitstellungen erfordern Richtlinien zur Drehung von Anmeldeinformationen, sichere Injektion in Docker-Container oder Kubernetes-Pods und Überwachung für AccessDeniedException, wenn IAM-Richtlinienabweichungen stille Fehler verursachen.IronOCR benötigt eine einzige Zeichenkette: einen Lizenzschlüssel, der einmalig beim Start festgelegt wird.

Preise pro Seite haben keine Obergrenze. Der Basissatz von 0,0015 $ pro Seite für DetectDocumentText ist die Untergrenze, nicht die Kosten. Jedes Dokument mit Tabellen löst AnalyzeDocument bei 0,015 $ pro Seite aus - das Zehnfache der Grundrate. Formulare kosten zusätzlich 0,05 $ pro Seite. Ein gemischter Dokumentenworkflow mit 100.000 Seiten pro Monat und Tabellenextraktion kostet 18.000 US-Dollar pro Jahr, wobei diese Zahl jedes Jahr im Januar zurückgesetzt wird. Die IronOCR Professional -Lizenz kostet einmalig 2.999 US-Dollar. Danach betragen die Kosten pro Seite unabhängig vom Umfang null.

Die asynchrone PDF-Pipeline ist eine Wartungshaftung. Das Verarbeiten eines mehrseitigen PDFs durch Textract erfordert fünf verschiedene Phasen: S3-Upload, StartDocumentTextDetection, Polling-Schleife, Paginierte Ergebnisabfrage und S3-Bereinigung. Jede Phase ist ein unabhängiger Fehlermodus, der eine eigene Fehlerbehandlung, Rückversuchsstrategie und Timeout-Management erfordert. Teams, die diese Pipeline in die Produktion überführen, berichten übereinstimmend, dass sie mehr Zeit für deren Wartung aufwenden als für deren Entwicklung.IronOCR liest eine PDF-Datei mit nur zwei Codezeilen.

Kein Internetzugang bedeutet kein Textract. Docker-Container in isolierten Netzwerksegmenten, On-Premise-Server, abgeschottete Forschungsumgebungen und industrielle Systeme mit Beschränkungen des ausgehenden Datenverkehrs haben alle eine Gemeinsamkeit: Textract ist nicht verfügbar.IronOCR wird als NuGet Paket installiert und funktioniert nach dem ersten Herunterladen des Pakets ohne Netzwerkaufrufe.

Bei jedem Aufruf verlassen Daten Ihre Infrastruktur. Jeder OCR-Vorgang mit Textract überträgt Dokumentinhalte an die Server von Amazon. Für Organisationen im Gesundheitswesen, die PHI verarbeiten, für Rüstungsunternehmen, die CUI handhaben, für Rechtsteams, die privilegierte Kommunikation verarbeiten, und für Finanzinstitute, die Zahlungskartenbilder verarbeiten, handelt es sich hierbei nicht um eine Konfigurationsoption – es handelt sich um eine architektonische Einschränkung, die den Einsatz von Textract in regulierten Umgebungen gänzlich ausschließt.IronOCR verarbeitet Prozesse auf Ihrer Hardware. Es gibt keinen Cloud-Modus zum Deaktivieren; Die lokale Verarbeitung ist der einzige Modus.

Ratenbegrenzungen beschränken den Batch-Durchsatz. Das Standard-StartDocumentTextDetection TPS-Limit beträgt 5 Anfragen pro Sekunde. Batch-Jobs, die Hunderte von Dokumenten verarbeiten, erfordern SemaphoreSlim Drosselung, exponentielles Zurückgehen bei ProvisionedThroughputExceededException und Ratenauffüllungs-Timer. Für die Beantragung einer Erhöhung der Transaktionsanzahl pro Sekunde (TPS) ist ein formeller AWS-Supportfall erforderlich, dessen Ausgang nicht garantiert ist.IronOCR verarbeitet so schnell, wie es die lokale CPU erlaubt - ein 16-Kern-Server verarbeitet 16 Dokumente gleichzeitig mit einem einfachen Parallel.ForEach, keine Dienststufenaushandlung erforderlich.

Das grundsätzliche Problem

Jeder Textract-Einsatz beginnt mit dieser Zeremonie – bevor auch nur eine einzige Seite verarbeitet werden kann:

// Textract: five environment variables, an IAM role, and an S3 bucket
// — all required before the first OCR call

// Environment must have:
//   AWS_ACCESS_KEY_ID=AKIA...
//   AWS_SECRET_ACCESS_KEY=...
//   AWS_DEFAULT_REGION=us-east-1
//   IAM role: textract:DetectDocumentText, textract:AnalyzeDocument
//   IAM role: s3:PutObject, s3:DeleteObject (for any PDF processing)
//   S3 bucket: my-textract-staging-bucket (with lifecycle policy to prevent cost accumulation)

public class TextractOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client; // required for PDFs

    public TextractOcrService()
    {
        // Fails with AmazonClientException if credentials not found in chain
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
    }
}
// Textract: five environment variables, an IAM role, and an S3 bucket
// — all required before the first OCR call

// Environment must have:
//   AWS_ACCESS_KEY_ID=AKIA...
//   AWS_SECRET_ACCESS_KEY=...
//   AWS_DEFAULT_REGION=us-east-1
//   IAM role: textract:DetectDocumentText, textract:AnalyzeDocument
//   IAM role: s3:PutObject, s3:DeleteObject (for any PDF processing)
//   S3 bucket: my-textract-staging-bucket (with lifecycle policy to prevent cost accumulation)

public class TextractOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client; // required for PDFs

    public TextractOcrService()
    {
        // Fails with AmazonClientException if credentials not found in chain
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
    }
}
Imports Amazon
Imports Amazon.Textract
Imports Amazon.S3

' Textract: five environment variables, an IAM role, and an S3 bucket
' — all required before the first OCR call

' Environment must have:
'   AWS_ACCESS_KEY_ID=AKIA...
'   AWS_SECRET_ACCESS_KEY=...
'   AWS_DEFAULT_REGION=us-east-1
'   IAM role: textract:DetectDocumentText, textract:AnalyzeDocument
'   IAM role: s3:PutObject, s3:DeleteObject (for any PDF processing)
'   S3 bucket: my-textract-staging-bucket (with lifecycle policy to prevent cost accumulation)

Public Class TextractOcrService
    Private ReadOnly _textractClient As AmazonTextractClient
    Private ReadOnly _s3Client As AmazonS3Client ' required for PDFs

    Public Sub New()
        ' Fails with AmazonClientException if credentials not found in chain
        _textractClient = New AmazonTextractClient(Amazon.RegionEndpoint.USEast1)
        _s3Client = New AmazonS3Client(Amazon.RegionEndpoint.USEast1)
    End Sub
End Class
$vbLabelText   $csharpLabel

IronOCR ersetzt die gesamte Anmeldeinformationskette durch eine einzige Zuweisung:

// IronOCR: one line, one string, done
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Or from environment (no IAM, no rotation, no S3)
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// IronOCR: one line, one string, done
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Or from environment (no IAM, no rotation, no S3)
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
Imports System

' IronOCR: one line, one string, done
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Or from environment (no IAM, no rotation, no S3)
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
$vbLabelText   $csharpLabel

IronOCR vs. AWS Textract: Funktionsvergleich

Die folgende Tabelle vergleicht die Fähigkeiten beider Bibliotheken anhand der für Migrationsentscheidungen relevantesten Dimensionen.

Feature AWS Textract IronOCR
Bearbeitungsort Amazon Cloud (obligatorisch) Nur lokal / vor Ort
Internet erforderlich Stets Niemals
Einrichtung der Anmeldeinformationen IAM-Benutzer/Rolle + optionaler S3-Bucket Einzelner Lizenzschlüssel
Mehrseitiges PDF Erfordert S3-Staging + asynchronen Job Direkte synchrone input.LoadPdf()
Passwortgeschütztes PDF Nicht unterstützt input.LoadPdf(path, Password: "x")
Multi-Frame TIFF Nicht unterstützt input.LoadImageFrames("multi.tiff")
Asynchrone Abfrage erforderlich Ja (für alle PDFs) Nein – standardmäßig synchron.
Ratengrenzen 5 TPS Standard (StartDocumentTextDetection) Keine – nur CPU-gebunden
Kosten pro Seite 0,0015–0,065 US-Dollar pro Seite 0 € nach Lizenzkauf
Automatische Vorverarbeitung Intern, nicht konfigurierbar Entzerren, Rauschen entfernen, Kontrast erhöhen, Binärisierung, Schärfen, Skalieren
Durchsuchbare PDF-Ausgabe Nicht verfügbar result.SaveAsSearchablePdf()
hOCR-Export Nicht verfügbar result.SaveAsHocrFile()
Barcode-Lesung Nicht verfügbar ocr.Configuration.ReadBarCodes = true
Regionsbasierte OCR Über AnalyzeDocument + Blockbeziehungen CropRectangle(x, y, width, height)
Strukturierte Ergebnisse Flache List<Block> gefiltert nach BlockType Typisierte Pages, Paragraphs, Lines, Words
Unterstützte Sprachen Englischsprachige Vorliebe (weitere auswählen) Über 125 Sprachpakete via NuGet
Mehrsprachige Simultanübertragung Nicht unterstützt OcrLanguage.French + OcrLanguage.German
Air-Gapped-Einsatz Nicht möglich Vollständig unterstützt
HIPAA ohne BAA Nicht möglich Kein externer Prozessor – nur lokal
Docker-Bereitstellung Erfordert die Eingabe von AWS-Anmeldeinformationen Keine Anmeldeinformationen erforderlich – einfaches NuGet Paket
Plattformübergreifend AWS-verwaltet (nur Linux) Windows, Linux, macOS, Docker, Azure, AWS EC2
Lizenzierungsmodell Abrechnung pro Seite (laufende Kosten) Unbefristetes einmaliges ($999 / 1.499 $ / 2.999 $)

Schnellstart: Migration von AWS Textractzu IronOCR

Schritt 1: Ersetzen des NuGet-Pakets

Entfernen Sie die AWS SDK-Pakete:

dotnet remove package AWSSDK.Textract
dotnet remove package AWSSDK.S3
dotnet remove package AWSSDK.Core
dotnet remove package AWSSDK.Textract
dotnet remove package AWSSDK.S3
dotnet remove package AWSSDK.Core
SHELL

Installieren Sie IronOCR über NuGet :

dotnet add package IronOcr

Schritt 2: Namespaces aktualisieren

Ersetzen Sie alle AWS-Namespace-Importe durch den einzelnen IronOCR Namespace:

// Before (AWS Textract)
using Amazon.Textract;
using Amazon.Textract.Model;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Runtime;

// After (IronOCR)
using IronOcr;
// Before (AWS Textract)
using Amazon.Textract;
using Amazon.Textract.Model;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Runtime;

// After (IronOCR)
using IronOcr;
Imports Amazon.Textract
Imports Amazon.Textract.Model
Imports Amazon.S3
Imports Amazon.S3.Model
Imports Amazon.Runtime

Imports IronOcr
$vbLabelText   $csharpLabel

Schritt 3: Lizenz initialisieren

Fügen Sie die Lizenzinitialisierung einmalig beim Anwendungsstart hinzu. Entfernen Sie alle eingerichteten AWS-Anmeldeinformationsumgebungsvariablen.

// Remove from startup:
//   AWS_ACCESS_KEY_ID environment variable
//   AWS_SECRET_ACCESS_KEY environment variable
//   AWS_DEFAULT_REGION environment variable
//   ~/.aws/credentials file entries
//   IAM role bindings on the execution context

// Add instead:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Remove from startup:
//   AWS_ACCESS_KEY_ID environment variable
//   AWS_SECRET_ACCESS_KEY environment variable
//   AWS_DEFAULT_REGION environment variable
//   ~/.aws/credentials file entries
//   IAM role bindings on the execution context

// Add instead:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
' Remove from startup:
'   AWS_ACCESS_KEY_ID environment variable
'   AWS_SECRET_ACCESS_KEY environment variable
'   AWS_DEFAULT_REGION environment variable
'   ~/.aws/credentials file entries
'   IAM role bindings on the execution context

' Add instead:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

Beispiele für die Code-Migration

Ersetzen des AmazonTextractClient-Konstruktors und der IAM-Konfiguration

Die sichtbarste Änderung bei der Migration ist die Clientinitialisierung. Textract benötigt einen Client mit Dependency Injection und zugrundeliegender Credential Resolution – das heißt, sowohl der Client als auch alle seine Abhängigkeiten müssen in jeder Bereitstellungsumgebung vorkonfiguriert sein. Jede Fehlkonfiguration schlägt leise fehl, bis der erste OCR-Aufruf AmazonClientException auslöst.

AWS Textract-Ansatz:

// Requires: NuGet AWSSDK.Textract, AWSSDK.S3
// Requires: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION in environment
// Requires: IAM policy with textract:* and s3:PutObject, s3:DeleteObject

using Amazon.S3;
using Amazon.Textract;

public class DocumentOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client;
    private readonly string _stagingBucket;

    public DocumentOcrService(IConfiguration config)
    {
        // Credential chain: environment → ~/.aws/credentials → EC2 instance profile
        // Fails if none found — runtime exception, not compile-time
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
        _stagingBucket = config["Textract:StagingBucket"]; // bucket must exist and have permissions
    }

    public async Task<string> ReadImageAsync(string imagePath)
    {
        var bytes = File.ReadAllBytes(imagePath);
        var response = await _textractClient.DetectDocumentTextAsync(
            new DetectDocumentTextRequest
            {
                Document = new Document { Bytes = new MemoryStream(bytes) }
            });

        return string.Join("\n", response.Blocks
            .Where(b => b.BlockType == BlockType.LINE)
            .Select(b => b.Text));
    }
}
// Requires: NuGet AWSSDK.Textract, AWSSDK.S3
// Requires: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION in environment
// Requires: IAM policy with textract:* and s3:PutObject, s3:DeleteObject

using Amazon.S3;
using Amazon.Textract;

public class DocumentOcrService
{
    private readonly AmazonTextractClient _textractClient;
    private readonly AmazonS3Client _s3Client;
    private readonly string _stagingBucket;

    public DocumentOcrService(IConfiguration config)
    {
        // Credential chain: environment → ~/.aws/credentials → EC2 instance profile
        // Fails if none found — runtime exception, not compile-time
        _textractClient = new AmazonTextractClient(Amazon.RegionEndpoint.USEast1);
        _s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
        _stagingBucket = config["Textract:StagingBucket"]; // bucket must exist and have permissions
    }

    public async Task<string> ReadImageAsync(string imagePath)
    {
        var bytes = File.ReadAllBytes(imagePath);
        var response = await _textractClient.DetectDocumentTextAsync(
            new DetectDocumentTextRequest
            {
                Document = new Document { Bytes = new MemoryStream(bytes) }
            });

        return string.Join("\n", response.Blocks
            .Where(b => b.BlockType == BlockType.LINE)
            .Select(b => b.Text));
    }
}
Imports Amazon.S3
Imports Amazon.Textract
Imports System.IO
Imports System.Threading.Tasks
Imports Microsoft.Extensions.Configuration

Public Class DocumentOcrService
    Private ReadOnly _textractClient As AmazonTextractClient
    Private ReadOnly _s3Client As AmazonS3Client
    Private ReadOnly _stagingBucket As String

    Public Sub New(config As IConfiguration)
        ' Credential chain: environment → ~/.aws/credentials → EC2 instance profile
        ' Fails if none found — runtime exception, not compile-time
        _textractClient = New AmazonTextractClient(Amazon.RegionEndpoint.USEast1)
        _s3Client = New AmazonS3Client(Amazon.RegionEndpoint.USEast1)
        _stagingBucket = config("Textract:StagingBucket") ' bucket must exist and have permissions
    End Sub

    Public Async Function ReadImageAsync(imagePath As String) As Task(Of String)
        Dim bytes = File.ReadAllBytes(imagePath)
        Dim response = Await _textractClient.DetectDocumentTextAsync(
            New DetectDocumentTextRequest With {
                .Document = New Document With {.Bytes = New MemoryStream(bytes)}
            })

        Return String.Join(vbCrLf, response.Blocks _
            .Where(Function(b) b.BlockType = BlockType.LINE) _
            .Select(Function(b) b.Text))
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Ansatz:

// Requires: NuGet IronOcr
// Requires: one license key string — nothing else

using IronOcr;

public class DocumentOcrService
{
    private readonly IronTesseract _ocr;

    public DocumentOcrService()
    {
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // or set at Program.cs startup
        _ocr = new IronTesseract();
    }

    public string ReadImage(string imagePath)
    {
        // No credential chain, no region, no bucket — just read
        return _ocr.Read(imagePath).Text;
    }
}
// Requires: NuGet IronOcr
// Requires: one license key string — nothing else

using IronOcr;

public class DocumentOcrService
{
    private readonly IronTesseract _ocr;

    public DocumentOcrService()
    {
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; // or set at Program.cs startup
        _ocr = new IronTesseract();
    }

    public string ReadImage(string imagePath)
    {
        // No credential chain, no region, no bucket — just read
        return _ocr.Read(imagePath).Text;
    }
}
Imports IronOcr

Public Class DocumentOcrService
    Private ReadOnly _ocr As IronTesseract

    Public Sub New()
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" ' or set at Program.vb startup
        _ocr = New IronTesseract()
    End Sub

    Public Function ReadImage(imagePath As String) As String
        ' No credential chain, no region, no bucket — just read
        Return _ocr.Read(imagePath).Text
    End Function
End Class
$vbLabelText   $csharpLabel

Die gesamte AWS-Anmeldeinfrastruktur – IAM-Rollen, Umgebungsvariablen, ~/.aws/credentials-Dateien, S3-Bucket-Lebenszyklusrichtlinien und Regionskonfiguration – wird entfernt. Der DocumentOcrService-Konstruktor wechselt von einer 3-Abhängigkeiten-Injektionsstelle mit Laufzeit-Fehlermodi zu einer einzigen Lizenzzuweisung. Für Details zu den Bereitstellungsmustern siehe den IronTesseract-Einrichtungsleitfaden .

Ersetzen von StartDocumentTextDetection durch TIFF-Mehrbildverarbeitung

Die asynchrone Job-API von Textract (StartDocumentTextDetection) wird für jedes mehrseitige Dokument benötigt. Ein gängiges Muster bei der Digitalisierung von Dokumenten ist der Empfang gescannter Dokumente als Multi-Frame-TIFFs – ein Frame pro gescannter Seite. Textract kann TIFF-Eingaben überhaupt nicht direkt verarbeiten; Das Dokument muss in das PDF-Format konvertiert und in S3 hochgeladen werden, bevor ein Auftrag beginnen kann.IronOCR liest TIFF-Dateien mit mehreren Einzelbildern nativ.

AWS Textract-Ansatz:

// Textract cannot accept TIFF directly — requires PDF conversion + S3 + async job
// This shows the conversion overhead plus the minimum async pipeline

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ProcessScannedTiffAsync(string tiffPath, string s3Bucket)
{
    // Step 0: Convert TIFF to PDF first (Textract does not accept TIFF for async jobs)
    // Requires a separate PDF conversion library — not shown here
    var pdfPath = Path.ChangeExtension(tiffPath, ".pdf");
    ConvertTiffToPdf(tiffPath, pdfPath); // external dependency required

    // Step 1: Upload converted PDF to S3
    var s3Key = $"staging/{Guid.NewGuid()}.pdf";
    using (var stream = File.OpenRead(pdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = s3Key,
            InputStream = stream
        });
    }

    try
    {
        // Step 2: Start async detection job
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = s3Key }
                }
            });

        // Step 3: Poll every 5 seconds — can block for 30–120 seconds on large files
        GetDocumentTextDetectionResponse pollResp;
        do
        {
            await Task.Delay(5000);
            pollResp = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest { JobId = startResp.JobId });
        } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

        if (pollResp.JobStatus != JobStatus.SUCCEEDED)
            throw new Exception($"Job failed: {pollResp.StatusMessage}");

        // Step 4: Collect paginated results
        var text = new StringBuilder();
        string token = null;
        do
        {
            var page = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest
                {
                    JobId = startResp.JobId,
                    NextToken = token
                });
            foreach (var block in page.Blocks.Where(b => b.BlockType == BlockType.LINE))
                text.AppendLine(block.Text);
            token = page.NextToken;
        } while (token != null);

        return text.ToString();
    }
    finally
    {
        // Step 5: Clean up S3 — orphaned objects accumulate storage costs
        await _s3Client.DeleteObjectAsync(s3Bucket, s3Key);
        File.Delete(pdfPath); // clean up intermediate PDF
    }
}
// Textract cannot accept TIFF directly — requires PDF conversion + S3 + async job
// This shows the conversion overhead plus the minimum async pipeline

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ProcessScannedTiffAsync(string tiffPath, string s3Bucket)
{
    // Step 0: Convert TIFF to PDF first (Textract does not accept TIFF for async jobs)
    // Requires a separate PDF conversion library — not shown here
    var pdfPath = Path.ChangeExtension(tiffPath, ".pdf");
    ConvertTiffToPdf(tiffPath, pdfPath); // external dependency required

    // Step 1: Upload converted PDF to S3
    var s3Key = $"staging/{Guid.NewGuid()}.pdf";
    using (var stream = File.OpenRead(pdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = s3Key,
            InputStream = stream
        });
    }

    try
    {
        // Step 2: Start async detection job
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = s3Key }
                }
            });

        // Step 3: Poll every 5 seconds — can block for 30–120 seconds on large files
        GetDocumentTextDetectionResponse pollResp;
        do
        {
            await Task.Delay(5000);
            pollResp = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest { JobId = startResp.JobId });
        } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

        if (pollResp.JobStatus != JobStatus.SUCCEEDED)
            throw new Exception($"Job failed: {pollResp.StatusMessage}");

        // Step 4: Collect paginated results
        var text = new StringBuilder();
        string token = null;
        do
        {
            var page = await _textractClient.GetDocumentTextDetectionAsync(
                new GetDocumentTextDetectionRequest
                {
                    JobId = startResp.JobId,
                    NextToken = token
                });
            foreach (var block in page.Blocks.Where(b => b.BlockType == BlockType.LINE))
                text.AppendLine(block.Text);
            token = page.NextToken;
        } while (token != null);

        return text.ToString();
    }
    finally
    {
        // Step 5: Clean up S3 — orphaned objects accumulate storage costs
        await _s3Client.DeleteObjectAsync(s3Bucket, s3Key);
        File.Delete(pdfPath); // clean up intermediate PDF
    }
}
Imports Amazon.S3
Imports Amazon.S3.Model
Imports Amazon.Textract
Imports Amazon.Textract.Model
Imports System.IO
Imports System.Text
Imports System.Threading.Tasks

Public Async Function ProcessScannedTiffAsync(tiffPath As String, s3Bucket As String) As Task(Of String)
    ' Step 0: Convert TIFF to PDF first (Textract does not accept TIFF for async jobs)
    ' Requires a separate PDF conversion library — not shown here
    Dim pdfPath = Path.ChangeExtension(tiffPath, ".pdf")
    ConvertTiffToPdf(tiffPath, pdfPath) ' external dependency required

    ' Step 1: Upload converted PDF to S3
    Dim s3Key = $"staging/{Guid.NewGuid()}.pdf"
    Using stream = File.OpenRead(pdfPath)
        Await _s3Client.PutObjectAsync(New PutObjectRequest With {
            .BucketName = s3Bucket,
            .Key = s3Key,
            .InputStream = stream
        })
    End Using

    Try
        ' Step 2: Start async detection job
        Dim startResp = Await _textractClient.StartDocumentTextDetectionAsync(
            New StartDocumentTextDetectionRequest With {
                .DocumentLocation = New DocumentLocation With {
                    .S3Object = New S3Object With {.Bucket = s3Bucket, .Name = s3Key}
                }
            })

        ' Step 3: Poll every 5 seconds — can block for 30–120 seconds on large files
        Dim pollResp As GetDocumentTextDetectionResponse
        Do
            Await Task.Delay(5000)
            pollResp = Await _textractClient.GetDocumentTextDetectionAsync(
                New GetDocumentTextDetectionRequest With {.JobId = startResp.JobId})
        Loop While pollResp.JobStatus = JobStatus.IN_PROGRESS

        If pollResp.JobStatus <> JobStatus.SUCCEEDED Then
            Throw New Exception($"Job failed: {pollResp.StatusMessage}")
        End If

        ' Step 4: Collect paginated results
        Dim text = New StringBuilder()
        Dim token As String = Nothing
        Do
            Dim page = Await _textractClient.GetDocumentTextDetectionAsync(
                New GetDocumentTextDetectionRequest With {
                    .JobId = startResp.JobId,
                    .NextToken = token
                })
            For Each block In page.Blocks.Where(Function(b) b.BlockType = BlockType.LINE)
                text.AppendLine(block.Text)
            Next
            token = page.NextToken
        Loop While token IsNot Nothing

        Return text.ToString()
    Finally
        ' Step 5: Clean up S3 — orphaned objects accumulate storage costs
        Await _s3Client.DeleteObjectAsync(s3Bucket, s3Key)
        File.Delete(pdfPath) ' clean up intermediate PDF
    End Try
End Function
$vbLabelText   $csharpLabel

IronOCR Ansatz:

//IronOCR reads multi-frame TIFF directly — no conversion, no S3, no polling

using IronOcr;

public string ProcessScannedTiff(string tiffPath)
{
    using var input = new OcrInput();
    input.LoadImageFrames(tiffPath); // reads all frames natively

    var ocr = new IronTesseract();
    var result = ocr.Read(input);

    // Access per-page results if needed
    foreach (var page in result.Pages)
        Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words detected");

    return result.Text;
}
//IronOCR reads multi-frame TIFF directly — no conversion, no S3, no polling

using IronOcr;

public string ProcessScannedTiff(string tiffPath)
{
    using var input = new OcrInput();
    input.LoadImageFrames(tiffPath); // reads all frames natively

    var ocr = new IronTesseract();
    var result = ocr.Read(input);

    // Access per-page results if needed
    foreach (var page in result.Pages)
        Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words detected");

    return result.Text;
}
Imports IronOcr

Public Function ProcessScannedTiff(tiffPath As String) As String
    Using input As New OcrInput()
        input.LoadImageFrames(tiffPath) ' reads all frames natively

        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(input)

        ' Access per-page results if needed
        For Each page In result.Pages
            Console.WriteLine($"Page {page.PageNumber}: {page.Words.Length} words detected")
        Next

        Return result.Text
    End Using
End Function
$vbLabelText   $csharpLabel

Der TIFF-zu-PDF-Konvertierungsschritt, der S3-Upload, die asynchrone Abfrageschleife, die paginierte Ergebnisakkumulation und die S3-Bereinigung werden allesamt eliminiert. Die IronOCR-Implementierung liest Frames direkt und bietet seitenweisen Zugriff durch result.Pages ohne Zwischenformatkonvertierung. Der Leitfaden zur TIFF- und GIF-Eingabe behandelt die Auswahl von Einzelbildern für Fälle, in denen nur eine Teilmenge der Einzelbilder benötigt wird.

Ersetzen von S3-Staging durch durchsuchbare PDF-Ausgabe

Ein typischer Anwendungsfall für Textract ist die Umwandlung gescannter PDF-Archive in ein durchsuchbares Format. Der Textract-Ansatz erfordert das Hochladen jeder PDF-Datei auf S3, das Ausführen des asynchronen Jobs, das Sammeln des extrahierten Textes und anschließend die Verwendung einer separaten PDF-Bibliothek, um diesen Text als durchsuchbare Ebene einzubetten.IronOCR führt den Scan durch und erstellt in einem einzigen Aufruf eine durchsuchbare PDF-Datei.

AWS Textract-Ansatz:

// Textract extracts text but cannot produce searchable PDFs
// Requires: S3 upload + async job + separate PDF library to embed the text layer

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ExtractTextForSearchableLayerAsync(
    string scannedPdfPath,
    string s3Bucket)
{
    // Must upload to S3 — cannot pass PDF bytes directly for async jobs
    var key = $"ocr-input/{Guid.NewGuid()}.pdf";

    await using (var fs = File.OpenRead(scannedPdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = key,
            InputStream = fs,
            ContentType = "application/pdf"
        });
    }

    string jobId;
    try
    {
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = key }
                }
            });
        jobId = startResp.JobId;
    }
    catch
    {
        await _s3Client.DeleteObjectAsync(s3Bucket, key);
        throw;
    }

    // Poll — minimum 5s wait; typical 15–60s for a 10-page PDF
    GetDocumentTextDetectionResponse pollResp;
    int pollAttempts = 0;
    const int maxAttempts = 120; // 10 minute timeout

    do
    {
        await Task.Delay(5000);
        pollResp = await _textractClient.GetDocumentTextDetectionAsync(
            new GetDocumentTextDetectionRequest { JobId = jobId });

        if (++pollAttempts >= maxAttempts)
            throw new TimeoutException("Textract job timed out after 10 minutes");

    } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

    await _s3Client.DeleteObjectAsync(s3Bucket, key); // cleanup regardless of outcome

    if (pollResp.JobStatus != JobStatus.SUCCEEDED)
        throw new Exception($"Textract job status: {pollResp.JobStatus} — {pollResp.StatusMessage}");

    // Return extracted text — caller must use a separate library to produce searchable PDF
    return string.Join("\n", pollResp.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .Select(b => b.Text));

    // Caller still needs: iTextSharp, PdfSharp, or similar to embed text layer
}
// Textract extracts text but cannot produce searchable PDFs
// Requires: S3 upload + async job + separate PDF library to embed the text layer

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<string> ExtractTextForSearchableLayerAsync(
    string scannedPdfPath,
    string s3Bucket)
{
    // Must upload to S3 — cannot pass PDF bytes directly for async jobs
    var key = $"ocr-input/{Guid.NewGuid()}.pdf";

    await using (var fs = File.OpenRead(scannedPdfPath))
    {
        await _s3Client.PutObjectAsync(new PutObjectRequest
        {
            BucketName = s3Bucket,
            Key = key,
            InputStream = fs,
            ContentType = "application/pdf"
        });
    }

    string jobId;
    try
    {
        var startResp = await _textractClient.StartDocumentTextDetectionAsync(
            new StartDocumentTextDetectionRequest
            {
                DocumentLocation = new DocumentLocation
                {
                    S3Object = new S3Object { Bucket = s3Bucket, Name = key }
                }
            });
        jobId = startResp.JobId;
    }
    catch
    {
        await _s3Client.DeleteObjectAsync(s3Bucket, key);
        throw;
    }

    // Poll — minimum 5s wait; typical 15–60s for a 10-page PDF
    GetDocumentTextDetectionResponse pollResp;
    int pollAttempts = 0;
    const int maxAttempts = 120; // 10 minute timeout

    do
    {
        await Task.Delay(5000);
        pollResp = await _textractClient.GetDocumentTextDetectionAsync(
            new GetDocumentTextDetectionRequest { JobId = jobId });

        if (++pollAttempts >= maxAttempts)
            throw new TimeoutException("Textract job timed out after 10 minutes");

    } while (pollResp.JobStatus == JobStatus.IN_PROGRESS);

    await _s3Client.DeleteObjectAsync(s3Bucket, key); // cleanup regardless of outcome

    if (pollResp.JobStatus != JobStatus.SUCCEEDED)
        throw new Exception($"Textract job status: {pollResp.JobStatus} — {pollResp.StatusMessage}");

    // Return extracted text — caller must use a separate library to produce searchable PDF
    return string.Join("\n", pollResp.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .Select(b => b.Text));

    // Caller still needs: iTextSharp, PdfSharp, or similar to embed text layer
}
Imports Amazon.S3
Imports Amazon.S3.Model
Imports Amazon.Textract
Imports Amazon.Textract.Model

Public Async Function ExtractTextForSearchableLayerAsync(
    scannedPdfPath As String,
    s3Bucket As String) As Task(Of String)

    ' Must upload to S3 — cannot pass PDF bytes directly for async jobs
    Dim key = $"ocr-input/{Guid.NewGuid()}.pdf"

    Await Using fs = File.OpenRead(scannedPdfPath)
        Await _s3Client.PutObjectAsync(New PutObjectRequest With {
            .BucketName = s3Bucket,
            .Key = key,
            .InputStream = fs,
            .ContentType = "application/pdf"
        })
    End Using

    Dim jobId As String
    Try
        Dim startResp = Await _textractClient.StartDocumentTextDetectionAsync(
            New StartDocumentTextDetectionRequest With {
                .DocumentLocation = New DocumentLocation With {
                    .S3Object = New S3Object With {.Bucket = s3Bucket, .Name = key}
                }
            })
        jobId = startResp.JobId
    Catch
        Await _s3Client.DeleteObjectAsync(s3Bucket, key)
        Throw
    End Try

    ' Poll — minimum 5s wait; typical 15–60s for a 10-page PDF
    Dim pollResp As GetDocumentTextDetectionResponse
    Dim pollAttempts As Integer = 0
    Const maxAttempts As Integer = 120 ' 10 minute timeout

    Do
        Await Task.Delay(5000)
        pollResp = Await _textractClient.GetDocumentTextDetectionAsync(
            New GetDocumentTextDetectionRequest With {.JobId = jobId})

        If System.Threading.Interlocked.Increment(pollAttempts) >= maxAttempts Then
            Throw New TimeoutException("Textract job timed out after 10 minutes")
        End If

    Loop While pollResp.JobStatus = JobStatus.IN_PROGRESS

    Await _s3Client.DeleteObjectAsync(s3Bucket, key) ' cleanup regardless of outcome

    If pollResp.JobStatus <> JobStatus.SUCCEEDED Then
        Throw New Exception($"Textract job status: {pollResp.JobStatus} — {pollResp.StatusMessage}")
    End If

    ' Return extracted text — caller must use a separate library to produce searchable PDF
    Return String.Join(vbLf, pollResp.Blocks _
        .Where(Function(b) b.BlockType = BlockType.LINE) _
        .Select(Function(b) b.Text))

    ' Caller still needs: iTextSharp, PdfSharp, or similar to embed text layer
End Function
$vbLabelText   $csharpLabel

IronOCR Ansatz:

//IronOCR reads the PDF and produces a searchable PDF output directly
// No S3, no polling, no external PDF library needed

using IronOcr;

public void ConvertToSearchablePdf(string scannedPdfPath, string outputPath)
{
    var ocr = new IronTesseract();

    using var input = new OcrInput();
    input.LoadPdf(scannedPdfPath);

    var result = ocr.Read(input);

    // Embed extracted text as searchable layer in one call
    result.SaveAsSearchablePdf(outputPath);

    Console.WriteLine($"Pages processed: {result.Pages.Length}");
    Console.WriteLine($"Overall confidence: {result.Confidence:F1}%");
}
//IronOCR reads the PDF and produces a searchable PDF output directly
// No S3, no polling, no external PDF library needed

using IronOcr;

public void ConvertToSearchablePdf(string scannedPdfPath, string outputPath)
{
    var ocr = new IronTesseract();

    using var input = new OcrInput();
    input.LoadPdf(scannedPdfPath);

    var result = ocr.Read(input);

    // Embed extracted text as searchable layer in one call
    result.SaveAsSearchablePdf(outputPath);

    Console.WriteLine($"Pages processed: {result.Pages.Length}");
    Console.WriteLine($"Overall confidence: {result.Confidence:F1}%");
}
Imports IronOcr

Public Sub ConvertToSearchablePdf(scannedPdfPath As String, outputPath As String)
    Dim ocr As New IronTesseract()

    Using input As New OcrInput()
        input.LoadPdf(scannedPdfPath)

        Dim result = ocr.Read(input)

        ' Embed extracted text as searchable layer in one call
        result.SaveAsSearchablePdf(outputPath)

        Console.WriteLine($"Pages processed: {result.Pages.Length}")
        Console.WriteLine($"Overall confidence: {result.Confidence:F1}%")
    End Using
End Sub
$vbLabelText   $csharpLabel

Die Logik für das Polling-Timeout, das S3-Upload- und Bereinigungsmuster sowie die Abhängigkeit von der externen PDF-Bibliothek sind entfallen. SaveAsSearchablePdf bettet den erkannten Text direkt in die Ausgabedatei ein und macht jede gescannte Seite volltextdurchsuchbar ohne eine zweite Bibliothek. Der durchsuchbare PDF-Leitfaden behandelt Seitenauswahl, Komprimierungsoptionen und Metadateneinbettung. Für einen umfassenderen Kontext von PDF-OCR-Pipelines siehe den PDF-Eingabeleitfaden .

Ersetzen der AnalyzeDocument-Blockgraphendurchlaufung durch strukturierte Seitenergebnisse

Textracts AnalyzeDocument mit TABLES und FORMS liefert ein flaches List<Block>, bei dem jedes Element – Zeilen, Wörter, Zellen, Tabellenüberschriften, Formularschlüssel, Formularwerte – vom gleichen Block-Typ ist, der nur durch BlockType unterschieden und mit RelationshipType.CHILD-ID-Arrays verknüpft wird. Die Rekonstruktion der logischen Struktur eines Dokuments erfordert das Durchlaufen dieses Beziehungsgraphen.IronOCR gibt eine typisierte Hierarchie zurück: Seiten, Absätze, Zeilen, Wörter, jeweils mit Koordinatenzugriff.

AWS Textract-Ansatz:

// AnalyzeDocument with TABLES returns blocks that must be graph-traversed
// to determine which words belong to which paragraphs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<List<DocumentSection>> ExtractDocumentStructureAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "TABLES", "FORMS" }
            // Note: AnalyzeDocument (TABLES) costs $0.015/page — 10x DetectDocumentText
        });

    var sections = new List<DocumentSection>();

    // Filter LINE blocks for paragraph reconstruction
    // LINE blocks are not grouped into paragraphs — must infer from Y proximity
    var lineBlocks = response.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .OrderBy(b => b.Geometry?.BoundingBox?.Top ?? 0)
        .ToList();

    // Group lines into pseudo-paragraphs by vertical gap heuristic
    var currentParagraph = new List<Block>();
    float? lastBottom = null;
    const float paragraphGapThreshold = 0.02f; // 2% of page height

    foreach (var line in lineBlocks)
    {
        var top = line.Geometry?.BoundingBox?.Top ?? 0;
        var height = line.Geometry?.BoundingBox?.Height ?? 0;

        if (lastBottom.HasValue && (top - lastBottom.Value) > paragraphGapThreshold)
        {
            if (currentParagraph.Any())
            {
                sections.Add(new DocumentSection
                {
                    Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
                    LineCount = currentParagraph.Count,
                    // Confidence: average of all LINE block confidence values
                    Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
                });
                currentParagraph.Clear();
            }
        }

        currentParagraph.Add(line);
        lastBottom = top + height;
    }

    if (currentParagraph.Any())
        sections.Add(new DocumentSection
        {
            Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
            LineCount = currentParagraph.Count,
            Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
        });

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public float Confidence { get; set; }
}
// AnalyzeDocument with TABLES returns blocks that must be graph-traversed
// to determine which words belong to which paragraphs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<List<DocumentSection>> ExtractDocumentStructureAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "TABLES", "FORMS" }
            // Note: AnalyzeDocument (TABLES) costs $0.015/page — 10x DetectDocumentText
        });

    var sections = new List<DocumentSection>();

    // Filter LINE blocks for paragraph reconstruction
    // LINE blocks are not grouped into paragraphs — must infer from Y proximity
    var lineBlocks = response.Blocks
        .Where(b => b.BlockType == BlockType.LINE)
        .OrderBy(b => b.Geometry?.BoundingBox?.Top ?? 0)
        .ToList();

    // Group lines into pseudo-paragraphs by vertical gap heuristic
    var currentParagraph = new List<Block>();
    float? lastBottom = null;
    const float paragraphGapThreshold = 0.02f; // 2% of page height

    foreach (var line in lineBlocks)
    {
        var top = line.Geometry?.BoundingBox?.Top ?? 0;
        var height = line.Geometry?.BoundingBox?.Height ?? 0;

        if (lastBottom.HasValue && (top - lastBottom.Value) > paragraphGapThreshold)
        {
            if (currentParagraph.Any())
            {
                sections.Add(new DocumentSection
                {
                    Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
                    LineCount = currentParagraph.Count,
                    // Confidence: average of all LINE block confidence values
                    Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
                });
                currentParagraph.Clear();
            }
        }

        currentParagraph.Add(line);
        lastBottom = top + height;
    }

    if (currentParagraph.Any())
        sections.Add(new DocumentSection
        {
            Text = string.Join(" ", currentParagraph.Select(b => b.Text)),
            LineCount = currentParagraph.Count,
            Confidence = (float)currentParagraph.Average(b => b.Confidence ?? 0)
        });

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public float Confidence { get; set; }
}
Imports Amazon.Textract
Imports Amazon.Textract.Model
Imports System.IO
Imports System.Linq
Imports System.Threading.Tasks

Public Class DocumentProcessor
    Private _textractClient As IAmazonTextract

    Public Sub New(textractClient As IAmazonTextract)
        _textractClient = textractClient
    End Sub

    Public Async Function ExtractDocumentStructureAsync(imagePath As String) As Task(Of List(Of DocumentSection))
        Dim bytes = File.ReadAllBytes(imagePath)

        Dim response = Await _textractClient.AnalyzeDocumentAsync(
            New AnalyzeDocumentRequest With {
                .Document = New Document With {.Bytes = New MemoryStream(bytes)},
                .FeatureTypes = New List(Of String) From {"TABLES", "FORMS"}
            })

        Dim sections = New List(Of DocumentSection)()

        Dim lineBlocks = response.Blocks _
            .Where(Function(b) b.BlockType = BlockType.LINE) _
            .OrderBy(Function(b) If(b.Geometry?.BoundingBox?.Top, 0)) _
            .ToList()

        Dim currentParagraph = New List(Of Block)()
        Dim lastBottom As Single? = Nothing
        Const paragraphGapThreshold As Single = 0.02F

        For Each line In lineBlocks
            Dim top = If(line.Geometry?.BoundingBox?.Top, 0)
            Dim height = If(line.Geometry?.BoundingBox?.Height, 0)

            If lastBottom.HasValue AndAlso (top - lastBottom.Value) > paragraphGapThreshold Then
                If currentParagraph.Any() Then
                    sections.Add(New DocumentSection With {
                        .Text = String.Join(" ", currentParagraph.Select(Function(b) b.Text)),
                        .LineCount = currentParagraph.Count,
                        .Confidence = CSng(currentParagraph.Average(Function(b) If(b.Confidence, 0)))
                    })
                    currentParagraph.Clear()
                End If
            End If

            currentParagraph.Add(line)
            lastBottom = top + height
        Next

        If currentParagraph.Any() Then
            sections.Add(New DocumentSection With {
                .Text = String.Join(" ", currentParagraph.Select(Function(b) b.Text)),
                .LineCount = currentParagraph.Count,
                .Confidence = CSng(currentParagraph.Average(Function(b) If(b.Confidence, 0)))
            })
        End If

        Return sections
    End Function
End Class

Public Class DocumentSection
    Public Property Text As String
    Public Property LineCount As Integer
    Public Property Confidence As Single
End Class
$vbLabelText   $csharpLabel

IronOCR Ansatz:

//IronOCR returns a typed hierarchy — paragraphs are first-class objects
// No block graph, no heuristic gap detection, no confidence averaging

using IronOcr;

public List<DocumentSection> ExtractDocumentStructure(string imagePath)
{
    var ocr = new IronTesseract();
    var result = ocr.Read(imagePath);

    var sections = new List<DocumentSection>();

    foreach (var page in result.Pages)
    {
        foreach (var paragraph in page.Paragraphs)
        {
            sections.Add(new DocumentSection
            {
                Text = paragraph.Text,
                LineCount = paragraph.Lines.Length,
                // Coordinate access: exact pixel position on the page
                LocationX = paragraph.X,
                LocationY = paragraph.Y,
                Width = paragraph.Width,
                Height = paragraph.Height,
                Confidence = paragraph.Confidence
            });
        }
    }

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public int LocationX { get; set; }
    public int LocationY { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public double Confidence { get; set; }
}
//IronOCR returns a typed hierarchy — paragraphs are first-class objects
// No block graph, no heuristic gap detection, no confidence averaging

using IronOcr;

public List<DocumentSection> ExtractDocumentStructure(string imagePath)
{
    var ocr = new IronTesseract();
    var result = ocr.Read(imagePath);

    var sections = new List<DocumentSection>();

    foreach (var page in result.Pages)
    {
        foreach (var paragraph in page.Paragraphs)
        {
            sections.Add(new DocumentSection
            {
                Text = paragraph.Text,
                LineCount = paragraph.Lines.Length,
                // Coordinate access: exact pixel position on the page
                LocationX = paragraph.X,
                LocationY = paragraph.Y,
                Width = paragraph.Width,
                Height = paragraph.Height,
                Confidence = paragraph.Confidence
            });
        }
    }

    return sections;
}

public class DocumentSection
{
    public string Text { get; set; }
    public int LineCount { get; set; }
    public int LocationX { get; set; }
    public int LocationY { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public double Confidence { get; set; }
}
Imports IronOcr

Public Function ExtractDocumentStructure(imagePath As String) As List(Of DocumentSection)
    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(imagePath)

    Dim sections As New List(Of DocumentSection)()

    For Each page In result.Pages
        For Each paragraph In page.Paragraphs
            sections.Add(New DocumentSection With {
                .Text = paragraph.Text,
                .LineCount = paragraph.Lines.Length,
                .LocationX = paragraph.X,
                .LocationY = paragraph.Y,
                .Width = paragraph.Width,
                .Height = paragraph.Height,
                .Confidence = paragraph.Confidence
            })
        Next
    Next

    Return sections
End Function

Public Class DocumentSection
    Public Property Text As String
    Public Property LineCount As Integer
    Public Property LocationX As Integer
    Public Property LocationY As Integer
    Public Property Width As Integer
    Public Property Height As Integer
    Public Property Confidence As Double
End Class
$vbLabelText   $csharpLabel

Absätze, Zeilen und Wörter sind direkt als typisierte Sammlungen zugänglich mit .X, .Y, .Width, .Height und .Confidence-Eigenschaften. Keine BoundingBox-Normalisierung, keine Heuristiken zur Lückenschwelle, keine Blocktypfilterung. Der Read-Results-Leitfaden dokumentiert die vollständige OcrResult-Hierarchie einschließlich des Zugriffs auf die Koordinatenebene. Für Rechnungs- und Beleg-Workflows, die auf dieser Struktur basieren, siehe das Tutorial zur Rechnungs-OCR .

Ersetzen der ratenbegrenzten Stapelverarbeitung durch uneingeschränkte parallele Ausführung

Das Batch-Bildverarbeitung ist der Bereich, in dem Textracts TPS-Limits die sichtbarsten Betriebskosten verursachen. Die DetectDocumentText-synchrone API ist ratengesteuert; Das Senden von mehr als 5 Anfragen pro Sekunde erzeugt ProvisionedThroughputExceededException. Korrekte Stapelverarbeitung erfordert SemaphoreSlim-Drosselung, Rückgabelogik mit Rückgang und vorsichtige Betrachtung der in Bearbeitung befindlichen Anfragen.IronOCR hat keine Ratenbegrenzung – der Durchsatz ist nur durch die verfügbaren CPU-Kerne begrenzt.

AWS Textract-Ansatz:

// Batch processing requires SemaphoreSlim throttle + retry on rate limit exceeded

using Amazon.Textract;
using Amazon.Textract.Model;
using System.Collections.Concurrent;

public async Task<Dictionary<string, string>> ProcessImageBatchAsync(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var results = new ConcurrentDictionary<string, string>();
    var semaphore = new SemaphoreSlim(5); // 5 concurrent — Textract default TPS limit
    var tasks = new List<Task>();
    int completed = 0;

    foreach (var path in imagePaths)
    {
        await semaphore.WaitAsync();

        tasks.Add(Task.Run(async () =>
        {
            try
            {
                string text = null;
                int retryCount = 0;

                while (text == null && retryCount < 3)
                {
                    try
                    {
                        var bytes = await File.ReadAllBytesAsync(path);
                        var response = await _textractClient.DetectDocumentTextAsync(
                            new DetectDocumentTextRequest
                            {
                                Document = new Document { Bytes = new MemoryStream(bytes) }
                            });

                        text = string.Join("\n", response.Blocks
                            .Where(b => b.BlockType == BlockType.LINE)
                            .Select(b => b.Text));
                    }
                    catch (AmazonTextractException ex)
                        when (ex.ErrorCode == "ProvisionedThroughputExceededException")
                    {
                        // Exponential backoff on rate limit — blocks the entire thread
                        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
                        retryCount++;
                    }
                }

                results[path] = text ?? string.Empty;
                var done = Interlocked.Increment(ref completed);
                progress?.Report((done, imagePaths.Count));
            }
            finally
            {
                semaphore.Release();
            }
        }));
    }

    await Task.WhenAll(tasks);
    return results.ToDictionary(x => x.Key, x => x.Value);
}
// Batch processing requires SemaphoreSlim throttle + retry on rate limit exceeded

using Amazon.Textract;
using Amazon.Textract.Model;
using System.Collections.Concurrent;

public async Task<Dictionary<string, string>> ProcessImageBatchAsync(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var results = new ConcurrentDictionary<string, string>();
    var semaphore = new SemaphoreSlim(5); // 5 concurrent — Textract default TPS limit
    var tasks = new List<Task>();
    int completed = 0;

    foreach (var path in imagePaths)
    {
        await semaphore.WaitAsync();

        tasks.Add(Task.Run(async () =>
        {
            try
            {
                string text = null;
                int retryCount = 0;

                while (text == null && retryCount < 3)
                {
                    try
                    {
                        var bytes = await File.ReadAllBytesAsync(path);
                        var response = await _textractClient.DetectDocumentTextAsync(
                            new DetectDocumentTextRequest
                            {
                                Document = new Document { Bytes = new MemoryStream(bytes) }
                            });

                        text = string.Join("\n", response.Blocks
                            .Where(b => b.BlockType == BlockType.LINE)
                            .Select(b => b.Text));
                    }
                    catch (AmazonTextractException ex)
                        when (ex.ErrorCode == "ProvisionedThroughputExceededException")
                    {
                        // Exponential backoff on rate limit — blocks the entire thread
                        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
                        retryCount++;
                    }
                }

                results[path] = text ?? string.Empty;
                var done = Interlocked.Increment(ref completed);
                progress?.Report((done, imagePaths.Count));
            }
            finally
            {
                semaphore.Release();
            }
        }));
    }

    await Task.WhenAll(tasks);
    return results.ToDictionary(x => x.Key, x => x.Value);
}
Imports Amazon.Textract
Imports Amazon.Textract.Model
Imports System.Collections.Concurrent
Imports System.IO
Imports System.Threading

Public Async Function ProcessImageBatchAsync(
    imagePaths As IReadOnlyList(Of String),
    Optional progress As IProgress(Of (Completed As Integer, Total As Integer)) = Nothing) As Task(Of Dictionary(Of String, String))

    Dim results As New ConcurrentDictionary(Of String, String)()
    Dim semaphore As New SemaphoreSlim(5) ' 5 concurrent — Textract default TPS limit
    Dim tasks As New List(Of Task)()
    Dim completed As Integer = 0

    For Each path In imagePaths
        Await semaphore.WaitAsync()

        tasks.Add(Task.Run(Async Function()
            Try
                Dim text As String = Nothing
                Dim retryCount As Integer = 0

                While text Is Nothing AndAlso retryCount < 3
                    Try
                        Dim bytes = Await File.ReadAllBytesAsync(path)
                        Dim response = Await _textractClient.DetectDocumentTextAsync(
                            New DetectDocumentTextRequest With {
                                .Document = New Document With {.Bytes = New MemoryStream(bytes)}
                            })

                        text = String.Join(vbLf, response.Blocks _
                            .Where(Function(b) b.BlockType = BlockType.LINE) _
                            .Select(Function(b) b.Text))
                    Catch ex As AmazonTextractException When ex.ErrorCode = "ProvisionedThroughputExceededException"
                        ' Exponential backoff on rate limit — blocks the entire thread
                        Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)))
                        retryCount += 1
                    End Try
                End While

                results(path) = If(text, String.Empty)
                Dim done = Interlocked.Increment(completed)
                If progress IsNot Nothing Then
                    progress.Report((done, imagePaths.Count))
                End If
            Finally
                semaphore.Release()
            End Try
        End Function))
    Next

    Await Task.WhenAll(tasks)
    Return results.ToDictionary(Function(x) x.Key, Function(x) x.Value)
End Function
$vbLabelText   $csharpLabel

IronOCR Ansatz:

// No rate limits, no semaphore, no retry logic — Parallel.ForEach saturates all cores

using IronOcr;
using System.Collections.Concurrent;

public Dictionary<string, string> ProcessImageBatch(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var ocr = new IronTesseract();
    var results = new ConcurrentDictionary<string, string>();
    int completed = 0;

    Parallel.ForEach(imagePaths, path =>
    {
        var result = ocr.Read(path);
        results[path] = result.Text;

        var done = Interlocked.Increment(ref completed);
        progress?.Report((done, imagePaths.Count));
    });

    return results.ToDictionary(x => x.Key, x => x.Value);
}
// No rate limits, no semaphore, no retry logic — Parallel.ForEach saturates all cores

using IronOcr;
using System.Collections.Concurrent;

public Dictionary<string, string> ProcessImageBatch(
    IReadOnlyList<string> imagePaths,
    IProgress<(int Completed, int Total)> progress = null)
{
    var ocr = new IronTesseract();
    var results = new ConcurrentDictionary<string, string>();
    int completed = 0;

    Parallel.ForEach(imagePaths, path =>
    {
        var result = ocr.Read(path);
        results[path] = result.Text;

        var done = Interlocked.Increment(ref completed);
        progress?.Report((done, imagePaths.Count));
    });

    return results.ToDictionary(x => x.Key, x => x.Value);
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading

Public Function ProcessImageBatch(
    imagePaths As IReadOnlyList(Of String),
    Optional progress As IProgress(Of (Completed As Integer, Total As Integer)) = Nothing) As Dictionary(Of String, String)

    Dim ocr As New IronTesseract()
    Dim results As New ConcurrentDictionary(Of String, String)()
    Dim completed As Integer = 0

    Parallel.ForEach(imagePaths, Sub(path)
                                     Dim result = ocr.Read(path)
                                     results(path) = result.Text

                                     Dim done = Interlocked.Increment(completed)
                                     If progress IsNot Nothing Then
                                         progress.Report((done, imagePaths.Count))
                                     End If
                                 End Sub)

    Return results.ToDictionary(Function(x) x.Key, Function(x) x.Value)
End Function
$vbLabelText   $csharpLabel

Das SemaphoreSlim, die Wiederholungsschleife, das exponentielle Zurückgehen und die ProvisionedThroughputExceededException-Catch-Blöcke werden alle entfernt. IronTesseract ist threadsicher und eine einzelne Instanz, die über Threads geteilt wird, verarbeitet die volle parallele Last ohne Sperren. Auf einer Maschine mit 8 Kernen verarbeitet dies 8 Dokumente gleichzeitig ohne Konfiguration; auf 32 Kernen, 32 gleichzeitig. Das Beispiel zur Multithreading-Architektur zeigt das vollständige Muster, und der Leitfaden zur Geschwindigkeitsoptimierung behandelt die Konfigurationsoptimierung der Engine für durchsatzorientierte Bereitstellungen.

Ersetzen der AnalyzeDocument-Formularextraktion durch regionenbasierte CropRectangle-OCR

Textracts AnalyzeDocument mit FORMS liefert KEY_VALUE_SET-Blöcke, die durch RelationshipType.VALUE-Beziehungen verbunden sind. Die Rekonstruktion von Formularfeld-Schlüsselwertpaaren erfordert das Filtern nach EntityTypes.Contains("KEY"), das Folgen von VALUE-Beziehungs-IDs und dann die Abrufung von untergeordneten WORD-Blöcken zur Zusammensetzung des Textes. Für fest formatierte Formulare, bei denen die Feldpositionen bekannt sind, extrahiert IronOCRs CropRectangle jedes Feld direkt ohne Durchlauf durch das Beziehungsdiagramm - und kostet null pro Seite.

AWS Textract-Ansatz:

// FORMS mode costs $0.05/page — the most expensive Textract tier
// Relationship graph traversal required to reconstruct key-value pairs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<Dictionary<string, string>> ExtractInvoiceFieldsAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    // $0.05/page for FORMS analysis
    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "FORMS" }
        });

    var allBlocks = response.Blocks;
    var fields = new Dictionary<string, string>();

    // Find KEY blocks only
    var keyBlocks = allBlocks.Where(b =>
        b.BlockType == BlockType.KEY_VALUE_SET &&
        b.EntityTypes?.Contains("KEY") == true);

    foreach (var keyBlock in keyBlocks)
    {
        // Assemble key text from CHILD WORD blocks
        var keyText = GetTextFromBlock(allBlocks, keyBlock);

        // Find associated VALUE block via VALUE relationship
        var valueBlockId = keyBlock.Relationships?
            .FirstOrDefault(r => r.Type == RelationshipType.VALUE)?
            .Ids.FirstOrDefault();

        if (valueBlockId == null) continue;

        var valueBlock = allBlocks.FirstOrDefault(b => b.Id == valueBlockId);
        if (valueBlock == null) continue;

        var valueText = GetTextFromBlock(allBlocks, valueBlock);
        fields[keyText.TrimEnd(':')] = valueText;
    }

    return fields;
}

private string GetTextFromBlock(IList<Block> allBlocks, Block block)
{
    if (block.Relationships == null) return string.Empty;

    var childWordIds = block.Relationships
        .Where(r => r.Type == RelationshipType.CHILD)
        .SelectMany(r => r.Ids);

    return string.Join(" ", allBlocks
        .Where(b => b.BlockType == BlockType.WORD && childWordIds.Contains(b.Id))
        .Select(b => b.Text));
}
// FORMS mode costs $0.05/page — the most expensive Textract tier
// Relationship graph traversal required to reconstruct key-value pairs

using Amazon.Textract;
using Amazon.Textract.Model;

public async Task<Dictionary<string, string>> ExtractInvoiceFieldsAsync(string imagePath)
{
    var bytes = File.ReadAllBytes(imagePath);

    // $0.05/page for FORMS analysis
    var response = await _textractClient.AnalyzeDocumentAsync(
        new AnalyzeDocumentRequest
        {
            Document = new Document { Bytes = new MemoryStream(bytes) },
            FeatureTypes = new List<string> { "FORMS" }
        });

    var allBlocks = response.Blocks;
    var fields = new Dictionary<string, string>();

    // Find KEY blocks only
    var keyBlocks = allBlocks.Where(b =>
        b.BlockType == BlockType.KEY_VALUE_SET &&
        b.EntityTypes?.Contains("KEY") == true);

    foreach (var keyBlock in keyBlocks)
    {
        // Assemble key text from CHILD WORD blocks
        var keyText = GetTextFromBlock(allBlocks, keyBlock);

        // Find associated VALUE block via VALUE relationship
        var valueBlockId = keyBlock.Relationships?
            .FirstOrDefault(r => r.Type == RelationshipType.VALUE)?
            .Ids.FirstOrDefault();

        if (valueBlockId == null) continue;

        var valueBlock = allBlocks.FirstOrDefault(b => b.Id == valueBlockId);
        if (valueBlock == null) continue;

        var valueText = GetTextFromBlock(allBlocks, valueBlock);
        fields[keyText.TrimEnd(':')] = valueText;
    }

    return fields;
}

private string GetTextFromBlock(IList<Block> allBlocks, Block block)
{
    if (block.Relationships == null) return string.Empty;

    var childWordIds = block.Relationships
        .Where(r => r.Type == RelationshipType.CHILD)
        .SelectMany(r => r.Ids);

    return string.Join(" ", allBlocks
        .Where(b => b.BlockType == BlockType.WORD && childWordIds.Contains(b.Id))
        .Select(b => b.Text));
}
Imports Amazon.Textract
Imports Amazon.Textract.Model
Imports System.IO
Imports System.Threading.Tasks

Public Class InvoiceFieldExtractor
    Private _textractClient As AmazonTextractClient

    Public Async Function ExtractInvoiceFieldsAsync(imagePath As String) As Task(Of Dictionary(Of String, String))
        Dim bytes = File.ReadAllBytes(imagePath)

        ' $0.05/page for FORMS analysis
        Dim response = Await _textractClient.AnalyzeDocumentAsync(
            New AnalyzeDocumentRequest With {
                .Document = New Document With {.Bytes = New MemoryStream(bytes)},
                .FeatureTypes = New List(Of String) From {"FORMS"}
            })

        Dim allBlocks = response.Blocks
        Dim fields As New Dictionary(Of String, String)()

        ' Find KEY blocks only
        Dim keyBlocks = allBlocks.Where(Function(b) 
                                            Return b.BlockType = BlockType.KEY_VALUE_SET AndAlso
                                                   b.EntityTypes?.Contains("KEY") = True
                                        End Function)

        For Each keyBlock In keyBlocks
            ' Assemble key text from CHILD WORD blocks
            Dim keyText = GetTextFromBlock(allBlocks, keyBlock)

            ' Find associated VALUE block via VALUE relationship
            Dim valueBlockId = keyBlock.Relationships?.
                FirstOrDefault(Function(r) r.Type = RelationshipType.VALUE)?.
                Ids.FirstOrDefault()

            If valueBlockId Is Nothing Then Continue For

            Dim valueBlock = allBlocks.FirstOrDefault(Function(b) b.Id = valueBlockId)
            If valueBlock Is Nothing Then Continue For

            Dim valueText = GetTextFromBlock(allBlocks, valueBlock)
            fields(keyText.TrimEnd(":"c)) = valueText
        Next

        Return fields
    End Function

    Private Function GetTextFromBlock(allBlocks As IList(Of Block), block As Block) As String
        If block.Relationships Is Nothing Then Return String.Empty

        Dim childWordIds = block.Relationships.
            Where(Function(r) r.Type = RelationshipType.CHILD).
            SelectMany(Function(r) r.Ids)

        Return String.Join(" ", allBlocks.
            Where(Function(b) b.BlockType = BlockType.WORD AndAlso childWordIds.Contains(b.Id)).
            Select(Function(b) b.Text))
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Ansatz:

// CropRectangle extracts each field zone directly — no relationship graph
// Works for any fixed-format form where field positions are known

using IronOcr;

// Define the field zones for your form template once
private static readonly Dictionary<string, CropRectangle> InvoiceFieldZones =
    new Dictionary<string, CropRectangle>
    {
        { "InvoiceNumber", new CropRectangle(450, 80,  300, 35) },
        { "InvoiceDate",   new CropRectangle(450, 120, 300, 35) },
        { "VendorName",    new CropRectangle(50,  160, 400, 35) },
        { "TotalAmount",   new CropRectangle(450, 680, 200, 35) },
        { "DueDate",       new CropRectangle(450, 720, 200, 35) }
    };

public Dictionary<string, string> ExtractInvoiceFields(string imagePath)
{
    var ocr = new IronTesseract();
    var fields = new Dictionary<string, string>();

    foreach (var zone in InvoiceFieldZones)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath, zone.Value); // load only the defined region

        var result = ocr.Read(input);
        fields[zone.Key] = result.Text.Trim();
    }

    return fields;
}
// CropRectangle extracts each field zone directly — no relationship graph
// Works for any fixed-format form where field positions are known

using IronOcr;

// Define the field zones for your form template once
private static readonly Dictionary<string, CropRectangle> InvoiceFieldZones =
    new Dictionary<string, CropRectangle>
    {
        { "InvoiceNumber", new CropRectangle(450, 80,  300, 35) },
        { "InvoiceDate",   new CropRectangle(450, 120, 300, 35) },
        { "VendorName",    new CropRectangle(50,  160, 400, 35) },
        { "TotalAmount",   new CropRectangle(450, 680, 200, 35) },
        { "DueDate",       new CropRectangle(450, 720, 200, 35) }
    };

public Dictionary<string, string> ExtractInvoiceFields(string imagePath)
{
    var ocr = new IronTesseract();
    var fields = new Dictionary<string, string>();

    foreach (var zone in InvoiceFieldZones)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath, zone.Value); // load only the defined region

        var result = ocr.Read(input);
        fields[zone.Key] = result.Text.Trim();
    }

    return fields;
}
Imports IronOcr

' CropRectangle extracts each field zone directly — no relationship graph
' Works for any fixed-format form where field positions are known

' Define the field zones for your form template once
Private Shared ReadOnly InvoiceFieldZones As New Dictionary(Of String, CropRectangle) From {
    {"InvoiceNumber", New CropRectangle(450, 80, 300, 35)},
    {"InvoiceDate", New CropRectangle(450, 120, 300, 35)},
    {"VendorName", New CropRectangle(50, 160, 400, 35)},
    {"TotalAmount", New CropRectangle(450, 680, 200, 35)},
    {"DueDate", New CropRectangle(450, 720, 200, 35)}
}

Public Function ExtractInvoiceFields(imagePath As String) As Dictionary(Of String, String)
    Dim ocr As New IronTesseract()
    Dim fields As New Dictionary(Of String, String)()

    For Each zone In InvoiceFieldZones
        Using input As New OcrInput()
            input.LoadImage(imagePath, zone.Value) ' load only the defined region

            Dim result = ocr.Read(input)
            fields(zone.Key) = result.Text.Trim()
        End Using
    Next

    Return fields
End Function
$vbLabelText   $csharpLabel

Die KEY_VALUE_SET-Blockfilterung, die RelationshipType.VALUE-Durchquerung und der GetTextFromBlock-Helfer werden eliminiert. Jedes Feld liest exakt den Pixelbereich, der es enthält – nicht mehr und nicht weniger. Der Leitfaden zur regionsbasierten OCR behandelt CropRectangle-Koordinaten und wie man Zonen aus Vorlagenmessungen definiert. Für rechnungsspezifische Arbeitsabläufe demonstrieren der Blogbeitrag zur OCR-Texterkennung von Rechnungen und das Tutorial zum Scannen von Belegen vollständige Pipelines zur Feldextraktion.

AWS TextractAPI zu IronOCR Mapping-Referenz

AWS Textract IronOCR-Äquivalent
AmazonTextractClient IronTesseract
AmazonS3Client Nicht erforderlich
DetectDocumentTextRequest OcrInput mit input.LoadImage()
DetectDocumentTextResponse OcrResult
AnalyzeDocumentRequest (TABELLEN/FORMULARE) OcrInput mit optionalem CropRectangle
StartDocumentTextDetectionRequest OcrInput mit input.LoadPdf() — synchron, kein Jobstart
GetDocumentTextDetectionRequest Nicht zutreffend – Ergebnisse liegen sofort vor.
Document.Bytes input.LoadImage(byteArray) oder input.LoadImage(stream)
S3Object (Dokumentenstaging) Dateipfad oder Stream – kein Staging erforderlich
Block (BlockType.LINE) result.Lines (Typisierte Sammlung)
Block (BlockType.WORD) result.Words (Typisierte Sammlung)
Block (BlockType.TABLE) result.Words gruppiert nach Koordinatennähe
Block (BlockType.KEY_VALUE_SET) CropRectangle Regionsextraktion pro Feld
Block.Confidence word.Confidence / result.Confidence
Block.Geometry.BoundingBox word.X, word.Y, word.Width, word.Height
RelationshipType.CHILD-Durchquerung Direkter Zugriff auf Eigenschaften von typisierten Ergebnisobjekten
JobStatus.IN_PROGRESS Nicht anwendbar – kein asynchroner Zustand
JobStatus.SUCCEEDED Nicht zutreffend – synchrone Rückgabe
response.NextToken (Paginierung) Nicht zutreffend – Ergebnisse nicht paginiert
ProvisionedThroughputExceededException Nicht anwendbar – keine Drosselklappenpotentiometer-Grenzwerte
AccessDeniedException Nicht zutreffend – keine Anmeldeinformationskette
input.LoadImageFrames() Direkte Unterstützung für TIFF-Mehrbildformate (kein Textract-Äquivalent)
result.SaveAsSearchablePdf() Durchsuchbare PDF-Ausgabe (kein Textract-Äquivalent)

Gängige Migrationsprobleme und Lösungen

Problem 1: Falsch konfigurierte Anmeldeinformationen in neuen Bereitstellungsumgebungen

AWS Textract: Der AmazonTextractClient-Konstruktor löst Anmeldeinformationen aus einer Kette: Umgebungsvariablen, ~/.aws/credentials, EC2-Instanzprofil, ECS-Aufgabenrolle. In einem neuen Docker-Container oder in einer CI-Umgebung, wenn keiner dieser konfiguriert ist, wird der Client erstellt, ohne Fehler zu verursachen, aber jeder API-Aufruf wirft AmazonClientException: No credentials specified. Der Fehler ist erst zur Laufzeit sichtbar.

Lösung: Die Anmeldeinformationskette vollständig entfernen. Legen Sie den IronOCR-Lizenzschlüssel über eine Umgebungsvariable fest, die einfacher zu konfigurieren ist und keine IAM-Berechtigungen zur Verwaltung erfordert:

// Single environment variable — no IAM, no rotation, no chain resolution
var key = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
if (string.IsNullOrEmpty(key))
    throw new InvalidOperationException("IRONOCR_LICENSE environment variable is not set");

IronOcr.License.LicenseKey = key;
// Single environment variable — no IAM, no rotation, no chain resolution
var key = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
if (string.IsNullOrEmpty(key))
    throw new InvalidOperationException("IRONOCR_LICENSE environment variable is not set");

IronOcr.License.LicenseKey = key;
Imports System

' Single environment variable — no IAM, no rotation, no chain resolution
Dim key As String = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
If String.IsNullOrEmpty(key) Then
    Throw New InvalidOperationException("IRONOCR_LICENSE environment variable is not set")
End If

IronOcr.License.LicenseKey = key
$vbLabelText   $csharpLabel

Problem 2: Asynchrone Aufrufstellen im gesamten Quellcode

AWS Textract: Da das gesamte SDK nur asynchron ist (DetectDocumentTextAsync, StartDocumentTextDetectionAsync, GetDocumentTextDetectionAsync), verbreiten alle Textract-Aufrufstellen async Task<t> durch den Aufrufstapel. Die Migration zur synchronen API von IronOCR erfordert eine Entscheidung an jedem Standort.

Lösung: Bei Hintergrundverarbeitungsdiensten und Controller-Aktionen sind die synchronen Aufrufe von IronOCR auf Hintergrundthreads sicher. Umschließen Sie in Task.Run nur, wenn die bestehende Aufrufkette asynchron bleiben muss:

// If the call site must remain async (e.g., an ASP.NET controller action):
public async Task<IActionResult> ProcessUpload(IFormFile file)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms);

    // Offload synchronous OCR to thread pool — keeps controller non-blocking
    var text = await Task.Run(() =>
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(ms.ToArray());
        return ocr.Read(input).Text;
    });

    return Ok(text);
}
// If the call site must remain async (e.g., an ASP.NET controller action):
public async Task<IActionResult> ProcessUpload(IFormFile file)
{
    using var ms = new MemoryStream();
    await file.CopyToAsync(ms);

    // Offload synchronous OCR to thread pool — keeps controller non-blocking
    var text = await Task.Run(() =>
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(ms.ToArray());
        return ocr.Read(input).Text;
    });

    return Ok(text);
}
Imports System.IO
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore.Mvc

Public Class YourController
    Inherits Controller

    ' If the call site must remain async (e.g., an ASP.NET controller action):
    Public Async Function ProcessUpload(file As IFormFile) As Task(Of IActionResult)
        Using ms As New MemoryStream()
            Await file.CopyToAsync(ms)

            ' Offload synchronous OCR to thread pool — keeps controller non-blocking
            Dim text = Await Task.Run(Function()
                                          Dim ocr = New IronTesseract()
                                          Using input As New OcrInput()
                                              input.LoadImage(ms.ToArray())
                                              Return ocr.Read(input).Text
                                          End Using
                                      End Function)

            Return Ok(text)
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Für Batch-Prozessoren und Hintergrunddienste, die bereits auf Nicht-UI-Threads laufen, synchron auf ocr.Read() aufrufen — Task.Run fügt in diesen Kontexten Überkopf ohne Vorteil hinzu. Der Leitfaden zur asynchronen OCR beschreibt die empfohlenen Muster.

Problem 3: Logik zur Bereinigung des S3-Buckets im gesamten Code verstreut

AWS Textract: Jeder Code, der zur Verarbeitung durch Textract in S3 hochgeladen wird, muss nach Abschluss des Auftrags auch wieder aus S3 gelöscht werden. Diese Bereinigung lebt häufig in finally-Blöcken und wird manchmal auf Fehlerpfaden ausgelassen, was verwaiste Objekte verursacht, die Speicherkosten ansammeln. Bei der Migration wird der S3-Bereinigungscode leicht übersehen, da er konzeptionell nicht mit OCR zusammenhängt.

Lösung: Für das gesamte S3-Upload- und Bereinigungsmuster gibt es kein IronOCR Äquivalent. Löschen Sie alle PutObjectAsync, DeleteObjectAsync und S3-bezogenen try/finally-Blöcke vollständig.IronOCR liest direkt von lokalen Pfaden oder Datenströmen:

// Before: upload → job → poll → extract → cleanup (50+ lines)
// After: two lines, no cleanup required

using var input = new OcrInput();
input.LoadPdf(localPdfPath);
var text = new IronTesseract().Read(input).Text;
// Before: upload → job → poll → extract → cleanup (50+ lines)
// After: two lines, no cleanup required

using var input = new OcrInput();
input.LoadPdf(localPdfPath);
var text = new IronTesseract().Read(input).Text;
Imports IronOcr

Using input As New OcrInput()
    input.LoadPdf(localPdfPath)
    Dim text As String = New IronTesseract().Read(input).Text
End Using
$vbLabelText   $csharpLabel

Nach der Migration den S3-Staging-Bucket außer Betrieb nehmen. Der PDF-Eingabeleitfaden und der Stream-Eingabeleitfaden decken jedes Eingabemuster ab, das den gestaffelten Dokumentenzugriff über S3 ersetzt.

Problem 4: Blockgraph-Traversierungscode hat kein direktes Äquivalent

AWS Textract: Der Code, der Block.Relationships durchquert, um Tabellenzellen, Formularfelder oder Absatzgruppen aus RelationshipType.CHILD-ID-Arrays zu rekonstruieren, ist der zeitaufwendigste Code zur Migration. Es gibt keinen direkten Ersatz, da IronOCR typisierte Sammlungen anstelle eines Beziehungsgraphen zurückgibt.

Lösung: Ersetzen Sie jedes Traversierungsmuster durch die entsprechende typisierte Eigenschaft. Beziehungs-ID-Abfragen werden zu einem direkten Zugriff auf Eigenschaften:

// Before: filter by BlockType + traverse relationship IDs
var paragraphText = string.Join(" ", allBlocks
    .Where(b => b.BlockType == BlockType.WORD &&
                childIds.Contains(b.Id))
    .Select(b => b.Text));

// After: direct paragraph text (no filtering, no relationship lookup)
foreach (var page in result.Pages)
    foreach (var para in page.Paragraphs)
        Console.WriteLine(para.Text); // already assembled
// Before: filter by BlockType + traverse relationship IDs
var paragraphText = string.Join(" ", allBlocks
    .Where(b => b.BlockType == BlockType.WORD &&
                childIds.Contains(b.Id))
    .Select(b => b.Text));

// After: direct paragraph text (no filtering, no relationship lookup)
foreach (var page in result.Pages)
    foreach (var para in page.Paragraphs)
        Console.WriteLine(para.Text); // already assembled
' Before: filter by BlockType + traverse relationship IDs
Dim paragraphText As String = String.Join(" ", allBlocks _
    .Where(Function(b) b.BlockType = BlockType.WORD AndAlso _
                childIds.Contains(b.Id)) _
    .Select(Function(b) b.Text))

' After: direct paragraph text (no filtering, no relationship lookup)
For Each page In result.Pages
    For Each para In page.Paragraphs
        Console.WriteLine(para.Text) ' already assembled
    Next
Next
$vbLabelText   $csharpLabel

Für die Tabellrekonstruktion, die von BlockType.CELL und ColumnIndex-Eigenschaften abhing, verwenden Sie die Wortkoordinatengruppierung auf result.Words. Die Tabellenleseanleitung behandelt den koordinatenbasierten Ansatz.

Problem 5: Catch-Blöcke für ProvisionedThroughputExceededException

AWS Textract: Robuster Batch-Verarbeitungscode fängt AmazonTextractException mit ErrorCode == "ProvisionedThroughputExceededException" ein und implementiert eine Rückversuchsstrategie mit Rückgang. Dieser Fehlercode ist ein Textract-spezifisches Konzept, für das es kein Äquivalent in IronOCR gibt.

Lösung: Löschen Sie alle ProvisionedThroughputExceededException-Catch-Blöcke.IronOCR hat keine TPS-Beschränkungen. Ersetzen Sie das gesamte gedrosselte Batch-Muster durch Parallel.ForEach:

// Delete: SemaphoreSlim, retry loops, ProvisionedThroughputExceededException handlers
// Replace with:
Parallel.ForEach(imagePaths, path =>
{
    var result = new IronTesseract().Read(path);
    results[path] = result.Text;
});
// Delete: SemaphoreSlim, retry loops, ProvisionedThroughputExceededException handlers
// Replace with:
Parallel.ForEach(imagePaths, path =>
{
    var result = new IronTesseract().Read(path);
    results[path] = result.Text;
});
Imports System.Threading.Tasks

Parallel.ForEach(imagePaths, Sub(path)
    Dim result = New IronTesseract().Read(path)
    results(path) = result.Text
End Sub)
$vbLabelText   $csharpLabel

Problem 6: Regionskonfiguration in Client-Konstruktoren integriert

AWS Textract: new AmazonTextractClient(Amazon.RegionEndpoint.USEast1) codiert eine Region fest. Für Multi-Region-Bereitstellungen sind mehrere Clientinstanzen oder eine dynamische Regionsauswahl erforderlich. Wenn Textract die Unterstützung in neuen Regionen hinzufügt, muss der Code aktualisiert werden.

Lösung:IronOCR kennt kein Regionskonzept. Entfernen Sie alle Amazon.RegionEndpoint-Referenzen. Der IronTesseract Konstruktor benötigt keine Netzwerkkonfiguration - die Verarbeitung erfolgt lokal. Bei Multi-Region-Bereitstellungen auf AWS-Infrastruktur, bei denen jede Region ihre eigene Rechenkapazität betreibt, verarbeitet jede IronOCR Instanz Dokumente lokal innerhalb dieser Rechenkapazitätsgrenze ohne regionsübergreifende Aufrufe.

Checkliste für die AWS Textract-Migration

Vor der Migration

Überprüfen Sie die gesamte Verwendung von Textract und dem S3 SDK im Quellcode:

grep -rn "Amazon.Textract\|AmazonTextractClient\|DetectDocumentText" --include="*.cs" .
grep -rn "StartDocumentTextDetection\|GetDocumentTextDetection\|JobStatus" --include="*.cs" .
grep -rn "AmazonS3Client\|PutObjectRequest\|DeleteObjectAsync" --include="*.cs" .
grep -rn "BlockType\|RelationshipType\|KEY_VALUE_SET" --include="*.cs" .
grep -rn "ProvisionedThroughputExceededException\|AmazonTextractException" --include="*.cs" .
grep -rn "AWSSDK\|Amazon.RegionEndpoint" --include="*.csproj" .
grep -rn "Amazon.Textract\|AmazonTextractClient\|DetectDocumentText" --include="*.cs" .
grep -rn "StartDocumentTextDetection\|GetDocumentTextDetection\|JobStatus" --include="*.cs" .
grep -rn "AmazonS3Client\|PutObjectRequest\|DeleteObjectAsync" --include="*.cs" .
grep -rn "BlockType\|RelationshipType\|KEY_VALUE_SET" --include="*.cs" .
grep -rn "ProvisionedThroughputExceededException\|AmazonTextractException" --include="*.cs" .
grep -rn "AWSSDK\|Amazon.RegionEndpoint" --include="*.csproj" .
SHELL

Prüfen Sie vor dem Schreiben des Ersatzcodes Folgendes:

  • Zählen Sie alle Dateien, die AmazonTextractClient enthalten – jede ist ein Ziel zum Ersetzen
  • Listen Sie alle AnalyzeDocument-Aufrufstellen auf – notieren Sie, ob TABLES, FORMS oder beides verwendet wird
  • Identifizieren Sie alle asynchronen Polling-Schleifen (do { await Task.Delay } while JobStatus == IN_PROGRESS)
  • Finden Sie alle finally S3-Bereinigungsblöcke – diese werden vollständig gelöscht, nicht ersetzt
  • Zählen Sie alle ProvisionedThroughputExceededException-Catch-Blöcke – gelöscht, nicht ersetzt
  • Notieren Sie alle BlockType.TABLE, BlockType.CELL, BlockType.KEY_VALUE_SET-Durchquerungslogik – jeder benötigt eine strukturierte Ausgabenersetzung

Code-Migration

  1. Entfernen Sie AWSSDK.Textract, AWSSDK.S3 und AWSSDK.Core aus allen .csproj-Dateien
  2. Führen Sie dotnet add package IronOcr in jedem Projekt aus, das OCR durchführt
  3. Fügen Sie IronOcr.License.LicenseKey = ... einmal beim Anwendungsstart hinzu (Program.cs oder Äquivalent)
  4. Entfernen Sie alle using Amazon.Textract;, using Amazon.Textract.Model;, using Amazon.S3;, using Amazon.Runtime;-Anweisungen
  5. Fügen Sie using IronOcr; zu jeder Datei hinzu, die zuvor Textract-Namensräume importierte
  6. Ersetzen Sie alle AmazonTextractClient-Konstruktoraufrufe mit new IronTesseract()
  7. Ersetzen Sie alle AmazonS3Client-Instanzierungen und alle PutObjectAsync / DeleteObjectAsync-Aufrufe durch direkte Dateipfad- oder Streamlesungen
  8. Ersetzen Sie alle DetectDocumentTextAsync-Aufrufe: var text = ocr.Read(path).Text
  9. Ersetzen Sie alle StartDocumentTextDetectionAsync + Polling-Schleifen mit input.LoadPdf(path) + ocr.Read(input)
  10. Ersetzen Sie alle mehrrahmigen TIFF-Pipelines (TIFF → PDF → S3 → async) mit input.LoadImageFrames(tiffPath)
  11. Ersetzen Sie BlockType.LINE / BlockType.WORD-Filterketten mit result.Lines / result.Words
  12. Ersetzen Sie die BlockType.KEY_VALUE_SET-Beziehungsdurchquerung mit CropRectangle-Zonenextraktion
  13. Löschen Sie alle ProvisionedThroughputExceededException-Catch-Blöcke und SemaphoreSlim-Drosselungen
  14. Ersetzen Sie gedrosselte Batch-Muster mit Parallel.ForEach unter Verwendung einer gemeinsam genutzten IronTesseract-Instanz
  15. Entfernen Sie sämtliche Konfigurationen von AWS-Anmeldeinformationen in den Umgebungsvariablen aus den Bereitstellungsmanifesten und CI-Pipelines.
  16. S3-Staging-Buckets außer Betrieb nehmen, nachdem der gesamte Verarbeitungscode migriert und verifiziert wurde.

Nach der Migration

  • Überprüfen Sie, ob die Anwendung sauber startet, wenn nur IRONOCR_LICENSE gesetzt ist und alle AWS-Umgebungsvariablen entfernt wurden
  • Führen Sie die OCR-Analyse auf denselben Beispielbildern durch, die zuvor von Textract verarbeitet wurden, und vergleichen Sie den extrahierten Text auf Genauigkeit.
  • Mehrseitige PDFs direkt verarbeiten und überprüfen, ob alle Seiten ohne S3 oder asynchrones Polling extrahiert wurden
  • Verarbeiten Sie eine TIFF-Datei mit mehreren Einzelbildern und überprüfen Sie, ob die Seitenzahl mit der Ausgabe von Textract für dasselbe Dokument übereinstimmt.
  • Testen Sie die Batch-Verarbeitung mit einem Satz von 50+ Bildern und bestätigen Sie, dass kein ProvisionedThroughputExceededException-Äquivalent auftritt
  • Führen Sie die Anwendung in einem Docker-Container ohne ausgehenden Internetzugang aus und überprüfen Sie, ob die OCR-Operation erfolgreich abgeschlossen wurde.
  • Überprüfen Sie, ob die durchsuchbare PDF-Ausgabe in Adobe Reader korrekt geöffnet wird und die Volltextsuche möglich ist.
  • Bestätigen Sie, dass das Entfernen von AWS_ACCESS_KEY_ID und AWS_SECRET_ACCESS_KEY aus der Bereitstellungsumgebung nichts beschädigt
  • Testen Sie beliebige Workflows zur Formular- oder Rechnungsextraktion anhand bekannter Ausgaben von Textract, um die Feldgenauigkeit zu validieren.
  • Messung der Verarbeitungslatenz für einen repräsentativen Dokumentensatz und Bestätigung der Beseitigung der minimalen Abfragewartezeit von 5 Sekunden.

Wichtigste Vorteile der Migration zu IronOCR

Infrastrukturkonsolidierung zu einem einzigen NuGet Paket. Drei AWS SDK-Pakete, ein S3-Bucket mit Lebenszyklusrichtlinien, IAM-Rollen in jeder Bereitstellungsumgebung und AWS-Anmeldeinformationsrotationsverfahren werden durch ein einziges NuGet Paket ersetzt. Die .csproj-Änderung ist dotnet remove package AWSSDK.Textract gefolgt von dotnet add package IronOcr. Alle Folgeerscheinungen der AWS-Anmeldeinformationsverwaltung – Sicherheitsaudits, Rotationspläne, Umgebungsvariablenhygiene, Docker-Secrets-Konfiguration – verschwinden damit.

Vorhersehbare Gesamtbetriebskosten. Das Abrechnungsmodell pro Seite erzeugt variable Kosten, die mit dem Dokumentenvolumen unbegrenzt skalieren. Nach der Migration zu IronOCR betragen die Kosten pro zusätzlich verarbeiteter Seite unabhängig vom Volumen null. Ein Team, das 200.000 Seiten pro Monat zum Textract AnalyzeDocument-Satz für Tabellen verarbeitet, gibt 36.000 $ pro Jahr allein für OCR aus. Mit der IronOCR Enterprise Lizenz für 2.999 US-Dollar amortisieren sich diese Kosten in weniger als 31 Tagen durch vermiedene Rechnungsstellung. Auf der IronOCR -Lizenzseite finden Sie alle Details zu den Lizenzstufen und den SaaS-Weitervertriebsbedingungen.

Synchrone Verarbeitung eliminiert die fünfphasige asynchrone Komplexität. Der S3-Upload, der Jobstart, die Abfrageschleife, die paginierte Ergebniserfassung und der abschließende Bereinigungsblock stellen fünf unabhängige Fehlermodi in der Textract-PDF-Pipeline dar. Nach der Migration wird eine PDF-Datei in zwei Zeilen eingelesen. Die Fehlerbehandlung reduziert sich auf ein einzelnes try/catch um den ocr.Read()-Aufruf. Die Polling-Timeout-Logik, die do/while JobStatus == IN_PROGRESS Schleife, der nextToken Paginierungsakkumulator – keines dieser Konzepte existiert im IronOCR-Codebas. Der Wartungsaufwand sinkt proportional zum entfernten Code.

Vollständige Flexibilität bei der Bereitstellung. Textract benötigt bei jedem OCR-Aufruf einen Internetzugang.IronOCR benötigt nur zum Herunterladen des NuGet Pakets während der Installation einen Internetzugang. Danach funktioniert es in abgeschotteten Netzwerken, Docker-Containern ohne ausgehende Regeln, auf lokalen Servern und AWS EC2-Instanzen ohne Textract-Zugriff. Dieselbe Binärdatei, die im Produktivbetrieb auf Windows Server läuft, läuft auch in einem Linux-Container. Der Docker-Bereitstellungsleitfaden und der Linux-Bereitstellungsleitfaden beschreiben die spezifische Konfiguration für containerisierte Umgebungen, die bisher von der Nutzung von Textract ausgeschlossen waren.

Datensouveränität wird durch die Architektur, nicht durch die Konfiguration erreicht.IronOCR verfügt über keinen deaktivierbaren Netzwerkübertragungsmodus – die lokale Verarbeitung ist der einzige Modus. Mit IronOCR verarbeitete Dokumente verlassen niemals den Host-Rechner. Für Anwendungen im Gesundheitswesen, die PHI (geschützte Gesundheitsdaten) verarbeiten, für Anwendungen im Verteidigungsbereich, die CUI (kontrollierte unleserliche Informationen) verarbeiten, und für Finanzanwendungen, die Zahlungskartenbilder verarbeiten, bietet dies eine Compliance-Lösung, die keine BAA-Verhandlung (Business Associate Agreement), keine Konfiguration des regionalen Datenspeicherorts und keine Prüfung der Datenschutzrichtlinien von Amazon erfordert. Der Geltungsbereich der Compliance entspricht den Grenzen Ihrer eigenen Infrastruktur, die Sie bereits kontrollieren. Auf der IronOCR -Produktseite finden Teams, die eine Compliance-Prüfung durchführen, eine vollständige Funktionsübersicht und die Bereitstellungsdokumentation.

Konfigurierbare Vorverarbeitung für die Dokumentenqualitätskontrolle. Die interne Vorverarbeitung von Textract ist weder zugänglich noch konfigurierbar. Wenn ein gescanntes Dokument ein schlechtes Ergebnis liefert, bleibt nur die Möglichkeit, das Ergebnis zu akzeptieren oder erneut zu scannen.IronOCR deckt die gesamte Vorverarbeitungspipeline ab: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), Erode() und DeepCleanBackgroundNoise(). Teams, die Dokumente aufgrund von unzuverlässigen Ergebnissen bei schwierigen Scans aus Textract verschoben haben, können die Ursache direkt mit Filtern beheben, die auf den jeweiligen Defekt abgestimmt sind – Rotation, Rauschen, geringer Kontrast oder unzureichende Auflösung. Die Seite zu den Vorverarbeitungsfunktionen und der Leitfaden zur Bildqualitätskorrektur dokumentieren die verfügbaren Filter und ihre jeweiligen Anwendungsfälle.

Hinweis:AWS Textract, PDFSharp, Tesseract und iText sind eingetragene Marken ihrer jeweiligen Eigentümer. Diese Website ist nicht mit Amazon Web Services, Google, empira Software GmbH oder iText Group verbunden, anerkannt oder unterstützt. Alle Produktnamen, Logos und Marken sind Eigentum ihrer jeweiligen Inhaber. Vergleiche dienen nur zu Informationszwecken und spiegeln öffentlich zugängliche Informationen zum Zeitpunkt des Schreibens wider.

Häufig gestellte Fragen

Warum sollte ich von Amazon Textract zu IronOCR migrieren?

Zu den häufigsten Gründen gehören die Beseitigung der Komplexität der COM-Interoperabilität, die Ablösung der dateibasierten Lizenzverwaltung, die Vermeidung der seitenweisen Abrechnung, die Ermöglichung der Docker-/Container-Bereitstellung und die Einführung eines NuGet-nativen Workflows, der sich in die Standard-.NET-Werkzeuge integriert.

Was sind die wichtigsten Code-Änderungen bei der Migration von Amazon Textract zu IronOCR?

Ersetzen Sie die Initialisierungssequenzen von Amazon Textract durch die Instanziierung von IronTesseract, entfernen Sie das COM-Lebenszyklusmanagement (explizite Create/Load/Close-Muster) und aktualisieren Sie die Namen der Ergebniseigenschaften. Das Ergebnis sind deutlich weniger Boilerplate-Zeilen.

Wie installiere ich IronOCR, um die Migration zu beginnen?

Führen Sie 'Install-Package IronOcr' in der Paketmanager-Konsole oder 'dotnet add package IronOcr' in der CLI aus. Sprachpakete sind separate Pakete: 'dotnet add package IronOcr.Languages.French' für Französisch, zum Beispiel.

Kann IronOCR die OCR-Genauigkeit von Amazon Textract für Standardgeschäftsdokumente erreichen?

IronOCR erzielt eine hohe Genauigkeit bei Standardgeschäftsinhalten wie Rechnungen, Verträgen, Quittungen und getippten Formularen. Bildvorverarbeitungsfilter (Schräglagenkorrektur, Rauschunterdrückung, Kontrastverbesserung) verbessern die Erkennungsgenauigkeit bei verschlechterten Eingaben weiter.

Wie geht IronOCR mit den Sprachdaten um, die von Amazon Textract separat installiert werden?

Die Sprachdaten in IronOCR werden als NuGet-Pakete verteilt. mit 'dotnet add package IronOcr.Languages.German' wird die deutsche Unterstützung installiert. Es sind keine manuellen Dateiplatzierungen oder Verzeichnispfade erforderlich.

Erfordert die Migration von Amazon Textract zu IronOCR Änderungen an der Bereitstellungsinfrastruktur?

IronOCR erfordert weniger Änderungen an der Infrastruktur als Amazon Textract. Es gibt keine SDK-Binärpfade, keine Platzierung von Lizenzdateien oder Lizenzserverkonfigurationen. Das NuGet-Paket enthält die komplette OCR-Engine, und der Lizenzschlüssel ist eine im Anwendungscode festgelegte Zeichenfolge.

Wie konfiguriere ich die IronOCR-Lizenzierung nach der Migration?

Weisen Sie IronOcr.License.LicenseKey = "YOUR-KEY" im Startup-Code der Anwendung zu. In Docker oder Kubernetes speichern Sie den Schlüssel als Umgebungsvariable und lesen ihn beim Starten aus. Verwenden Sie License.IsValidLicense zur Validierung, bevor Sie den Datenverkehr akzeptieren.

Kann IronOCR PDFs auf die gleiche Weise verarbeiten wie Amazon Textract?

Ja, IronOCR liest sowohl native als auch gescannte PDFs. Instanziieren Sie IronTesseract, rufen Sie ocr.Read(input) auf, wobei input ein PDF-Pfad oder OcrPdfInput ist, und iterieren Sie die OcrResult-Seiten. Es ist keine separate PDF-Rendering-Pipeline erforderlich.

Wie handhabt IronOCR das Threading bei der Verarbeitung großer Datenmengen?

IronTesseract kann sicher pro Thread instanziiert werden. Sie können eine Instanz pro Thread in einem Parallel.ForEach- oder Task-Pool aufsetzen, OCR gleichzeitig ausführen und jede Instanz nach Abschluss entsorgen. Es ist kein globaler Zustand oder Sperren erforderlich.

Welche Ausgabeformate unterstützt IronOCR nach der Textextraktion?

IronOCR liefert strukturierte Ergebnisse mit Text, Wortkoordinaten, Konfidenzwerten und Seitenstruktur. Zu den Exportoptionen gehören reiner Text, durchsuchbare PDF-Dateien und strukturierte Ergebnisobjekte für die nachgeschaltete Verarbeitung.

Ist die Preisgestaltung von IronOCR für die Skalierung von Arbeitslasten berechenbarer als die von Amazon Textract?

IronOCR verwendet eine pauschale, unbefristete Lizenzierung ohne Seiten- oder Volumengebühren. Egal, ob Sie 10.000 oder 10 Millionen Seiten verarbeiten, die Lizenzkosten bleiben konstant. Optionen für Volumen- und Teamlizenzen finden Sie auf der IronOCR-Preisseite.

Was passiert mit meinen bestehenden Tests nach der Migration von Amazon Textract zu IronOCR?

Tests, die extrahierte Textinhalte überprüfen, sollten auch nach der Migration bestehen. Tests, die API-Aufrufmuster oder den Lebenszyklus von COM-Objekten validieren, müssen aktualisiert werden, um das einfachere Initialisierungs- und Ergebnismodell von IronOCR widerzuspiegeln.

Kannaopat Udonpant
Software Ingenieur
Bevor er Software-Ingenieur wurde, absolvierte Kannapat ein PhD in Umweltressourcen an der Hokkaido University in Japan. Während seines Studiums wurde Kannapat auch Mitglied des Vehicle Robotics Laboratory, das Teil der Fakultät für Bioproduktionstechnik ist. Im Jahr 2022 nutzte er seine C#-Kenntnisse, um dem Engineering-Team von Iron Software ...
Weiterlesen

Iron-Support-Team

Wir sind 24 Stunden am Tag, 5 Tage die Woche online.
Chat
E-Mail
Rufen Sie mich an