Skip to footer content
MIGRATION GUIDES

Migrating from Dynamsoft OCR to IronOCR

This guide provides a complete migration path for .NET developers moving from Dynamsoft Label Recognizer to IronOCR. It covers NuGet package replacement, namespace updates, license initialization, and practical code migration examples for scenarios distinct from general capability comparisons — including template configuration elimination, runtime settings JSON removal, region definition migration, and result parsing simplification.

Why Migrate from Dynamsoft OCR

Dynamsoft Label Recognizer is a purpose-built specialist. That precision works well for narrow deployments but creates friction the moment application scope grows beyond the original MRZ or VIN use case.

Runtime Settings JSON Is a Maintenance Surface. Every Dynamsoft recognition workflow begins with AppendSettingsFromString, loading a JSON document that declares parameter arrays, character models, and region references. Those JSON templates are separate deployment artifacts — not compiled into your binary. They require version control, deployment pipeline management, and manual updates when Dynamsoft changes template schemas across SDK versions. IronOCR requires zero configuration files: instantiate IronTesseract, call .Read(), and the engine selects appropriate settings automatically.

Annual Subscription Pricing Compounds. Dynamsoft Label Recognizer licenses at $599+ per device per year. There is no perpetual option. A five-device processing cluster costs $2,995+ annually — and that budget covers only MRZ and structured label recognition. Add Dynamsoft Barcode Reader for barcodes or Dynamsoft Document Normalizer for edge correction, and the annual bill multiplies by product count. IronOCR's Lite license is $999 one-time and covers general OCR, structured labels, barcodes, native PDF, searchable PDF output, and 125+ languages from a single package.

Region Definition Requires JSON Templates. Defining a recognition region in Dynamsoft means authoring a ReferenceRegionArray JSON block, referencing it in a LabelRecognizerParameterArray, and loading the entire document before any image processing begins. IronOCR uses a single CropRectangle(x, y, width, height) constructor passed directly to OcrInput.LoadImage. Changing a region is one number edit, not a JSON document reparse.

Result Parsing Is Entirely Your Responsibility. RecognizeFile and RecognizeByFile return LineResult arrays containing raw text strings. Every structure you need — field offsets, date conversions, check digit validation, name separator handling — requires custom parsing code on top of that raw output. IronOCR returns an OcrResult with .Pages, .Lines, .Words, and .Characters as typed collections with bounding box coordinates, confidence scores, and font metadata already populated.

No Native PDF Support Creates a Hidden Dependency. Dynamsoft Label Recognizer has no PDF input capability. Any PDF that arrives at your processing pipeline requires an external library to render each page to a bitmap, a page iteration loop, and a result aggregation step before the first Dynamsoft call. IronOCR reads PDFs natively: new IronTesseract().Read("document.pdf") processes every page without a secondary dependency, a rendering loop, or a temporary image folder.

Multi-Product Initialization Blocks Grow With Scope. A Dynamsoft application that handles structured labels, barcodes, and document normalization carries three static InitLicense calls at startup, three disposal chains, and three independent SDK version dependencies. Upgrading the SDK means coordinating across all products. IronOCR has one initialization line and one package version regardless of which capabilities the application uses.

The Fundamental Problem

Dynamsoft requires a JSON configuration file loaded at runtime before recognition can start:

// Dynamsoft: JSON template required before any image processing
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

string settings = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Label"",
        ""ReferenceRegionNameArray"": [""VinRegion""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VinRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [5, 40],
            ""SecondPoint"": [95, 40],
            ""ThirdPoint"":  [95, 60],
            ""FourthPoint"": [5, 60]
        }
    }]
}";

recognizer.AppendSettingsFromString(settings);  // fails silently if JSON is malformed
var results = recognizer.RecognizeFile(imagePath);
// Dynamsoft: JSON template required before any image processing
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

string settings = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Label"",
        ""ReferenceRegionNameArray"": [""VinRegion""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VinRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [5, 40],
            ""SecondPoint"": [95, 40],
            ""ThirdPoint"":  [95, 60],
            ""FourthPoint"": [5, 60]
        }
    }]
}";

recognizer.AppendSettingsFromString(settings);  // fails silently if JSON is malformed
var results = recognizer.RecognizeFile(imagePath);
' Dynamsoft: JSON template required before any image processing
LabelRecognizer.InitLicense(licenseKey)
Dim recognizer As New LabelRecognizer()

Dim settings As String = "{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Label"",
        ""ReferenceRegionNameArray"": [""VinRegion""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VinRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [5, 40],
            ""SecondPoint"": [95, 40],
            ""ThirdPoint"":  [95, 60],
            ""FourthPoint"": [5, 60]
        }
    }]
}"

recognizer.AppendSettingsFromString(settings)  ' fails silently if JSON is malformed
Dim results = recognizer.RecognizeFile(imagePath)
$vbLabelText   $csharpLabel
// IronOCR: no JSON, no template files — region is a constructor argument
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var region = new CropRectangle(x: 50, y: 400, width: 900, height: 200);
using var input = new OcrInput();
input.LoadImage(imagePath, region);

var result = new IronTesseract().Read(input);
Console.WriteLine(result.Text);
// IronOCR: no JSON, no template files — region is a constructor argument
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var region = new CropRectangle(x: 50, y: 400, width: 900, height: 200);
using var input = new OcrInput();
input.LoadImage(imagePath, region);

var result = new IronTesseract().Read(input);
Console.WriteLine(result.Text);
Imports IronOcr

' IronOCR: no JSON, no template files — region is a constructor argument
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Dim region As New CropRectangle(x:=50, y:=400, width:=900, height:=200)
Using input As New OcrInput()
    input.LoadImage(imagePath, region)

    Dim result = New IronTesseract().Read(input)
    Console.WriteLine(result.Text)
End Using
$vbLabelText   $csharpLabel

IronOCR vs Dynamsoft OCR: Feature Comparison

The table below covers the full capability gap between the two libraries.

Feature Dynamsoft Label Recognizer IronOCR
General document OCR Not supported Full support
MRZ recognition Specialized (raw text output) Built-in with structured fields
VIN recognition Specialized (template required) Standard OCR with region targeting
Industrial label reading Specialized (template required) Yes, via CropRectangle
Runtime settings configuration Required (JSON via AppendSettingsFromString) Not required
Template deployment artifacts Required (JSON files) None
Native PDF input Not supported Yes
Password-protected PDF Not supported Yes
Multi-page TIFF input Manual page iteration Native (LoadImageFrames)
Searchable PDF output Not supported Yes (SaveAsSearchablePdf)
Barcode reading Separate product (Dynamsoft Barcode Reader) Built-in (ReadBarCodes = true)
Automatic preprocessing Limited Yes (Deskew, DeNoise, Contrast, Binarize, Sharpen, Dilate, Erode)
Structured output (Words, Lines, Paragraphs) Raw LineResult text strings Fully typed with coordinates and confidence
Language support Limited (Latin MRZ) 125+ languages, bundled as NuGet packages
Multi-language simultaneous Not supported Yes (OcrLanguage.French + OcrLanguage.German)
Confidence scores Per-line only Per word, line, page
hOCR export Not supported Yes
Licensing model Annual subscription ($599+/device/year) Perpetual ($999 one-time, Lite tier)
Multiple products required Yes (3+ for full coverage) No (one package)
.NET Framework support .NET Framework 4.x+ .NET Framework 4.6.2+, .NET 5/6/7/8/9
Cross-platform deployment Yes Yes (Windows, Linux, macOS, Docker, Azure, AWS)
NuGet package Dynamsoft.LabelRecognizer IronOcr

Quick Start: Dynamsoft OCR to IronOCR Migration

Step 1: Replace NuGet Package

Remove the Dynamsoft package:

dotnet remove package Dynamsoft.LabelRecognizer
dotnet remove package Dynamsoft.LabelRecognizer
SHELL

Install IronOCR from the NuGet gallery:

dotnet add package IronOcr

Step 2: Update Namespaces

// Before (Dynamsoft)
using Dynamsoft.LabelRecognizer;
using Dynamsoft.Core;

// After (IronOCR)
using IronOcr;
// Before (Dynamsoft)
using Dynamsoft.LabelRecognizer;
using Dynamsoft.Core;

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

Step 3: Initialize License

Replace each Dynamsoft static InitLicense call with a single IronOCR license assignment at application startup:

// Before (Dynamsoft — one call per product)
LabelRecognizer.InitLicense("DYNAMSOFT-LICENSE-KEY");

// After (IronOCR — one line, all features)
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Before (Dynamsoft — one call per product)
LabelRecognizer.InitLicense("DYNAMSOFT-LICENSE-KEY");

// After (IronOCR — one line, all features)
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
' Before (Dynamsoft — one call per product)
LabelRecognizer.InitLicense("DYNAMSOFT-LICENSE-KEY")

' After (IronOCR — one line, all features)
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

Code Migration Examples

Template-Based Recognition to Zero-Config Engine

Dynamsoft's JSON template system gives the SDK detailed instructions about character models, region specifications, and recognition parameters before any image is processed. For most real-world OCR workloads, that configuration adds setup complexity without recognition benefits, because the underlying Tesseract engine handles layout detection automatically.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;
using Dynamsoft.Core;

// JSON template required before recognition
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

string invoiceTemplate = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""Invoice_Header"",
        ""ReferenceRegionNameArray"": [""HeaderRegion""],
        ""CharacterModelName"": ""NumberLetter""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""HeaderRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [0, 0],
            ""SecondPoint"": [100, 0],
            ""ThirdPoint"":  [100, 20],
            ""FourthPoint"": [0, 20]
        }
    }]
}";

recognizer.AppendSettingsFromString(invoiceTemplate);

var results = recognizer.RecognizeFile("invoice.jpg");
var headerText = new StringBuilder();
foreach (var result in results)
    foreach (var line in result.LineResults)
        headerText.AppendLine(line.Text);

Console.WriteLine(headerText.ToString());
recognizer.Dispose();
using Dynamsoft.LabelRecognizer;
using Dynamsoft.Core;

// JSON template required before recognition
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

string invoiceTemplate = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""Invoice_Header"",
        ""ReferenceRegionNameArray"": [""HeaderRegion""],
        ""CharacterModelName"": ""NumberLetter""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""HeaderRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [0, 0],
            ""SecondPoint"": [100, 0],
            ""ThirdPoint"":  [100, 20],
            ""FourthPoint"": [0, 20]
        }
    }]
}";

recognizer.AppendSettingsFromString(invoiceTemplate);

var results = recognizer.RecognizeFile("invoice.jpg");
var headerText = new StringBuilder();
foreach (var result in results)
    foreach (var line in result.LineResults)
        headerText.AppendLine(line.Text);

Console.WriteLine(headerText.ToString());
recognizer.Dispose();
Imports Dynamsoft.LabelRecognizer
Imports Dynamsoft.Core
Imports System.Text

' JSON template required before recognition
LabelRecognizer.InitLicense(licenseKey)
Dim recognizer As New LabelRecognizer()

Dim invoiceTemplate As String = "{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""Invoice_Header"",
        ""ReferenceRegionNameArray"": [""HeaderRegion""],
        ""CharacterModelName"": ""NumberLetter""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""HeaderRegion"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [0, 0],
            ""SecondPoint"": [100, 0],
            ""ThirdPoint"":  [100, 20],
            ""FourthPoint"": [0, 20]
        }
    }]
}"

recognizer.AppendSettingsFromString(invoiceTemplate)

Dim results = recognizer.RecognizeFile("invoice.jpg")
Dim headerText As New StringBuilder()
For Each result In results
    For Each line In result.LineResults
        headerText.AppendLine(line.Text)
    Next
Next

Console.WriteLine(headerText.ToString())
recognizer.Dispose()
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

// No template file — region specified inline, engine auto-configures
var region = new CropRectangle(x: 0, y: 0, width: 1200, height: 200);
using var input = new OcrInput();
input.LoadImage("invoice.jpg", region);
input.Deskew();

var result = new IronTesseract().Read(input);
Console.WriteLine(result.Text);
Console.WriteLine($"Confidence: {result.Confidence}%");
using IronOcr;

// No template file — region specified inline, engine auto-configures
var region = new CropRectangle(x: 0, y: 0, width: 1200, height: 200);
using var input = new OcrInput();
input.LoadImage("invoice.jpg", region);
input.Deskew();

var result = new IronTesseract().Read(input);
Console.WriteLine(result.Text);
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr

' No template file — region specified inline, engine auto-configures
Dim region As New CropRectangle(x:=0, y:=0, width:=1200, height:=200)
Using input As New OcrInput()
    input.LoadImage("invoice.jpg", region)
    input.Deskew()

    Dim result = New IronTesseract().Read(input)
    Console.WriteLine(result.Text)
    Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
$vbLabelText   $csharpLabel

The Dynamsoft approach requires authoring, validating, and deploying a JSON template before any image is processed. A typo in AppendSettingsFromString returns empty results at runtime — there is no compile-time validation. IronOCR's CropRectangle is a plain constructor: compile-time type checking, no external files, no deployment artifact. See the region-based OCR guide for all targeting patterns.

VIN Code Scanning With Region Migration

Vehicle identification number extraction is a Dynamsoft Label Recognizer specialty. Its VIN template model optimizes for the specific 17-character alphanumeric format. Migrating to IronOCR replaces the template model with a preprocessing pipeline and region targeting, which handles the same VIN plate conditions without requiring a character model configuration.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

// VIN requires a specific character model and region configuration
string vinTemplate = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Scan"",
        ""ReferenceRegionNameArray"": [""VIN_Zone""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VIN_Zone"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [10, 35],
            ""SecondPoint"": [90, 35],
            ""ThirdPoint"":  [90, 65],
            ""FourthPoint"": [10, 65]
        }
    }]
}";
recognizer.AppendSettingsFromString(vinTemplate);

var results = recognizer.RecognizeFile("vehicle-vin.jpg");
string rawVin = results
    .SelectMany(r => r.LineResults)
    .Select(l => l.Text)
    .FirstOrDefault() ?? string.Empty;

Console.WriteLine($"Raw VIN text: {rawVin}");  // still requires validation
recognizer.Dispose();
using Dynamsoft.LabelRecognizer;

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();

// VIN requires a specific character model and region configuration
string vinTemplate = @"{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Scan"",
        ""ReferenceRegionNameArray"": [""VIN_Zone""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VIN_Zone"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [10, 35],
            ""SecondPoint"": [90, 35],
            ""ThirdPoint"":  [90, 65],
            ""FourthPoint"": [10, 65]
        }
    }]
}";
recognizer.AppendSettingsFromString(vinTemplate);

var results = recognizer.RecognizeFile("vehicle-vin.jpg");
string rawVin = results
    .SelectMany(r => r.LineResults)
    .Select(l => l.Text)
    .FirstOrDefault() ?? string.Empty;

Console.WriteLine($"Raw VIN text: {rawVin}");  // still requires validation
recognizer.Dispose();
Imports Dynamsoft.LabelRecognizer

LabelRecognizer.InitLicense(licenseKey)
Dim recognizer As New LabelRecognizer()

' VIN requires a specific character model and region configuration
Dim vinTemplate As String = "{
    ""LabelRecognizerParameterArray"": [{
        ""Name"": ""VIN_Scan"",
        ""ReferenceRegionNameArray"": [""VIN_Zone""],
        ""CharacterModelName"": ""VIN""
    }],
    ""ReferenceRegionArray"": [{
        ""Name"": ""VIN_Zone"",
        ""Localization"": {
            ""SourceType"": ""LST_MANUAL_SPECIFICATION"",
            ""FirstPoint"":  [10, 35],
            ""SecondPoint"": [90, 35],
            ""ThirdPoint"":  [90, 65],
            ""FourthPoint"": [10, 65]
        }
    }]
}"
recognizer.AppendSettingsFromString(vinTemplate)

Dim results = recognizer.RecognizeFile("vehicle-vin.jpg")
Dim rawVin As String = results _
    .SelectMany(Function(r) r.LineResults) _
    .Select(Function(l) l.Text) _
    .FirstOrDefault() OrElse String.Empty

Console.WriteLine($"Raw VIN text: {rawVin}")  ' still requires validation
recognizer.Dispose()
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;
using System.Text.RegularExpressions;

// Target the VIN plate region directly — no template file
var vinRegion = new CropRectangle(x: 100, y: 350, width: 800, height: 300);

using var input = new OcrInput();
input.LoadImage("vehicle-vin.jpg", vinRegion);
input.Contrast();   // recover faded stamped digits
input.Sharpen();    // clarify character boundaries on embossed plates

var result = new IronTesseract().Read(input);

// VIN: 17 chars, no I/O/Q
var vinMatch = Regex.Match(result.Text, @"\b[A-HJ-NPR-Z0-9]{17}\b");
string vin = vinMatch.Success ? vinMatch.Value : result.Text.Trim();

Console.WriteLine($"VIN: {vin}");
Console.WriteLine($"Confidence: {result.Confidence}%");
using IronOcr;
using System.Text.RegularExpressions;

// Target the VIN plate region directly — no template file
var vinRegion = new CropRectangle(x: 100, y: 350, width: 800, height: 300);

using var input = new OcrInput();
input.LoadImage("vehicle-vin.jpg", vinRegion);
input.Contrast();   // recover faded stamped digits
input.Sharpen();    // clarify character boundaries on embossed plates

var result = new IronTesseract().Read(input);

// VIN: 17 chars, no I/O/Q
var vinMatch = Regex.Match(result.Text, @"\b[A-HJ-NPR-Z0-9]{17}\b");
string vin = vinMatch.Success ? vinMatch.Value : result.Text.Trim();

Console.WriteLine($"VIN: {vin}");
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr
Imports System.Text.RegularExpressions

' Target the VIN plate region directly — no template file
Dim vinRegion As New CropRectangle(x:=100, y:=350, width:=800, height:=300)

Using input As New OcrInput()
    input.LoadImage("vehicle-vin.jpg", vinRegion)
    input.Contrast()   ' recover faded stamped digits
    input.Sharpen()    ' clarify character boundaries on embossed plates

    Dim result = New IronTesseract().Read(input)

    ' VIN: 17 chars, no I/O/Q
    Dim vinMatch = Regex.Match(result.Text, "\b[A-HJ-NPR-Z0-9]{17}\b")
    Dim vin As String = If(vinMatch.Success, vinMatch.Value, result.Text.Trim())

    Console.WriteLine($"VIN: {vin}")
    Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
$vbLabelText   $csharpLabel

CropRectangle coordinates use the same pixel reference frame as Dynamsoft's percentage-based FirstPoint/SecondPoint specification — convert by multiplying Dynamsoft's percentage values by your image dimensions. The preprocessing pipeline (Contrast, Sharpen) replaces the character model optimization Dynamsoft bakes into its VIN template. The image quality correction guide covers filter selection for embossed and low-contrast surfaces.

Multi-Page TIFF Batch Processing

Dynamsoft Label Recognizer processes single images. Multi-page TIFF documents — common in document management systems, medical imaging archives, and fax pipelines — require external iteration: load each frame, pass it through the recognizer individually, and aggregate results. IronOCR handles multi-frame TIFF natively through LoadImageFrames.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;
// Requires an external TIFF library to extract frames

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(genericTemplate);

// External library needed to iterate TIFF frames
var tiffImage = System.Drawing.Image.FromFile("scanned-batch.tiff");
int frameCount = tiffImage.GetFrameCount(
    System.Drawing.Imaging.FrameDimension.Page);

var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
    tiffImage.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
    string tempPath = $"frame_{i}.jpg";
    tiffImage.Save(tempPath, System.Drawing.Imaging.ImageFormat.Jpeg);

    var results = recognizer.RecognizeFile(tempPath);
    foreach (var r in results)
        foreach (var line in r.LineResults)
            allText.AppendLine(line.Text);

    System.IO.File.Delete(tempPath);  // clean up temp files
}

Console.WriteLine(allText.ToString());
recognizer.Dispose();
using Dynamsoft.LabelRecognizer;
// Requires an external TIFF library to extract frames

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(genericTemplate);

// External library needed to iterate TIFF frames
var tiffImage = System.Drawing.Image.FromFile("scanned-batch.tiff");
int frameCount = tiffImage.GetFrameCount(
    System.Drawing.Imaging.FrameDimension.Page);

var allText = new StringBuilder();
for (int i = 0; i < frameCount; i++)
{
    tiffImage.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
    string tempPath = $"frame_{i}.jpg";
    tiffImage.Save(tempPath, System.Drawing.Imaging.ImageFormat.Jpeg);

    var results = recognizer.RecognizeFile(tempPath);
    foreach (var r in results)
        foreach (var line in r.LineResults)
            allText.AppendLine(line.Text);

    System.IO.File.Delete(tempPath);  // clean up temp files
}

Console.WriteLine(allText.ToString());
recognizer.Dispose();
Imports Dynamsoft.LabelRecognizer
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Text

' Requires an external TIFF library to extract frames

LabelRecognizer.InitLicense(licenseKey)
Dim recognizer As New LabelRecognizer()
recognizer.AppendSettingsFromString(genericTemplate)

' External library needed to iterate TIFF frames
Dim tiffImage As Image = Image.FromFile("scanned-batch.tiff")
Dim frameCount As Integer = tiffImage.GetFrameCount(FrameDimension.Page)

Dim allText As New StringBuilder()
For i As Integer = 0 To frameCount - 1
    tiffImage.SelectActiveFrame(FrameDimension.Page, i)
    Dim tempPath As String = $"frame_{i}.jpg"
    tiffImage.Save(tempPath, Imaging.ImageFormat.Jpeg)

    Dim results = recognizer.RecognizeFile(tempPath)
    For Each r In results
        For Each line In r.LineResults
            allText.AppendLine(line.Text)
        Next
    Next

    File.Delete(tempPath)  ' clean up temp files
Next

Console.WriteLine(allText.ToString())
recognizer.Dispose()
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

// No frame iteration, no temp files, no external TIFF library
using var input = new OcrInput();
input.LoadImageFrames("scanned-batch.tiff");  // loads all frames natively
input.Deskew();
input.DeNoise();

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Results organized per page
foreach (var page in result.Pages)
{
    Console.WriteLine($"Frame {page.PageNumber}: {page.Lines.Count} lines");
    Console.WriteLine(page.Text);
}

// Save entire batch as a single searchable PDF
result.SaveAsSearchablePdf("batch-searchable.pdf");
using IronOcr;

// No frame iteration, no temp files, no external TIFF library
using var input = new OcrInput();
input.LoadImageFrames("scanned-batch.tiff");  // loads all frames natively
input.Deskew();
input.DeNoise();

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Results organized per page
foreach (var page in result.Pages)
{
    Console.WriteLine($"Frame {page.PageNumber}: {page.Lines.Count} lines");
    Console.WriteLine(page.Text);
}

// Save entire batch as a single searchable PDF
result.SaveAsSearchablePdf("batch-searchable.pdf");
Imports IronOcr

' No frame iteration, no temp files, no external TIFF library
Using input As New OcrInput()
    input.LoadImageFrames("scanned-batch.tiff") ' loads all frames natively
    input.Deskew()
    input.DeNoise()

    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(input)

    ' Results organized per page
    For Each page In result.Pages
        Console.WriteLine($"Frame {page.PageNumber}: {page.Lines.Count} lines")
        Console.WriteLine(page.Text)
    Next

    ' Save entire batch as a single searchable PDF
    result.SaveAsSearchablePdf("batch-searchable.pdf")
End Using
$vbLabelText   $csharpLabel

The Dynamsoft approach requires System.Drawing for frame extraction, a temporary file on disk per frame, and manual cleanup after each pass. IronOCR's LoadImageFrames reads all frames in a single call — no temp files, no frame index tracking. After OCR, SaveAsSearchablePdf archives the entire batch as a searchable document in one method call. The multi-frame TIFF guide and searchable PDF output guide cover both capabilities in detail.

Structured Word-Level Result Parsing

Dynamsoft RecognizeFile returns a DLRResult array. Each DLRResult contains a LineResults collection of DLRLineResult objects with a Text property and a Location property holding the bounding quadrilateral. Extracting word-level positions requires splitting the line text on whitespace and proportionally dividing the line bounding box — an approximation that breaks on proportional fonts. IronOCR surfaces word-level bounding boxes as typed properties on every OcrResult.Word object.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(settingsJson);

var results = recognizer.RecognizeFile("form-scan.jpg");

foreach (var dlrResult in results)
{
    foreach (var lineResult in dlrResult.LineResults)
    {
        // Line-level data only — word boundaries are not provided
        Console.WriteLine($"Text: {lineResult.Text}");
        Console.WriteLine($"Confidence: {lineResult.Confidence}");

        // Location is a quadrilateral — four corner points
        var loc = lineResult.Location;
        Console.WriteLine($"Top-left: ({loc.Points[0].X}, {loc.Points[0].Y})");

        // To get word positions: split text and divide bounding box manually
        var words = lineResult.Text.Split(' ');
        int approxWidth = (loc.Points[1].X - loc.Points[0].X) / words.Length;
        for (int i = 0; i < words.Length; i++)
        {
            int wordX = loc.Points[0].X + (i * approxWidth);
            Console.WriteLine($"  Word '{words[i]}' approx at x={wordX}");
        }
    }
}
recognizer.Dispose();
using Dynamsoft.LabelRecognizer;

LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(settingsJson);

var results = recognizer.RecognizeFile("form-scan.jpg");

foreach (var dlrResult in results)
{
    foreach (var lineResult in dlrResult.LineResults)
    {
        // Line-level data only — word boundaries are not provided
        Console.WriteLine($"Text: {lineResult.Text}");
        Console.WriteLine($"Confidence: {lineResult.Confidence}");

        // Location is a quadrilateral — four corner points
        var loc = lineResult.Location;
        Console.WriteLine($"Top-left: ({loc.Points[0].X}, {loc.Points[0].Y})");

        // To get word positions: split text and divide bounding box manually
        var words = lineResult.Text.Split(' ');
        int approxWidth = (loc.Points[1].X - loc.Points[0].X) / words.Length;
        for (int i = 0; i < words.Length; i++)
        {
            int wordX = loc.Points[0].X + (i * approxWidth);
            Console.WriteLine($"  Word '{words[i]}' approx at x={wordX}");
        }
    }
}
recognizer.Dispose();
Imports Dynamsoft.LabelRecognizer

LabelRecognizer.InitLicense(licenseKey)
Dim recognizer As New LabelRecognizer()
recognizer.AppendSettingsFromString(settingsJson)

Dim results = recognizer.RecognizeFile("form-scan.jpg")

For Each dlrResult In results
    For Each lineResult In dlrResult.LineResults
        ' Line-level data only — word boundaries are not provided
        Console.WriteLine($"Text: {lineResult.Text}")
        Console.WriteLine($"Confidence: {lineResult.Confidence}")

        ' Location is a quadrilateral — four corner points
        Dim loc = lineResult.Location
        Console.WriteLine($"Top-left: ({loc.Points(0).X}, {loc.Points(0).Y})")

        ' To get word positions: split text and divide bounding box manually
        Dim words = lineResult.Text.Split(" "c)
        Dim approxWidth As Integer = (loc.Points(1).X - loc.Points(0).X) \ words.Length
        For i As Integer = 0 To words.Length - 1
            Dim wordX As Integer = loc.Points(0).X + (i * approxWidth)
            Console.WriteLine($"  Word '{words(i)}' approx at x={wordX}")
        Next
    Next
Next
recognizer.Dispose()
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

var result = new IronTesseract().Read("form-scan.jpg");

// Word-level positions are first-class properties — no manual division
foreach (var page in result.Pages)
{
    foreach (var paragraph in page.Paragraphs)
    {
        Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}");

        foreach (var line in paragraph.Lines)
        {
            foreach (var word in line.Words)
            {
                Console.WriteLine(
                    $"  Word: '{word.Text}' " +
                    $"at ({word.X},{word.Y}) " +
                    $"size {word.Width}x{word.Height} " +
                    $"confidence {word.Confidence}%");
            }
        }
    }
}
using IronOcr;

var result = new IronTesseract().Read("form-scan.jpg");

// Word-level positions are first-class properties — no manual division
foreach (var page in result.Pages)
{
    foreach (var paragraph in page.Paragraphs)
    {
        Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}");

        foreach (var line in paragraph.Lines)
        {
            foreach (var word in line.Words)
            {
                Console.WriteLine(
                    $"  Word: '{word.Text}' " +
                    $"at ({word.X},{word.Y}) " +
                    $"size {word.Width}x{word.Height} " +
                    $"confidence {word.Confidence}%");
            }
        }
    }
}
Imports IronOcr

Dim result = New IronTesseract().Read("form-scan.jpg")

' Word-level positions are first-class properties — no manual division
For Each page In result.Pages
    For Each paragraph In page.Paragraphs
        Console.WriteLine($"Paragraph at ({paragraph.X}, {paragraph.Y}): {paragraph.Text}")

        For Each line In paragraph.Lines
            For Each word In line.Words
                Console.WriteLine(
                    $"  Word: '{word.Text}' " &
                    $"at ({word.X},{word.Y}) " &
                    $"size {word.Width}x{word.Height} " &
                    $"confidence {word.Confidence}%")
            Next
        Next
    Next
Next
$vbLabelText   $csharpLabel

IronOCR provides a true hierarchy: pages contain paragraphs, paragraphs contain lines, lines contain words, words contain characters. Every level exposes .X, .Y, .Width, .Height, and .Confidence as typed integer and double properties — no quadrilateral coordinate arithmetic required. The structured results guide documents every accessible property, and the confidence scoring guide explains confidence thresholding for automated document review pipelines.

Async Parallel Processing for High-Volume Label Scanning

Dynamsoft Label Recognizer is thread-safe, but its RecognizeFile call is synchronous. Wrapping it in Task.Run produces parallel processing, but each call shares configuration state through AppendSettingsFromString — loading templates on multiple threads requires careful initialization sequencing. IronOCR's IronTesseract instances are independent: create one per task, call Read, and the thread model is straightforward.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;
using System.Threading.Tasks;

// InitLicense must be called once before parallel work begins
LabelRecognizer.InitLicense(licenseKey);

// Each thread needs its own recognizer instance with its own template load
var results = new System.Collections.Concurrent.ConcurrentBag<string>();

await Task.WhenAll(imagePaths.Select(async path =>
{
    // Cannot share recognizer instances safely across tasks
    var recognizer = new LabelRecognizer();
    recognizer.AppendSettingsFromString(templateJson);  // reload JSON per instance

    await Task.Run(() =>
    {
        var dlrResults = recognizer.RecognizeFile(path);
        var text = string.Join("\n",
            dlrResults.SelectMany(r => r.LineResults).Select(l => l.Text));
        results.Add(text);
    });

    recognizer.Dispose();
}));

Console.WriteLine($"Processed {results.Count} images");
using Dynamsoft.LabelRecognizer;
using System.Threading.Tasks;

// InitLicense must be called once before parallel work begins
LabelRecognizer.InitLicense(licenseKey);

// Each thread needs its own recognizer instance with its own template load
var results = new System.Collections.Concurrent.ConcurrentBag<string>();

await Task.WhenAll(imagePaths.Select(async path =>
{
    // Cannot share recognizer instances safely across tasks
    var recognizer = new LabelRecognizer();
    recognizer.AppendSettingsFromString(templateJson);  // reload JSON per instance

    await Task.Run(() =>
    {
        var dlrResults = recognizer.RecognizeFile(path);
        var text = string.Join("\n",
            dlrResults.SelectMany(r => r.LineResults).Select(l => l.Text));
        results.Add(text);
    });

    recognizer.Dispose();
}));

Console.WriteLine($"Processed {results.Count} images");
Imports Dynamsoft.LabelRecognizer
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

' InitLicense must be called once before parallel work begins
LabelRecognizer.InitLicense(licenseKey)

' Each thread needs its own recognizer instance with its own template load
Dim results As New ConcurrentBag(Of String)()

Await Task.WhenAll(imagePaths.Select(Async Function(path)
    ' Cannot share recognizer instances safely across tasks
    Dim recognizer As New LabelRecognizer()
    recognizer.AppendSettingsFromString(templateJson)  ' reload JSON per instance

    Await Task.Run(Sub()
        Dim dlrResults = recognizer.RecognizeFile(path)
        Dim text = String.Join(vbLf, dlrResults.SelectMany(Function(r) r.LineResults).Select(Function(l) l.Text))
        results.Add(text)
    End Sub)

    recognizer.Dispose()
End Function))

Console.WriteLine($"Processed {results.Count} images")
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;

var extractedTexts = new ConcurrentDictionary<string, string>();

// Each IronTesseract instance is independent — no shared state
Parallel.ForEach(imagePaths, imagePath =>
{
    var ocr = new IronTesseract();
    var result = ocr.Read(imagePath);
    extractedTexts[imagePath] = result.Text;
});

foreach (var (path, text) in extractedTexts)
    Console.WriteLine($"{System.IO.Path.GetFileName(path)}: {text.Length} characters");
using IronOcr;
using System.Collections.Concurrent;
using System.Threading.Tasks;

var extractedTexts = new ConcurrentDictionary<string, string>();

// Each IronTesseract instance is independent — no shared state
Parallel.ForEach(imagePaths, imagePath =>
{
    var ocr = new IronTesseract();
    var result = ocr.Read(imagePath);
    extractedTexts[imagePath] = result.Text;
});

foreach (var (path, text) in extractedTexts)
    Console.WriteLine($"{System.IO.Path.GetFileName(path)}: {text.Length} characters");
Imports IronOcr
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

Dim extractedTexts As New ConcurrentDictionary(Of String, String)()

' Each IronTesseract instance is independent — no shared state
Parallel.ForEach(imagePaths, Sub(imagePath)
    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(imagePath)
    extractedTexts(imagePath) = result.Text
End Sub)

For Each kvp In extractedTexts
    Dim path = kvp.Key
    Dim text = kvp.Value
    Console.WriteLine($"{System.IO.Path.GetFileName(path)}: {text.Length} characters")
Next
$vbLabelText   $csharpLabel

IronOCR requires no initialization sequencing before parallel work begins. One IronOcr.License.LicenseKey assignment at application startup activates all instances. No template reload per thread, no JSON validation per instance. For non-blocking server-side scenarios, the async OCR guide covers await-based patterns for ASP.NET Core and Azure Functions.

Searchable PDF Output From Scanned Label Archives

Dynamsoft has no PDF output capability of any kind. Scanned label archives processed through Dynamsoft remain as image-only files — unsearchable, not indexable by document management systems, and not compliant with PDF/A archival standards. IronOCR adds searchable PDF output as a single method call on the OcrResult object.

Dynamsoft Approach:

using Dynamsoft.LabelRecognizer;

// Process scanned label — extract text into a string
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(labelTemplate);

var results = recognizer.RecognizeFile("scanned-labels.jpg");
var extractedText = new StringBuilder();
foreach (var r in results)
    foreach (var line in r.LineResults)
        extractedText.AppendLine(line.Text);

// Cannot create a searchable PDF — save text to a sidecar .txt file instead
System.IO.File.WriteAllText("scanned-labels.txt", extractedText.ToString());

// The original image remains unsearchable
Console.WriteLine("No PDF output available in Dynamsoft Label Recognizer.");
recognizer.Dispose();
using Dynamsoft.LabelRecognizer;

// Process scanned label — extract text into a string
LabelRecognizer.InitLicense(licenseKey);
var recognizer = new LabelRecognizer();
recognizer.AppendSettingsFromString(labelTemplate);

var results = recognizer.RecognizeFile("scanned-labels.jpg");
var extractedText = new StringBuilder();
foreach (var r in results)
    foreach (var line in r.LineResults)
        extractedText.AppendLine(line.Text);

// Cannot create a searchable PDF — save text to a sidecar .txt file instead
System.IO.File.WriteAllText("scanned-labels.txt", extractedText.ToString());

// The original image remains unsearchable
Console.WriteLine("No PDF output available in Dynamsoft Label Recognizer.");
recognizer.Dispose();
Imports Dynamsoft.LabelRecognizer
Imports System.IO
Imports System.Text

' Process scanned label — extract text into a string
LabelRecognizer.InitLicense(licenseKey)
Dim recognizer = New LabelRecognizer()
recognizer.AppendSettingsFromString(labelTemplate)

Dim results = recognizer.RecognizeFile("scanned-labels.jpg")
Dim extractedText = New StringBuilder()
For Each r In results
    For Each line In r.LineResults
        extractedText.AppendLine(line.Text)
    Next
Next

' Cannot create a searchable PDF — save text to a sidecar .txt file instead
File.WriteAllText("scanned-labels.txt", extractedText.ToString())

' The original image remains unsearchable
Console.WriteLine("No PDF output available in Dynamsoft Label Recognizer.")
recognizer.Dispose()
$vbLabelText   $csharpLabel

IronOCR Approach:

using IronOcr;

// Read, preprocess, and produce a searchable PDF archive in one pipeline
using var input = new OcrInput();
input.LoadImage("scanned-labels.jpg");
input.Deskew();
input.Contrast();

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Searchable PDF: original image layer preserved, invisible text layer added
result.SaveAsSearchablePdf("scanned-labels-searchable.pdf");

Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%");
Console.WriteLine($"Extracted text preview: {result.Text.Substring(0, 100)}");
using IronOcr;

// Read, preprocess, and produce a searchable PDF archive in one pipeline
using var input = new OcrInput();
input.LoadImage("scanned-labels.jpg");
input.Deskew();
input.Contrast();

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Searchable PDF: original image layer preserved, invisible text layer added
result.SaveAsSearchablePdf("scanned-labels-searchable.pdf");

Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%");
Console.WriteLine($"Extracted text preview: {result.Text.Substring(0, 100)}");
Imports IronOcr

' Read, preprocess, and produce a searchable PDF archive in one pipeline
Using input As New OcrInput()
    input.LoadImage("scanned-labels.jpg")
    input.Deskew()
    input.Contrast()

    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(input)

    ' Searchable PDF: original image layer preserved, invisible text layer added
    result.SaveAsSearchablePdf("scanned-labels-searchable.pdf")

    Console.WriteLine($"Searchable PDF created. Confidence: {result.Confidence}%")
    Console.WriteLine($"Extracted text preview: {result.Text.Substring(0, 100)}")
End Using
$vbLabelText   $csharpLabel

The searchable PDF output preserves the original scanned image at full resolution and adds an invisible OCR text layer on top. Document management systems, search indexes, and PDF viewers can now search the label content. The searchable PDF how-to and the scanned document processing guide cover multi-page archives and batch conversion workflows.

Dynamsoft OCR API to IronOCR Mapping Reference

Dynamsoft Label Recognizer IronOCR Equivalent
LabelRecognizer.InitLicense(key) IronOcr.License.LicenseKey = key
new LabelRecognizer() new IronTesseract()
recognizer.AppendSettingsFromString(json) Not required — auto-configured
recognizer.RecognizeFile(path) ocr.Read(path)
recognizer.RecognizeByFile(path, templateName) ocr.Read(path)
recognizer.RecognizeBuffer(buffer, width, height, ...) input.LoadImage(imageBytes) then ocr.Read(input)
DLRResult[] (array of results) OcrResult (single result object)
DLRResult.LineResults OcrResult.Lines
DLRLineResult.Text OcrResult.Line.Text
DLRLineResult.Confidence OcrResult.Line.Words[i].Confidence
DLRLineResult.Location.Points[i] OcrResult.Line.X, .Y, .Width, .Height
ReferenceRegionArray (JSON) new CropRectangle(x, y, w, h)
LabelRecognizerParameterArray (JSON) Not required
CharacterModelName in template Not required
recognizer.Dispose() using var ocr = new IronTesseract()
No PDF input support input.LoadPdf(path)
No searchable PDF output result.SaveAsSearchablePdf(path)
No multi-page TIFF input input.LoadImageFrames(path)
Barcode reading (separate product) ocr.Configuration.ReadBarCodes = true
Multiple InitLicense calls (per product) Single IronOcr.License.LicenseKey assignment

Common Migration Issues and Solutions

Issue 1: AppendSettingsFromString Returns Empty Results After Migration

Dynamsoft: Recognition silently returns empty DLRResult arrays when AppendSettingsFromString receives a malformed JSON string. No exception is thrown. Teams migrating from Dynamsoft sometimes add defensive null-checks throughout their result parsing code to guard against this silent failure.

Solution: Remove AppendSettingsFromString entirely. IronOCR requires no template configuration. If specific region targeting is needed, replace the ReferenceRegionArray JSON with a CropRectangle:

// Remove all AppendSettingsFromString calls
// Replace region JSON with a CropRectangle constructor
var region = new CropRectangle(x: 0, y: 400, width: 1200, height: 300);
using var input = new OcrInput();
input.LoadImage(imagePath, region);
var result = new IronTesseract().Read(input);
// Remove all AppendSettingsFromString calls
// Replace region JSON with a CropRectangle constructor
var region = new CropRectangle(x: 0, y: 400, width: 1200, height: 300);
using var input = new OcrInput();
input.LoadImage(imagePath, region);
var result = new IronTesseract().Read(input);
Imports System

' Remove all AppendSettingsFromString calls
' Replace region JSON with a CropRectangle constructor
Dim region As New CropRectangle(x:=0, y:=400, width:=1200, height:=300)
Using input As New OcrInput()
    input.LoadImage(imagePath, region)
    Dim result = New IronTesseract().Read(input)
End Using
$vbLabelText   $csharpLabel

The IronTesseract setup guide documents all engine configuration options that can be set as properties rather than JSON strings.

Issue 2: Multiple InitLicense Calls at Application Startup

Dynamsoft: An application using Label Recognizer, Barcode Reader, and Document Normalizer calls three separate InitLicense methods at startup, each requiring its own license key. Teams store three environment variables, rotate three keys independently, and debug three separate initialization failures.

Solution: Remove all Dynamsoft InitLicense calls. Replace with one IronOCR line:

// Remove:
// LabelRecognizer.InitLicense(mrzKey);
// BarcodeReader.InitLicense(barcodeKey);
// DocumentNormalizer.InitLicense(docKey);

// Add once, at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
// Remove:
// LabelRecognizer.InitLicense(mrzKey);
// BarcodeReader.InitLicense(barcodeKey);
// DocumentNormalizer.InitLicense(docKey);

// Add once, at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE");
' Remove:
' LabelRecognizer.InitLicense(mrzKey)
' BarcodeReader.InitLicense(barcodeKey)
' DocumentNormalizer.InitLicense(docKey)

' Add once, at application startup:
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE")
$vbLabelText   $csharpLabel

One environment variable activates all IronOCR capabilities — OCR, barcodes, PDF processing, and all 125+ languages — with no per-feature initialization required.

Issue 3: LineResult Text Aggregation Produces Garbled Output

Dynamsoft: RecognizeFile returns one DLRResult per detected label region. Applications that concatenate lineResult.Text across all results with \n separators produce output that mixes lines from different label zones on the page — because the order of DLRResult objects is region-detection order, not reading order.

Solution: IronOCR's result hierarchy organizes output in reading order. Use the PagesParagraphsLines structure to access text in natural document order:

var result = new IronTesseract().Read(imagePath);

// Reading-order text — no manual aggregation
Console.WriteLine(result.Text);

// Or access paragraph-level groupings
foreach (var paragraph in result.Pages[0].Paragraphs)
    Console.WriteLine(paragraph.Text);
var result = new IronTesseract().Read(imagePath);

// Reading-order text — no manual aggregation
Console.WriteLine(result.Text);

// Or access paragraph-level groupings
foreach (var paragraph in result.Pages[0].Paragraphs)
    Console.WriteLine(paragraph.Text);
Imports IronOcr

Dim result = New IronTesseract().Read(imagePath)

' Reading-order text — no manual aggregation
Console.WriteLine(result.Text)

' Or access paragraph-level groupings
For Each paragraph In result.Pages(0).Paragraphs
    Console.WriteLine(paragraph.Text)
Next
$vbLabelText   $csharpLabel

Issue 4: Template JSON Files Missing on Deployment Server

Dynamsoft: Template JSON files are separate deployment artifacts. When a template file is missing or has an incorrect path on a new server, AppendSettingsFromString fails — sometimes silently, sometimes with a runtime exception — and the deployment breaks recognition without touching application code.

Solution: IronOCR has no template files. After replacing the NuGet package and updating namespaces, the deployment artifact list shrinks to the application binaries and one environment variable. Add a startup validation check if needed:

// Validate license at startup — no template files to check
if (!IronOcr.License.IsValidLicense)
    throw new InvalidOperationException("IronOCR license key is invalid or missing.");

var ocr = new IronTesseract();
// Ready to process — no file dependencies
// Validate license at startup — no template files to check
if (!IronOcr.License.IsValidLicense)
    throw new InvalidOperationException("IronOCR license key is invalid or missing.");

var ocr = new IronTesseract();
// Ready to process — no file dependencies
Imports IronOcr

' Validate license at startup — no template files to check
If Not IronOcr.License.IsValidLicense Then
    Throw New InvalidOperationException("IronOCR license key is invalid or missing.")
End If

Dim ocr As New IronTesseract()
' Ready to process — no file dependencies
$vbLabelText   $csharpLabel

Issue 5: Disposal Chain Management Across Multiple Products

Dynamsoft: Each Dynamsoft product instance (LabelRecognizer, BarcodeReader, DocumentNormalizer) implements IDisposable. Service classes that aggregate multiple products carry multi-level Dispose implementations, and missing a Dispose call on any product instance causes native resource leaks that manifest as memory growth under load.

Solution: IronOCR's IronTesseract and OcrInput both implement IDisposable, but the disposal model is simpler — one class, one pattern. Register a single IronTesseract instance in the DI container as a singleton, or use using blocks for short-lived instances:

// Short-lived pattern
using var input = new OcrInput();
input.LoadImage(imagePath);
var result = new IronTesseract().Read(input);

// Long-lived singleton (register in Program.cs)
builder.Services.AddSingleton<IronTesseract>();
// Short-lived pattern
using var input = new OcrInput();
input.LoadImage(imagePath);
var result = new IronTesseract().Read(input);

// Long-lived singleton (register in Program.cs)
builder.Services.AddSingleton<IronTesseract>();
Imports IronOcr

' Short-lived pattern
Using input As New OcrInput()
    input.LoadImage(imagePath)
    Dim result = (New IronTesseract()).Read(input)
End Using

' Long-lived singleton (register in Program.vb)
builder.Services.AddSingleton(Of IronTesseract)()
$vbLabelText   $csharpLabel

The progress tracking guide covers lifetime management for long-running batch jobs, and the async OCR guide shows singleton patterns in ASP.NET Core.

Issue 6: Character Model Not Available for Custom Label Format

Dynamsoft: The CharacterModelName field in a template references a model file that must exist in the Dynamsoft SDK installation directory. Custom character models require downloading additional model files from Dynamsoft's portal and placing them in the correct SDK path. A missing model causes recognition to fail with a cryptic runtime error.

Solution: IronOCR does not use character model files. For custom or specialized fonts, use custom language training:

// No character model files needed
// For specialized fonts, use a custom language pack
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.English;  // or load a custom trained pack

using var input = new OcrInput();
input.LoadImage(specializedFontImage);
input.Binarize();  // helps with specialized font clarity

var result = ocr.Read(input);
// No character model files needed
// For specialized fonts, use a custom language pack
var ocr = new IronTesseract();
ocr.Language = OcrLanguage.English;  // or load a custom trained pack

using var input = new OcrInput();
input.LoadImage(specializedFontImage);
input.Binarize();  // helps with specialized font clarity

var result = ocr.Read(input);
Imports IronTesseract

' No character model files needed
' For specialized fonts, use a custom language pack
Dim ocr As New IronTesseract()
ocr.Language = OcrLanguage.English  ' or load a custom trained pack

Using input As New OcrInput()
    input.LoadImage(specializedFontImage)
    input.Binarize()  ' helps with specialized font clarity

    Dim result = ocr.Read(input)
End Using
$vbLabelText   $csharpLabel

The custom font training guide covers training a custom language pack for proprietary label fonts without any file path configuration.

Dynamsoft OCR Migration Checklist

Pre-Migration

Audit the codebase for all Dynamsoft references:

# Find all Dynamsoft using statements
grep -r "using Dynamsoft" --include="*.cs" .

# Find InitLicense calls (one per product)
grep -r "InitLicense" --include="*.cs" .

# Find AppendSettingsFromString calls (template loads)
grep -r "AppendSettingsFromString" --include="*.cs" .

# Find template JSON strings (inline or file references)
grep -r "LabelRecognizerParameterArray\|ReferenceRegionArray\|CharacterModelName" --include="*.cs" .

# Find RecognizeFile and RecognizeByFile calls
grep -r "RecognizeFile\|RecognizeByFile\|RecognizeBuffer" --include="*.cs" .

# Find LineResult result parsing patterns
grep -r "LineResults\|DLRResult\|DLRLineResult" --include="*.cs" .

# Identify any JSON template files in the project
find . -name "*.json" | xargs grep -l "LabelRecognizerParameterArray" 2>/dev/null
# Find all Dynamsoft using statements
grep -r "using Dynamsoft" --include="*.cs" .

# Find InitLicense calls (one per product)
grep -r "InitLicense" --include="*.cs" .

# Find AppendSettingsFromString calls (template loads)
grep -r "AppendSettingsFromString" --include="*.cs" .

# Find template JSON strings (inline or file references)
grep -r "LabelRecognizerParameterArray\|ReferenceRegionArray\|CharacterModelName" --include="*.cs" .

# Find RecognizeFile and RecognizeByFile calls
grep -r "RecognizeFile\|RecognizeByFile\|RecognizeBuffer" --include="*.cs" .

# Find LineResult result parsing patterns
grep -r "LineResults\|DLRResult\|DLRLineResult" --include="*.cs" .

# Identify any JSON template files in the project
find . -name "*.json" | xargs grep -l "LabelRecognizerParameterArray" 2>/dev/null
SHELL

Inventory findings:

  • Count InitLicense call sites (one per Dynamsoft product in use)
  • Count AppendSettingsFromString call sites (one per recognition template)
  • List all JSON template files that will be deleted after migration
  • Identify any ReferenceRegionArray definitions (convert to CropRectangle)
  • Note which features are covered by separate products (barcodes, document normalization)

Code Migration

  1. Remove Dynamsoft.LabelRecognizer NuGet package (and any other Dynamsoft packages)
  2. Install IronOcr NuGet package (dotnet add package IronOcr)
  3. Replace all using Dynamsoft.LabelRecognizer; and using Dynamsoft.Core; with using IronOcr;
  4. Replace all LabelRecognizer.InitLicense(key) calls with a single IronOcr.License.LicenseKey = key at application startup
  5. Remove all AppendSettingsFromString(json) calls — no equivalent required
  6. Replace ReferenceRegionArray JSON coordinates with new CropRectangle(x, y, width, height) constructors
  7. Replace new LabelRecognizer() instantiation with new IronTesseract()
  8. Replace recognizer.RecognizeFile(path) with ocr.Read(path)
  9. Replace DLRResult/DLRLineResult result iteration with OcrResult.Pages/.Lines/.Words hierarchy
  10. Replace manual lineResult.Text string aggregation with result.Text or structured page enumeration
  11. Replace BarcodeReader.InitLicense + BarcodeReader.DecodeFile with ocr.Configuration.ReadBarCodes = true
  12. Remove multi-product Dispose chains and replace with using var ocr = new IronTesseract()
  13. Delete JSON template files from the project and deployment artifacts
  14. Replace frame iteration loops over TIFF files with input.LoadImageFrames(path)
  15. Add result.SaveAsSearchablePdf(outputPath) wherever scanned outputs need to be archived

Post-Migration

  • Verify IronOcr.License.IsValidLicense returns true at application startup
  • Confirm all recognition results return non-empty result.Text for previously working inputs
  • Check result.Confidence on representative samples — values above 80% indicate good recognition quality
  • Test region-targeting accuracy by comparing CropRectangle outputs against previous Dynamsoft region results
  • Validate barcode reading by running ocr.Configuration.ReadBarCodes = true on inputs that previously required Dynamsoft Barcode Reader
  • Verify multi-page TIFF processing produces one OcrResult.Page per frame
  • Confirm searchable PDF output is text-searchable in a PDF viewer
  • Run parallel processing tests with Parallel.ForEach and verify no thread contention
  • Test all deployment targets (Linux, Docker, Azure) to confirm single-package deployment works without JSON template files
  • Confirm no Dynamsoft license key environment variables remain in deployment configuration

Key Benefits of Migrating to IronOCR

One Package Eliminates the Multi-Product Coordination Tax. Every capability — general OCR, MRZ extraction, barcode reading, native PDF input, searchable PDF output, preprocessing, and 125+ languages — ships as a single IronOcr NuGet package. Version upgrades affect one package entry in one .csproj file. License key rotation is one environment variable. The deployment artifact inventory shrinks to application binaries, no JSON template files, no character model directories.

Zero Configuration File Dependencies. The Dynamsoft template system requires JSON files to be present and correctly pathed at runtime. IronOCR has no runtime file dependencies beyond the NuGet package itself. Docker images become deterministic: what passes CI is exactly what runs in production. The Docker deployment guide shows a complete Dockerfile requiring only one apt-get install line for Linux.

Structured Output Reduces Integration Code. Dynamsoft returns raw LineResult text strings. Structured data extraction — field positions, word boundaries, confidence per token — requires post-processing your code owns and tests. IronOCR's result hierarchy exposes pages, paragraphs, lines, words, and characters as typed collections with coordinates and confidence scores at every level. Teams migrating from Dynamsoft typically remove 30-50% of their result-processing code because IronOCR delivers the structure they were building by hand. The OCR results feature page documents the complete output model.

Perpetual Licensing Removes Annual Budget Uncertainty. Dynamsoft Label Recognizer has no perpetual option. A processing cluster running for five years costs $2,995+ per year for label recognition alone, before adding barcode or document normalization products. IronOCR's $999 Lite license is a one-time purchase that covers all features indefinitely. For government, enterprise, and long-lived production deployments, removing annual renewal dependencies from a core document processing component reduces procurement complexity and eliminates the risk of a mid-project subscription lapse. The IronOCR licensing page lists all tiers and what each covers.

Cross-Platform Without Platform-Specific Configuration. IronOCR resolves the correct runtime binary for Windows x64, Linux x64, macOS x64, and macOS ARM through NuGet's runtime identifier graph. No conditional <PackageReference> blocks, no platform detection code, no native binary path setup. The same using IronOcr; code compiles and runs on Windows, Linux, AWS Lambda, and Azure App Service without modification.

Language Coverage Scales With Application Needs. Dynamsoft Label Recognizer is engineered for Latin-character MRZ formats. Non-Latin scripts — Arabic, Chinese, Japanese, Korean, Hebrew — fall outside its design scope. IronOCR supports 125+ languages installed as individual NuGet packages: dotnet add package IronOcr.Languages.Arabic adds Arabic support with no code change in the recognition pipeline. The languages index lists every available language pack. For applications processing international identity documents, multilingual shipping labels, or cross-border invoices, this breadth means one library covers every document type a growing application encounters.

Please noteDynamsoft and Tesseract are registered trademarks of their respective owners. This site is not affiliated with, endorsed by, or sponsored by Dynamsoft or Google. 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 Dynamsoft OCR SDK 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 Dynamsoft OCR SDK to IronOCR?

Replace Dynamsoft OCR 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 Dynamsoft OCR SDK 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 Dynamsoft OCR SDK 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 Dynamsoft OCR SDK to IronOCR require changes to deployment infrastructure?

IronOCR requires fewer infrastructure changes than Dynamsoft OCR SDK. 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 Dynamsoft OCR 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 Dynamsoft OCR SDK 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 Dynamsoft OCR SDK 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