Migration d'AWS Textract vers IronOCR
Ce guide décrit la migration complète d'AWS Textract vers IronOCR pour les développeurs .NET qui ont besoin d'un traitement de documents local sans dépendance au cloud. Il couvre la suppression des identifiants, la suppression du pipeline asynchrone, l'élimination de S3 et les remplacements de code spécifiques nécessaires pour déplacer chaque opération Textreat vers son équivalent IronOCR .
Pourquoi migrer depuis AWS TextRect ?
AWS Textract est un service géré performant, mais son architecture impose des coûts — financiers et opérationnels — qui augmentent à mesure que votre charge de travail documentaire s'accroît. Les équipes qui mettent en place des systèmes de traitement de documents complexes finissent par rencontrer tous ces points de friction.
L'infrastructure d'identification AWS complexifie chaque déploiement. Chaque environnement exécutant du code Textract nécessite des identifiants AWS : un utilisateur ou un rôle IAM, une clé et un secret d'accès, la configuration de la région et, pour le traitement des PDF, les autorisations d'accès au compartiment S3. Cela représente cinq étapes de configuration distinctes avant même le traitement de la première page. Les déploiements en production nécessitent des politiques de rotation des identifiants, une injection sécurisée dans les conteneurs Docker ou les pods Kubernetes, et une surveillance pour AccessDeniedException lorsque la dérive des politiques IAM provoque des échecs silencieux. IronOCR nécessite une seule chaîne de caractères : une clé de licence, définie une seule fois au démarrage.
La tarification par page n'a pas de plafond. Le taux de base par page de 0,0015 $ pour DetectDocumentText est le plancher, pas le coût. Tout document avec des tableaux déclenche AnalyzeDocument à 0,015 $ par page — dix fois le taux de base. Les formulaires ajoutent 0,05 $ par page. Un flux de travail documentaire mixte de 100 000 pages par mois avec extraction de tableaux coûte 18 000 $ par an, et ce montant est remis à zéro chaque année en janvier. La licence IronOCR Professional coûte 2 999 $ une seule fois. Après cela, le coût par page est nul, quel que soit le volume.
Le pipeline PDF asynchrone est une responsabilité de maintenance. Le traitement de tout PDF multi-page via Textract nécessite cinq phases distinctes : téléchargement S3, StartDocumentTextDetection, boucle de sondage, récupération des résultats paginés et nettoyage S3. Chaque phase est un mode de défaillance indépendant nécessitant sa propre gestion des erreurs, stratégie de reprise et gestion des délais. Les équipes qui déploient ce pipeline en production indiquent systématiquement passer plus de temps à le maintenir qu'à le construire. IronOCR lit un PDF avec deux lignes de code.
Pas d'accès à Internet signifie pas de Textract. Les conteneurs Docker dans des segments de réseau isolés, les serveurs sur site, les environnements de recherche isolés du réseau et les systèmes industriels avec des restrictions de trafic sortant ont tous une caractéristique commune : Textract est indisponible. IronOCR s'installe en tant que package NuGet et fonctionne sans aucun appel réseau après le téléchargement initial du package.
Des données quittent votre infrastructure à chaque appel. Chaque opération OCR effectuée avec Textract transmet le contenu des documents aux serveurs d'Amazon. Pour les organismes de santé traitant des informations de santé protégées, les entreprises de défense gérant des informations confidentielles non classifiées, les équipes juridiques traitant des communications privilégiées et les institutions financières traitant des images de cartes de paiement, il ne s'agit pas d'une option de configuration, mais d'une contrainte architecturale qui interdit totalement Textract dans les environnements réglementés. Le traitement IronOCR s'effectue sur votre matériel. Il n'existe pas de mode cloud à désactiver ; Le traitement local est le seul mode disponible.
Les limites de niveau contraignent le débit par lots. Le StartDocumentTextDetection TPS par défaut est de 5 requêtes par seconde. Les travaux par lots traitant des centaines de documents nécessitent SemaphoreSlim, l'exponentiel décroissant sur ProvisionedThroughputExceededException, et des minuteries de réapprovisionnement des taux. Demander une augmentation du TPS nécessite une demande d'assistance AWS formelle, sans garantie de résultat. IronOCR traite aussi vite que le permet le CPU local — un serveur à 16 cœurs traite 16 documents simultanément avec un simple Parallel.ForEach, sans négociation de niveau de service requise.
Le problème fondamental
Chaque déploiement de Textret commence par cette cérémonie — avant même qu'une seule page puisse être traitée :
// 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
IronOCR remplace l'ensemble de la chaîne d'authentification par une seule attribution :
// 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")
Comparaison des fonctionnalités : IronOCR vs AWS Textract
Le tableau suivant compare les capacités des deux bibliothèques selon les dimensions les plus pertinentes pour les décisions de migration.
| Fonction | AWS Textret | IronOCR |
|---|---|---|
| Lieu de traitement | Cloud Amazon (obligatoire) | Local / sur site uniquement |
| Internet requis | Toujours | Jamais |
| Configuration des informations d'identification | Utilisateur/rôle IAM + compartiment S3 optionnel | Chaîne de clé de licence unique |
| PDF multipage | Nécessite un environnement de transit S3 et une tâche asynchrone | Direct synchrone input.LoadPdf() |
| PDF protégé par mot de passe | Non pris en charge | input.LoadPdf(path, Password: "x") |
| TIFF multi-images | Non pris en charge | input.LoadImageFrames("multi.tiff") |
| Interrogation asynchrone requise | Oui (pour tous les PDF) | Non — synchrone par défaut |
| Limites de tarifs | 5 TPS par défaut (Détection de texte du document de démarrage) | Aucun — limité uniquement par le processeur |
| Coût par page | 0,0015 $ à 0,065 $ par page | 0 $ après l'achat de la licence |
| Prétraitement automatique | Interne, non configurable | Redresser, réduire le bruit, contraster, binariser, accentuer, mettre à l'échelle |
| Sortie PDF consultable | Non disponible | result.SaveAsSearchablePdf() |
| Exportation hOCR | Non disponible | result.SaveAsHocrFile() |
| Lecture de codes-barres | Non disponible | ocr.Configuration.ReadBarCodes = true |
| OCR basé sur la région | Via AnalyzeDocument + relations de blocage | CropRectangle(x, y, width, height) |
| Résultats structurés | Plat List<Block> filtré par BlockType |
Typed Pages, Paragraphs, Lines, Words |
| Langues prises en charge | Langue dominante : anglais (sélectionnez des options supplémentaires) | Plus de 125 packs de langue via NuGet |
| Multilingue simultané | Non pris en charge | OcrLanguage.French + OcrLanguage.German |
| Déploiement isolé | Pas possible | Entièrement pris en charge |
| HIPAA sans BAA | Pas possible | Aucun processeur externe — uniquement sur site |
| Déploiement Docker | Nécessite des identifiants AWS injectés | Aucune authentification requise — package NuGet simple |
| Cross-Platform | Géré par AWS (Linux uniquement) | Windows, Linux, macOS, Docker, Azure, AWS EC2 |
| Modèle de licence | Facturation à la page (dépenses perpétuelles) | Perpétuel en une fois ($999 / 1 499 $ / 2 999 $) |
Démarrage rapide : Migration d'AWS Textract vers IronOCR
Étape 1 : Remplacer le package NuGet
Supprimez les packages du kit de développement logiciel AWS :
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
Installez IronOCR depuis NuGet :
dotnet add package IronOcr
Étape 2 : Mise à jour des espaces de noms
Remplacez toutes les importations d'espace de noms AWS par l'espace de noms unique 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
Étape 3 : initialisation de la licence
Ajoutez l'initialisation de la licence une seule fois au démarrage de l'application. Supprimez toute configuration des variables d'environnement d'identification 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"
Exemples de migration de code
Remplacement du constructeur AmazonTextClient et de la configuration IAM
Le changement le plus visible lors de la migration concerne l'initialisation du client. Textract nécessite un client à injection de dépendances avec résolution d'identifiants sous-jacente — ce qui signifie que le client et toutes ses dépendances doivent être préconfigurés dans chaque environnement de déploiement. Toute mauvaise configuration échoue silencieusement jusqu'à ce que le premier appel OCR lance AmazonClientException.
Approche AWS Textreat :
// 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
Approche 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
L'infrastructure complète d'identifiants AWS — rôles IAM, variables d'environnement, fichiers ~/.aws/credentials, politiques de cycle de vie des seaux S3, et configuration de région — est supprimée. Le constructeur DocumentOcrService passe d'un site d'injection à 3 dépendances avec des modes de défaillance d'exécution à une seule attribution de licence. Pour plus de détails sur les modèles de déploiement, consultez le guide d'installation d'IronTesseract .
Remplacement de StartDocumentTextDetection par le traitement multi-images TIFF
L'API de travail asynchrone de Textract (StartDocumentTextDetection) est requise pour tout document multi-page. Dans les chaînes de traitement de la numérisation de documents, il est courant de recevoir les documents numérisés sous forme de fichiers TIFF multi-images — une image par page numérisée. Textract ne peut absolument pas accepter directement les fichiers TIFF en entrée ; Le document doit être converti au format PDF et téléchargé sur S3 avant que la tâche puisse démarrer. IronOCR lit nativement les fichiers TIFF multi-images.
Approche AWS Textreat :
// 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
Approche 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
L'étape de conversion TIFF-PDF, le chargement sur S3, la boucle d'interrogation asynchrone, l'accumulation des résultats paginés et le nettoyage S3 sont tous éliminés. L'implémentation IronOCR lit les images directement et fournit un accès par page via result.Pages sans aucune conversion de format intermédiaire. Le guide d'entrée TIFF et GIF traite de la sélection des images pour les cas où seul un sous-ensemble d'images est nécessaire.
Remplacement de la zone de transit S3 par une sortie PDF consultable
Un cas d'utilisation courant de Textret consiste à convertir des archives PDF numérisées en un format consultable. L'approche Textract nécessite de télécharger chaque PDF sur S3, d'exécuter la tâche asynchrone, de collecter le texte extrait, puis d'utiliser une bibliothèque PDF distincte pour intégrer ce texte en tant que couche consultable. IronOCR effectue l'analyse et produit un PDF consultable en une seule opération.
Approche AWS Textreat :
// 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
Approche 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
La logique de délai d'attente d'interrogation, le modèle de chargement et de nettoyage S3 et la dépendance à la bibliothèque PDF externe ont tous disparu. SaveAsSearchablePdf intègre le texte reconnu directement dans le fichier de sortie, rendant chaque page scannée consultable en texte intégral sans une deuxième bibliothèque. Le guide PDF consultable couvre la sélection des pages, les options de compression et l'intégration des métadonnées. Pour un contexte plus large sur les pipelines OCR PDF, consultez le guide d'entrée PDF .
Remplacement du parcours de graphe de blocs AnalyzeDocument par des résultats de pages structurées
Le AnalyzeDocument de Textract avec TABLES et FORMS renvoie un List<Block> plat où chaque élément — lignes, mots, cellules, en-têtes de tableau, clés de formulaire, valeurs de formulaire — est du même type Block uniquement distingué par BlockType et lié par des tableaux ID RelationshipType.CHILD. La reconstitution de la structure logique d'un document nécessite de parcourir ce graphe de relations. IronOCR renvoie une hiérarchie typée : pages, paragraphes, lignes, mots, chacun avec un accès aux coordonnées.
Approche AWS Textreat :
// 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
Approche 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
Les paragraphes, lignes et mots sont directement accessibles en tant que collections typées avec les propriétés .X, .Y, .Width, .Height et .Confidence. Aucune normalisation BoundingBox, aucune heuristique de seuil d'écart, aucun filtrage par type de bloc. Le guide de lecture des résultats documente la hiérarchie complète OcrResult y compris l'accès aux coordonnées au niveau des caractères. Pour les flux de travail de facturation et de reçu qui dépendent de cette structure, consultez le tutoriel sur la reconnaissance optique de caractères (OCR) des factures .
Remplacement du traitement par lots à débit limité par une exécution parallèle sans contrainte
Le traitement par lots d'images est là où les limites TPS de Textract produisent le coût opérationnel le plus visible. L'API synchrone DetectDocumentText est soumise à des limites de taux; envoyer plus de 5 requêtes par seconde produit ProvisionedThroughputExceededException. La gestion correcte des lots nécessite SemaphoreSlim, logique de reprise avec recul et comptage attentif des requêtes en cours. IronOCR n'a pas de limite de débit — le débit est uniquement limité par les cœurs de processeur disponibles.
Approche AWS Textreat :
// 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
Approche 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
Le SemaphoreSlim, la boucle de reprise, le recul exponentiel et les blocs ProvisionedThroughputExceededException catch sont tous supprimés. IronTesseract est thread-safe et une seule instance partagée entre les threads gère la charge complète en parallèle sans verrous. Sur une machine à 8 cœurs, ce programme traite 8 documents simultanément sans aucune configuration ; sur 32 cœurs, 32 simultanément. L' exemple de multithreading illustre le modèle complet et le guide d'optimisation de la vitesse couvre le réglage de la configuration du moteur pour les déploiements axés sur le débit.
Remplacement de l'extraction de formulaire AnalyzeDocument par l'OCR CropRectangle basé sur la région
Le AnalyzeDocument de Textract avec FORMS renvoie des blocs KEY_VALUE_SET liés par des relations RelationshipType.VALUE. La reconstitution des paires clé-valeur de champ de formulaire nécessite de filtrer par EntityTypes.Contains("KEY"), de suivre les ID de relation VALUE, puis récupérer les blocs WORD enfants pour assembler le texte. Pour les formulaires à format fixe où les positions des champs sont connues, le CropRectangle de IronOCR extrait directement chaque champ sans parcours de graphe de relations — et ne coûte rien par page.
Approche AWS Textreat :
// 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
Approche 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
Le filtrage par blocs KEY_VALUE_SET, le parcours RelationshipType.VALUE et le helper GetTextFromBlock sont éliminés. Chaque champ lit exactement la région de pixels qui le contient — ni plus, ni moins. Le guide OCR basé sur la région couvre les coordonnées CropRectangle et comment définir les zones à partir des mesures de modèle. Pour les flux de travail spécifiques aux factures, l' article de blog sur la reconnaissance optique de caractères des factures et le tutoriel sur la numérisation des reçus présentent des pipelines d'extraction de champs complets.
Référence de mappage de l'API AWS Textret vers IronOCR
| AWS Textret | Équivalent d'IronOCR |
|---|---|
AmazonTextractClient |
IronTesseract |
AmazonS3Client |
Non requis |
DetectDocumentTextRequest |
OcrInput avec input.LoadImage() |
DetectDocumentTextResponse |
OcrResult |
AnalyzeDocumentRequest (TABLEAUX/FORMULAIRES) |
OcrInput avec optionnel CropRectangle |
StartDocumentTextDetectionRequest |
OcrInput avec input.LoadPdf() — synchrone, pas de démarrage de tâche |
GetDocumentTextDetectionRequest |
Sans objet – les résultats sont immédiats |
Document.Bytes |
input.LoadImage(byteArray) ou input.LoadImage(stream) |
S3Object (mise en scène de documents) |
Chemin de fichier ou flux — aucune étape intermédiaire requise |
Block (BlockType.LINE) |
result.Lines (collection typée) |
Block (BlockType.WORD) |
result.Words (collection typée) |
Block (BlockType.TABLE) |
result.Words groupé par proximité de coordonnées |
Block (BlockType.KEY_VALUE_SET) |
CropRectangle extraction région par champ |
Block.Confidence |
word.Confidence / result.Confidence |
Block.Geometry.BoundingBox |
word.X, word.Y, word.Width, word.Height |
RelationshipType.CHILD parcours |
Accès direct aux propriétés des objets de résultat typés |
JobStatus.IN_PROGRESS |
Non applicable — aucun état asynchrone |
JobStatus.SUCCEEDED |
Non applicable — retour synchrone |
response.NextToken (pagination) |
Non applicable — résultats non paginés |
ProvisionedThroughputExceededException |
Non applicable — aucune limite TPS |
AccessDeniedException |
Non applicable — aucune chaîne de certification |
input.LoadImageFrames() |
Prise en charge directe du format TIFF multi-images (pas d'équivalent Textret) |
result.SaveAsSearchablePdf() |
Sortie PDF consultable (pas d'équivalent Textrect) |
Problèmes de migration courants et solutions
Problème 1 : Identifiants mal configurés dans les nouveaux environnements de déploiement
AWS Textract : Le constructeur AmazonTextractClient résout les identifiants à partir d'une chaîne : variables d'environnement, ~/.aws/credentials, profil d'instance EC2, rôle de tâche ECS. Dans un nouveau conteneur Docker ou environnement CI, si aucun d'entre eux n'est configuré, le client se construit sans erreur mais chaque appel d'API génère AmazonClientException: No credentials specified. La défaillance est invisible jusqu'à l'exécution.
Solution : Supprimer complètement la chaîne d'authentification. Définissez la clé de licence IronOCR à partir d'une variable d'environnement plus simple à configurer et ne nécessitant aucune autorisation 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
Problème n° 2 : Points d'appel asynchrones dans l'ensemble du code source
AWS Textract : Parce que l'ensemble du SDK est uniquement asynchrone (DetectDocumentTextAsync, StartDocumentTextDetectionAsync, GetDocumentTextDetectionAsync), tous les sites d'appel Textract propagent async Task<t> à travers la pile d'appels. La migration vers l'API synchrone d'IronOCR nécessite une décision sur chaque site.
Solution : Pour les services de traitement en arrière-plan et les actions du contrôleur, les appels synchrones d'IronOCR sont sûrs sur les threads d'arrière-plan. Enveloppez dans Task.Run uniquement lorsque la chaîne d'appels existante doit rester asynchrone :
// 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
Pour les processeurs de lots et les services d'arrière-plan qui fonctionnent déjà sur des threads non-UI, appelez ocr.Read() de façon synchrone — Task.Run ajoute une surcharge sans avantage dans ces contextes. Le guide OCR asynchrone couvre les modèles recommandés.
Problème n° 3 : La logique de nettoyage des compartiments S3 est dispersée dans le code.
AWS Textract : tout code téléchargé sur S3 pour traitement Textract doit également être supprimé de S3 une fois la tâche terminée. Cette opération de nettoyage vit souvent dans les blocs finally et est parfois omise sur les chemins d'erreur, provoquant des objets orphelins qui accumulent les coûts de stockage. Lors d'une migration, le code de nettoyage S3 est facile à négliger car il n'est pas conceptuellement lié à la reconnaissance optique de caractères (OCR).
Solution : Le modèle complet de chargement et de nettoyage S3 n'a pas d'équivalent IronOCR . Supprimez tous les blocs PutObjectAsync, DeleteObjectAsync et try/finally liés à S3 en totalité. IronOCR lit directement à partir de chemins ou de flux locaux :
// 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
Mettez hors service le compartiment de transit S3 après la migration. Le guide d'entrée PDF et le guide d'entrée de flux couvrent tous les modèles d'entrée qui remplacent l'accès aux documents intermédiaires S3.
Problème 4 : Le code de parcours de graphe par blocs n'a pas d'équivalent direct
AWS Textract : Le code qui parcourt Block.Relationships pour reconstruire les cellules de tableau, champs de formulaire ou regroupements de paragraphes à partir des tableaux ID RelationshipType.CHILD est le code le plus chronophage à migrer. Il n'existe pas de remplacement direct car IronOCR renvoie des collections typées au lieu d'un graphe de relations.
Solution : Remplacez chaque modèle de parcours par la propriété typée appropriée. La recherche d'identifiants de relation devient un accès direct à la propriété :
// 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
Pour la reconstitution de tableau qui dépendait de BlockType.CELL et des propriétés ColumnIndex, utilisez le regroupement de coordonnées de mots sur result.Words. Le guide de lecture des tableaux couvre l'approche basée sur les coordonnées.
Problème 5 : Blocs de capture de l'exception ProvisionedThroughputExceededException
AWS Textract : Un code de traitement par lots robuste attrape AmazonTextractException avec ErrorCode == "ProvisionedThroughputExceededException" et implémente une reprise avec recul. Ce code d'erreur est un concept spécifique à Textract et n'a pas d'équivalent IronOCR .
Solution : Supprimez tous les blocs ProvisionedThroughputExceededException catch. IronOCR n'a pas de limite de TPS. Remplacez le modèle de lot à débit limité entier par 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)
Problème n° 6 : Configuration régionale intégrée aux constructeurs clients
AWS Textract : new AmazonTextractClient(Amazon.RegionEndpoint.USEast1) code en dur une région. Les déploiements multirégionaux nécessitent plusieurs instances client ou une sélection dynamique de la région. Lorsque Textret ajoute la prise en charge de nouvelles régions, le code doit être mis à jour.
Solution : IronOCR ne gère pas la notion de région. Supprimez toutes les références Amazon.RegionEndpoint. Le constructeur IronTesseract ne prend aucune configuration réseau — le traitement est local. Pour les déploiements multirégionaux sur l'infrastructure AWS où chaque région exécute ses propres calculs, chaque instance IronOCR traite les documents localement à l'intérieur de cette limite de calcul sans appels interrégionaux.
Liste de contrôle de migration de texte AWS
Pré-migration
Auditez l'ensemble de l'utilisation des SDK Textract et S3 dans le code source :
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" .
Avant d'écrire tout code de remplacement, veuillez inventorier les éléments suivants :
- Comptez tous les fichiers contenant
AmazonTextractClient— chacun est une cible de remplacement - Listez tous les sites d'appel
AnalyzeDocument— notez siTABLES,FORMSou les deux sont utilisés - Identifiez toutes les boucles de sondage asynchrones (
do { await Task.Delay } while JobStatus == IN_PROGRESS) - Trouvez tous les blocs de nettoyage S3
finally— ceux-ci sont supprimés en totalité, pas remplacés - Comptez tous les blocs
ProvisionedThroughputExceededExceptioncatch — supprimés, pas remplacés - Notez toute logique de parcours
BlockType.TABLE,BlockType.CELL,BlockType.KEY_VALUE_SET— chacune nécessite un remplacement par une sortie structurée
Migration de code
- Retirez
AWSSDK.Textract,AWSSDK.S3etAWSSDK.Corede tous les fichiers.csproj - Exécutez
dotnet add package IronOcrdans chaque projet qui effectue de l'OCR - Ajoutez
IronOcr.License.LicenseKey = ...une fois au démarrage de l'application (Program.csou équivalent) - Supprimez toutes les déclarations
using Amazon.Textract;,using Amazon.Textract.Model;,using Amazon.S3;,using Amazon.Runtime; - Ajoutez
using IronOcr;à chaque fichier qui importait auparavant des espaces de noms Textract - Remplacez les appels de constructeur
AmazonTextractClientparnew IronTesseract() - Remplacez l'instantiation
AmazonS3Clientet tous les appelsPutObjectAsync/DeleteObjectAsyncpar des lectures de chemin de fichier direct ou de flux - Remplacez tous les appels
DetectDocumentTextAsync:var text = ocr.Read(path).Text - Remplacez tous les
StartDocumentTextDetectionAsync+ boucles de sondage avecinput.LoadPdf(path)+ocr.Read(input) - Remplacez tous les pipelines TIFF multi-image (TIFF → PDF → S3 → asynchrone) par
input.LoadImageFrames(tiffPath) - Remplacez les chaînes de filtres
BlockType.LINE/BlockType.WORDparresult.Lines/result.Words - Remplacez le parcours de relation
BlockType.KEY_VALUE_SETpar l'extraction de zoneCropRectangle - Supprimez tous les blocs
ProvisionedThroughputExceededExceptioncatch etSemaphoreSlimthrottles - Remplacez les modèles de lots à débit limité par
Parallel.ForEachen utilisant une instance partagéeIronTesseract - Supprimez toute configuration de variable d'environnement d'identification AWS des manifestes de déploiement et des pipelines CI.
- Mettez hors service les compartiments de transit S3 une fois que tout le code de traitement a été migré et vérifié.
Après la migration
- Vérifiez que l'application démarre proprement avec uniquement
IRONOCR_LICENSEdéfini et toutes les variables d'environnement AWS supprimées - Effectuez une reconnaissance optique de caractères (OCR) sur les mêmes images d'exemple précédemment traitées par Textract et comparez le texte extrait pour en vérifier l'exactitude.
- Traiter directement un PDF multipage et vérifier que toutes les pages sont extraites sans passer par S3 ni par un sondage asynchrone.
- Traiter un fichier TIFF multi-images et vérifier que le nombre de pages correspond à la sortie de Textract pour le même document.
- Testez le traitement par lots avec un ensemble de plus de 50 images et confirmez qu'aucun équivalent
ProvisionedThroughputExceededExceptionne se produit - Exécutez l'application dans un conteneur Docker sans accès Internet sortant et vérifiez que la reconnaissance optique de caractères (OCR) s'effectue correctement.
- Vérifier que le fichier PDF consultable s'ouvre correctement dans Adobe Reader et que la recherche en texte intégral est possible.
- Confirmez que la suppression de
AWS_ACCESS_KEY_IDetAWS_SECRET_ACCESS_KEYde l'environnement de déploiement ne casse rien - Tester les flux de travail d'extraction de formulaires ou de factures par rapport aux résultats connus de Textract afin de valider l'exactitude des champs.
- Mesurer la latence de traitement pour un ensemble de documents représentatif et confirmer l'élimination du délai d'attente minimal de 5 secondes.
Principaux avantages de la migration vers IronOCR
Consolidation de l'infrastructure dans un seul package NuGet . Trois packages AWS SDK, un compartiment S3 avec des politiques de cycle de vie, des rôles IAM dans chaque environnement de déploiement et des procédures de rotation des informations d'identification AWS sont remplacés par un seul package NuGet . Le changement .csproj est dotnet remove package AWSSDK.Textract suivi par dotnet add package IronOcr. Toutes les conséquences en aval de la gestion des identifiants AWS — audits de sécurité, calendriers de rotation, hygiène des variables d'environnement, configuration des secrets Docker — disparaissent avec elle.
Coût total de possession prévisible. Le modèle de facturation à la page génère des coûts variables qui évoluent indéfiniment en fonction du volume de documents. Après la migration vers IronOCR, le coût par page supplémentaire traitée est nul, quel que soit le volume. Une équipe traitant 200 000 pages par mois au taux AnalyzeDocument de Textract pour les tableaux dépense 36 000 $ par an uniquement pour l'OCR. La licence IronOCR Enterprise à 2 999 $ permet de récupérer ce coût en moins de 31 jours grâce aux factures évitées. Consultez la page de licence IronOCR pour obtenir tous les détails sur les différents niveaux de service et les conditions de redistribution SaaS.
Le traitement synchrone élimine la complexité asynchrone en cinq phases. Le chargement sur S3, le démarrage de la tâche, la boucle d'interrogation, la collecte des résultats paginés et le bloc final de nettoyage représentent cinq modes de défaillance indépendants dans le pipeline Textract PDF. Après la migration, un PDF s'affiche sur deux lignes. La gestion des erreurs se réduit à un simple try/catch autour de l'appel ocr.Read(). La logique de délai d'attente de sondage, la boucle do/while JobStatus == IN_PROGRESS, l'accumulateur de pagination nextToken — aucun de ces concepts n'existe dans la base de code IronOCR. La charge de maintenance diminue proportionnellement au code supprimé.
Flexibilité de déploiement totale. Textrect nécessite un accès Internet pour chaque appel OCR. IronOCR nécessite un accès Internet uniquement pour télécharger le package NuGet lors de son installation. Ensuite, il fonctionne sur des réseaux isolés, dans des conteneurs Docker sans règles de trafic sortant, sur des serveurs sur site et sur des instances AWS EC2 sans accès à Textract. Le même fichier binaire qui s'exécute en production sur Windows Server s'exécute également dans un conteneur Linux. Les guides de déploiement Docker et Linux couvrent la configuration spécifique des environnements conteneurisés qui étaient auparavant empêchés d'utiliser Textract.
La souveraineté des données est assurée par l'architecture, et non par la configuration. IronOCR ne propose aucun mode de transmission réseau désactivable : seul le traitement local est possible. Les documents traités par IronOCR ne quittent jamais la machine hôte. Pour les applications de santé traitant des informations de santé protégées (PHI), les applications de défense traitant des informations confidentielles contrôlées (CUI) et les applications financières traitant des images de cartes de paiement, cette approche garantit la conformité sans nécessiter de négociation d'accord de partenariat commercial (BAA), de configuration de résidence des données régionales ni d'audit des politiques de gestion des données d'Amazon. Le périmètre de conformité correspond aux limites de votre propre infrastructure, que vous contrôlez déjà. La page produit IronOCR fournit le résumé complet des fonctionnalités et la documentation de déploiement pour les équipes effectuant un audit de conformité.
Prétraitement configurable pour le contrôle qualité des documents. Le prétraitement interne de Textract n'est ni accessible ni configurable. Lorsqu'un document numérisé donne de mauvais résultats, le seul recours est d'accepter le résultat ou de procéder à une nouvelle numérisation. IronOCR expose tout le pipeline de prétraitement : Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), Erode() et DeepCleanBackgroundNoise(). Les équipes qui ont déplacé des documents depuis Textract en raison de résultats peu fiables sur des numérisations difficiles peuvent s'attaquer directement à la cause profonde grâce à des filtres adaptés au défaut spécifique : rotation, bruit, faible contraste ou résolution insuffisante. La page relative aux fonctionnalités de prétraitement et le guide de correction de la qualité d'image documentent les filtres disponibles et leurs cas d'utilisation appropriés.
Questions Fréquemment Posées
Pourquoi devrais-je migrer d'Amazon Textract vers IronOcr ?
Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.
Quels sont les principaux changements de code lors de la migration d'Amazon Textract vers IronOCR ?
Remplacer les séquences d'initialisation d'Amazon Textract par l'instanciation d'IronTesseract, supprimer la gestion du cycle de vie de COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.
Comment installer IronOCR pour commencer la migration ?
Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.
IronOCR offre-t-il une précision d'OCR comparable à celle d'Amazon Textract pour les documents commerciaux standard ?
IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.
Comment IronOCR gère-t-il les données linguistiques qu'Amazon Textract installe séparément ?
Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.
La migration d'Amazon Textract vers IronOCR nécessite-t-elle de modifier l'infrastructure de déploiement ?
IronOCR nécessite moins de changements d'infrastructure qu'Amazon Textract. Il n'y a pas de chemins binaires SDK, de placements de fichiers de licence ou de configurations de serveurs de licence. Le package NuGet contient le moteur d'OCR complet, et la clé de licence est une chaîne définie dans le code de l'application.
Comment configurer les licences IronOCR après la migration ?
Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.
IronOcr peut-il traiter les PDF de la même manière qu'Amazon Textract ?
Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.
Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?
IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.
Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?
IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.
La tarification d'IronOCR est-elle plus prévisible que celle d'Amazon Textract pour la mise à l'échelle des charges de travail ?
IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.
Qu'advient-il de mes tests existants après la migration d'Amazon Textract vers IronOCR ?
Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

