Przejdź do treści stopki
FILMY

Migracja z AWS Textract do IronOCR

Ten przewodnik przedstawia kompletną migrację zAWS Textractdo IronOCR dla .NET dla programistów .NET, którzy potrzebują lokalnego przetwarzania dokumentów bez zależności od chmury. Obejmuje to usuwanie danych uwierzytelniających, usuwanie potoku asynchronicznego, eliminację S3 oraz konkretne zamiany kodu wymagane do przeniesienia każdej operacji Textract do jej odpowiednika w IronOCR.

Dlaczego warto przejść z AWS Textract

AWS Textract to wydajna usługa zarządzana, ale jej architektura wiąże się z kosztami — finansowymi i operacyjnymi — które rosną wraz ze wzrostem obciążenia dokumentami. Zespoły tworzące zaawansowane procesy przetwarzania dokumentów w końcu napotykają wszystkie te przeszkody.

Infrastruktura poświadczeń AWS komplikuje każde wdrożenie. Każde środowisko, w którym działa kod Textract, wymaga poświadczeń AWS: użytkownika lub roli IAM, klucza dostępu i sekretu, konfiguracji regionu, a w przypadku przetwarzania plików PDF dodatkowo uprawnień do zasobnika S3. Oznacza to pięć odrębnych kroków konfiguracyjnych przed przetworzeniem pierwszej strony. Produkcja wdrożeń wymaga zasad rotacji poświadczeń, bezpiecznego wstrzykiwania do kontenerów Docker lub podów Kubernetes oraz monitorowania dla AccessDeniedException, gdy IAM policy drift powoduje ciche awarie.IronOCR wymaga jednego ciągu znaków: klucza licencyjnego, ustawianego jednorazowo podczas uruchamiania.

Opłata za stronę nie ma ograniczeń. Podstawowa stawka $0,0015 za stronę dla DetectDocumentText to dolna granica, a nie koszt. Każdy dokument z tabelami uruchamia AnalyzeDocument za $0,015 za stronę — dziesięć razy więcej niż stawka podstawowa. Formularze zwiększają cenę o kolejne 0,05 USD za stronę. Mieszany przepływ dokumentów wynoszący 100 000 stron miesięcznie z ekstrakcją tabel kosztuje 18 000 USD rocznie, a kwota ta jest resetowana każdego stycznia. Licencja IronOCR Professional kosztuje jednorazowo 2999 USD. Następnie koszt za stronę wynosi zero, niezależnie od objętości.

Asynchroniczny potok PDF jest obciążeniem utrzymaniowym. Przetwarzanie dowolnego dokumentu PDF wielostronicowego przez Textract wymaga pięciu odrębnych faz: przesłania do S3, StartDocumentTextDetection, pętli ankietowej, pobierania wyników ze stronicowaniem oraz czyszczenia S3. Każda faza to niezależny tryb awarii wymagający własnej obsługi błędów, strategii ponownych prób i zarządzania czasem oczekiwania. Zespoły, które wdrażają ten proces do produkcji, konsekwentnie zgłaszają, że poświęcają więcej czasu na jego utrzymanie niż na jego stworzenie.IronOCR odczytuje plik PDF za pomocą dwóch wierszy kodu.

Brak dostępu do Internetu oznacza brak Textract. Kontenery Docker w izolowanych segmentach sieci, serwery lokalne, środowiska odizolowane oraz systemy przemysłowe z ograniczeniami ruchu wychodzącego mają jedną wspólną cechę: Textract jest niedostępny.IronOCR instaluje się jako pakiet NuGet i działa bez żadnych połączeń sieciowych po początkowym pobraniu pakietu.

Dane opuszczają Twoją infrastrukturę przy każdym wywołaniu. Każda operacja OCR w Textract przesyła zawartość dokumentu na serwery Amazon. Dla organizacji opieki zdrowotnej przetwarzających dane medyczne (PHI), wykonawców z sektóra obronnego zajmujących się informacjami o znaczeniu krytycznym (CUI), zespołów prawnych przetwarzających poufną korespondencję oraz instytucji finansowych przetwarzających obrazy kart płatniczych nie jest to opcja konfiguracyjna — jest to ograniczenie architektoniczne, które całkowicie uniemożliwia stosowanie Textract w środowiskach podlegających regulacjom.IronOCR działa na Twoim sprzęcie. Nie ma trybu chmury, który można by wyłączyć; Jedynym trybem jest przetwarzanie lokalne.

Limity stawki ograniczają przepustowość zgrupowań. Domyślny limit StartDocumentTextDetection TPS wynosi 5 żądań na sekundę. Prace wsadowe przetwarzające setki dokumentów wymagają SemaphoreSlim throttlingu, wykładniczego cofania się na ProvisionedThroughputExceededException oraz liczników odzyskiwania stawki. Wniosek o zwiększenie limitu TPS wymaga złożenia formalnego zgłoszenia do pomocy technicznej AWS, przy czym nie ma gwarancji pozytywnego rozpatrzenia.IronOCR przetwarza tak szybko, jak pozwala lokalna CPU — serwer z 16 rdzeniami przetwarza 16 dokumentów jednocześnie przy użyciu zwykłego Parallel.ForEach, bez konieczności negocjacji poziomów usług.

Podstawowy problem

Każde wdrożenie Textract rozpoczyna się od tej ceremonii — zanim będzie można przetworzyć choćby jedną stronę:

// 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 zastępuje cały łańcuch poświadczeń pojedynczym przypisaniem:

// 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 a AWS Textract: porównanie funkcji

Poniższa tabela przedstawia możliwości obu bibliotek w aspektach najbardziej istotnych dla decyzji dotyczących migracji.

Funkcja AWS Textract IronOCR
Miejsce przetwarzania Amazon Cloud (obowiązkowe) Tylko lokalnie / na miejscu
Wymagane połączenie z Internetem Zawsze Nigdy
Konfiguracja poświadczeń Użytkownik/rola IAM + opcjonalny zasób S3 Pojedynczy ciąg klucza licencyjnego
Wielostronicowy plik PDF Wymagane jest środowisko testowe S3 + zadanie asynchroniczne Bezpośredni synchroniczny input.LoadPdf()
Plik PDF chroniony hasłem Nieobsługiwane input.LoadPdf(path, Password: "x")
TIFF wieloklatkowy Nieobsługiwane input.LoadImageFrames("multi.tiff")
Wymagane asynchroniczne odpytywanie Tak (dla wszystkich plików PDF) Nie — domyślnie synchroniczne
Limity częstotliwości 5 TPS domyślnie (StartDocumentTextDetection) Brak — tylko obciążenie procesora
Koszt za stronę 0,0015–0,065 USD za stronę 0 USD po zakupie licencji
Automatyczne przetwarzanie wstępne Wewnętrzne, niekonfigurowalne Wyrównanie, usuwanie szumów, kontrast, binarizacja, wyostrzanie, skalowanie
Wyjście w formacie PDF z możliwością wyszukiwania Niedostępne result.SaveAsSearchablePdf()
Eksport hOCR Niedostępne result.SaveAsHocrFile()
Odczytywanie BarCode Niedostępne ocr.Configuration.ReadBarCodes = true
OCR oparte na regionie Za pomocą AnalyzeDocument + relacje blokowe CropRectangle(x, y, width, height)
Wyniki uporządkowane Płaski List<Block> filtrowany przez BlockType Typowany Pages, Paragraphs, Lines, Words
Obsługiwane języki Dominujący język angielski (wybierz dodatkowy) Ponad 125 pakietów językowych dostępnych za pośrednictwem NuGet
Wielojęzyczne tłumaczenie symultaniczne Nieobsługiwane OcrLanguage.French + OcrLanguage.German
Wdrożenie w środowisku izolowanym Niemożliwe W pełni obsługiwane
HIPAA bez BAA Niemożliwe Brak zewnętrznego procesora — wyłącznie lokalnie
Wdrażanie Docker Wymagane są wstrzyknięte poświadczenia AWS Brak poświadczeń — zwykły pakiet NuGet
Wielopłatformowe Zarządzane przez AWS (tylko Linux) Windows, Linux, macOS, Docker, Azure, AWS EC2
Model licencyjny Rozliczenie według liczby stron (wydatki bieżące) Wieczysta jednorazowa ($999 / $1 499 / $2 999)

Szybki start: Migracja zAWS Textractdo IronOCR

Krok 1: Zastąp pakiet NuGet

Usuń pakiety AWS SDK:

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

Zainstaluj IronOCR z NuGet:

dotnet add package IronOcr

Krok 2: Aktualizacja przestrzeni nazw

Zastąp wszystkie importy przestrzeni nazw AWS pojedynczą przestrzenią nazw 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;
// 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

Krok 3: Inicjalizacja licencji

Dodaj inicjalizację licencji jednorazowo podczas uruchamiania aplikacji. Usuń wszystkie ustawienia zmiennych środowiskowych poświadczeń AWS:

// 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

Przykłady migracji kodu

Zastąpienie konstruktóra AmazonTextractClient i konfiguracji IAM

Najbardziej widoczną zmianą związaną z migracją jest inicjalizacja klienta. Textract wymaga klienta z wstrzykiwaniem zależności i podstawową obsługą uwierzytelniania — oznacza to, że zarówno klient, jak i wszystkie jego zależności muszą być wstępnie skonfigurowane w każdym środowisku wdrożeniowym. Każda błędna konfiguracja zawiedzie cicho, dopóki pierwsze wywołanie OCR nie wyrzuci AmazonClientException.

Podejście AWS Textract:

// 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

Podejście IronOCR:

// 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

Cała infrastruktura poświadczeń AWS — role IAM, zmienne środowiskowe, pliki ~/.aws/credentials, polityki okresu życia bucketów S3 oraz konfiguracja regionu — jest usuwana. Konstruktor DocumentOcrService przechodzi z 3-miejscowej wstrzykiwanego miejsca zależności z trybami awarii w czasie wykonywania do pojedynczego przypisania licencji. Szczegółowe informacje na temat wzorców wdrażania można znaleźć w przewodniku konfiguracji IronTesseract.

Zastąpienie funkcji StartDocumentTextDetection przetwarzaniem wieloklatkowym TIFF

Asynchroniczne API zadań Textract (StartDocumentTextDetection) jest wymagane dla dowolnego dokumentu wielostronicowego. Typowym schematem w procesach digitalizacji dokumentów jest otrzymywanie zeskanowanych dokumentów w postaci wielo-ramkowych plików TIFF — jedna ramka na każdą zeskanowaną stronę. Textract w ogóle nie akceptuje plików TIFF jako danych wejściowych; Przed rozpoczęciem zadania dokument musi zostać przekonwertowany do formatu PDF i przesłany do S3.IronOCR natywnie odczytuje pliki TIFF zawierające wiele klatek.

Podejście AWS Textract:

// 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

Podejście IronOCR:

//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

Eliminuje się etap konwersji plików TIFF do formatu PDF, przesyłanie do S3, asynchroniczną pętlę odpytywania, gromadzenie wyników w postaci stron oraz czyszczenie S3. Implementacja IronOCR odczytuje ramki bezpośrednio i zapewnia dostęp do każdej strony przez result.Pages bez żadnej pośredniej konwersji formatu. Przewodnik dotyczący plików wejściowych TIFF i GIF obejmuje wybór klatek w przypadkach, gdy potrzebny jest tylko podzbiór klatek.

Zastąpienie S3 Staging przez pliki PDF z funkcją wyszukiwania

Typowym zastosowaniem Textract jest konwersja zeskanowanych archiwów PDF do formatu umożliwiającego wyszukiwanie. Podejście Textract wymaga przesłania każdego pliku PDF do S3, uruchomienia zadania asynchronicznego, zebrania wyodrębnionego tekstu, a następnie użycia oddzielnej biblioteki PDF do osadzenia tego tekstu jako warstwy z możliwością wyszukiwania.IronOCR skanuje i tworzy plik PDF z możliwością wyszukiwania w ramach jednego wywołania.

Podejście AWS Textract:

// 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

Podejście IronOCR:

//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

Logika limitu czasu odpytywania, wzorzec przesyłania i czyszczenia S3 oraz zależność od zewnętrznej biblioteki PDF zostały usunięte. SaveAsSearchablePdf bezpośrednio osadza rozpoznany tekst w pliku wyjściowym, dzięki czemu każda zeskanowana strona jest pełnotekstowo przeszukiwana bez potrzeby drugiej biblioteki. Przewodnik w formacie PDF z funkcją wyszukiwania obejmuje wybór stron, opcje kompresji oraz osadzanie metadanych. Szerszy kontekst procesów OCR plików PDF można znaleźć w przewodniku dotyczącym plików PDF.

Zastąpienie analizy dokumentu poprzez przeglądanie grafu bloków wynikami stron ustrukturyzowanych

Textract's AnalyzeDocument z TABLES i FORMS zwraca płaski List<Block>, gdzie każdy element — linie, słowa, komórki, nagłówki tabel, klucze formularzy, wartości formularzy — jest tego samego Block typu rozróżnianego tylko przez BlockType i połączonego przez tablice ID RelationshipType.CHILD. Odtworzenie struktury logicznej dokumentu wymaga przejścia przez ten graf relacji.IronOCR zwraca hierarchię tekstową: strony, akapity, wiersze, słowa, z których każdy ma dostęp do współrzędnych.

Podejście AWS Textract:

// 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

Podejście IronOCR:

//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

Akapity, linie i słowa są bezpośrednio dostępne jako typowane kolekcje z .X, .Y, .Width, .Height, i .Confidence właściwościami. Bez normalizacji BoundingBox, bez heurystyki progu odstępów, bez filtrowania typów bloków. Przewodnik po wynikach odczytu dokumentuje pełną hierarchię OcrResult, w tym dostęp do współrzędnych na poziomie znaku. W przypadku procesów związanych z fakturami i paragonami, które opierają się na tej strukturze, zapoznaj się z samouczkiem dotyczącym OCR faktur.

Zastąpienie przetwarzania wsadowego z ograniczeniami szybkości nieograniczonym wykonywaniem równoległym

Przetwarzanie wsadowe obrazów to miejsce, gdzie limity TPS Textract generują najbardziej widoczne koszty operacyjne. Synchronous API DetectDocumentText jest ograniczone przez stawkę; wysyłanie więcej niż 5 żądań na sekundę generuje ProvisionedThroughputExceededException. Poprawna obsługa wsadów wymaga SemaphoreSlim throttlingu, logiki powtórzeń z cofnięciem oraz starannego księgowania dla będących w toku żądań.IronOCR nie ma ograniczeń szybkości — przepustowość jest ograniczona jedynie dostępnymi rdzeniami procesora.

Podejście AWS Textract:

// 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

Podejście IronOCR:

// 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

The SemaphoreSlim, pętla powtórzeń, wykładnicze cofnięcie i bloki przechwytywania ProvisionedThroughputExceededException są usunięte. IronTesseract jest bezpieczny w kontekście wątków, a pojedyncza instancja udostępniona między wątkami obsługuje pełne obciążenie równoległe bez blokad. Na komputerze z 8 rdzeniami przetwarza to 8 dokumentów jednocześnie bez żadnej konfiguracji; na 32 rdzeniach, 32 jednocześnie. Przykład wielowątkowości pokazuje kompletny wzorzec, a przewodnik po optymalizacji szybkości obejmuje dostosowanie konfiguracji silnika dla wdrożeń zorientowanych na przepustowość.

Zastąpienie funkcji AnalyzeDocument Form Extraction funkcją Region-Based CropRectangle OCR

Textract's AnalyzeDocument z FORMS zwraca bloki KEY_VALUE_SET, połączone relacjami RelationshipType.VALUE. Odtwarzanie par klucz-wartość pól formularzy wymaga filtrowania przez EntityTypes.Contains("KEY"), podążania według identyfikatorów relacji VALUE, a następnie pobierania bloków potomnych WORD w celu złożenia tekstu. W przypadku formularzy o stałym formacie, gdzie pozycje pól są znane, CropRectangleIronOCR wyodrębnia każdy element bezpośrednio, bez konieczności przeglądania grafów relacji — i kosztuje zero za stronę.

Podejście AWS Textract:

// 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

Podejście IronOCR:

// 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

Filtrowanie bloków KEY_VALUE_SET, przechodzenie z RelationshipType.VALUE oraz GetTextFromBlock pomocnicze są eliminowane. Każde pole odczytuje dokładnie ten obszar pikseli, który je zawiera — ani więcej, ani mniej. Przewodnik po OCR bazującym na regionie obejmuje współrzędne CropRectangle oraz jak definiować strefy z wymiarów szablonów. W przypadku procesów związanych z fakturami, wpis na blogu o OCR faktur oraz samouczek skanowania paragonów pokazują pełne procesy ekstrakcji danych z pól.

Dokumentacja APIAWS Textractdo IronOCR

AWS Textract Odpowiednik IronOCR
AmazonTextractClient IronTesseract
AmazonS3Client Nie jest wymagane
DetectDocumentTextRequest OcrInput z input.LoadImage()
DetectDocumentTextResponse OcrResult
AnalyzeDocumentRequest (TABLES/FORMS) OcrInput z opcjonalnym CropRectangle
StartDocumentTextDetectionRequest OcrInput z input.LoadPdf() — synchroniczne, bez uruchamiania zadań
GetDocumentTextDetectionRequest Nie dotyczy — wyniki są natychmiastowe
Document.Bytes input.LoadImage(byteArray) lub input.LoadImage(stream)
S3Object (staging dokumentów) Ścieżka pliku lub strumień — nie jest wymagane przygotowanie
Block (BlockType.LINE) result.Lines (typowana kolekcja)
Block (BlockType.WORD) result.Words (typowana kolekcja)
Block (BlockType.TABLE) result.Words pogrupowane według bliskości współrzędnych
Block (BlockType.KEY_VALUE_SET) CropRectangle wyodrębnianie regionu na pole
Block.Confidence word.Confidence / result.Confidence
Block.Geometry.BoundingBox word.X, word.Y, word.Width, word.Height
RelationshipType.CHILD przegląd Bezpośredni dostęp do właściwości w obiektach wynikowych typu
JobStatus.IN_PROGRESS Nie dotyczy — brak stanu asynchronicznego
JobStatus.SUCCEEDED Nie dotyczy — zwrot synchroniczny
response.NextToken (stronicowanie) Nie dotyczy — wyniki nie są podzielone na strony
ProvisionedThroughputExceededException Nie dotyczy — brak limitów TPS
AccessDeniedException Nie dotyczy — brak łańcucha poświadczeń
input.LoadImageFrames() Bezpośrednia obsługa plików TIFF z wieloma ramkami (bez odpowiednika Textract)
result.SaveAsSearchablePdf() Wynik w formacie PDF z możliwością wyszukiwania (bez odpowiednika Textract)

Typowe problemy związane z migracją i ich rozwiązania

Problem 1: Nieprawidłowa konfiguracja poświadczeń w nowych środowiskach wdrożeniowych

AWS Textract: Konstruktor AmazonTextractClient rozwiązuje poświadczenia z łańcucha: zmienne środowiskowe, ~/.aws/credentials, profil instancji EC2, rola zadania ECS. W nowym kontenerze Docker lub środowisku CI, jeśli żadne z tych elementów nie są skonfigurowane, klient skonstruuje się bez błędu, ale każde wywołanie API wyrzuci AmazonClientException: No credentials specified. Błąd jest niewidoczny aż do momentu uruchomienia.

Rozwiązanie: Całkowicie usuń łańcuch poświadczeń. Ustaw klucz licencyjny IronOCR w zmiennej środowiskowej, która jest łatwiejsza w konfiguracji i nie wymaga zarządzania uprawnieniami IAM:

// 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: Miejsca wywołań asynchronicznych w całym kodzie źródłowym

AWS Textract: Ponieważ całe SDK jest wyłącznie asynchroniczne (DetectDocumentTextAsync, StartDocumentTextDetectionAsync, GetDocumentTextDetectionAsync), wszystkie miejsca wywołania Textract propagują async Task<t> przez stos wywołań. Przejście na synchroniczne API IronOCR wymaga podjęcia decyzji w każdej lokalizacji.

Rozwiązanie: W przypadku usług przetwarzania w tle i akcji kontrolera synchroniczne wywołania IronOCRsą bezpieczne w wątkach działających w tle. Opakuj w Task.Run tylko wtedy, gdy istniejący łańcuch wywołań musi pozostać asynchroniczny:

// 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

Dla przetworników wsadowych i usług działających w tle, które już uruchomione są na wątkach nie-UI, bezpośrednio wywołaj ocr.Read() synchronicznie — Task.Run dodaje obciążenie bez korzyści w tych kontekstach. Przewodnik po asynchronicznym OCR zawiera zalecane wzorce.

Problem 3: Logika czyszczenia zasobników S3 rozproszona w kodzie

AWS Textract: Każdy kod przesyłany do S3 w celu przetworzenia przez Textract musi zostać usunięty z S3 po zakończeniu zadania. To czyszczenie często żyje w blokach finally i czasami jest pomijane na ścieżkach błędów, powodując osierocone obiekty, które gromadzą koszty przechowywania. Podczas migracji łatwo przeoczyć kod czyszczący S3, ponieważ nie jest on koncepcyjnie powiązany z OCR.

Rozwiązanie: Cały wzorzec przesyłania i czyszczenia w S3 nie ma odpowiednika w IronOCR. Usuń całkowicie wszystkie bloki PutObjectAsync, DeleteObjectAsync, i S3 związane z blokami try/finally.IronOCR odczytuje dane bezpośrednio z lokalnych ścieżek lub strumieni:

// 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

Po migracji należy wyłączyć zasób S3 staging bucket. Przewodnik po danych wejściowych w formacie PDF oraz przewodnik po danych wejściowych strumieniowych obejmują wszystkie wzorce danych wejściowych, które zastępują dostęp do dokumentów w fazie S3.

Problem 4: Kod przechodzenia przez wykres blokowy nie ma bezpośredniego odpowiednika

AWS Textract: Kod przechodzący przez Block.Relationships aby odtworzyć komórki tabel, pola formularzy lub grupowania akapitów z tablic ID RelationshipType.CHILD jest najbardziej czasochłonnym kodem do zmigrowania. Nie ma jednoznacznego zamiennika, ponieważIronOCR zwraca kolekcje typów zamiast grafu relacji.

Rozwiązanie: Zastąp każdy wzorzec traversal odpowiednią właściwością typu. Wyszukiwanie identyfikatorów relacji staje się bezpośrednim dostępem do właściwości:

// 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

Do rekonstrukcji tabel, które polegały na BlockType.CELL oraz właściwościach ColumnIndex, należy użyć grupowania według współrzędnych słów na result.Words. Przewodnik po czytaniu tabeli obejmuje podejście oparte na współrzędnych.

Problem 5: Bloki przechwytujące wyjątek ProvisionedThroughputExceededException

AWS Textract: Solidny kod przetwarzania wsadowego przechwytuje AmazonTextractException z ErrorCode == "ProvisionedThroughputExceededException" i implementuje ponowne próby z cofnięciem. Ten kod błędu jest pojęciem specyficznym dla Textract i nie ma jego odpowiednika w IronOCR.

Rozwiązanie: Usuń wszystkie bloki przechwytywania ProvisionedThroughputExceededException.IronOCR nie ma ograniczeń dotyczących TPS. Zastąp cały wzorzec ograniczonej partii z 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: Konfiguracja regionu wbudowana w konstruktory klienta

AWS Textract: new AmazonTextractClient(Amazon.RegionEndpoint.USEast1) hardcodes a region. Wdrożenia wieloregionalne wymagają wielu instancji klienta lub dynamicznego wyboru regionu. Gdy Textract rozszerzy obsługę o nowe regiony, kod musi zostać zaktualizowany.

Rozwiązanie:IronOCR nie ma pojęcia regionu. Usuń wszystkie referencje Amazon.RegionEndpoint. Konstruktor IronTesseract nie przyjmuje żadnej konfiguracji sieciowej — przetwarzanie jest lokalne. W przypadku wdrożeń wieloregionalnych w infrastrukturze AWS, gdzie każdy region obsługuje własne zasoby obliczeniowe, każda instancja IronOCR przetwarza dokumenty lokalnie w obrębie tych zasobów, bez wywołań międzyregionowych.

Lista kontrolna migracji AWS Textract

Przed migracją

Sprawdź wszystkie użycia Textract i S3 SDK w kodzie źródłowym:

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

Przed napisaniem jakiegokolwiek kodu zastępczego należy:

  • Policz wszystkie pliki zawierające AmazonTextractClient — każde to cel zastąpienia
  • Lista wszystkich miejsc wywołań AnalyzeDocument — zanotuj, czy używane są TABLES, FORMS, czy oba
  • Zidentyfikuj wszystkie asynchroniczne pętle ankietowe (do { await Task.Delay } while JobStatus == IN_PROGRESS)
  • Znajdź wszystkie bloki czyszczenia S3 finally — te są usuwane hurtowo, a nie zastępowane
  • Policz wszystkie bloki przechwytywania ProvisionedThroughputExceededException — usunięte, nie zastępowane
  • Zanotuj całą logikę przeglądania BlockType.TABLE, BlockType.CELL, BlockType.KEY_VALUE_SET — każdy potrzebuje strukturalnego zastąpienia danych wyjściowych

Migracja kodu

  1. Usuń AWSSDK.Textract, AWSSDK.S3, i AWSSDK.Core ze wszystkich plików .csproj
  2. Uruchom dotnet add package IronOcr w każdym projekcie, który wykonuje OCR
  3. Dodaj IronOcr.License.LicenseKey = ... raz przy uruchomieniu aplikacji (Program.cs lub równoważny)
  4. Usuń wszystkie instrukcje using Amazon.Textract;, using Amazon.Textract.Model;, using Amazon.S3;, using Amazon.Runtime;
  5. Dodaj using IronOcr; do każdego pliku, który wcześniej importował przestrzenie nazw Textract
  6. Zamień wywołania konstruktora AmazonTextractClient wywołaniami new IronTesseract()
  7. Zamień instancjonowanie AmazonS3Client i wszystkie wywołania PutObjectAsync / DeleteObjectAsync bezpośrednimi odczytami ścieżek do plików lub strumieni
  8. Zamień wszystkie wywołania DetectDocumentTextAsync: var text = ocr.Read(path).Text
  9. Zamień wszystkie StartDocumentTextDetectionAsync + pętle ankietowe na input.LoadPdf(path) + ocr.Read(input)
  10. Zamień wszystkie złożone potoki TIFF (TIFF → PDF → S3 → async) na input.LoadImageFrames(tiffPath)
  11. Zamień łańcuchy filtrów BlockType.LINE / BlockType.WORD na result.Lines / result.Words
  12. Zamień przegląd relacji BlockType.KEY_VALUE_SET na wyodrębnianie strefy CropRectangle
  13. Usuń wszystkie bloki przechwytywania ProvisionedThroughputExceededException i ograniczenia SemaphoreSlim
  14. Zamień wzorce wsadowe ograniczeń na Parallel.ForEach używając wspólnego IronTesseract instancji
  15. Usuń wszystkie konfiguracje zmiennych środowiskowych poświadczeń AWS z manifestów wdrożeniowych i potoków CI
  16. Wyłącz z użycia tymczasowe zasoby S3 po migracji i weryfikacji całego kodu przetwarzającego

Po migracji

  • Zweryfikuj, czy aplikacja uruchamia się poprawnie tylko z IRONOCR_LICENSE ustawionym i wszystkie zmienne środowiskowe AWS są usunięte
  • Uruchom OCR na tych samych przykładowych obrazach, które zostały wcześniej przetworzone przez Textract, i porównaj dokładność wyodrębnionego tekstu
  • Bezpośrednio przetwarzaj wielostronicowe pliki PDF i sprawdzaj, czy wszystkie strony zostały wyodrębnione bez korzystania z S3 lub asynchronicznego odpytywania
  • Przetwarzaj plik TIFF zawierający wiele ramek i sprawdź, czy liczba stron zgadza się z wynikami Textract dla tego samego dokumentu
  • Przetestuj przetwarzanie wsadowe z zestawem 50+ obrazów i potwierdź, że nie występuje równoważnik ProvisionedThroughputExceededException
  • Uruchom aplikację w kontenerze Docker bez dostępu do Internetu i sprawdź, czy OCR zakończyło się pomyślnie
  • Sprawdź, czy plik PDF z możliwością wyszukiwania otwiera się poprawnie w programie Adobe Reader i umożliwia wyszukiwanie pełnotekstowe
  • Potwierdź, że usunięcie AWS_ACCESS_KEY_ID i AWS_SECRET_ACCESS_KEY z środowiska wdrażania niczego nie zepsuje
  • Przetestuj wszystkie procesy wyodrębniania formularzy lub faktur w oparciu o znane wyniki z Textract, aby zweryfikować poprawność pól
  • Zmierz opóźnienie przetwarzania dla reprezentatywnego zestawu dokumentów i potwierdź wyeliminowanie minimalnego 5-sekundowego czasu oczekiwania na odpytanie

Kluczowe korzyści z migracji do IronOCR

Konsolidacja infrastruktury w jednym pakiecie NuGet. Trzy pakiety AWS SDK, zasób S3 z zasadami cyklu życia, role IAM we wszystkich środowiskach wdrożeniowych oraz procedury rotacji poświadczeń AWS zostały zastąpione jednym pakietem NuGet. Zmiana .csproj jest dotnet remove package AWSSDK.Textract, a następnie dotnet add package IronOcr. Wraz z tym znikają wszystkie dalsze konsekwencje zarządzania poświadczeniami AWS — audyty bezpieczeństwa, harmonogramy rotacji, higiena zmiennych środowiskowych, konfiguracja sekretów Docker.

Przewidywalny całkowity koszt posiadania. Model rozliczeń za stronę generuje koszty zmienne, które rosną w nieskończoność wraz z objętością dokumentów. Po migracji do IronOCR koszt przetworzenia każdej dodatkowej strony wynosi zero, niezależnie od ilości. Zespół przetwarzający 200 000 stron miesięcznie po stawce Textract AnalyzeDocument dla tabel wydaje $36 000 rocznie tylko na OCR. Licencja IronOCR Enterprise w cenie 2999 USD zwraca ten koszt w mniej niż 31 dni dzięki uniknięciu kosztów. Pełne informacje na temat poziomów licencji oraz warunków redystrybucji SaaS można znaleźć na stronie licencyjnej IronOCR.

Przetwarzanie synchroniczne eliminuje złożoność pięciofazowego przetwarzania asynchronicznego. Przesyłanie do S3, uruchomienie zadania, pętla odpytywania, zbieranie wyników w formacie paginowanym oraz blok końcowy czyszczenia reprezentują pięć niezależnych trybów awarii w potoku przetwarzania plików PDF w Textract. Po migracji kod dotyczący pliku PDF zajmuje zaledwie dwa wiersze. Obsługa błędów sprowadza się do pojedynczego try/catch wokół wywołania ocr.Read(). Logika czasu oczekiwania na pętlę, pętla do/while JobStatus == IN_PROGRESS, akumulator stronicowania nextToken — żadna z tych koncepcji nie istnieje w kodzie bazującym na IronOCR. Obciążenie związane z utrzymaniem kodu zmniejsza się proporcjonalnie do ilości usuniętego kodu.

Pełna elastyczność wdrożenia. Textract wymaga dostępu do Internetu przy każdym wywołaniu funkcji OCR.IronOCR wymaga dostępu do Internetu wyłącznie w celu pobrania pakietu NuGet podczas instalacji. Następnie działa w sieciach typu air-gapped, kontenerach Docker bez reguł wychodzących, serwerach lokalnych oraz instancjach AWS EC2 bez dostępu do Textract. Ten sam plik binarny, który działa w środowisku produkcyjnym na serwerze Windows Server, działa również w kontenerze Linux. Przewodnik wdrażania Docker oraz przewodnik wdrażania Linux obejmują konkretną konfigurację dla środowisk kontenerowych, które wcześniej nie mogły korzystać z Textract.

Suwerenność danych osiągnięta dzięki architekturze, a nie konfiguracji.IronOCR nie posiada trybu transmisji sieciowej, który można wyłączyć — jedynym trybem jest przetwarzanie lokalne. Dokumenty przetwarzane przez IronOCR nigdy nie opuszczają komputera hosta. W przypadku aplikacji medycznych przetwarzających dane PHI, aplikacji obronnych przetwarzających dane CUI oraz aplikacji finansowych przetwarzających obrazy kart płatniczych jest to podejście zgodne z przepisami, które nie wymaga negocjacji umowy BAA, konfiguracji regionalnej lokalizacji danych ani audytu zasad przetwarzania danych przez Amazon. Zakres zgodności to granice Twojej własnej infrastruktury, nad którą już sprawujesz kontrolę. Strona produktu IronOCR zawiera pełne podsumowanie funkcji oraz dokumentację wdrożeniową dla zespołów przeprowadzających przegląd zgodności.

Konfigurowalne przetwarzanie wstępne do kontroli jakości dokumentów. Wewnętrzne przetwarzanie wstępne Textract nie jest dostępne ani konfigurowalne. Gdy zeskanowany dokument daje słabe wyniki, jedynym rozwiązaniem jest zaakceptowanie wyniku lub ponowne skanowanie.IronOCR eksponuje pełny potok przetwarzania wstępnego: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), Erode(), i DeepCleanBackgroundNoise(). Zespoły, które przeniosły dokumenty z Textract z powodu wyników o niskim poziomie pewności w przypadku trudnych skanów, mogą bezpośrednio zająć się przyczyną źródłową, stosując filtry dopasowane do konkretnej wady — obrotu, szumu, niskiego kontrastu lub niewystarczającej rozdzielczości. Strona poświęcona funkcjom przetwarzania wstępnego oraz przewodnik po korekcji jakości obrazu zawierają informacje na temat dostępnych filtrów i ich odpowiednich zastosowań.

Zwróć uwagęAWS Textract, PDFSharp, Tesseract i iText są zarejestrowanymi znakami towarowymi swoich odpowiednich właścicieli. Ta strona nie jest powiązana, akceptowana ani sponsorowana przez Amazon Web Services, Google, empira Software GmbH lub iText Group. Wszystkie nazwy produktów, logotypy i marki są własnością ich odpowiednich właścicieli. Porównania mają charakter wyłącznie informacyjny i odzwierciedlają informacje dostępne publicznie w momencie pisania.

Często Zadawane Pytania

Dlaczego warto przejść z Amazon Textract na IronOCR?

Typowe czynniki motywujące to eliminacja złożoności interoperacyjności COM, zastąpienie zarządzania licencjami opartego na plikach, uniknięcie rozliczeń za stronę, umożliwienie wdrażania w Dockerze/kontenerach oraz przyjęcie natywnego dla NuGet przepływu pracy, który integruje się ze standardowymi narzędziami .NET.

Jakie są główne zmiany w kodzie podczas migracji z Amazon Textract do IronOCR?

Zastąp sekwencje inicjalizacji Amazon Textract instancjonowaniem IronTesseract, usuń zarządzanie cyklem życia COM (jawne wzorce Create/Load/Close) i zaktualizuj nazwy właściwości wyników. W rezultacie znacznie zmniejsza się liczba powtarzających się linii kodu.

Jak zainstalować IronOCR, aby rozpocząć migrację?

Uruchom polecenie „Install-Package IronOcr” w konsoli menedżera pakietów lub „dotnet add package IronOcr” w interfejsie CLI. Pakiety językowe są oddzielnymi pakietami: na przykład „dotnet add package IronOcr.Languages.French” dla języka francuskiego.

Czy IronOCR dorównuje dokładnością OCR Amazon Textract w przypadku standardowych dokumentów biznesowych?

IronOCR zapewnia wysoką dokładność w przypadku standardowych treści biznesowych, w tym faktur, umów, paragonów i formularzy wypełnionych na komputerze. Filtry przetwarzania wstępnego obrazu (prostowanie, usuwanie szumów, wzmacnianie kontrastu) dodatkowo poprawiają rozpoznawanie w przypadku pogorszonej jakości danych wejściowych.

W jaki sposób IronOCR obsługuje dane językowe, które Amazon Textract instaluje oddzielnie?

Dane językowe w IronOCR są dystrybuowane jako pakiety NuGet. Polecenie „dotnet add package IronOcr.Languages.German” instaluje obsługę języka niemiećkiego. Nie wymaga to ręcznego umieszczania plików ani podawania ścieżek katalogów.

Czy migracja z Amazon Textract do IronOCR wymaga zmian w infrastrukturze wdrożeniowej?

IronOCR wymaga mniej zmian w infrastrukturze niż Amazon Textract. Nie ma ścieżek binarnych SDK, lokalizacji plików licencyjnych ani konfiguracji serwerów licencyjnych. Pakiet NuGet zawiera kompletny silnik OCR, a klucz licencyjny jest ciągiem znaków ustawionym w kodzie aplikacji.

Jak skonfigurować licencjonowanie IronOCR po migracji?

W kodzie uruchamiającym aplikację przypisz IronOcr.License.LicenseKey = „YOUR-KEY”. W Dockerze lub Kubernetesie zapisz klucz jako zmienną środowiskową i odczytaj go podczas uruchamiania. Użyj License.IsValidLicense do sprawdzenia ważności przed przyjęciem ruchu.

Czy IronOCR może przetwarzać pliki PDF w taki sam sposób jak Amazon Textract?

Tak. IronOCR odczytuje zarówno natywne, jak i zeskanowane pliki PDF. Należy utworzyć instancję IronTesseract, wywołać ocr.Read(input), gdzie input jest ścieżką do pliku PDF lub obiektem OcrPdfInput, a następnie iterować strony OcrResult. Nie jest wymagany oddzielny proces renderowania plików PDF.

W jaki sposób IronOCR radzi sobie z wątkami podczas przetwarzania dużych ilości danych?

IronTesseract można bezpiecznie instancjonować dla każdego wątku. Uruchom jedną instancję na wątek w Parallel.ForEach lub puli zadań, uruchom OCR równolegle i usuń każdą instancję po zakończeniu. Nie jest wymagany żaden stan globalny ani blokowanie.

Jakie formaty wyjściowe obsługuje IronOCR po wyodrębnieniu tekstu?

IronOCR zwraca ustrukturyzowane wyniki, w tym tekst, współrzędne słów, wyniki pewności i strukturę strony. Opcje eksportu obejmują zwykły tekst, PDF z możliwością wyszukiwania oraz obiekty wyników ustrukturyzowanych do dalszego przetwarzania.

Czy ceny IronOCR są bardziej przewidywalne niż Amazon Textract w przypadku skalowania obciążeń?

IronOCR stosuje licencję wieczystą z opłatą ryczałtową, bez opłat za stronę lub wolumen. Niezależnie od tego, czy przetwarzasz 10 000, czy 10 milionów stron, koszt licencji pozostaje stały. Opcje licencji wolumenowych i zespołowych znajdują się na stronie z cennikiem IronOCR.

Co stanie się z moimi istniejącymi testami po migracji z Amazon Textract do IronOCR?

Testy sprawdzające wyodrębnioną treść tekstową powinny nadal przechodzić pomyślnie po migracji. Testy weryfikujące wzorce wywołań API lub cykl życia obiektów COM będą wymagały aktualizacji, aby odzwierciedlały prostszy model inicjalizacji i wyników IronOCR.

Kannaopat Udonpant
Inżynier oprogramowania
Zanim stał się inżynierem oprogramowania, Kannapat ukończył doktorat z zasobów środowiskowych na Uniwersytecie Hokkaido w Japonii. W czasie studiowania, Kannapat również został członkiem Laboratorium Robotyki Pojazdów, które jest częścią Wydziału Inżynierii Bioprodukcji. W 2022 roku wykorzystał swoje umiejętności w ...
Czytaj więcej

Zespół wsparcia Iron

Jesteśmy online 24 godziny, 5 dni w tygodniu.
Czat
E-mail
Zadzwoń do mnie