AWS TextractからIronOCRへの移行
このガイドでは、クラウドへの依存なしにローカルでドキュメント処理を行う必要がある.NET開発者向けに、AWS TextractからIronOCRへの完全な移行手順を解説します。 このドキュメントでは、認証情報の削除、非同期パイプラインの削除、S3の削除、および各Textract操作をIronOCRの同等の操作に移行するために必要な具体的なコード置換について説明します。
AWS Textractから移行する理由
AWS Textractは優れたマネージドサービスですが、そのアーキテクチャ上、ドキュメントのワークロードが増加するにつれて、金銭的コストと運用コストの両方が増大します。 本格的な文書処理パイプラインを構築するチームは、最終的にこれらの問題点すべてに直面することになる。
AWS認証インフラストラクチャは、あらゆるデプロイメントを複雑化させます。Textractコードを実行するすべての環境には、AWS認証情報が必要です。IAMユーザーまたはロール、アクセスキーとシークレット、リージョン構成、そしてPDF処理の場合はS3バケットのアクセス許可などです。最初のページが処理されるまでに、5つの異なる構成手順が必要になります。 本番デプロイには資格情報の回転ポリシー、DockerコンテナまたはKubernetesポッドへの安全な注入、およびIAMポリシーのドリフトが静かに失敗を引き起こす際のAccessDeniedExceptionの監視が必要です。 IronOCRには、起動時に一度だけ設定するライセンスキーという文字列が1つ必要です。
ページごとの価格に上限はありません。 AnalyzeDocumentをトリガーします。これは基本料金の10倍です。 フォームは1ページにつき0.05ドル追加料金がかかります。 表抽出機能を含む、月間10万ページの混合文書ワークフローの費用は年間1万8000ドルで、この金額は毎年1月にリセットされます。 IronOCRProfessionalライセンスの価格は、2,999ドルの一括払いとなります。 その後は、ページ数に関わらず、1ページあたりの料金はゼロになります。
非同期PDFパイプラインはメンテナンスの責任です。 Textractでの多ページPDFの処理には、S3へのアップロード、StartDocumentTextDetection、ポーリングループ、ページネートされた結果の取得、S3のクリーンアップの5つの異なるフェーズが必要です。各フェーズは独立した失敗モードであり、それぞれにエラーハンドリング、リトライ戦略、タイムアウト管理が必要です。 このパイプラインを本番環境に導入したチームは、構築に費やした時間よりも、保守に費やした時間の方がはるかに長いと一貫して報告している。 IronOCRは、2行のコードでPDFを読み取ります。
**インターネットにアクセスできないと、Textract も利用できません。**隔離されたネットワークセグメント内の Docker コンテナ、オンプレミスサーバー、エアギャップされた研究環境、およびアウトバウンドトラフィックが制限されている産業システムはすべて、Textract が利用できないという共通の特徴を持っています。 IronOCRはNuGetパッケージとしてインストールされ、最初のパッケージダウンロード後はネットワーク呼び出しなしで動作します。
データは、呼び出しのたびにお客様のインフラストラクチャから送信されます。Textractを使用したOCR操作はすべて、ドキュメントの内容をAmazonのサーバーに送信します。 PHIを処理する医療機関、CUIを扱う防衛関連企業、機密通信を処理する法務チーム、決済カード画像を処理する金融機関にとって、これは設定オプションではなく、規制環境におけるTextractの使用を完全に禁止するアーキテクチャ上の制約です。 IronOCRは、お使いのハードウェア上で処理を実行します。 クラウドモードを無効にする機能はありません。 ローカル処理のみが唯一のモードです。
レート制限はバッチスループットを制約します。 デフォルトのStartDocumentTextDetection TPSリミットは1秒あたり5リクエストです。 数百のドキュメントを処理するバッチジョブには、ProvisionedThroughputExceededExceptionの指数的バックオフ、およびレート補充タイマーが必要です。 TPS(1秒あたりのトランザクション数)の増加をリクエストするには、正式なAWSサポートケースを申請する必要があり、必ずしも結果が保証されるわけではありません。 IronOCRはローカルCPUが許す速さで処理します–16コアのサーバーはParallel.ForEachを使用して16のドキュメントを同時に処理し、サービス階層の交渉は必要ありません。
基本的な問題
Textractの導入はすべて、1ページも処理される前に、この儀式から始まります。
// 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);
}
}
IronOCRは、認証情報チェーン全体を単一の割り当てに置き換えます。
// 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とAWS Textract:機能比較
以下の表は、移行の意思決定において最も重要な側面に関して、両ライブラリの機能をマッピングしたものです。
| フィーチャー | AWS テクストラクト | IronOCR |
|---|---|---|
| 加工場所 | Amazonクラウド(必須) | 地域限定/店内飲食のみ |
| インターネットが必要です。 | いつも | 一度もない |
| 認証情報の設定 | IAMユーザー/ロール + オプションのS3バケット | 単一ライセンスキー文字列 |
| 複数ページPDF | S3ステージングと非同期ジョブが必要です | 同期input.LoadPdf()の直接 |
| パスワードで保護されたPDF | サポートされていません | input.LoadPdf(path, Password: "x") |
| マルチフレームTIFF | サポートされていません | input.LoadImageFrames("multi.tiff") |
| 非同期ポーリングが必要です | はい(すべてのPDFファイルに対応) | いいえ — デフォルトでは同期 |
| 料金の制限 | 5 TPS デフォルト (StartDocumentTextDetection) | なし — CPUバウンドのみ |
| ページあたりの料金 | 1ページあたり0.0015ドル~0.065ドル | ライセンス購入後は0ドル |
| 自動前処理 | 内部仕様のため、設定変更不可 | 傾き補正、ノイズ除去、コントラスト調整、二値化、シャープ化、スケール調整 |
| 検索可能なPDF出力 | 不可 | result.SaveAsSearchablePdf() |
| hOCRエクスポート | 不可 | result.SaveAsHocrFile() |
| バーコード読み取り | 不可 | ocr.Configuration.ReadBarCodes = true |
| 地域ベースのOCR | AnalyzeDocument + ブロック関係を介して | CropRectangle(x, y, width, height) |
| 構造化された結果 | フラットなBlockTypeによってフィルタリングされます | 型付きWords |
| 対応言語 | 英語が主流(追加項目を選択) | NuGet経由で125以上の言語パックを利用可能 |
| 多言語同時配信 | サポートされていません | OcrLanguage.French + OcrLanguage.German |
| エアギャップ展開 | 不可 | 完全サポート |
| BAAなしのHIPAA | 不可 | 外部プロセッサ不要 - オンプレミスのみ |
| Dockerデプロイメント | AWS認証情報の挿入が必要です | 認証情報なし — 通常のNuGetパッケージ |
| クロスプラットフォーム。 | AWSマネージド(Linuxのみ) | Windows、Linux、macOS、Docker、Azure、AWS EC2 |
| ライセンスモデル | ページごとの従量課金(継続課金) | 永続的な一度きりの($999 / $1,499 / $2,999) |
クイックスタート:AWS TextractからIronOCRへの移行
ステップ 1: NuGet パッケージを置き換える
AWS SDK パッケージを削除します。
dotnet remove package AWSSDK.Textract
dotnet remove package AWSSDK.S3
dotnet remove package AWSSDK.Core
NuGetからIronOCRをインストールしてください。
ステップ 2: 名前空間の更新
すべてのAWSネームスペースのインポートを単一の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;
ステップ 3: ライセンスの初期化
アプリケーション起動時にライセンス初期化を一度だけ追加します。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";
コード移行の例
AmazonTextractClientコンストラクターとIAMセットアップの置き換え
最も目立つ移行変更点は、クライアントの初期化です。 Textractは、基盤となる認証情報解決機能を備えた依存性注入型クライアントを必要とします。つまり、クライアントとそのすべての依存関係は、すべての展開環境で事前に構成しておく必要があります。 誤った構成は、最初のOCRコールがAmazonClientExceptionをスローするまで静かに失敗します。
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));
}
}
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;
}
}
IAMロール、環境変数、~/.aws/credentialsファイル、S3バケットライフサイクルポリシー、リージョン設定など、AWSの資格情報インフラ全体が削除されます。 DocumentOcrServiceコンストラクタは、ランタイムの失敗モードを持つ3つの依存関係挿入サイトから、単一のライセンス割り当てに変わります。 デプロイメントパターンの詳細については、 IronTesseract セットアップガイドを参照してください。
StartDocumentTextDetectionをTIFFマルチフレーム処理に置き換える
Textractの非同期ジョブAPI (StartDocumentTextDetection)は、多ページドキュメントに必須です。 文書のデジタル化パイプラインでよく見られるパターンは、スキャンされた文書をマルチフレームTIFFファイル(スキャンされたページごとに1フレーム)として受け取ることです。 TextractはTIFF形式の入力を直接受け付けることはできません。 ジョブを開始する前に、ドキュメントをPDFに変換してS3にアップロードする必要があります。 IronOCRは、マルチフレームTIFFファイルをネイティブに読み取ることができます。
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
}
}
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;
}
TIFFからPDFへの変換ステップ、S3へのアップロード、非同期ポーリングループ、ページ分割された結果の蓄積、およびS3のクリーンアップはすべて省略されます。 IronOCRの実装はフレームを直接読み取り、result.Pagesを通じてページごとのアクセスを提供します。中間フォーマット変換は行いません。 TIFFおよびGIF入力ガイドでは、フレームの一部のみが必要な場合のフレーム選択について説明しています。
S3ステージングを検索可能なPDF出力に置き換える
Textractの一般的な使用例としては、スキャンしたPDFアーカイブを検索可能な形式に変換することが挙げられます。 Textractの手法では、各PDFをS3にアップロードし、非同期ジョブを実行して抽出されたテキストを収集し、その後、別のPDFライブラリを使用してそのテキストを検索可能なレイヤーとして埋め込む必要があります。 IronOCRは、1回の呼び出しでスキャンを実行し、検索可能なPDFを生成します。
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
}
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}%");
}
ポーリングのタイムアウトロジック、S3へのアップロードとクリーンアップのパターン、および外部PDFライブラリへの依存関係はすべてなくなりました。 SaveAsSearchablePdfは認識されたテキストを出力ファイルに直接埋め込み、スキャンされたすべてのページが2番目のライブラリなしで全文検索可能になります。 検索可能なPDFガイドでは、ページ選択、圧縮オプション、メタデータ埋め込みについて解説しています。 PDF OCRパイプラインのより広範なコンテキストについては、 PDF入力ガイドを参照してください。
AnalyzeDocument ブロックグラフトラバーサルを構造化ページ結果に置き換える
TextractのRelationshipType.CHILD ID配列でリンクされています。 文書の論理構造を再構築するには、この関係グラフをたどる必要がある。 IronOCRは、ページ、段落、行、単語といった階層構造を型付けして返します。各階層には座標へのアクセス権があります。
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; }
}
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; }
}
段落、行、単語は型付きコレクションとして.Confidenceプロパティで直接アクセス可能です。 バウンディングボックスの正規化、ギャップ閾値のヒューリスティック、ブロックタイプのフィルタリングは行いません。 読み取り結果ガイドでは、文字レベルの座標アクセスを含む完全なOcrResult階層が文書化されています。 この構造に依存する請求書および領収書のワークフローについては、請求書OCRチュートリアルを参照してください。
処理速度制限のあるバッチ処理を、制約のない並列実行に置き換える
バッチ画像処理では、TextractのTPS制限が最も顕著に運用コストに影響を与えます。DetectDocumentText同期APIにはレート制限があります; 1秒あたり5以上のリクエストを送信するとProvisionedThroughputExceededExceptionが発生します。 トランザクションハンドリングにはSemaphoreSlimのスロットリング、バックオフ付きリトライロジック、および処理中のリクエストの慎重な計算が必要です。 IronOCRにはレート制限がありません。スループットは利用可能なCPUコア数によってのみ制限されます。
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);
}
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);
}
ProvisionedThroughputExceededExceptionキャッチブロックはすべて削除されます。 IronTesseractはスレッドセーフで、スレッド間で共有される単一インスタンスがロックなしで完全な並列負荷を処理します。 8コアのマシンでは、設定不要で8つのドキュメントを同時に処理できます。 32コアで、32コアを同時に処理します。 マルチスレッドの例では完全なパターンを示しており、速度最適化ガイドではスループット重視のデプロイメントにおけるエンジン構成のチューニングについて解説しています。
AnalyzeDocument Form Extraction を Region-Based CropRectangle OCR に置き換える
TextractのRelationshipType.VALUEの関係でリンクして返します。 フォームフィールドのキーと値のペアを再構築するには、WORDブロックを取得してテキストを組み立てる必要があります。 フィールド位置が固定されている固定形式フォームの場合、IronOCRのCropRectangleは関係グラフのトラバースをせずに各フィールドを直接抽出し、1ページ当たりのコストはゼロです。
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));
}
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;
}
GetTextFromBlockヘルパーは排除されます。 各フィールドは、それを含むピクセル領域を正確に読み取ります。それ以上でもそれ以下でもありません。 領域ベースのOCRガイドは、CropRectangleの座標とテンプレート測定からのゾーンの定義方法を説明しています。 請求書固有のワークフローについては、請求書OCRに関するブログ記事と領収書スキャンチュートリアルで、フィールド抽出パイプライン全体を解説しています。
AWS テクストラクト API からIronOCRへのマッピング リファレンス
| AWS テクストラクト | IronOCR相当値 |
|---|---|
AmazonTextractClient | IronTesseract |
AmazonS3Client | 不要 |
DetectDocumentTextRequest | input.LoadImage() |
DetectDocumentTextResponse | OcrResult |
AnalyzeDocumentRequest (TABLES/FORMS) | CropRectangle |
StartDocumentTextDetectionRequest | input.LoadPdf() — 同期、ジョブ開始なし |
GetDocumentTextDetectionRequest | 該当なし — 結果は即座に判明します |
Document.Bytes | input.LoadImage(stream) |
S3Object (ドキュメントステージング) | ファイルパスまたはストリーム - ステージングは不要 |
Block (BlockType.LINE) | result.Lines (型付きコレクション) |
Block (BlockType.WORD) | result.Words (型付きコレクション) |
Block (BlockType.TABLE) | result.Wordsが座標の近接によってグループ化されます |
Block (BlockType.KEY_VALUE_SET) | CropRectangle フィールドごとの領域抽出 |
Block.Confidence | word.Confidence / result.Confidence |
Block.Geometry.BoundingBox | word.X, word.Y, word.Width, word.Height |
RelationshipType.CHILDのトラバース | 型付き結果オブジェクトへのプロパティの直接アクセス |
JobStatus.IN_PROGRESS | 該当なし — 非同期状態なし |
JobStatus.SUCCEEDED | 該当なし — 同期リターン |
response.NextToken (ページネーション) | 該当なし — 結果がページ分けされていません |
ProvisionedThroughputExceededException | 該当なし — TPS制限なし |
AccessDeniedException | 該当なし — 認証チェーンなし |
input.LoadImageFrames() | マルチフレームTIFFの直接サポート(Textractには同等の機能はありません) |
result.SaveAsSearchablePdf() | 検索可能なPDF出力(Textractのような機能はありません) |
一般的な移行の問題と解決策
問題1:新規展開環境における認証情報の設定ミス
AWS Textract: ~/.aws/credentials、EC2インスタンスプロファイル、ECSタスクロールのチェーンから資格情報を解決します。 新しいDockerコンテナまたはCI環境では、これらが設定されていない場合、クライアントはエラーなしにコンストラクトされますが、すべてのAPIコールがAmazonClientException: No credentials specifiedをスローします。 この障害は実行時まで目に見えない。
**解決策:**認証情報チェーンを完全に削除する。 IronOCRのライセンスキーを環境変数から設定します。環境変数は設定が簡単で、管理に必要な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;
問題2:コードベース全体における非同期呼び出し箇所
AWS Textract: SDK全体が非同期専用であるため (async Task<t>で伝播します。 IronOCRの同期APIへの移行には、各拠点での決定が必要です。
**解決策:**バックグラウンド処理サービスおよびコントローラアクションの場合、IronOCRの同期呼び出しはバックグラウンドスレッド上でも安全です。 既存のコールチェーンを非同期のままにする必要がある場合のみ、Task.Runでラップします。
// 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);
}
既に非UIスレッドで動作しているバッチプロセッサやバックグラウンドサービスの場合、Task.Runはそのコンテキストで利益なしにオーバーヘッドを追加します。 非同期OCRガイドでは、推奨されるパターンについて説明しています。
問題3:S3バケットのクリーンアップロジックがコード全体に散在している
AWS Textract: Textract処理のためにS3にアップロードされるコードは、処理完了後にS3から削除する必要があります。 このクリーンアップはfinallyブロックで頻繁に行われ、エラーパスで省略されることがあり、ストレージコストを伴うオブジェクトが孤立してしまいます。 移行作業中、S3のクリーンアップコードはOCRとは概念的に関連がないため、見落とされやすい。
解決策: S3へのアップロードとクリーンアップのパターン全体には、 IronOCRに相当する機能はありません。 すべてのtry/finallyブロックを完全に削除します。 IronOCRはローカルパスまたはストリームから直接読み取ります。
// 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;
移行後、S3ステージングバケットを廃止してください。 PDF入力ガイドとストリーム入力ガイドは、 S3ステージングされたドキュメントアクセスを置き換えるすべての入力パターンを網羅しています。
問題4:ブロックグラフ走査コードには直接的な同等物がない
AWS Textract: RelationshipType.CHILD ID配列からテーブルセル、フォームフィールド、段落グループを再構築するコードは、移行に最も時間がかかります。 IronOCRは関係グラフではなく型付きコレクションを返すため、1対1の置換は不可能です。
**解決策:**各トラバーサルパターンを適切な型付きプロパティに置き換えます。 リレーションシップIDの検索が、プロパティへの直接アクセスとなる。
// 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
result.Wordsの単語座標グループ化を使用します。 表の読み方ガイドでは、座標に基づくアプローチについて説明しています。
問題5:ProvisionedThroughputExceededExceptionキャッチブロック
AWS Textract: ロバストなバッチ処理コードは、ErrorCode == "ProvisionedThroughputExceededException"でキャッチし、バックオフ付きのリトライを実装します。 このエラーコードはTextract固有の概念であり、 IronOCRには同等のコードはありません。
解決策: すべてのProvisionedThroughputExceededExceptionキャッチブロックを削除します。 IronOCRにはTPS(1秒あたりの処理速度)の制限はありません。 完全なスロットルバッチパターンをParallel.ForEachに置き換えます。
// Delete: SemaphoreSlim, retry loops, ProvisionedThroughputExceededException handlers
// Replace with:
Parallel.ForEach(imagePaths, path =>
{
var result = new IronTesseract().Read(path);
results[path] = result.Text;
});
問題6:クライアントコンストラクタに組み込まれたリージョン構成
AWS Textract: new AmazonTextractClient(Amazon.RegionEndpoint.USEast1)はリージョンをハードコーディングします。 複数リージョン展開には、複数のクライアントインスタンスまたは動的なリージョン選択が必要です。 Textractが新しい地域でのサポートを追加する場合、コードを更新する必要があります。
解決策: IronOCRには領域の概念がありません。 すべてのAmazon.RegionEndpointリファレンスを削除します。 IronTesseractコンストラクタはネットワーク構成を受け取らず、処理はローカルです。 AWSインフラストラクチャ上で各リージョンが独自のコンピューティングを実行するマルチリージョン展開の場合、各IronOCRインスタンスは、リージョン間呼び出しを行うことなく、そのコンピューティング境界内でドキュメントをローカルに処理します。
AWS テクストラクト 移行チェックリスト
移行前
コードベース内のTextractおよびS3 SDKの使用状況をすべて監査する。
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" .
代替コードを作成する前に、以下の項目を確認してください。
AmazonTextractClientを含むすべてのファイルをカウントします—各ファイルが置換対象です- すべての
FORMSの使用があるかどうかを確認します - すべての非同期ポーリングループを特定します (
do { await Task.Delay } while JobStatus == IN_PROGRESS) - すべてのS3クリーンアップ
finallyブロックを見つけます—これらはまるごと削除され、置換はしません - すべての
ProvisionedThroughputExceededExceptionキャッチブロックをカウントします—削除し、置換はしません - すべての
BlockType.KEY_VALUE_SETトラバースロジックを示しておきます—それぞれに構造化された出力置換が必要です
コードの移行
- すべての
AWSSDK.Coreを削除します - OCRを実行する各プロジェクトで
dotnet add package IronOcrを実行します - アプリケーション起動時に
IronOcr.License.LicenseKey = ...を1度追加します (Program.csまたは同等のもの) - すべての
using Amazon.Runtime;文を削除します - 以前にTextract名前空間をインポートしていた各ファイルに
using IronOcr;を追加します new IronTesseract()に置き換えますPutObjectAsync/DeleteObjectAsync呼び出しを直接ファイルパスまたはストリームリードに置き換えます- すべての
DetectDocumentTextAsync呼び出しを置き換えます:var text = ocr.Read(path).Text - すべての
StartDocumentTextDetectionAsync+ ポーリングループをinput.LoadPdf(path)+ocr.Read(input)に置き換えます - すべての多フレームTIFFパイプライン (TIFF → PDF → S3 → 非同期) を
input.LoadImageFrames(tiffPath)に置き換えます BlockType.LINE/result.Lines/result.Wordsに置き換えますCropRectangleのゾーン抽出に置き換えます- すべての
SemaphoreSlimスロットルを削除します - スロットルバッチパターンを共有の
Parallel.ForEachで置き換えます - デプロイマニフェストとCIパイプラインから、すべてのAWS認証情報環境変数設定を削除します。
- すべての処理コードの移行と検証が完了したら、S3ステージングバケットを廃止します。
移行後
- アプリケーションが
IRONOCR_LICENSEを設定したままクリーンに起動し、すべてのAWS環境変数が削除されていることを確認します Textractで以前に処理した同じサンプル画像に対してOCRを実行し、抽出されたテキストの精度を比較します。 - 複数ページのPDFを直接処理し、S3や非同期ポーリングを使用せずにすべてのページが抽出されていることを確認します。
- マルチフレームTIFFを処理し、ページ数が同じドキュメントに対するTextractの出力と一致することを確認します。
- 50以上の画像セットでバッチ処理をテストし、
ProvisionedThroughputExceededException相当が発生しないことを確認します - アプリケーションをインターネットへの外部アクセスがないDockerコンテナで実行し、OCRが正常に完了することを確認します。 検索可能なPDF出力がAdobe Readerで正しく開き、全文検索が可能であることを確認します。
- デプロイ環境から
AWS_SECRET_ACCESS_KEYを削除しても何も壊れないことを確認します - フォームや請求書の抽出ワークフローをTextractの既知の出力と比較してテストし、フィールドの正確性を検証する 代表的な文書セットの処理遅延を測定し、最小5秒のポーリング待機が解消されたことを確認する。
IronOCRへの移行の主なメリット
インフラストラクチャを単一のNuGetパッケージに統合。3つの AWS SDK パッケージ、ライフサイクル ポリシーを含む S3 バケット、すべてのデプロイ環境にわたる IAM ロール、および AWS 認証情報のローテーション手順が、1 つのNuGetパッケージに置き換えられます。 dotnet add package IronOcrです。 AWS認証情報管理に伴うあらゆる下流工程(セキュリティ監査、ローテーションスケジュール、環境変数管理、Dockerシークレットの設定など)は、これと共に消滅します。
**総所有コストの予測可能性。**ページ単位の課金モデルでは、文書量に応じて変動するコストが際限なく増加します。 IronOCRに移行後は、処理量に関わらず、追加ページあたりのコストはゼロになります。 Textract AnalyzeDocument料金で1か月に200,000ページを処理するチームは、OCRにのみ年間$36,000を費やします。 IronOCR Enterpriseライセンス(2,999ドル)は、請求が発生しないことで31日以内にその費用を回収できます。 ライセンス階層の詳細およびSaaS再配布条件については、 IronOCRのライセンスページをご覧ください。
同期処理により、5段階の非同期処理の複雑さが解消されます。S3へのアップロード、ジョブの開始、ポーリングループ、ページ分割された結果の収集、およびクリーンアップの最終ブロックは、Textract PDFパイプラインにおける5つの独立した障害モードを表しています。移行後、PDFは2行で読み込まれます。 エラーハンドリングはocr.Read()呼び出しの周りの単一のtry/catchに減少します。 ポーリングのタイムアウトロジック、nextTokenページネーションアキュムレータ—これらの概念はIronOCRコードベースには存在しません。 メンテナンスの負担は、削除されたコードの量に比例して減少する。
完全な導入柔軟性。Textractは、OCR処理のたびにインターネット接続を必要とします。 IronOCRは、インストール時にNuGetパッケージをダウンロードするためだけにインターネットアクセスを必要とします。その後は、エアギャップネットワーク、アウトバウンドルールが設定されていないDockerコンテナ、オンプレミスサーバー、およびTextractへのアクセス権限を持たないAWS EC2インスタンスで動作します。 Windows Server上で本番環境で実行されるバイナリは、Linuxコンテナ内でも実行されます。 Docker導入ガイドとLinux導入ガイドでは、これまでTextractの使用が制限されていたコンテナ環境向けの具体的な構成について説明しています。
データ主権は構成ではなくアーキテクチャによって実現されます。IronOCRにはネットワーク送信モードを無効にする機能はありません。ローカル処理のみが唯一のモードです。 IronOCRで処理された文書は、ホストマシンから外部に持ち出されることはありません。PHI(保護対象医療情報)を処理する医療アプリケーション、CUI(機密情報)を処理する防衛アプリケーション、および決済カード画像を処理する金融アプリケーションにとって、これはBAA(事業提携契約)の交渉、地域別データ所在地の設定、およびAmazonのデータ処理ポリシーの監査を必要としないコンプライアンス体制です。 コンプライアンスの範囲は、既に管理している自社のインフラストラクチャの境界です。 IronOCRの製品ページには、コンプライアンスレビューを実施するチーム向けに、機能概要と導入に関する詳細なドキュメントが掲載されています。
文書品質管理のための設定可能な前処理。Textractの内部前処理機能はアクセスも設定もできません。 スキャンした文書の仕上がりが悪い場合、唯一の対処法は出力結果を受け入れるか、再スキャンすることである。 IronOCRは完全な前処理パイプラインを公開します: DeepCleanBackgroundNoise()。 スキャン結果の信頼性が低いという理由でTextractからドキュメントを移行したチームは、回転、ノイズ、低コントラスト、解像度不足といった特定の欠陥に合わせたフィルターを使用することで、根本原因に直接対処できます。 前処理機能ページと画像品質補正ガイドには、利用可能なフィルターとその適切な使用例が記載されています。
