Skip to footer content
MIGRATION GUIDES

Migrating from RapidOCR.NET to IronOCR

This guide covers the complete migration path from RapidOCR.NET (RapidOcrNet) to IronOCR for .NET developers who need to eliminate ONNX model file management from their OCR pipeline. It walks through package replacement, code translation, and the operational changes that follow when external model dependencies are removed entirely.

Why Migrate from RapidOCR.NET

RapidOCR.NET works — for a narrow set of use cases, in controlled environments, where someone has already solved the model distribution problem. When any of those conditions change, the library's architectural constraints become engineering costs.

ONNX Model Files Are a Deployment Artifact, Not a Package. RapidOCR.NET requires four external files — det.onnx, cls.onnx, rec.onnx, and a character dictionary — before a single character can be recognized. These files are not bundled in the NuGet package. They live on GitHub release pages, require manual download, require explicit path configuration in code, and require custom MSBuild rules to copy on build. Every new developer, every CI pipeline, every deployment environment repeats that ceremony.

Language Switching Means File Replacement, Not Configuration. Changing from English OCR to Chinese OCR in RapidOCR.NET requires downloading a different recognition model and a different character dictionary, then rebuilding the engine instance. Spanish, French, German, Russian, Arabic, and more than 100 other languages have no available model in the RapidOCR model catalog at all. An application that needs to process documents across a mix of languages has no viable path inside RapidOCR.NET for the unsupported ones.

Model Version Updates Require Manual Intervention. When the upstream RapidOCR project releases improved model weights, teams must download new files, replace them in every environment, validate paths, and redeploy. There is no package restore step that handles this automatically. In a multi-environment setup with development, staging, and production, that propagation is a manual operation each time.

The ONNX Runtime Dependency Adds Platform Complexity. RapidOCR.NET depends on Microsoft.ML.OnnxRuntime, a package with platform-specific native binaries. CPU and GPU variants require different packages. A container image built for linux/amd64 requires different binaries than one built for linux/arm64. Each deployment target needs validation that the correct runtime variant is present and compatible with the installed model files.

Cold-Start Latency and Memory Footprint Are Fixed Costs. Loading the three ONNX models at startup takes 2–5 seconds and holds 300–500 MB in memory for the duration of the process. That cost is paid regardless of OCR volume, making the library a poor fit for serverless functions, lightweight containers, or low-traffic services where the startup penalty is disproportionate to throughput.

No Commercial Support Path. RapidOCR.NET is maintained by a single community developer under Apache 2.0 license. Production incidents — ONNX Runtime version conflicts, inference failures on unusual image formats, memory growth under sustained load — go to a GitHub issues queue with no guaranteed response timeline and no SLA.

The Fundamental Problem

Three ONNX model files plus a character dictionary, all downloaded separately, all configured by path:

// RapidOcrNet: 4 external files required before any OCR can execute
var engine = new RapidOcrEngine(new RapidOcrOptions
{
    DetModelPath = "./models/det.onnx",     // ~3 MB — downloaded from GitHub
    ClsModelPath = "./models/cls.onnx",     // ~1 MB — downloaded from GitHub
    RecModelPath = "./models/rec_en.onnx",  // ~2-10 MB — language-specific download
    KeysPath     = "./models/en_keys.txt"   // character dictionary — language-specific
});
// RapidOcrNet: 4 external files required before any OCR can execute
var engine = new RapidOcrEngine(new RapidOcrOptions
{
    DetModelPath = "./models/det.onnx",     // ~3 MB — downloaded from GitHub
    ClsModelPath = "./models/cls.onnx",     // ~1 MB — downloaded from GitHub
    RecModelPath = "./models/rec_en.onnx",  // ~2-10 MB — language-specific download
    KeysPath     = "./models/en_keys.txt"   // character dictionary — language-specific
});
' RapidOcrNet: 4 external files required before any OCR can execute
Dim engine = New RapidOcrEngine(New RapidOcrOptions With {
    .DetModelPath = "./models/det.onnx",     ' ~3 MB — downloaded from GitHub
    .ClsModelPath = "./models/cls.onnx",     ' ~1 MB — downloaded from GitHub
    .RecModelPath = "./models/rec_en.onnx",  ' ~2-10 MB — language-specific download
    .KeysPath = "./models/en_keys.txt"       ' character dictionary — language-specific
})
$vbLabelText   $csharpLabel

IronOCR has no model files, no path configuration, and no download step:

// IronOCR: install the NuGet package, write one line
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
// IronOCR: install the NuGet package, write one line
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read("document.jpg").Text;
' IronOCR: install the NuGet package, write one line
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text = New IronTesseract().Read("document.jpg").Text
$vbLabelText   $csharpLabel

IronOCR vs RapidOCR.NET: Feature Comparison

IronOCR and RapidOCR.NET overlap on basic image OCR. The gap opens on every surrounding concern.

Feature RapidOCR.NET IronOCR
NuGet installation Yes (RapidOcrNet) Yes (IronOcr)
External model files required Yes (4 files, manual download) No
Path configuration required Yes No
MSBuild copy rules required Yes No
Works immediately after NuGet install No Yes
ONNX Runtime dependency Yes (~30–50 MB) No
Languages supported ~5 (CJK + English only) 125+ via NuGet language packs
Language switching File swap + engine rebuild Property assignment
European language support No Yes (30+)
Arabic / Hebrew support No Yes
Cyrillic (Russian, Ukrainian) support No Yes
Native PDF input No Yes
Password-protected PDF input No Yes
Searchable PDF output No Yes
Multi-page TIFF input No Yes
Stream and byte array input Limited Yes
Built-in image preprocessing No Yes (automatic + manual filters)
Deskew / DeNoise / Contrast filters No Yes
Structured output (paragraphs, lines, words) Partial (blocks only) Yes, with coordinates
Per-word confidence scores Yes (per block) Yes
Barcode reading during OCR No Yes
hOCR export No Yes
Thread-safe parallel processing Limited Yes (one instance per thread)
Cross-platform deployment Requires ONNX Runtime binaries per platform Yes (Windows, Linux, macOS, Docker)
Docker deployment Manual model COPY instructions required Out of the box
Cold-start overhead 2–5 seconds (model loading) Minimal
Commercial support No Yes
License Apache 2.0 (free) Perpetual ($999 Lite, $1,499 Pro, $2,999 Enterprise)

Quick Start: RapidOCR.NET to IronOCR Migration

Step 1: Replace NuGet Package

Remove RapidOCR.NET and the ONNX Runtime dependency:

dotnet remove package RapidOcrNet
dotnet remove package Microsoft.ML.OnnxRuntime
dotnet remove package RapidOcrNet
dotnet remove package Microsoft.ML.OnnxRuntime
SHELL

Install IronOCR from NuGet:

dotnet add package IronOcr

Step 2: Update Namespaces

Replace the RapidOCR.NET namespace with the IronOCR namespace:

// Before (RapidOCR.NET)
using RapidOcrNet;

// After (IronOCR)
using IronOcr;
// Before (RapidOCR.NET)
using RapidOcrNet;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

Step 3: Initialize License

Add license initialization at application startup, before any IronTesseract calls:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

A free trial key is available from the IronOCR licensing page.

Code Migration Examples

ONNX Model Path Configuration Removal

The most mechanical change in this migration is deleting the RapidOcrOptions configuration block and replacing it with a zero-argument constructor.

RapidOCR.NET Approach:

using RapidOcrNet;

// Startup validation — written because a missing model crashes at runtime, not at install
private static void EnsureModelsPresent(string modelDir)
{
    var required = new[]
    {
        Path.Combine(modelDir, "det.onnx"),
        Path.Combine(modelDir, "cls.onnx"),
        Path.Combine(modelDir, "rec_en.onnx"),
        Path.Combine(modelDir, "en_keys.txt")
    };

    var missing = required.Where(f => !File.Exists(f)).ToList();
    if (missing.Any())
        throw new FileNotFoundException(
            $"Missing model files: {string.Join(", ", missing)}\n" +
            "Download from: https://github.com/RapidAI/RapidOCR/releases");
}

// Engine factory — called once at startup, held for lifetime of service
public RapidOcrEngine CreateEngine(string modelDir)
{
    EnsureModelsPresent(modelDir);
    return new RapidOcrEngine(new RapidOcrOptions
    {
        DetModelPath = Path.Combine(modelDir, "det.onnx"),
        ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
        RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
        KeysPath     = Path.Combine(modelDir, "en_keys.txt"),
        UseGpu       = false,
        NumThreads   = Environment.ProcessorCount
    });
}
using RapidOcrNet;

// Startup validation — written because a missing model crashes at runtime, not at install
private static void EnsureModelsPresent(string modelDir)
{
    var required = new[]
    {
        Path.Combine(modelDir, "det.onnx"),
        Path.Combine(modelDir, "cls.onnx"),
        Path.Combine(modelDir, "rec_en.onnx"),
        Path.Combine(modelDir, "en_keys.txt")
    };

    var missing = required.Where(f => !File.Exists(f)).ToList();
    if (missing.Any())
        throw new FileNotFoundException(
            $"Missing model files: {string.Join(", ", missing)}\n" +
            "Download from: https://github.com/RapidAI/RapidOCR/releases");
}

// Engine factory — called once at startup, held for lifetime of service
public RapidOcrEngine CreateEngine(string modelDir)
{
    EnsureModelsPresent(modelDir);
    return new RapidOcrEngine(new RapidOcrOptions
    {
        DetModelPath = Path.Combine(modelDir, "det.onnx"),
        ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
        RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
        KeysPath     = Path.Combine(modelDir, "en_keys.txt"),
        UseGpu       = false,
        NumThreads   = Environment.ProcessorCount
    });
}
Imports RapidOcrNet

' Startup validation — written because a missing model crashes at runtime, not at install
Private Shared Sub EnsureModelsPresent(modelDir As String)
    Dim required = {
        Path.Combine(modelDir, "det.onnx"),
        Path.Combine(modelDir, "cls.onnx"),
        Path.Combine(modelDir, "rec_en.onnx"),
        Path.Combine(modelDir, "en_keys.txt")
    }

    Dim missing = required.Where(Function(f) Not File.Exists(f)).ToList()
    If missing.Any() Then
        Throw New FileNotFoundException(
            $"Missing model files: {String.Join(", ", missing)}" & vbCrLf &
            "Download from: https://github.com/RapidAI/RapidOCR/releases")
    End If
End Sub

' Engine factory — called once at startup, held for lifetime of service
Public Function CreateEngine(modelDir As String) As RapidOcrEngine
    EnsureModelsPresent(modelDir)
    Return New RapidOcrEngine(New RapidOcrOptions With {
        .DetModelPath = Path.Combine(modelDir, "det.onnx"),
        .ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
        .RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
        .KeysPath = Path.Combine(modelDir, "en_keys.txt"),
        .UseGpu = False,
        .NumThreads = Environment.ProcessorCount
    })
End Function
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

// No model validation, no path configuration, no GPU flags
// IronTesseract is thread-safe; create one per thread or on demand
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
using IronOcr;

// No model validation, no path configuration, no GPU flags
// IronTesseract is thread-safe; create one per thread or on demand
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
Imports IronOcr

' No model validation, no path configuration, no GPU flags
' IronTesseract is thread-safe; create one per thread or on demand
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
$vbLabelText   $csharpLabel

The entire EnsureModelsPresent validation method, the RapidOcrOptions configuration object, and the engine factory class can be deleted. There are no model files to validate because IronOCR ships its engine internally as part of the NuGet package. The IronTesseract setup guide covers initialization options and license key placement in detail.

Detection, Classification, and Recognition Pipeline Consolidation

RapidOCR.NET runs a three-stage ONNX pipeline — detection, direction classification, then recognition — and returns an unordered flat list of text blocks that the caller must sort and assemble. IronOCR exposes a single .Read() call backed by its internal Tesseract 5 engine, returning structured output with reading order already applied.

RapidOCR.NET Approach:

using RapidOcrNet;

public class InvoiceTextExtractor
{
    private readonly RapidOcrEngine _engine;

    public InvoiceTextExtractor(string modelDir)
    {
        // Three separate ONNX models run in sequence on every call
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),   // Stage 1: detect text regions
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),   // Stage 2: classify direction
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),// Stage 3: recognize characters
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    public string ExtractInvoiceText(string imagePath)
    {
        var result = _engine.Run(imagePath);

        // Blocks are unordered — must sort by vertical position, then horizontal
        var orderedBlocks = result.TextBlocks
            .OrderBy(b => b.BoundingBox.Top)
            .ThenBy(b => b.BoundingBox.Left)
            .ToList();

        // Manual assembly — no paragraph or line structure
        return string.Join(Environment.NewLine,
            orderedBlocks.Select(b => b.Text));
    }
}
using RapidOcrNet;

public class InvoiceTextExtractor
{
    private readonly RapidOcrEngine _engine;

    public InvoiceTextExtractor(string modelDir)
    {
        // Three separate ONNX models run in sequence on every call
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),   // Stage 1: detect text regions
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),   // Stage 2: classify direction
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),// Stage 3: recognize characters
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    public string ExtractInvoiceText(string imagePath)
    {
        var result = _engine.Run(imagePath);

        // Blocks are unordered — must sort by vertical position, then horizontal
        var orderedBlocks = result.TextBlocks
            .OrderBy(b => b.BoundingBox.Top)
            .ThenBy(b => b.BoundingBox.Left)
            .ToList();

        // Manual assembly — no paragraph or line structure
        return string.Join(Environment.NewLine,
            orderedBlocks.Select(b => b.Text));
    }
}
Imports RapidOcrNet
Imports System.IO
Imports System.Linq

Public Class InvoiceTextExtractor
    Private ReadOnly _engine As RapidOcrEngine

    Public Sub New(modelDir As String)
        ' Three separate ONNX models run in sequence on every call
        _engine = New RapidOcrEngine(New RapidOcrOptions With {
            .DetModelPath = Path.Combine(modelDir, "det.onnx"),   ' Stage 1: detect text regions
            .ClsModelPath = Path.Combine(modelDir, "cls.onnx"),   ' Stage 2: classify direction
            .RecModelPath = Path.Combine(modelDir, "rec_en.onnx"), ' Stage 3: recognize characters
            .KeysPath = Path.Combine(modelDir, "en_keys.txt")
        })
    End Sub

    Public Function ExtractInvoiceText(imagePath As String) As String
        Dim result = _engine.Run(imagePath)

        ' Blocks are unordered — must sort by vertical position, then horizontal
        Dim orderedBlocks = result.TextBlocks _
            .OrderBy(Function(b) b.BoundingBox.Top) _
            .ThenBy(Function(b) b.BoundingBox.Left) _
            .ToList()

        ' Manual assembly — no paragraph or line structure
        Return String.Join(Environment.NewLine, orderedBlocks.Select(Function(b) b.Text))
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

public class InvoiceTextExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ExtractInvoiceText(string imagePath)
    {
        // Single call — detection, recognition, reading order all internal
        var result = _ocr.Read(imagePath);
        return result.Text; // Already in reading order
    }

    public IEnumerable<string> ExtractInvoiceParagraphs(string imagePath)
    {
        var result = _ocr.Read(imagePath);
        // Structured paragraphs with coordinates — no sorting or assembly needed
        foreach (var page in result.Pages)
            foreach (var paragraph in page.Paragraphs)
                yield return paragraph.Text;
    }
}
using IronOcr;

public class InvoiceTextExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ExtractInvoiceText(string imagePath)
    {
        // Single call — detection, recognition, reading order all internal
        var result = _ocr.Read(imagePath);
        return result.Text; // Already in reading order
    }

    public IEnumerable<string> ExtractInvoiceParagraphs(string imagePath)
    {
        var result = _ocr.Read(imagePath);
        // Structured paragraphs with coordinates — no sorting or assembly needed
        foreach (var page in result.Pages)
            foreach (var paragraph in page.Paragraphs)
                yield return paragraph.Text;
    }
}
Imports IronOcr

Public Class InvoiceTextExtractor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ExtractInvoiceText(imagePath As String) As String
        ' Single call — detection, recognition, reading order all internal
        Dim result = _ocr.Read(imagePath)
        Return result.Text ' Already in reading order
    End Function

    Public Iterator Function ExtractInvoiceParagraphs(imagePath As String) As IEnumerable(Of String)
        Dim result = _ocr.Read(imagePath)
        ' Structured paragraphs with coordinates — no sorting or assembly needed
        For Each page In result.Pages
            For Each paragraph In page.Paragraphs
                Yield paragraph.Text
            Next
        Next
    End Function
End Class
$vbLabelText   $csharpLabel

The three-stage pipeline is entirely internal to IronOCR. The result.TextBlocks list with its manual OrderBy chain collapses to result.Text. For callers that needed bounding-box data from TextBlocks, the result.Pages[i].Paragraphs, .Lines, and .Words collections provide equivalent coordinates through a structured API. The read results how-to and OCR results features page document the full structured output model.

Custom Model Loading Replacement

Applications that need to switch OCR configurations at runtime — for example, routing documents through different recognition parameters based on document type — must rebuild the entire RapidOcrEngine in RapidOCR.NET because configuration is constructor-bound. IronOCR exposes engine configuration as properties that can be adjusted per-read on a single instance.

RapidOCR.NET Approach:

using RapidOcrNet;

public class DocumentRouter
{
    private readonly string _modelDir;

    public DocumentRouter(string modelDir) => _modelDir = modelDir;

    // Must create separate engine instances per configuration
    // Each engine holds ~300-500 MB of loaded model weights
    private RapidOcrEngine BuildEnglishEngine() =>
        new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(_modelDir, "en_rec.onnx"),
            KeysPath     = Path.Combine(_modelDir, "en_keys.txt")
        });

    private RapidOcrEngine BuildChineseEngine() =>
        new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(_modelDir, "ch_rec.onnx"),  // separate download
            KeysPath     = Path.Combine(_modelDir, "ch_keys.txt")   // separate download
        });

    public string ProcessDocument(string imagePath, string language)
    {
        // Rebuild engine for each language — model reload cost on every switch
        using var engine = language == "chinese"
            ? BuildChineseEngine()
            : BuildEnglishEngine();

        var result = engine.Run(imagePath);
        return string.Join("\n", result.TextBlocks
            .OrderBy(b => b.BoundingBox.Top)
            .Select(b => b.Text));
    }
}
using RapidOcrNet;

public class DocumentRouter
{
    private readonly string _modelDir;

    public DocumentRouter(string modelDir) => _modelDir = modelDir;

    // Must create separate engine instances per configuration
    // Each engine holds ~300-500 MB of loaded model weights
    private RapidOcrEngine BuildEnglishEngine() =>
        new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(_modelDir, "en_rec.onnx"),
            KeysPath     = Path.Combine(_modelDir, "en_keys.txt")
        });

    private RapidOcrEngine BuildChineseEngine() =>
        new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(_modelDir, "ch_rec.onnx"),  // separate download
            KeysPath     = Path.Combine(_modelDir, "ch_keys.txt")   // separate download
        });

    public string ProcessDocument(string imagePath, string language)
    {
        // Rebuild engine for each language — model reload cost on every switch
        using var engine = language == "chinese"
            ? BuildChineseEngine()
            : BuildEnglishEngine();

        var result = engine.Run(imagePath);
        return string.Join("\n", result.TextBlocks
            .OrderBy(b => b.BoundingBox.Top)
            .Select(b => b.Text));
    }
}
Imports RapidOcrNet

Public Class DocumentRouter
    Private ReadOnly _modelDir As String

    Public Sub New(modelDir As String)
        _modelDir = modelDir
    End Sub

    ' Must create separate engine instances per configuration
    ' Each engine holds ~300-500 MB of loaded model weights
    Private Function BuildEnglishEngine() As RapidOcrEngine
        Return New RapidOcrEngine(New RapidOcrOptions With {
            .DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            .ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            .RecModelPath = Path.Combine(_modelDir, "en_rec.onnx"),
            .KeysPath = Path.Combine(_modelDir, "en_keys.txt")
        })
    End Function

    Private Function BuildChineseEngine() As RapidOcrEngine
        Return New RapidOcrEngine(New RapidOcrOptions With {
            .DetModelPath = Path.Combine(_modelDir, "det.onnx"),
            .ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
            .RecModelPath = Path.Combine(_modelDir, "ch_rec.onnx"),  ' separate download
            .KeysPath = Path.Combine(_modelDir, "ch_keys.txt")   ' separate download
        })
    End Function

    Public Function ProcessDocument(imagePath As String, language As String) As String
        ' Rebuild engine for each language — model reload cost on every switch
        Using engine As RapidOcrEngine = If(language = "chinese", BuildChineseEngine(), BuildEnglishEngine())
            Dim result = engine.Run(imagePath)
            Return String.Join(vbLf, result.TextBlocks _
                .OrderBy(Function(b) b.BoundingBox.Top) _
                .Select(Function(b) b.Text))
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

public class DocumentRouter
{
    // One instance handles all languages — language is a property, not a constructor param
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ProcessDocument(string imagePath, string language)
    {
        // Language switch requires no model reload, no rebuild
        _ocr.Language = language switch
        {
            "chinese"  => OcrLanguage.ChineseSimplified,
            "japanese" => OcrLanguage.Japanese,
            "arabic"   => OcrLanguage.Arabic,
            "russian"  => OcrLanguage.Russian,
            _          => OcrLanguage.English
        };

        return _ocr.Read(imagePath).Text;
    }
}
using IronOcr;

public class DocumentRouter
{
    // One instance handles all languages — language is a property, not a constructor param
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ProcessDocument(string imagePath, string language)
    {
        // Language switch requires no model reload, no rebuild
        _ocr.Language = language switch
        {
            "chinese"  => OcrLanguage.ChineseSimplified,
            "japanese" => OcrLanguage.Japanese,
            "arabic"   => OcrLanguage.Arabic,
            "russian"  => OcrLanguage.Russian,
            _          => OcrLanguage.English
        };

        return _ocr.Read(imagePath).Text;
    }
}
Imports IronOcr

Public Class DocumentRouter
    ' One instance handles all languages — language is a property, not a constructor param
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessDocument(imagePath As String, language As String) As String
        ' Language switch requires no model reload, no rebuild
        _ocr.Language = If(language = "chinese", OcrLanguage.ChineseSimplified,
                           If(language = "japanese", OcrLanguage.Japanese,
                           If(language = "arabic", OcrLanguage.Arabic,
                           If(language = "russian", OcrLanguage.Russian,
                           OcrLanguage.English))))

        Return _ocr.Read(imagePath).Text
    End Function
End Class
$vbLabelText   $csharpLabel

No engine rebuild, no model reload, no separate download per language. Language packs for non-English targets install via NuGet — dotnet add package IronOcr.Languages.ChineseSimplified — and the restore step handles deployment automatically. The multiple languages how-to covers language pack installation and the languages index lists all 125+ available packs.

Batch Processing Migration

RapidOCR.NET has no thread safety guarantees on a single RapidOcrEngine instance. Batch processing requires either a single-threaded queue or per-thread engine instantiation, each carrying its own 300–500 MB model footprint. IronOCR is explicitly thread-safe: create one IronTesseract per thread and run them concurrently without locks.

RapidOCR.NET Approach:

using RapidOcrNet;

public class BatchOcrProcessor
{
    private readonly string _modelDir;

    public BatchOcrProcessor(string modelDir) => _modelDir = modelDir;

    // Thread-pool processing — each thread needs its own engine copy
    // 4 threads × 300-500 MB model footprint = 1.2-2 GB RAM minimum
    public Dictionary<string, string> ProcessBatch(IReadOnlyList<string> imagePaths)
    {
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();

        Parallel.ForEach(imagePaths, new ParallelOptions { MaxDegreeOfParallelism = 4 },
            imagePath =>
            {
                // Each thread must create its own engine — not safe to share
                using var engine = new RapidOcrEngine(new RapidOcrOptions
                {
                    DetModelPath = Path.Combine(_modelDir, "det.onnx"),
                    ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
                    RecModelPath = Path.Combine(_modelDir, "rec_en.onnx"),
                    KeysPath     = Path.Combine(_modelDir, "en_keys.txt")
                });

                var result = engine.Run(imagePath);
                results[imagePath] = string.Join("\n",
                    result.TextBlocks
                          .OrderBy(b => b.BoundingBox.Top)
                          .Select(b => b.Text));
            });

        return new Dictionary<string, string>(results);
    }
}
using RapidOcrNet;

public class BatchOcrProcessor
{
    private readonly string _modelDir;

    public BatchOcrProcessor(string modelDir) => _modelDir = modelDir;

    // Thread-pool processing — each thread needs its own engine copy
    // 4 threads × 300-500 MB model footprint = 1.2-2 GB RAM minimum
    public Dictionary<string, string> ProcessBatch(IReadOnlyList<string> imagePaths)
    {
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();

        Parallel.ForEach(imagePaths, new ParallelOptions { MaxDegreeOfParallelism = 4 },
            imagePath =>
            {
                // Each thread must create its own engine — not safe to share
                using var engine = new RapidOcrEngine(new RapidOcrOptions
                {
                    DetModelPath = Path.Combine(_modelDir, "det.onnx"),
                    ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
                    RecModelPath = Path.Combine(_modelDir, "rec_en.onnx"),
                    KeysPath     = Path.Combine(_modelDir, "en_keys.txt")
                });

                var result = engine.Run(imagePath);
                results[imagePath] = string.Join("\n",
                    result.TextBlocks
                          .OrderBy(b => b.BoundingBox.Top)
                          .Select(b => b.Text));
            });

        return new Dictionary<string, string>(results);
    }
}
Imports RapidOcrNet
Imports System.Collections.Concurrent
Imports System.IO
Imports System.Threading.Tasks

Public Class BatchOcrProcessor
    Private ReadOnly _modelDir As String

    Public Sub New(modelDir As String)
        _modelDir = modelDir
    End Sub

    ' Thread-pool processing — each thread needs its own engine copy
    ' 4 threads × 300-500 MB model footprint = 1.2-2 GB RAM minimum
    Public Function ProcessBatch(imagePaths As IReadOnlyList(Of String)) As Dictionary(Of String, String)
        Dim results = New ConcurrentDictionary(Of String, String)()

        Parallel.ForEach(imagePaths, New ParallelOptions With {.MaxDegreeOfParallelism = 4},
            Sub(imagePath)
                ' Each thread must create its own engine — not safe to share
                Using engine = New RapidOcrEngine(New RapidOcrOptions With {
                    .DetModelPath = Path.Combine(_modelDir, "det.onnx"),
                    .ClsModelPath = Path.Combine(_modelDir, "cls.onnx"),
                    .RecModelPath = Path.Combine(_modelDir, "rec_en.onnx"),
                    .KeysPath = Path.Combine(_modelDir, "en_keys.txt")
                })

                    Dim result = engine.Run(imagePath)
                    results(imagePath) = String.Join(vbLf,
                        result.TextBlocks _
                              .OrderBy(Function(b) b.BoundingBox.Top) _
                              .Select(Function(b) b.Text))
                End Using
            End Sub)

        Return New Dictionary(Of String, String)(results)
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

public class BatchOcrProcessor
{
    // Thread-safe: create IronTesseract per thread, no shared state required
    public Dictionary<string, string> ProcessBatch(IReadOnlyList<string> imagePaths)
    {
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();

        Parallel.ForEach(imagePaths, imagePath =>
        {
            // Lightweight construction — no model loading overhead per thread
            var ocr = new IronTesseract();
            var result = ocr.Read(imagePath);
            results[imagePath] = result.Text;
        });

        return new Dictionary<string, string>(results);
    }
}
using IronOcr;

public class BatchOcrProcessor
{
    // Thread-safe: create IronTesseract per thread, no shared state required
    public Dictionary<string, string> ProcessBatch(IReadOnlyList<string> imagePaths)
    {
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();

        Parallel.ForEach(imagePaths, imagePath =>
        {
            // Lightweight construction — no model loading overhead per thread
            var ocr = new IronTesseract();
            var result = ocr.Read(imagePath);
            results[imagePath] = result.Text;
        });

        return new Dictionary<string, string>(results);
    }
}
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks

Public Class BatchOcrProcessor
    ' Thread-safe: create IronTesseract per thread, no shared state required
    Public Function ProcessBatch(imagePaths As IReadOnlyList(Of String)) As Dictionary(Of String, String)
        Dim results = New ConcurrentDictionary(Of String, String)()

        Parallel.ForEach(imagePaths, Sub(imagePath)
            ' Lightweight construction — no model loading overhead per thread
            Dim ocr = New IronTesseract()
            Dim result = ocr.Read(imagePath)
            results(imagePath) = result.Text
        End Sub)

        Return New Dictionary(Of String, String)(results)
    End Function
End Class
$vbLabelText   $csharpLabel

The per-thread RapidOcrEngine instantiation disappears. IronOCR thread instances are lightweight — no external model load on construction. The multithreading example demonstrates concurrent processing patterns for high-throughput pipelines.

Multi-Frame TIFF Processing

RapidOCR.NET accepts only single image files. Processing a multi-page TIFF — the standard format for fax-received documents and scanned archives — requires splitting it into individual frames with a separate imaging library, saving those frames to temporary files, running engine.Run() on each, and cleaning up afterward. IronOCR handles multi-frame TIFF natively through OcrInput.LoadImageFrames.

RapidOCR.NET Approach:

using RapidOcrNet;
// Also requires: SixLabors.ImageSharp or System.Drawing for TIFF frame extraction

public class TiffOcrProcessor
{
    private readonly RapidOcrEngine _engine;

    public TiffOcrProcessor(string modelDir)
    {
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();
        var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(tempDir);

        try
        {
            // External library required to split TIFF frames
            var framePaths = SplitTiffIntoFrames(tiffPath, tempDir); // not in RapidOcrNet

            foreach (var framePath in framePaths)
            {
                var result = _engine.Run(framePath);
                pageTexts.Add(string.Join("\n",
                    result.TextBlocks
                          .OrderBy(b => b.BoundingBox.Top)
                          .Select(b => b.Text)));
            }
        }
        finally
        {
            // Clean up temp frame files
            Directory.Delete(tempDir, recursive: true);
        }

        return string.Join("\n\n", pageTexts);
    }

    private IEnumerable<string> SplitTiffIntoFrames(string tiffPath, string outputDir)
    {
        // Requires external library — implementation depends on what is installed
        throw new NotImplementedException("Add SixLabors.ImageSharp or similar");
    }
}
using RapidOcrNet;
// Also requires: SixLabors.ImageSharp or System.Drawing for TIFF frame extraction

public class TiffOcrProcessor
{
    private readonly RapidOcrEngine _engine;

    public TiffOcrProcessor(string modelDir)
    {
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    public string ProcessMultiPageTiff(string tiffPath)
    {
        var pageTexts = new List<string>();
        var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(tempDir);

        try
        {
            // External library required to split TIFF frames
            var framePaths = SplitTiffIntoFrames(tiffPath, tempDir); // not in RapidOcrNet

            foreach (var framePath in framePaths)
            {
                var result = _engine.Run(framePath);
                pageTexts.Add(string.Join("\n",
                    result.TextBlocks
                          .OrderBy(b => b.BoundingBox.Top)
                          .Select(b => b.Text)));
            }
        }
        finally
        {
            // Clean up temp frame files
            Directory.Delete(tempDir, recursive: true);
        }

        return string.Join("\n\n", pageTexts);
    }

    private IEnumerable<string> SplitTiffIntoFrames(string tiffPath, string outputDir)
    {
        // Requires external library — implementation depends on what is installed
        throw new NotImplementedException("Add SixLabors.ImageSharp or similar");
    }
}
Imports RapidOcrNet
' Also requires: SixLabors.ImageSharp or System.Drawing for TIFF frame extraction

Public Class TiffOcrProcessor
    Private ReadOnly _engine As RapidOcrEngine

    Public Sub New(modelDir As String)
        _engine = New RapidOcrEngine(New RapidOcrOptions With {
            .DetModelPath = Path.Combine(modelDir, "det.onnx"),
            .ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            .RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            .KeysPath = Path.Combine(modelDir, "en_keys.txt")
        })
    End Sub

    Public Function ProcessMultiPageTiff(tiffPath As String) As String
        Dim pageTexts As New List(Of String)()
        Dim tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())
        Directory.CreateDirectory(tempDir)

        Try
            ' External library required to split TIFF frames
            Dim framePaths = SplitTiffIntoFrames(tiffPath, tempDir) ' not in RapidOcrNet

            For Each framePath In framePaths
                Dim result = _engine.Run(framePath)
                pageTexts.Add(String.Join(vbLf,
                    result.TextBlocks _
                          .OrderBy(Function(b) b.BoundingBox.Top) _
                          .Select(Function(b) b.Text)))
            Next
        Finally
            ' Clean up temp frame files
            Directory.Delete(tempDir, recursive:=True)
        End Try

        Return String.Join(vbLf & vbLf, pageTexts)
    End Function

    Private Function SplitTiffIntoFrames(tiffPath As String, outputDir As String) As IEnumerable(Of String)
        ' Requires external library — implementation depends on what is installed
        Throw New NotImplementedException("Add SixLabors.ImageSharp or similar")
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

public class TiffOcrProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath); // All frames loaded — no external library needed

        var result = _ocr.Read(input);
        return result.Text; // Pages assembled in order automatically
    }

    public IEnumerable<(int PageNumber, string Text, double Confidence)> ProcessTiffWithPageData(string tiffPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        foreach (var page in result.Pages)
            yield return (page.PageNumber, page.Text, page.Confidence);
    }
}
using IronOcr;

public class TiffOcrProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ProcessMultiPageTiff(string tiffPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath); // All frames loaded — no external library needed

        var result = _ocr.Read(input);
        return result.Text; // Pages assembled in order automatically
    }

    public IEnumerable<(int PageNumber, string Text, double Confidence)> ProcessTiffWithPageData(string tiffPath)
    {
        using var input = new OcrInput();
        input.LoadImageFrames(tiffPath);

        var result = _ocr.Read(input);

        foreach (var page in result.Pages)
            yield return (page.PageNumber, page.Text, page.Confidence);
    }
}
Imports IronOcr

Public Class TiffOcrProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessMultiPageTiff(tiffPath As String) As String
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath) ' All frames loaded — no external library needed

            Dim result = _ocr.Read(input)
            Return result.Text ' Pages assembled in order automatically
        End Using
    End Function

    Public Iterator Function ProcessTiffWithPageData(tiffPath As String) As IEnumerable(Of (PageNumber As Integer, Text As String, Confidence As Double))
        Using input As New OcrInput()
            input.LoadImageFrames(tiffPath)

            Dim result = _ocr.Read(input)

            For Each page In result.Pages
                Yield (page.PageNumber, page.Text, page.Confidence)
            Next
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

No external imaging library, no temporary files, no cleanup logic. LoadImageFrames reads all TIFF frames into the OcrInput pipeline in a single call. The TIFF and GIF input how-to covers frame selection, page range filtering, and memory-efficient handling of large multi-frame documents.

Structured Data Extraction from Scanned Forms

RapidOCR.NET returns text blocks with bounding boxes but no higher-level document structure — no concept of paragraphs, lines, or words. Extracting individual fields from a scanned form requires writing coordinate intersection logic against the raw block list. IronOCR provides a structured result tree down to the character level, with coordinates at every level.

RapidOCR.NET Approach:

using RapidOcrNet;

public class FormFieldExtractor
{
    private readonly RapidOcrEngine _engine;

    public FormFieldExtractor(string modelDir)
    {
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    // Extract text within a defined region by filtering block coordinates manually
    public string ExtractFieldByRegion(string imagePath, float regionLeft, float regionTop,
                                        float regionRight, float regionBottom)
    {
        var result = _engine.Run(imagePath);

        // Filter blocks whose bounding box intersects the target region
        var blocksInRegion = result.TextBlocks
            .Where(b =>
                b.BoundingBox.Left   < regionRight  &&
                b.BoundingBox.Right  > regionLeft   &&
                b.BoundingBox.Top    < regionBottom &&
                b.BoundingBox.Bottom > regionTop)
            .OrderBy(b => b.BoundingBox.Top)
            .ThenBy(b => b.BoundingBox.Left);

        return string.Join(" ", blocksInRegion.Select(b => b.Text));
    }
}
using RapidOcrNet;

public class FormFieldExtractor
{
    private readonly RapidOcrEngine _engine;

    public FormFieldExtractor(string modelDir)
    {
        _engine = new RapidOcrEngine(new RapidOcrOptions
        {
            DetModelPath = Path.Combine(modelDir, "det.onnx"),
            ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            KeysPath     = Path.Combine(modelDir, "en_keys.txt")
        });
    }

    // Extract text within a defined region by filtering block coordinates manually
    public string ExtractFieldByRegion(string imagePath, float regionLeft, float regionTop,
                                        float regionRight, float regionBottom)
    {
        var result = _engine.Run(imagePath);

        // Filter blocks whose bounding box intersects the target region
        var blocksInRegion = result.TextBlocks
            .Where(b =>
                b.BoundingBox.Left   < regionRight  &&
                b.BoundingBox.Right  > regionLeft   &&
                b.BoundingBox.Top    < regionBottom &&
                b.BoundingBox.Bottom > regionTop)
            .OrderBy(b => b.BoundingBox.Top)
            .ThenBy(b => b.BoundingBox.Left);

        return string.Join(" ", blocksInRegion.Select(b => b.Text));
    }
}
Imports RapidOcrNet

Public Class FormFieldExtractor
    Private ReadOnly _engine As RapidOcrEngine

    Public Sub New(modelDir As String)
        _engine = New RapidOcrEngine(New RapidOcrOptions With {
            .DetModelPath = Path.Combine(modelDir, "det.onnx"),
            .ClsModelPath = Path.Combine(modelDir, "cls.onnx"),
            .RecModelPath = Path.Combine(modelDir, "rec_en.onnx"),
            .KeysPath = Path.Combine(modelDir, "en_keys.txt")
        })
    End Sub

    ' Extract text within a defined region by filtering block coordinates manually
    Public Function ExtractFieldByRegion(imagePath As String, regionLeft As Single, regionTop As Single,
                                         regionRight As Single, regionBottom As Single) As String
        Dim result = _engine.Run(imagePath)

        ' Filter blocks whose bounding box intersects the target region
        Dim blocksInRegion = result.TextBlocks _
            .Where(Function(b) b.BoundingBox.Left < regionRight AndAlso
                               b.BoundingBox.Right > regionLeft AndAlso
                               b.BoundingBox.Top < regionBottom AndAlso
                               b.BoundingBox.Bottom > regionTop) _
            .OrderBy(Function(b) b.BoundingBox.Top) _
            .ThenBy(Function(b) b.BoundingBox.Left)

        Return String.Join(" ", blocksInRegion.Select(Function(b) b.Text))
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

public class FormFieldExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    // Use CropRectangle to OCR only the target region — no post-filter needed
    public string ExtractFieldByRegion(string imagePath, int x, int y, int width, int height)
    {
        var region = new CropRectangle(x, y, width, height);

        using var input = new OcrInput();
        input.LoadImage(imagePath, region);

        return _ocr.Read(input).Text;
    }

    // Extract all fields with their coordinates from a full-page scan
    public IEnumerable<(string Text, int X, int Y, double Confidence)> ExtractAllWords(string imagePath)
    {
        var result = _ocr.Read(imagePath);

        foreach (var page in result.Pages)
            foreach (var word in page.Words)
                yield return (word.Text, word.X, word.Y, word.Confidence);
    }
}
using IronOcr;

public class FormFieldExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    // Use CropRectangle to OCR only the target region — no post-filter needed
    public string ExtractFieldByRegion(string imagePath, int x, int y, int width, int height)
    {
        var region = new CropRectangle(x, y, width, height);

        using var input = new OcrInput();
        input.LoadImage(imagePath, region);

        return _ocr.Read(input).Text;
    }

    // Extract all fields with their coordinates from a full-page scan
    public IEnumerable<(string Text, int X, int Y, double Confidence)> ExtractAllWords(string imagePath)
    {
        var result = _ocr.Read(imagePath);

        foreach (var page in result.Pages)
            foreach (var word in page.Words)
                yield return (word.Text, word.X, word.Y, word.Confidence);
    }
}
Imports IronOcr

Public Class FormFieldExtractor
    Private ReadOnly _ocr As New IronTesseract()

    ' Use CropRectangle to OCR only the target region — no post-filter needed
    Public Function ExtractFieldByRegion(imagePath As String, x As Integer, y As Integer, width As Integer, height As Integer) As String
        Dim region As New CropRectangle(x, y, width, height)

        Using input As New OcrInput()
            input.LoadImage(imagePath, region)
            Return _ocr.Read(input).Text
        End Using
    End Function

    ' Extract all fields with their coordinates from a full-page scan
    Public Iterator Function ExtractAllWords(imagePath As String) As IEnumerable(Of (Text As String, X As Integer, Y As Integer, Confidence As Double))
        Dim result = _ocr.Read(imagePath)

        For Each page In result.Pages
            For Each word In page.Words
                Yield (word.Text, word.X, word.Y, word.Confidence)
            Next
        Next
    End Function
End Class
$vbLabelText   $csharpLabel

CropRectangle confines OCR to the exact region of interest, which is faster and more accurate than running full-page OCR and filtering results after the fact. Per-word coordinates and confidence values are available directly on result.Pages[i].Words without any manual bounding-box intersection code. The region-based OCR how-to and the crop rectangle example cover this pattern in detail.

RapidOCR.NET API to IronOCR Mapping Reference

RapidOCR.NET IronOCR Equivalent
using RapidOcrNet using IronOcr
new RapidOcrEngine(new RapidOcrOptions { ... }) new IronTesseract()
RapidOcrOptions.DetModelPath Not needed — bundled internally
RapidOcrOptions.ClsModelPath Not needed — bundled internally
RapidOcrOptions.RecModelPath Not needed — bundled internally
RapidOcrOptions.KeysPath Not needed — bundled internally
RapidOcrOptions.UseGpu Not applicable — CPU-optimized internally
RapidOcrOptions.NumThreads Use Parallel.ForEach with one IronTesseract per thread
engine.Run(imagePath) ocr.Read(imagePath)
engine.Dispose() using var ocr = new IronTesseract()
result.TextBlocks result.Pages[i].Words / .Lines / .Paragraphs
result.TextBlocks[i].Text result.Words[i].Text
result.TextBlocks[i].Confidence result.Words[i].Confidence
result.TextBlocks[i].BoundingBox.Top result.Words[i].Y
result.TextBlocks[i].BoundingBox.Left result.Words[i].X
Manual OrderBy(b => b.BoundingBox.Top) sort Not needed — result.Text is in reading order
string.Join("\n", result.TextBlocks.Select(b => b.Text)) result.Text
Language file swap (download different model) ocr.Language = OcrLanguage.French
Engine rebuild for language change Not needed — set ocr.Language per call
PDF-to-image + engine.Run() loop ocr.Read("document.pdf")
Multi-frame TIFF manual frame split input.LoadImageFrames("document.tiff")
No searchable PDF capability result.SaveAsSearchablePdf("output.pdf")
No barcode capability ocr.Configuration.ReadBarCodes = true

Common Migration Issues and Solutions

Issue 1: Models Directory Still Exists After Migration

RapidOCR.NET: The models/ directory in the project contains det.onnx, cls.onnx, rec_en.onnx, and en_keys.txt, along with MSBuild <Content> entries that copy them on build. After switching to IronOCR, this directory and those entries remain and still inflate the build output.

Solution: Delete the models/ directory, remove the corresponding <ItemGroup> from .csproj, and remove any startup validation logic that checked for missing files. Also remove the Microsoft.ML.OnnxRuntime NuGet reference if it was installed separately. The published output of a .NET application using IronOCR contains no external model files.


<ItemGroup>
  <Content Include="models\**\*.*">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

<ItemGroup>
  <Content Include="models\**\*.*">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
XML

Issue 2: Per-Thread Engine Construction Pattern

RapidOCR.NET: Parallel processing code that created a new RapidOcrEngine per thread to avoid shared-state problems carried a significant memory cost: each engine instance loaded 300–500 MB of ONNX model weights independently.

Solution: IronOCR IronTesseract instances are thread-safe and lightweight. Create one per thread in a Parallel.ForEach without worrying about a per-instance model load cost. The IronOCR approach is identical to the Batch Processing Migration example above — IronTesseract handles this scenario with the same per-thread construction pattern, but without the 300–500 MB model load cost that each RapidOcrEngine instance carried. The multithreading example shows the standard pattern for high-throughput pipelines.

Issue 3: Language Not Supported Exception

RapidOCR.NET: Code that routed non-CJK documents through RapidOCR.NET — or attempted to build an engine with a non-existent Spanish/French/German model — would throw at runtime with a file-not-found error or produce empty results.

Solution: Install the appropriate language pack NuGet package and set ocr.Language to the target OcrLanguage enum value. No model download, no engine rebuild, no additional code path for each language:

dotnet add package IronOcr.Languages.Spanish, IronOcr.Languages.French, IronOcr.Languages.Arabic
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.Spanish;
var result = ocr.Read("spanish-document.jpg");
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.Spanish;
var result = ocr.Read("spanish-document.jpg");
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.Spanish
Dim result = ocr.Read("spanish-document.jpg")
$vbLabelText   $csharpLabel

The custom language packs guide covers advanced language configuration beyond the standard 125+ packs.

Issue 4: Text Block Sorting Logic Breaks After Migration

RapidOCR.NET: Because result.TextBlocks was an unordered flat list, codebases typically contained .OrderBy(b => b.BoundingBox.Top).ThenBy(b => b.BoundingBox.Left) chains scattered throughout result-processing code.

Solution: Delete this sorting logic entirely. result.Text in IronOCR is already assembled in natural reading order. For code that also consumed bounding-box coordinates from the sorted blocks, replace the block reference with result.Pages[i].Words[j]:

// Before: manual sort + coordinate extraction
var sorted = result.TextBlocks
    .OrderBy(b => b.BoundingBox.Top)
    .ThenBy(b => b.BoundingBox.Left);

foreach (var block in sorted)
    Console.WriteLine($"{block.Text} at ({block.BoundingBox.Left}, {block.BoundingBox.Top})");

// After: structured access, already in order
foreach (var page in result.Pages)
    foreach (var word in page.Words)
        Console.WriteLine($"{word.Text} at ({word.X}, {word.Y})");
// Before: manual sort + coordinate extraction
var sorted = result.TextBlocks
    .OrderBy(b => b.BoundingBox.Top)
    .ThenBy(b => b.BoundingBox.Left);

foreach (var block in sorted)
    Console.WriteLine($"{block.Text} at ({block.BoundingBox.Left}, {block.BoundingBox.Top})");

// After: structured access, already in order
foreach (var page in result.Pages)
    foreach (var word in page.Words)
        Console.WriteLine($"{word.Text} at ({word.X}, {word.Y})");
Imports System
Imports System.Linq

' Before: manual sort + coordinate extraction
Dim sorted = result.TextBlocks _
    .OrderBy(Function(b) b.BoundingBox.Top) _
    .ThenBy(Function(b) b.BoundingBox.Left)

For Each block In sorted
    Console.WriteLine($"{block.Text} at ({block.BoundingBox.Left}, {block.BoundingBox.Top})")

' After: structured access, already in order
For Each page In result.Pages
    For Each word In page.Words
        Console.WriteLine($"{word.Text} at ({word.X}, {word.Y})")
    Next
Next
$vbLabelText   $csharpLabel

Issue 5: CI/CD Pipeline Fails After Model Files Are Removed

RapidOCR.NET: Build pipelines that cached or fetched the models/ directory as a separate step — either from an artifact store, a shared S3 bucket, or a Git LFS repository — will fail when those steps find nothing to restore after the migration.

Solution: Remove the model file fetch and cache steps from the CI pipeline entirely. IronOCR's engine is restored as part of the standard dotnet restore step. No additional pipeline stages are required. For containerized deployments, remove any COPY models/ ./models/ Docker instructions — the IronOCR Docker deployment guide documents the one required system package (libgdiplus on Debian/Ubuntu images) and nothing more.

Issue 6: ONNX Runtime Version Conflicts After Partial Migration

RapidOCR.NET: Applications that also use other ONNX-based ML packages (ML.NET, ONNX object detection, etc.) may have had Microsoft.ML.OnnxRuntime pinned to a specific version for RapidOCR.NET compatibility. Removing RapidOCR.NET can expose version conflicts in those other packages.

Solution: Remove Microsoft.ML.OnnxRuntime from the explicit package list. IronOCR has no ONNX Runtime dependency, so removing the RapidOCR.NET reference eliminates the version pin entirely. Other ML packages that genuinely require ONNX Runtime can then resolve their own compatible version through standard NuGet dependency resolution without the RapidOCR.NET constraint.

RapidOCR.NET Migration Checklist

Pre-Migration Tasks

Audit the codebase for all RapidOCR.NET usage before making changes:

# Find all files that reference RapidOcrNet
grep -r "RapidOcrNet\|RapidOcrEngine\|RapidOcrOptions" --include="*.cs" .

# Find model path configuration
grep -r "DetModelPath\|ClsModelPath\|RecModelPath\|KeysPath" --include="*.cs" .

# Find MSBuild model copy entries
grep -r "det\.onnx\|cls\.onnx\|rec.*\.onnx\|keys\.txt" --include="*.csproj" .

# Find model validation logic
grep -r "ValidateModel\|models/" --include="*.cs" .

# Find ONNX Runtime references
grep -r "OnnxRuntime\|Microsoft\.ML" --include="*.csproj" .

# Find language-switching patterns (multiple engine instances per language)
grep -r "CreateEnglishEngine\|CreateChineseEngine\|rec_en\|ch_rec\|en_keys\|ch_keys" --include="*.cs" .
# Find all files that reference RapidOcrNet
grep -r "RapidOcrNet\|RapidOcrEngine\|RapidOcrOptions" --include="*.cs" .

# Find model path configuration
grep -r "DetModelPath\|ClsModelPath\|RecModelPath\|KeysPath" --include="*.cs" .

# Find MSBuild model copy entries
grep -r "det\.onnx\|cls\.onnx\|rec.*\.onnx\|keys\.txt" --include="*.csproj" .

# Find model validation logic
grep -r "ValidateModel\|models/" --include="*.cs" .

# Find ONNX Runtime references
grep -r "OnnxRuntime\|Microsoft\.ML" --include="*.csproj" .

# Find language-switching patterns (multiple engine instances per language)
grep -r "CreateEnglishEngine\|CreateChineseEngine\|rec_en\|ch_rec\|en_keys\|ch_keys" --include="*.cs" .
SHELL

Inventory the results: note every place an engine is created, every place model paths are configured, every place text blocks are sorted, and every place PDF-to-image conversion feeds into engine.Run().

Code Update Tasks

  1. Remove RapidOcrNet NuGet package reference from all .csproj files.
  2. Remove Microsoft.ML.OnnxRuntime NuGet package reference from all .csproj files.
  3. Install IronOcr NuGet package.
  4. Install language pack NuGet packages for any non-English languages the application requires.
  5. Delete the models/ directory from the project and repository.
  6. Remove <Content Include="models\**\*.*"> MSBuild entries from all .csproj files.
  7. Remove startup model validation methods (the EnsureModelsPresent-style methods).
  8. Replace using RapidOcrNet with using IronOcr in all source files.
  9. Replace new RapidOcrEngine(new RapidOcrOptions { ... }) with new IronTesseract().
  10. Replace engine.Run(imagePath) with ocr.Read(imagePath).
  11. Replace result.TextBlocks assembly chains (.OrderBy().Select(b => b.Text)) with result.Text.
  12. Replace coordinate-filter field extraction with CropRectangle region input.
  13. Replace per-thread engine construction with per-thread IronTesseract construction.
  14. Replace language-specific engine factory methods with ocr.Language = OcrLanguage.X assignments.
  15. Remove PDF-to-image conversion code and replace with direct ocr.Read("file.pdf") calls.
  16. Remove multi-frame TIFF frame-splitting code and replace with input.LoadImageFrames("file.tiff").
  17. Add IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" at application startup.
  18. Remove model file fetch and cache steps from CI/CD pipeline definitions.
  19. Remove ONNX model COPY instructions from Dockerfiles.

Post-Migration Testing

  • Verify that all existing image OCR paths return text with accuracy equal to or better than RapidOCR.NET output.
  • Confirm result.Text reading order matches the expected field sequence for each document type.
  • Test language-switched reads for every OcrLanguage value the application uses.
  • Run the parallel batch processor and confirm no thread contention errors or stale-result issues.
  • Verify multi-frame TIFF processing returns the correct number of pages with correct per-page text.
  • Test form field extraction via CropRectangle against the expected coordinate regions.
  • Confirm the models/ directory is absent from build output and deployment packages.
  • Run the CI pipeline end-to-end and confirm no model fetch steps remain.
  • Build and run a Docker container and confirm no COPY models/ layer or file-not-found errors at startup.
  • Test a startup time measurement to verify cold-start latency has decreased.

Key Benefits of Migrating to IronOCR

Deployment Is Now Deterministic. dotnet restore and dotnet publish produce a complete, working OCR deployment with no external file dependencies. The same NuGet restore that installs the package version installs everything the engine needs to run. There are no model files to version separately, no CI cache steps to configure, and no deployment validation scripts to maintain. The pipeline is as simple as any other .NET package dependency.

Language Coverage Scales With Business Requirements. Adding support for a new document language means running dotnet add package IronOcr.Languages.X and setting ocr.Language. There is no upstream model availability check, no model download, and no engine refactor. Teams that start with English OCR and later need to process German contracts, Arabic invoices, or Russian purchase orders extend coverage without touching application architecture. All 125+ language packs follow the same installation pattern.

Structured Output Eliminates Coordinate Assembly Code. The result.Pages, .Paragraphs, .Lines, .Words, and .Characters hierarchy replaces the flat TextBlocks list and the sorting logic that worked around its lack of structure. Code that extracted reading-order text by sorting block coordinates is deleted. Code that needed per-word bounding boxes gets them from word.X, word.Y, word.Width, word.Height with no intersection filtering. The OCR results features page documents the full output model.

PDF and TIFF Processing Require No External Libraries. The two most common document formats beyond single-image JPGs — multi-page PDFs and multi-frame TIFFs — are handled natively by IronOCR. Every external library that was added to the dependency tree to support engine.Run() with PDF or TIFF input can be removed. Net result: fewer packages to update, fewer version compatibility surfaces, and simpler project files. The PDF input how-to and TIFF input how-to cover both formats in detail.

Production Incidents Have a Support Path. Commercial licenses include direct email support with a contact point for issues that cannot wait for a GitHub issue response. Teams with SLA obligations or business-critical document processing pipelines can escalate incidents to engineers who maintain the library rather than waiting for a community response. The IronOCR documentation hub provides reference documentation alongside that support path.

The $999 Perpetual License Is a One-Time Cost. There is no per-page pricing, no per-transaction billing, and no annual renewal that re-opens the cost conversation. Development teams that priced out the engineering hours spent on model management, PDF conversion workarounds, CI pipeline maintenance, and unsupported-language escalations consistently find the comparison favorable against the license cost.

Please noteRapidOCR and Tesseract are registered trademarks of their respective owners. This site is not affiliated with, endorsed by, or sponsored by Google or RapidOCR. All product names, logos, and brands are property of their respective owners. Comparisons are for informational purposes only and reflect publicly available information at the time of writing.

Frequently Asked Questions

Why should I migrate from RapidOCR.NET to IronOCR?

Common drivers include eliminating COM interop complexity, replacing file-based license management, avoiding per-page billing, enabling Docker/container deployment, and adopting a NuGet-native workflow that integrates with standard .NET tooling.

What are the main code changes when migrating from RapidOCR.NET to IronOCR?

Replace RapidOCR.NET initialization sequences with IronTesseract instantiation, remove COM lifecycle management (explicit Create/Load/Close patterns), and update result property names. The result is significantly fewer boilerplate lines.

How do I install IronOCR to begin the migration?

Run 'Install-Package IronOcr' in Package Manager Console or 'dotnet add package IronOcr' in the CLI. Language packs are separate packages: 'dotnet add package IronOcr.Languages.French' for French, for example.

Does IronOCR match the OCR accuracy of RapidOCR.NET for standard business documents?

IronOCR achieves high accuracy for standard business content including invoices, contracts, receipts, and typed forms. Image preprocessing filters (deskew, noise removal, contrast enhancement) further improve recognition on degraded input.

How does IronOCR handle the language data that RapidOCR.NET installs separately?

Language data in IronOCR is distributed as NuGet packages. 'dotnet add package IronOcr.Languages.German' installs German support. No manual file placement or directory paths are involved.

Does migrating from RapidOCR.NET to IronOCR require changes to deployment infrastructure?

IronOCR requires fewer infrastructure changes than RapidOCR.NET. There are no SDK binary paths, license file placements, or license server configurations. The NuGet package contains the complete OCR engine, and the license key is a string set in application code.

How do I configure IronOCR licensing after migration?

Assign IronOcr.License.LicenseKey = "YOUR-KEY" in application startup code. In Docker or Kubernetes, store the key as an environment variable and read it in startup. Use License.IsValidLicense to validate before accepting traffic.

Can IronOCR process PDFs the same way RapidOCR.NET does?

Yes. IronOCR reads both native and scanned PDFs. Instantiate IronTesseract, call ocr.Read(input) where input is a PDF path or OcrPdfInput, and iterate the OcrResult pages. No separate PDF rendering pipeline is required.

How does IronOCR handle threading in high-volume processing?

IronTesseract is safe to instantiate per-thread. Spin up one instance per thread in a Parallel.ForEach or Task pool, run OCR concurrently, and dispose each instance when done. No global state or locking is required.

What output formats does IronOCR support after text extraction?

IronOCR returns structured results including text, word coordinates, confidence scores, and page structure. Export options include plain text, searchable PDF, and structured result objects for downstream processing.

Is IronOCR pricing more predictable than RapidOCR.NET for scaling workloads?

IronOCR uses flat-rate perpetual licensing with no per-page or volume charges. Whether you process 10,000 or 10 million pages, the license cost remains constant. Volume and team licensing options are on the IronOCR pricing page.

What happens to my existing tests after migrating from RapidOCR.NET to IronOCR?

Tests that assert on extracted text content should continue to pass after migration. Tests that validate API call patterns or COM object lifecycle will need updating to reflect IronOCR's simpler initialization and result model.

Kannaopat Udonpant
Software Engineer
Before becoming a Software Engineer, Kannapat completed a Environmental Resources PhD from Hokkaido University in Japan. While pursuing his degree, Kannapat also became a member of the Vehicle Robotics Laboratory, which is part of the Department of Bioproduction Engineering. In 2022, he leveraged his C# skills to join Iron Software's engineering ...
Read More

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me