AWS Textract'dan IronOCR'a Geçiş
Bu kılavuz, bulut bağımlılıkları olmadan yerel belge işlemi gereken .NET geliştiricileri için AWS Textract'ten IronOCR'a tam bir geçişi ele alır. Genel bilgilerden, asenkron boru hattının kaldırılması, S3'ün ortadan kaldırılması ve her Textract işleminin IronOCR eşdeğerine taşınması için gereken özel kod değiştirmelerini kapsar.
AWS Textract'den Neden Göç Edilmeli
AWS Textract yetenekli bir yönetilen hizmettir, ancak mimarisi - mali ve operasyonel - belge yükü arttıkça artan maliyetler getirir. Ciddi belge işleme hatları inşa eden ekipler sonunda tüm bu sürtünme noktalarına ulaşırlar.
AWS Kimlik Bilgisi Altyapısı Her Dağıtımı Bileştirir. Textract kodunu çalıştıran her ortamın AWS kimlik bilgilerine ihtiyacı vardır: IAM kullanıcı veya rol, erişim anahtarı ve gizli, bölge yapılandırması ve — PDF işlemesi için — S3 kovası izinleri dahil olmak üzere. İşlemin başladığı ilk sayfadan önce beş farklı yapılandırma adımı vardır. Üretim dağıtımları, kimlik bilgisi döngü politikaları, Docker konteynerlarına veya Kubernetes podlarına güvenli enjeksiyon ve IAM politika kaymaları sessiz hatalara neden olduğunda AccessDeniedException için izleme gerektirir. IronOCR, başlatıldığında bir kez ayarlanan bir dize gerektirir: bir lisans anahtarı.
Sayfa Başına Fiyatlandırma Üst Sınır Yoktur. DetectDocumentText için sayfa başı taban fiyatı $0,0015'tir, bu maliyet değil tabandır. Tablo içeren herhangi bir belge, sayfa başına $0,015'te AnalyzeDocument tetikler — taban fiyatın on katı. Formlar, sayfa başına başka bir $0.05 ekler. Tablo çıkarımı ile ayda 100.000 sayfa karışık bir belge iş akışı yıllık $18.000 çalıştırır ve bu rakam her Ocak'ta sıfırlanır.IronOCR Professional lisansı bir kez $2,999 maliyetindedir. Bu noktadan sonra, hacimden bağımsız olarak sayfa başına maliyet sıfırdır.
Asenkron PDF Hattı Bir Bakım Sorumluluğudur. Textract ile herhangi bir çok sayfalı PDF işlemi beş farklı aşama gerektirir: S3 yüklemesi, StartDocumentTextDetection, polling döngüsü, sayfalı sonuç alma ve S3 temizliği. Her aşama, kendi hata işleme, tekrar stratejisi ve zaman aşımı yönetimine gerek duyan bağımsız bir hata modu oluşturur. Bu hattı üretim ortamına yollayan ekipler, bunu kurmaya harcadıklarından daha fazla zaman harcadıklarını sürekli bildirirler. IronOCR, iki satır kod ile PDF okur.
İnternet Erişimi Olmadan Textract Çalışmaz. İzole ağ segmentlerinde Docker konteynerleri, yerinde sunucular, izole araştırma ortamları ve giden trafiği sınırlamaları olan endüstriyel sistemler hepsi bir ortak özellik paylaşır: Textract kullanılamaz. IronOCR, bir NuGet paketi olarak kurulur ve ilk paket indirme sonrasında herhangi bir ağ çağrısı yapmaksızın çalışır.
Veriler Her Çağrıda Altyapınızdan Ayrılır. Textract ile yapılan her OCR işlemi, belge içeriğini Amazon'un sunucularına iletir. PHI işleyen sağlık kuruluşları, CUI işleyen savunma yüklenicileri, ayrıcalıklı iletişimleri işleyen yasal ekipler ve ödeme kartı görüntüleri işleyen finansal kuruluşlar için bu bir yapılandırma seçeneği değil — bu tamamen düzenlenmiş ortamlarda Textract'in kullanılmasını yasaklayan bir mimari kısıtlamadır. IronOCR, donanımınızda işler. Devre dışı bırakılacak bir bulut modu yok; yerel işleme, tek moddur.
Hız Limitleri Yığın İletimi Kısıtlar. Varsayılan StartDocumentTextDetection TPS limiti saniyede 5 istektir. Yüzlerce belge işleyen yığın işler, SemaphoreSlim kısıtlama, ProvisionedThroughputExceededException üzerine üstel gecikme ve hız yenileme zamanlayıcıları gerektirir. TPS artışı talep etmek, garanti edilmeyen bir sonuçla formal AWS destek davası gerektirir. IronOCR, yerel CPU'nun izin verdiği hızda işlem yapar — 16 çekirdekli bir sunucu, düz bir Parallel.ForEach ile 16 belgeyi eşzamanlı olarak işler, hizmet katmanı müzakeresi gerekmez.
Temel Sorun
Her Textract dağıtımı bu tören ile başlar — tek bir sayfa işlenmeden önce:
// 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, tüm kimlik bilgisi zincirini basit bir atama ile değiştirir:
// 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")
IronOCR ve AWS Textract: Özellik Karşılaştırması
Aşağıdaki tablo, her iki kütüphane'nin göç kararları için en önemli olan boyutlardaki yeteneklerini eşleştirir.
| Özellik | AWS Textract | IronOCR |
|---|---|---|
| İşleme Yeri | Amazon bulutu (zorunlu) | Yalnızca yerel / yerinde |
| İnternet Gerekli | Her zaman | Asla |
| Kimlik Bilgisi Kurulumu | IAM kullanıcı/rol + isteğe bağlı S3 kovası | Tek lisans anahtarı dizesi |
| Çok Sayfalı PDF | S3 aşamalandırma + asenkron iş gerektirir | Doğrudan eşzamanlı input.LoadPdf() |
| Şifre Korumalı PDF | Desteklenmiyor | input.LoadPdf(path, Password: "x") |
| Çok Çerçeveli TIFF | Desteklenmiyor | input.LoadImageFrames("multi.tiff") |
| Asenkron Anket Gerekir | Evet (tüm PDF'ler için) | Hayır — varsayılan olarak senkronize |
| Oran Sınırlamaları | 5 TPS varsayılan (StartDocumentTextDetection) | Yok — sadece CPU sınırlı |
| Sayfa Başına Maliyet | Sayfa başına 0,0015–0,065 $ | Lisans satın aldıktan sonra $0 |
| Otomatik Ön İşleme | Dahili, yapılandırılamaz | Yeniden hizalama, Gürültü Giderme, Kontrast, İkiliye Çevirme, Keskinleştirme, Ölçekleme |
| Aranabilir PDF Çıktısı | Mevcut değil | result.SaveAsSearchablePdf() |
| hOCR Dışa Aktarma | Mevcut değil | result.SaveAsHocrFile() |
| Barkod Okuma | Mevcut değil | ocr.Configuration.ReadBarCodes = true |
| Bölge Tabanlı OCR | AnalyzeDocument + blok ilişkileri aracılığıyla | CropRectangle(x, y, width, height) |
| Yapılandırılmış Sonuçlar | Düz List<Block>, BlockType ile filtrelenmiş |
Yazılı Pages, Paragraphs, Lines, Words |
| Desteklenen Diller | İngilizce ağırlıklı (ek seç) | 125+ dil paketi üzerinden NuGet |
| Çok Dilli Eşzamanlı | Desteklenmiyor | OcrLanguage.French + OcrLanguage.German |
| İzole Dağıtım | Mümkün değil | Tam destekli |
| BAA Olmadan HIPAA | Mümkün değil | Harici işlemci gerektirmez — sadece yerinde |
| Docker Dağıtımı | Enjekte edilmiş AWS kimlik bilgileri gerektirir | Kimlik bilgisi gerektirmez — düz NuGet paketi |
| Çapraz Platform | AWS yönetimi (yalnızca Linux) | Windows, Linux, macOS, Docker, Azure, AWS EC2 |
| Lisanslama Modeli | Sayfa başına ölçülmüş faturalama (sürekli harcama) | Süresiz tek seferlik ($999 / $1.499 / $2.999) |
Hızlı Başlangıç: AWS Textract'ten IronOCR'a Geçiş
Adım 1: NuGet Paketini Değiştirin
AWS SDK paketlerini kaldırın:
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
NuGet kullanarak IronOCR'u yükleyin:
dotnet add package IronOcr
Adım 2: Ad Alanlarını Güncelleyin
Tüm AWS alan adı içe aktarma işlemlerini tek bir IronOCR alan adı ile değiştirin:
// 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
Adım 3: Lisansa İzin Verin
Uygulama başlatıldığında bir kez lisans başlatmayı ekleyin. Tüm AWS kimlik bilgisi çevresel değişken kurulumunu kaldırın:
// 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"
Kod Göç Örnekleri
AmazonTextractClient Yapıcısını ve IAM Kurulumunu Değiştirme
En görünür göç değişikliği istemci başlatmasıdır. Textract, kimlik bilgisi çözümlemesi ile birlikte olan bir bağımlılık enjekte edilmiş istemci gerektirir — yani istemci ve tüm bağımlılıkları her dağıtım ortamında önceden yapılandırılmış olmalıdır. Herhangi bir yanlış yapılandırma, ilk OCR çağrısı AmazonClientException fırlatana kadar sessizce başarısız olur.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
// 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
Tüm AWS kimlik bilgileri altyapısı — IAM rolleri, ortam değişkenleri, ~/.aws/credentials dosyaları, S3 kovası yaşam döngüsü politikaları ve bölge yapılandırması — kaldırılır. DocumentOcrService yapıcısı, çalışma zamanı hata modlarına sahip 3 bağımlı enjeksiyon sitesinden tek bir lisans atamasına geçer. Dağıtım desenleri hakkında ayrıntılar için IronTesseract kurulum kılavuzuna bakın.
StartDocumentTextDetection'i Çok Çerçeveli TIFF İşleme ile Değiştirme
Textract'ın asenkron iş API'si (StartDocumentTextDetection) çok sayfalı herhangi bir belge için gereklidir. Belge dijitalleştirme hatlarında yaygın bir desen, her bir taranmış sayfa için bir çerçeve olan çok çerçeveli TIFF'ler olarak taranan belgeleri almak Textract, doğrudan TIFF girişi kabul edemez; iş bir job başlatana/yüklenene kadar belge PDF'ye dönüştürülmeli ve S3'e yüklenmelidir. IronOCR, çok çerçeveli TIFF'leri yerel olarak okur.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
//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
Belgeyi PDF'ye dönüştürme adımı, S3 yüklemesi, asenkron anket döngüsü, sayfalı sonuç birikimi ve S3 temizleme işlemi ortadan kalkar.IronOCR uygulaması, çerçeveleri doğrudan okur ve hiçbir ara format dönüşümü olmadan result.Pages aracılığıyla sayfa başına erişim sağlar. TIFF ve GIF giriş kılavuzu, yalnızca çerçevelerin bir alt kümesinin gerektiği durumlar için çerçeve seçimini kapsar.
S3 Aşamalandırmayı Aranabilir PDF Çıktısı ile Değiştirme
Yaygın bir Textract kullanım durumu, taranmış PDF arşivlerini aranabilir forma dönüştürmektir. Textract yaklaşımı, her PDF'yi S3'e yüklemeyi, asenkron işi çalıştırmayı, çıkarılan metni toplamayı, ardından bu metni aranabilir katman olarak gömmek için ayrı bir PDF kütüphanesi kullanmayı gerektirir. IronOCR, taramayı gerçekleştirir ve tek bir çağrıda aranabilir bir PDF üretir.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
//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
Anket zaman aşımı mantığı, S3 yükleme ve temizleme deseni ve dış PDF kütüphane bağımlılığı ortadan kalkar. SaveAsSearchablePdf, tanınan metni doğrudan çıktı dosyasına gömerek, her taranan sayfayı ikinci bir kütüphane olmadan tamamen metin arama yapılabilir hale getirir. aranabilir PDF kılavuzu, sayfa seçimini, sıkıştırma seçeneklerini ve meta veri gömmeyi kapsar. PDF OCR boru hatlarının daha geniş bağlamı için PDF giriş kılavuzuna bakın.
AnalyzeDocument Blok Graf Geçişini Yapılandırılmış Sayfa Sonuçları ile Değiştirme
Textract'ın AnalyzeDocument ile TABLES ve FORMS, her bir eleman — satırlar, kelimeler, hücreler, tablo başlıkları, form tuşları, form değerleri — aynı Block türünde olan ama sadece BlockType ile ayırt edilen ve RelationshipType.CHILD ID dizileri ile bağlantılı olan düz bir List<Block> döndürür. Bir belgenin mantıksal yapısını yeniden oluşturmak için bu ilişki grafı üzerinde gezinmek gerekir. IronOCR, türlendirilmiş bir hiyerarşi döndürür: sayfalar, paragraflar, satırlar, kelimeler, her biri koordinat erişimi ile.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
//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
Paragraflar, satırlar ve kelimeler, .X, .Y, .Width, .Height ve .Confidence özellikleri ile yazılı koleksiyonlar olarak doğrudan erişilebilir. BoundingBox normalizasyonu, boşluk eşiği sezgisel kararları, blok türü filtreleme yok. okuma sonuçları kılavuzu, karakter seviyesinde koordinat erişimi de dahil olmak üzere tam OcrResult hiyerarşisini belgelendirir. Bu yapıya bağlı fatura ve makbuz iş akışları için fatura OCR öğreticisine bakın.
Oran Sınırlı Toplu İşleme Yerine Sınırsız Paralel Yürütme ile Değiştirme
Yığın görüntü işleme, Textract'ın TPS limitlerinin en görünür operasyonel maliyeti ürettiği yerdir. DetectDocumentText eşzamanlı API'si hız sınırlıdır; Saniyede 5'ten fazla istek göndermek ProvisionedThroughputExceededException üretir. Doğru yığın işleme, SemaphoreSlim kısıtlama, geri çekilme ile tekrar mantığı ve uçuşta istekler için dikkatli muhasebe gerektirir. IronOCR'un oran sınırı yoktur — üretim hızı yalnızca mevcut CPU çekirdekleriyle sınırlıdır.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
// 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
SemaphoreSlim, tekrar döngüsü, üstel geri çekilme ve ProvisionedThroughputExceededException catch blokları kaldırılır. IronTesseract, iş parçacığı güvenlidir ve kilitler olmadan tam paralel yükü yöneten iş parçacıkları arasında paylaşılır tek bir örnektir. 8 çekirdekli bir makinede bu, 8 belgeyi yapılandırma gerektirmeksizin eşzamanlı işler; 32 çekirdekte, aynı anda 32. çok iş parçacıklı örnek tam deseni gösterir ve hız optimizasyonu kılavuzu, üretim hızına odaklanan dağıtımlar için motor yapılandırma ayarlarını kapsar.
AnalyzeDocument Form Çıkarımını Bölge Tabanlı CropRectangle OCR ile Değiştirme
Textract'ın AnalyzeDocument ile FORMS, KEY_VALUE_SET bloklarını RelationshipType.VALUE ilişkileri ile bağlanmış şekilde döndürür. Form alanı anahtar-değer çiftlerini yeniden oluşturmak için EntityTypes.Contains("KEY") ile filtreleme yapmanız, ardından VALUE ilişki kimliklerini takip etmeniz ve metni birleştirmek için alt WORD bloklarını getirmeniz gerekir. Alan konumlarının bilindiği sabit format formlar için IronOCR'ın CropRectangle, ilişki grafiği geçişi olmadan her alanı doğrudan çıkarır — ve sayfa başı maliyeti sıfırdır.
AWS Textract Yaklaşımı:
// 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
IronOCR Yaklaşımı:
// 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
KEY_VALUE_SET blok filtreleme, RelationshipType.VALUE geçişi ve GetTextFromBlock yardımcı kaldırılır. Her alan, yalnızca onu içeren piksel bölgesini okur — ne eksik ne fazla. bölge tabanlı OCR kılavuzu CropRectangle koordinatları ve şablon ölçümlerinden nasıl bölgeler tanımlanacağını kapsar. Fatura özgü iş akışları için fatura OCR blog yazısı ve makbuz tarama öğretici, tam alan çıkarma hatlarını gösterir.
AWS Textract API'den IronOCR Eşleştirme Referansı
| AWS Textract | IronOCR Karşılığı |
|---|---|
AmazonTextractClient |
IronTesseract |
AmazonS3Client |
Gerekli değil |
DetectDocumentTextRequest |
OcrInput ile input.LoadImage() |
DetectDocumentTextResponse |
OcrResult |
AnalyzeDocumentRequest (TABLOLAR/FORMLAR) |
OcrInput isteğe bağlı CropRectangle |
StartDocumentTextDetectionRequest |
OcrInput ile input.LoadPdf() — eş zamanlı, iş başlatması yok |
GetDocumentTextDetectionRequest |
Uygulanamaz — sonuçlar anında |
Document.Bytes |
input.LoadImage(byteArray) veya input.LoadImage(stream) |
S3Object (belge hazırlama) |
Dosya yolu veya akış — aşamalandırma gerektirmez |
Block (BlockType.LINE) |
result.Lines (yazılı koleksiyon) |
Block (BlockType.WORD) |
result.Words (yazılı koleksiyon) |
Block (BlockType.TABLE) |
result.Words koordinat yakınlığına göre gruplandırılmış |
Block (BlockType.KEY_VALUE_SET) |
CropRectangle alan başına bölge çıkarması |
Block.Confidence |
word.Confidence / result.Confidence |
Block.Geometry.BoundingBox |
word.X, word.Y, word.Width, word.Height |
RelationshipType.CHILD geçişi |
Türlenmiş sonuç nesnelerinde doğrudan özelliğe erişim |
JobStatus.IN_PROGRESS |
Uygulanamaz — eşzamansız durum yok |
JobStatus.SUCCEEDED |
Uygulanamaz — eşzamanlı dönüş |
response.NextToken (sayfalama) |
Uygulanamaz — sonuçlar sayfalandırılmamış |
ProvisionedThroughputExceededException |
Uygulanamaz — TPS sınırları yok |
AccessDeniedException |
Uygun değil — kimlik zinciri yok |
input.LoadImageFrames() |
Çok çerçeveli TIFF doğrudan destek( Textract'a eşdeğer yok) |
result.SaveAsSearchablePdf() |
Aranabilir PDF çıktısı (Textract'a eşdeğer yok) |
Yaygın Göç Sorunları ve Çözümleri
Problem 1: Yeni Dağıtım Ortamlarında Kimlik Bilgileri Yanlış Yapılandırılmış
AWS Textract: AmazonTextractClient yapıcısı kimlik bilgilerini bir zincirden çözümler: ortam değişkenleri, ~/.aws/credentials, EC2 örnek profili, ECS görev rolü. Yeni bir Docker konteynerında veya CI ortamında, eğer bunlardan hiçbiri yapılandırılmamışsa, istemci hata vermeden oluşturulur ama her API çağrısı AmazonClientException: No credentials specified fırlatır. Başarısızlık, çalışma zamanına kadar görünmez.
Çözüm: Kimlik bilgisi zincirini tamamen kaldırın. Yapılandırılması daha basit ve yönetilecek IAM izinleri olmayan bir çevresel değişkenden IronOCR lisans anahtarını ayarlayın:
// 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
Problem 2: Tüm Kod Tabanında Asenkron Çağrı Yerleri
AWS Textract: Tüm SDK asenkron olduğu için (DetectDocumentTextAsync, StartDocumentTextDetectionAsync, GetDocumentTextDetectionAsync), tüm Textract çağrı yerleri async Task<t> çağrı yığınına yayar. IronOCR'un eşzamanlı API'sine geçiş, her yerde bir karar gerektirir.
Çözüm: Arka plan işleme servisleri ve denetleyici eylemleri için, IronOCR'un eşzamanlı çağrıları, arka plan iş parçacıklarında güvenlidir. Mevcut çağrı zincirinin asenkron kalması gerektiğinde sadece Task.Run ile sarın:
// 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
Zaten arka plan iş parçacıklarında çalışan yığın işlemcileri ve arka plan hizmetleri için ocr.Read()'yi eşzamanlı olarak çağırın — Task.Run, bu bağlamlarda yarar sağlamadan ek yük ekler. asenkron OCR kılavuzu, önerilen desenleri kapsar.
Problem 3: Kod İsrafı Aracılığıyla Dağıtılan S3 Kova Temizleme Mantığı
AWS Textract: Textract işlemesi için S3'e yükleme yapan herhangi bir kod, iş tamamlandıktan sonra S3'den silmelidir. Bu temizlik sık sık finally bloklarında yaşar ve bazen hata yollarında atlanır, depolama maliyetlerini biriktiren yetim nesneler oluşturur. Göç sırasında, S3 temizleme kodu, OCR ile kavramsal olarak ilişkili olmadığı için göz ardı etmek kolaydır.
Çözüm:IronOCR için eşdeğer yoktur, tüm S3 yükleme ve temizleme desenleri. Tüm PutObjectAsync, DeleteObjectAsync ve S3 ile ilgili try/finally bloklarını tamamen silin.IronOCR yerel yollardan veya akışlardan doğrudan okur:
// 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
Göç sonrası S3 aşamalama kovasını devre dışı bırakın. PDF giriş kılavuzu ve akış girdi kılavuzu, S3 aşamalandırılmış belge erişiminin yerine geçen her girdi desenini kapsar.
Problem 4: Blok Graf Geçiş Kodu için Doğrudan Eşdeğer Yok
AWS Textract: Block.Relationships üzerinden tablo hücrelerini, form alanlarını veya paragraf gruplamalarını yeniden oluşturmak için kod geçiş yapmak üzere en çok zaman alan koddur. Bire bir değiştirme yoktur, çünkü IronOCR bir ilişki grafı yerine türlendirilmiş koleksiyonlar döndürür.
Çözüm: Her geçiş desenini uygun türlendirilmiş özellik ile değiştirin. İlişki ID aramaları doğrudan özellik erişimine dönüşür:
// 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
BlockType.CELL ve ColumnIndex özelliklerine bağlı tablo yeniden yapılandırması için, result.Words üzerine kelime koordinat gruplamaları kullanın. tablo okuma kılavuzu, koordinat bazlı yaklaşımı kapsar.
Problem 5: ProvisionedThroughputExceededException Yakalama Blokları
AWS Textract: Sağlam yığın işleme kodu AmazonTextractException ile ErrorCode == "ProvisionedThroughputExceededException" yakalar ve geri çekilme ile tekrar uygular. Bu hata kodu,IronOCR eşdeğeri olmayan Textract özel bir kavramıdır.
Çözüm: Tüm ProvisionedThroughputExceededException catch bloklarını silin. IronOCR'un TPS sınırı yoktur. Tamamen Parallel.ForEach ile kısıtlanmış yığın modelini değiştirin:
// 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)
Problem 6: Yapıcı İle İç İçe Geçirilmiş Bölge Kapsamı Ayarı
AWS Textract: new AmazonTextractClient(Amazon.RegionEndpoint.USEast1) bir bölgeyi sabitler. Çoklu bölge dağıtımları, birden fazla istemci örneği veya dinamik bölge seçimi gerektirir. Textract, yeni bölgelerde destek eklediğinde kodun güncellenmesi gerekir.
Çözüm: IronOCR'un bölge kavramı yok. Tüm Amazon.RegionEndpoint referanslarını kaldırın. IronTesseract yapıcısı ağ yapılandırması almaz — işleme yereldir. Her bölge kendi hesabının içinde çalıştığı AWS altyapısındaki çoklu bölge dağıtımları için,IronOCR örnekleri hiçbir çapraz bölge çağrısı olmaksızın belgeleri kendi hesap sınırları dâhilinde işler.
AWS Textract Göç Kontrol Listesi
Öncesi-Geçiş
Kod tabanındaki tüm Textract ve S3 SDK kullanımını denetleyin:
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" .
Aşağıdakilerini yazmadan önce envanterini yapın:
AmazonTextractClientiçeren tüm dosyaları sayın — her biri bir değiştirme hedefidir- Tüm
AnalyzeDocumentçağrı yerlerini listeleyin —TABLES,FORMSveya her ikisinin de kullanılıp kullanılmadığını not edin - Tüm asenkron polling döngülerini (
do { await Task.Delay } while JobStatus == IN_PROGRESS) belirleyin - Tüm S3 temizleme
finallybloklarını bulun — bunlar tümden silinir, yerine konulmaz - Tüm
ProvisionedThroughputExceededExceptioncatch bloklarını sayın — silinir, yerine konulmaz - Tüm
BlockType.TABLE,BlockType.CELL,BlockType.KEY_VALUE_SETgeçiş mantığını not edin — her biri yapılandırılmış çıktı değişimi gerektirir
Kod Geçişi
- Tüm
.csprojdosyalarındanAWSSDK.Textract,AWSSDK.S3veAWSSDK.Core'yi kaldırın - OCR gerçekleştiren her projede
dotnet add package IronOcrçalıştırın - Uygulama başlangıcında bir kez
IronOcr.License.LicenseKey = ...ekleyin (Program.csveya eşdeğeri) - Tüm
using Amazon.Textract;,using Amazon.Textract.Model;,using Amazon.S3;,using Amazon.Runtime;ifadelerini kaldırın - Daha önce Textract ad alanlarını içe aktaran her dosyaya
using IronOcr;ekleyin - Tüm
AmazonTextractClientyapıcı çağrılarınınew IronTesseract()ile değiştirin - Tüm
AmazonS3Clientörneklenmesini ve tümPutObjectAsync/DeleteObjectAsyncçağrılarını doğrudan dosya yolu veya akış okumaları ile değiştirin - Tüm
DetectDocumentTextAsyncçağrılarını değiştirin:var text = ocr.Read(path).Text - Tüm
StartDocumentTextDetectionAsyncve polling döngüleriniinput.LoadPdf(path)veocr.Read(input)ile değiştirin - Tüm çok çerçeveli TIFF hatlarını (TIFF → PDF → S3 → asenkron)
input.LoadImageFrames(tiffPath)ile değiştirin BlockType.LINE/BlockType.WORDfiltre zincirleriniresult.Lines/result.Wordsile değiştirinBlockType.KEY_VALUE_SETilişki geçişleriniCropRectanglebölge çıkarmaları ile değiştirin- Tüm
ProvisionedThroughputExceededExceptioncatch bloklarını veSemaphoreSlimkısıtlamalarını silin - Paylaşılan bir
IronTesseractörneği kullanarak kısıtlanmış yığın desenleriniParallel.ForEachile değiştirin - Tüm AWS kimlik bilgisi ortam değişkeni yapılandırmalarını dağıtım manifestolarından ve CI hatlarından kaldırın
- Tüm işleme kodu taşındı ve doğrulandıktan sonra S3 evreleme kovalıklarını devre dışı bırakın
Geçiş Sonrası
- Uygulamanın yalnızca
IRONOCR_LICENSEayarlı olarak temiz bir şekilde başlatıldığından ve tüm AWS ortam değişkenlerinin kaldırıldığından emin olun - Textract tarafından önceden işlenmiş aynı örnek resimlerde OCR çalıştırın ve çıkarılan metni doğruluk açısından karşılaştırın
- Çok sayfalı bir PDF'yi doğrudan işleyin ve tüm sayfaların S3 veya async anket olmadan çıkarıldığını doğrulayın
- Çok çerçeveli bir TIFF'i işleyin ve sayfa sayısının aynı belge için Textract'ın çıktısıyla eşleştiğini doğrulayın
- 50'den fazla resim seti ile yığın işlemesini test edin ve hiçbir
ProvisionedThroughputExceededExceptioneşdeğerinin oluşmadığını doğrulayın - İnternet çıkışı olmayan bir Docker konteynerında uygulamayı çalıştırın ve OCR'nin başarıyla tamamlandığını doğrulayın
- Aranabilir PDF çıktısının Adobe Reader'da doğru açıldığını ve tam metin aranabilir olduğunu doğrulayın
- Dağıtım ortamından
AWS_ACCESS_KEY_IDveAWS_SECRET_ACCESS_KEY'i kaldırmanın hiçbir şeyi bozmadığını doğrulayın - Bilinen Textract çıktılarına karşı form veya fatura çıkarma iş akışlarını test ederek alan doğruluğunu doğrulayın
- Temsilci bir belge seti için işleme gecikmesini ölçün ve en az 5 saniyelik anket bekleme süresinin ortadan kalktığını doğrulayın
IronOCR'a Geçişin Ana Faydaları
Tek Bir NuGet Paketi için Altyapı Konsolidasyonu. Üç AWS SDK paketi, yaşam döngüsü politikalarına sahip bir S3 kovalığı, her bir dağıtım ortamında IAM rolleri ve AWS kimlik bilgileri rotasyon prosedürleri bir NuGet paketiyle değiştirilmiştir. .csproj değişikliği dotnet remove package AWSSDK.Textract tarafından dotnet add package IronOcr takip edilir. AWS kimlik bilgisi yönetiminin tüm aşağı akış sonuçları — güvenlik denetimleri, rotasyon programları, ortam değişken hijyeni, Docker sırları yapılandırması — onunla ortadan kaybolur.
Öngörülebilir Toplam Sahip Olma Maliyeti. Sayfa başına faturalandırma modeli, belge hacmiyle orantılı olarak değişen maliyetler üretir. IronOCR'ye geçtikten sonra, işlenen ek sayfa başına maliyet, hacimden bağımsız olarak sıfırdır. Bir ekip, Textract AnalyzeDocument oranında ayda 200.000 sayfa işlem yaparken, yalnızca OCR için yılda 36.000 dolar harcar. $2,999 olan IronOCR Enterprise lisansı, faturalandırmadan kaçınmanın ilk 31 gününde bu maliyeti karşılar. Tam kademeli detaylar ve SaaS yeniden dağıtım şartları için IronOCR lisans sayfasını inceleyin.
Eşzamanlı İşleme, Beş Aşamalı Async Karmaşıklığını Ortadan Kaldırır. S3 yükleme, iş başlatma, anket döngüleri, sayfalı sonuç toplama ve temizleme sona erdirme bloku Textract PDF hattında beş bağımsız hata modu temsil eder. Göçten sonra, bir PDF iki satırda okunur. Hata işleme, ocr.Read() çağrısı etrafında tek bir try/catch'e indirgenir. Polling zaman aşımı mantığı, do/while JobStatus == IN_PROGRESS döngüsü, nextToken sayfalama akümülatörü — bu kavramların hiçbiri IronOCR kod tabanında mevcut değil. Bakım yükü, kaldırılan kodla orantılı olarak düşer.
Tam Dağıtım Esnekliği. Textract her OCR çağrısında internet erişimi gerektirir. IronOCR, sadece kurulum sırasında NuGet paketini indirmek için internet erişimi gerektirir. Bundan sonra, izole ağlarda, dışa yönelik kurallar olmayan Docker konteynerlerinde, yerel sunucularda ve Textract erişimi olmayan AWS EC2 örneklerinde çalışır. Windows Server'da üretimde çalışan aynı ikili, Linux konteynerında da çalışır. Önceden Textract kullanımı engellenmiş konteynerize ortamlar için belirli yapılandırmayı Docker dağıtım kılavuzu ve Linux dağıtım kılavuzu kapsar.
Veri Egemenliği Mimarisi, Yapılandırma Değil. IronOCR'nin devre dışı bırakılacak ağ iletim modu yoktur — yerel işleme tek moddur.IronOCR üzerinden işlenen belgeler asla ana bilgisayarı terk etmez. PHI işleyen sağlık uygulamaları, CUI işleyen savunma uygulamaları ve ödeme kartı görüntüleri işleyen finansal uygulamalar için bu, BAA pazarlığı gerektirmeyen, bölgesel veri ikametgahı yapılandırması gerekmeyen ve Amazon'un veri işleme politikalarının incelenmesini gerektirmeyen bir uyum duruşudur. Uyum kapsamı kendi altyapınızın sınırıdır; zaten kontrol ettiğiniz sınır. Bir uyum incelemesini tamamlayan ekipler için tam özellik özeti ve dağıtım belgeleri IronOCR ürün sayfasında bulunmaktadır.
Belge Kalite Kontrolü İçin Yapılandırılabilir Ön İşleme. Textract'ın dahili ön işlemesi erişilemez veya yapılandırılamaz. Tarama sonucunun kötü olduğu durumda, tek çare çıktıyı kabul etmek veya yeniden taramak. IronOCR, tam ön işleme hattını ortaya çıkarır: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), Erode() ve DeepCleanBackgroundNoise(). Zor taramalarda düşük güven sonuçları nedeniyle Textract'tan belgeleri taşıyan ekipler, döndürme, gürültü, düşük kontrast veya yetersiz çözünürlük gibi belirli kusura uyarlanmış filtrelerle kök nedeni doğrudan ele alabilirler. Mevcut filtreleri ve uygun kullanım durumlarını belgeleyen ön işleme özellikleri sayfasını ve görüntü kalitesi düzeltme kılavuzunu inceleyin.
Sıkça Sorulan Sorular
Amazon Textract'tan neden IronOCR'a geçmeliyim?
Yaygın geçiş nedenleri arasında COM ara bağlamının karmaşıklığını ortadan kaldırmak, dosya tabanlı lisans yönetimini değiştirmek, sayfa başına fatura düzenlemekten kaçınmak, Docker/kapsayıcı dağıtımını etkinleştirmek ve standart .NET araçlarıyla entegre olan bir NuGet doğal iş akışı benimsemek yer almaktadır.
Amazon Textract'tan IronOCR'a geçişteki ana kod değişiklikleri nelerdir?
Amazon Textract başlatma dizilerini IronTesseract oluşturma ile değiştirin, COM yaşam döngüsü yönetimini (açık Oluştur/Yükle/Kapat desenleri) kaldırın ve sonuç özellik adlarını güncelleyin. Sonuç, önemli ölçüde daha az sablon satırıdır.
Geçişi başlatmak için IronOCR'u nasıl kurarım?
Paket Yöneticisi Konsolunda 'Install-Package IronOcr' veya CLI'da 'dotnet add package IronOcr' çalıştırın. Dil paketleri ayrı paketlerdir: Örneğin, Fransızca için 'dotnet add package IronOcr.Languages.French'.
IronOCR, Amazon Textract'ın standart iş belgeleri için OCR doğruluğuna eşit midir?
IronOCR, faturalar, sözleşmeler, makbuzlar ve yazılı formlar dahil olmak üzere standart işletme içerikleri için yüksek doğruluğa ulaşır. Görüntü ön işleme filtreleri (dengelemeyi düzeltme, gürültü kaldırma, kontrast artırma) bozulmuş girdiler üzerindeki tanımayı daha da iyileştirir.
IronOCR, Amazon Textract'ın ayrı olarak yüklediği dil verilerini nasıl ele alır?
IronOCR'daki dil verileri, NuGet paketleri olarak dağıtılır. 'dotnet add package IronOcr.Languages.German' komutuyla Almanca desteği yüklenir. Herhangi bir manuel dosya yerleştirme veya dizin yolu gerekmez.
Amazon Textract'tan IronOCR'a geçiş, dağıtım altyapısında değişiklikler gerektirir mi?
IronOCR, Amazon Textract'tan daha az altyapı değişikliği gerektirir. SDK ikili yolları, lisans dosyası yerleştirmeleri veya lisans sunucu yapılandırmaları yoktur. NuGet paketi, tamamıyla OCR motorunu içerir ve lisans anahtarı uygulama kodunda bir dize olarak ayarlanır.
Geçişten sonra IronOCR lisanslamasını nasıl yapılandırırım?
Uygulama başlangıç kodunda IronOcr.License.LicenseKey = "SİZİN-ANAHTARINIZ" atayın. Docker veya Kubernetes'te, anahtarı bir ortam değişkeni olarak saklayın ve başlangıçta okuyun. Trafiği kabul etmeden önce doğrulamak için License.IsValidLicense kullanın.
IronOCR, PDF'leri Amazon Textract gibi işleyebilir mi?
Evet. IronOCR hem yerel hem de taranmış PDF'leri okur. Bir PDF yolu veya OcrPdfInput olan girdiye ocr.Read(input) çağrısı yapmak için IronTesseract örneği çağırın ve OcrResult sayfalarını yineleyin. Ayrı bir PDF işleme hattı gerekmez.
IronOCR, yüksek hacim işleme sırasında iş parçacığı nasıl ele alır?
IronTesseract, iş parçacığı başına örnek olacak şekilde güvenlidir. Paralel.ForEach veya Görev havuzunda her iş parçacığı için bir örnek oluşturun, OCR'u eşzamanlı olarak çalıştırın ve her örneği iş tamamlandığında bırakın. Küresel durum veya kilitleme gerekmez.
IronOCR metin çıktıktan sonra hangi çıktıları destekler?
IronOCR, metin, kelime koordinatları, güven skorları ve sayfa yapısını içeren yapılandırılmış sonuçlar döndürür. Dışa aktarma seçenekleri düz metin, aranabilir PDF ve aşağı akış işlemesi için yapılandırılmış sonuç nesneleri içerir.
IronOCR'un fiyatlandırması, Amazon Textract'tan daha öngörülebilir mi? İş yüklerini artırırken?
IronOCR, sayfa başına veya hacim ücretlendirmesi olmadan düz oranlı sürekli lisanslama kullanır. 10.000 veya 10 milyon sayfa işleseniz de lisans maliyeti sabit kalır. Hacim ve ekip lisanslama seçenekleri IronOCR fiyatlandırma sayfasındadır.
Amazon Textract'tan IronOCR'a geçişten sonra mevcut testlerime ne olur?
Çıkarılmış metin içeriğini doğrulayan testler, geçişten sonra çalışmaya devam etmelidir. API çağrı şablonlarını veya COM nesne yaşam döngüsünü doğrulayan testler, IronOCR'un daha basit başlatma ve sonuç modelini yansıtacak şekilde güncellenmelidir.

