PaddleOcrNet 2.1.0

dotnet add package PaddleOcrNet --version 2.1.0
                    
NuGet\Install-Package PaddleOcrNet -Version 2.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PaddleOcrNet" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PaddleOcrNet" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="PaddleOcrNet" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PaddleOcrNet --version 2.1.0
                    
#r "nuget: PaddleOcrNet, 2.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PaddleOcrNet@2.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PaddleOcrNet&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=PaddleOcrNet&version=2.1.0
                    
Install as a Cake Tool

<p align="center"> <img src="icon.png" alt="PaddleOcrNet" width="140" height="140" /> </p>

<h1 align="center">PaddleOcrNet</h1>

<p align="center"> <strong>The complete PaddleOCR document pipeline — natively in .NET, on ONNX Runtime.</strong><br/> Turn scans, photos, and PDFs into text, tables, formulas — and answers.<br/> <em>No Python. No native PaddlePaddle. No sidecar server. Just a NuGet package.</em> </p>

<p align="center"> <a href="https://www.nuget.org/packages/PaddleOcrNet"><img src="https://img.shields.io/nuget/v/PaddleOcrNet.svg?label=NuGet&color=004880" alt="NuGet"/></a> <a href="https://www.nuget.org/packages/PaddleOcrNet"><img src="https://img.shields.io/nuget/dt/PaddleOcrNet.svg?label=Downloads&color=004880" alt="Downloads"/></a> <img src="https://img.shields.io/badge/models-PP--OCRv5%20%2B%20PP--StructureV3-ff6f00" alt="PP-OCRv5 + PP-StructureV3"/> <img src="https://img.shields.io/badge/languages-100%2B-1f6feb" alt="100+ languages"/> <img src="https://img.shields.io/badge/.NET-10.0-512BD4" alt=".NET 10"/> <img src="https://img.shields.io/badge/AOT-ready-2ea44f" alt="AOT ready"/> <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"/></a> </p>


PaddleOcrNet turns scanned documents, photos, and PDFs into structured text — and into answers. It runs the full PP-OCRv5 + PP-StructureV3 pipeline — text detection, recognition, orientation correction, layout analysis, table extraction, and formula recognition — entirely in managed .NET on ONNX Runtime, then layers on LLM-backed key-information extraction and document Q&A through any provider you choose. Models download and cache on first use; everything after that runs in-process, offline-capable, and trim/AOT-friendly.

Highlights

  • High-accuracy text OCR — DB detection + SVTR recognition (PP-OCRv5) handles dense invoices, forms, receipts, handwriting, rotated scans, and curved text.
  • 100+ languages across 12 script families, with one shared detector and per-script recognizer packs.
  • Automatic language detection — pass OcrLanguage.Auto and PaddleOcrNet identifies the script and pulls the right model on demand (Python PaddleOCR requires you to name the language up front).
  • Document understandingAnalyzeDocumentAsync returns layout regions, reading order, tables as HTML, and formulas as LaTeX, and serializes the whole document to Markdown, HTML, JSON, Word, or Excel.
  • Ask your documents — LLM-backed key-information extraction, Q&A, and chart-to-data parsing, provider-agnostic: bring your own IChatModel or use the built-in OpenAI-compatible adapter (OpenAI, Azure, Ollama, vLLM, Groq, …). Charts are reconstructed to Markdown tables via a vision model — no local GPU. Or extract labeled fields offline with the heuristic, layout-based KIE extractor — no LLM, no network.
  • PDF in, searchable PDF out — rasterize and OCR PDFs, or emit a searchable PDF with an invisible text layer.
  • Robust by design — singleton-safe, thread-safe ONNX sessions; DI + health checks; OpenTelemetry metrics; typed exceptions; input/decompression-bomb guards; checksum-verified model downloads.
  • Deploys anywhere — pure-managed (no OpenCV), CPU by default, optional CUDA, Native AOT and single-file publish supported. Mobile models are a few MB each.

Installation

dotnet add package PaddleOcrNet

# Optional — NVIDIA CUDA acceleration (used automatically when present):
dotnet add package PaddleOcrNet.Gpu

Requires .NET 10 (net10.0). Windows, Linux, and macOS (x64/arm64).

PaddleOcrNet.Gpu targets CUDA 13.x (ONNX Runtime 1.27, cuDNN 9). To run it on a CUDA 12 machine, pin ONNX Runtime 1.26 in your own project — see the GPU package README.


Quick start

using PaddleOcrNet.Models;
using PaddleOcrNet.Services;

// ONNX models download + cache on first use; construction itself loads nothing.
await using var ocr = new PaddleOcrService();

OcrResult result = await ocr.ExtractTextFromImage("invoice.png", OcrLanguage.English);

Console.WriteLine(result.FullText);
foreach (var line in result.Lines)
    Console.WriteLine($"[{line.Confidence:F2}] {line.Text}");

Input can be a file path, byte[], Stream, or an already-decoded Image<Rgb24>:

await ocr.ExtractTextFromImage(bytes,  OcrLanguage.English);
await ocr.ExtractTextFromImage(stream, new[] { OcrLanguage.English, OcrLanguage.German });

// Detect-only (bounding boxes for redaction / cropping — no recognition):
var regions = await ocr.DetectRegionsAsync("page.png");

// Recognize caller-supplied regions (skip detection):
var partial = await ocr.RecognizeRegionsAsync(image, regions, new[] { OcrLanguage.English });

Automatic language detection

// OcrLanguage.Auto → PaddleOcrNet detects the dominant script, downloads the matching pack, and reports it.
OcrResult r = await ocr.ExtractTextFromImage("multilingual.png", OcrLanguage.Auto);

Console.WriteLine(string.Join(", ", r.DetectedLanguages)); // e.g. "arabic, latin, ch"

Document structure analysis

AnalyzeDocumentAsync runs the PP-StructureV3 pipeline — orientation → layout detection → formula recognition → whole-page OCR matched into the layout blocks → table recognition → reading-order reconstruction — and returns a structured document you can export straight to Markdown or JSON.

using PaddleOcrNet.Models;
using PaddleOcrNet.Services;
using PaddleOcrNet.Structure;

await using var ocr = new PaddleOcrService();

StructureResult doc = await ocr.AnalyzeDocumentAsync("report.png", new StructureOptions
{
    Languages         = new[] { OcrLanguage.English },
    UseDocOrientation = true,    // auto-rotate skewed scans (0/90/180/270°)
    RecognizeTables   = true,    // tables → HTML
    RecognizeFormulas = true,    // formulas → LaTeX
    // LayoutScoreThreshold = 0.4f,  // default 0.5 — lower to keep less-confident layout regions
});

foreach (var block in doc.Blocks)
    Console.WriteLine($"#{block.Order} {block.Type} — {block.Text}");

string markdown = doc.ToMarkdown();  // titles, paragraphs, tables (HTML), formulas ($$…$$)
string json     = doc.ToJson();      // structured blocks with bounding boxes + reading order
Stage Model Output
Layout analysis PP-DocLayoutV3 (RT-DETR) region boxes + 25 block types
Table recognition SLANet_plus (default) · SLANeXt v2 <table> HTML with cell text matched into the grid
Formula recognition LaTeX-OCR LaTeX string
Orientation / unwarp PP-LCNet · UVDoc de-skewed, de-warped page
Reading order XY-Cut++ (xycut_enhanced) multi-column document order

The engine follows Python PP-StructureV3's whole-page design: the page is OCR'd once, the lines are matched into the layout blocks (lines straddling two blocks are split and re-recognized), formulas are recognized first and masked out of the page so the text recognizer never reads half an equation, and sideways tables are detected and uprighted before structure recognition.

For tables, set StructureOptions.TableModel = TableRecognitionModel.SlaNeXt to use the PP-StructureV3 v2 path: a PP-LCNet classifier decides wired (bordered) vs wireless (borderless), then runs SLANeXt_wired for bordered tables and SLANet_plus for borderless ones — the model pairing PP-StructureV3 itself ships (downloads two extra small models on first use). The default, SlanetPlus, is a single end-to-end model. Either way, recognized tables expose per-cell rectangles via StructureBlock.CellBounds alongside the HTML.

Exports with embedded figures & native equations

The DOCX and HTML exporters take an optional image overload — pass the same image you analyzed and figure / chart / seal regions are cropped and embedded as real pixels (DOCX gets an inline word/media/ image part; HTML gets a data:image/png;base64,… <img>). The no-image overloads keep their bbox-placeholder behavior. The image-aware DOCX path also renders recovered formula LaTeX as native Word equations (OMML) via a best-effort LaTeX→OMML converter (PaddleOcrNet.Structure.Export.LatexToOmml) — fractions, sub/superscripts, roots, Greek letters, n-ary sum/integral/product, and common operators; unsupported constructs degrade gracefully to text.

using PaddleOcrNet.Structure.Export;

StructureResult doc = await ocr.AnalyzeDocumentAsync("report.png");
using var page = EasyImageSharp.Image.Load<EasyImageSharp.PixelFormats.Rgb24>("report.png");

byte[] docx = doc.ToDocx(page);          // figures/charts/seals as inline images; formulas as OMML equations
string html = doc.ToHtml(page, "Report"); // figures/charts/seals as inline <img data:image/png;base64,…>

Supported languages

A single DB detector serves every language; recognition selects a per-script recognizer pack (PP-OCRv5 mobile + the matching character dictionary). Languages are expressed with the OcrLanguage enum; each value maps to one of the representative recognizer codes below:

Pack Codes
Chinese / Japanese (default) ch zh ja japan
English (dedicated pack) en
Latin latin fr de es it pt nl pl tr vi fi ca eu gl lb rm qu
Cyrillic cyrillic bg sr mn kk ky tg mk tt ba sah
East-Slavic eslav ru uk be (+ ru_eslav uk_eslav be_eslav)
Arabic arabic ar fa ur ug ps sd bal
Devanagari devanagari hi mr ne sa
Korean korean ko
Thai · Greek · Telugu · Tamil thai/th · greek/el · telugu/te · tamil/ta
Traditional Chinese chinese_cht cht zh_tra

Routing follows Python PaddleOCR: English gets its own small en_PP-OCRv5 recognizer (not the shared ch/ja model), and Russian, Ukrainian and Belarusian are served by the East-Slavic (eslav) pack — a Cyrillic variant tuned for those languages — rather than the generic cyrillic pack (which still covers Bulgarian, Serbian, Mongolian, the Central-Asian Turkic languages, and more). Japanese shares the default recognizer's dictionary; upstream publishes no separate Japanese PP-OCRv5 model.

Or pass OcrLanguage.Auto to detect the script automatically.

Languages are enum-only — the OCR methods take OcrLanguage (there are no raw-string overloads). The single-language overload defaults to OcrLanguage.Auto, so ExtractTextFromImage("x.png") auto-detects with zero configuration:

using PaddleOcrNet.Models;

await ocr.ExtractTextFromImage("page.png");                                       // zero-config: defaults to OcrLanguage.Auto
await ocr.ExtractTextFromImage("page.png", OcrLanguage.French);                   // single language
await ocr.ExtractTextFromImage("page.png", new[] { OcrLanguage.English, OcrLanguage.German });  // multiple
await ocr.ExtractTextFromImage("page.png", OcrLanguage.Auto);                     // explicit auto-detect

// Got raw codes from config or the command line? Parse them into the enum:
OcrLanguage lang = OcrLanguageExtensions.FromCode("en");
IReadOnlyList<OcrLanguage> langs = OcrLanguageExtensions.FromCodes(new[] { "en", "de" });

ASP.NET Core / dependency injection

using PaddleOcrNet.Models;

builder.Services.AddPaddleOcrNet(o =>
{
    o.ModelCachePath = "/var/cache/ocr";
    // 180°-flip correction is on by default per call (RecognitionOptions.UseTextLineOrientation);
    // set it to false on a call to skip the classifier.
});

// Readiness probe — Healthy once models for these languages are cached:
builder.Services.AddHealthChecks()
    .AddPaddleOcrHealthCheck(languages: new[] { OcrLanguage.English, OcrLanguage.ChineseSimplified });

IPaddleOcrService is registered as a singleton — ONNX sessions are expensive to build and safe to share across threads. Call WarmUp(...) to pre-load models off the request path.


Configuration

Concern How
GPU Add PaddleOcrNet.Gpu (CUDA 13.x); it is detected and used automatically, otherwise CPU. For CUDA 12, pin ONNX Runtime 1.26 in your project. DeviceId picks the GPU on multi-GPU hosts. When OCR runs on CPU and you expected otherwise, see GPU diagnostics.
Model variant DetectionModel / RecognitionModelOcrModelVariant.Mobile (default) or Server for the larger, more accurate PP-OCRv5 networks. See Server models.
Crop padding RecognitionOptions.CropPadding — white border in pixels added around every detected line before recognition (default 0). A few pixels help when glyphs sit flush against the detected box.
Orientation handling UseTextLineOrientation / UseDocOrientation correct upside-down lines and pages (both on by default). The classifiers do misfire on upright text, so a verdict must clear TextLineOrientationThreshold (default 0.9) and is then confirmed by VerifyOrientationByRecognition (default true), which recognizes the crop both ways and keeps the more confident reading. Set the threshold to 0 and the verification to false for raw PaddleX 3.x behaviour.
Model cache %LOCALAPPDATA% / ~/.local/share by default; override via ModelCachePath or PADDLEOCRNET_CACHE.
Model host Defaults to the public Hugging Face repo; point at a private mirror via PADDLEOCRNET_MODEL_BASE_URL or ModelDownloadOptions.BaseUrlOverride.
Local models DetectionModelPath / RecognitionModelPath / RecognitionDictionaryPath load your own ONNX/dictionary files with no download at all. See Local / offline models.
Offline / air-gapped Pre-seed the cache (or a mirror) and run fully offline; downloads are SHA-256 verified.
Throughput BatchSize (applied per call), MaxDegreeOfParallelism, and reading-order / paragraph grouping via RecognitionOptions.
Input limits Built-in max-pixel / PDF page guards against decompression bombs.
Table model StructureOptions.TableModelSlanetPlus (default, single end-to-end model) or SlaNeXt (v2 path: wired/wireless classifier → SLANeXt_wired or SLANet_plus). UseTableOrientationClassification (on by default) uprights sideways tables first.
Layout model StructureOptions.LayoutModelRtDetrL (default, PP-DocLayoutV3, 25 classes, most accurate) or PicoDetS / PicoDetM (PP-DocLayout-S/M, far smaller and faster, fewer regions).
Layout threshold StructureOptions.LayoutScoreThreshold — global confidence floor, default 0.5. Per-class floors via LayoutClassThresholds; left null, the PP-StructureV3 per-class defaults apply (paragraph_title 0.3, text 0.4, formula 0.3, seal 0.45).
Layout clean-up Near-duplicate regions are collapsed (FilterOverlappingRegions) and NMS runs (LayoutNms) by default, as in PP-StructureV3; LayoutClassMergeModes / LayoutMergeMode resolve nested regions (Python's per-class defaults apply when unset), LayoutUnclipRatio grows boxes.
Reading order StructureOptions.ReadingOrderAuto (default) uses XY-Cut++ (xycut_enhanced, what Python PP-StructureV3 uses); Model trusts PP-DocLayoutV3's own predicted order; XyCut is the plain geometric cut.
Seals StructureOptions.RecognizeSeals (on by default) runs the PP-OCRv4 seal detector over detected seal regions, with curved-arc rectification for round stamps.
Markdown output ToMarkdown(MarkdownRenderOptions) — which block types render, <table border="1"> vs bare tables, and the page separator. See Output formats.

Server models

PP-OCRv5 ships each of detection and recognition in two sizes. PaddleOcrNet defaults to the mobile networks — a few MB each, fast on CPU. The server networks are roughly 3–5× larger and more accurate, and each side is selected independently:

await using var ocr = new PaddleOcrService(new PaddleOcrServiceOptions
{
    DetectionModel   = OcrModelVariant.Server,   // PP-OCRv5_server_det
    RecognitionModel = OcrModelVariant.Server,   // PP-OCRv5_server_rec
});

They download and cache on first use exactly like the mobile ones, with the same SHA-256 verification, so nothing else in your code changes.

Two things to know about the server recognizer:

  • It covers Chinese, English and Japanese only (it is built on ppocrv5_dict.txt). Every other language pack stays on its own mobile recognizer whatever this is set to — so RecognitionModel = Server is a no-op for, say, Korean or Arabic rather than an error.
  • Detection is language-independent, so DetectionModel = Server applies to every language. Setting only the detector to Server is a reasonable middle ground: it is the side that decides whether faint or small text is found at all.

Crop padding

Recognition runs on the rectified crop of each detected box. When the detector's box hugs the glyphs, the recognizer can clip the first or last character, or misread ascenders and descenders. CropPadding adds a white border around every crop before it is recognized:

var result = await ocr.ExtractTextFromImage("receipt.png", OcrLanguage.English, new RecognitionOptions
{
    CropPadding = 10,   // pixels of white on every side; default 0
});

10–20 px is a sensible range. The padding is applied before the text-line orientation classifier, so classification and recognition see identical pixels. It does not move the reported BoundingPolygon — coordinates still refer to the original image. If whole words rather than edge characters are being lost, grow the detected boxes themselves with DetectionOptions.UnclipRatio instead.

GPU diagnostics

A GPU that silently isn't used is worse than an error, so PaddleOcrNet reports the provider it is actually running on, not the one that was requested:

await using var ocr = new PaddleOcrService(new PaddleOcrServiceOptions { UseGpu = true });

Console.WriteLine(ocr.GetRuntimeInfo());
// PaddleOcrNet runtime:
//   ONNX Runtime:        1.27.0
//   Available providers: TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider
//   Requested provider:  Cuda
//   Resolved provider:   Cuda
//   Active provider:     Cpu
//   GPU probe:           NVIDIA GPU detected
//   Hint:                ... names the exact problem and fix ...
  • PaddleOcrService.ActiveExecutionProvider and OcrResult.ExecutionProvider / OcrResult.UsedGpu reflect the live provider — an accelerator that failed to attach and fell back to CPU reports CPU here, never a false UsedGpu = true.
  • GpuAccelerationHint (also included in GetRuntimeInfo()) explains why acceleration is off and names the fix. When no ILogger is configured, a provider attach failure is additionally written once per process to stderr so it is never completely invisible; set LogGpuHint = true to log the hint as a startup warning.

The most common CUDA problem: ONNX Runtime 1.27+ (and therefore PaddleOcrNet.Gpu) is built against CUDA 13, so on a CUDA 12 machine the provider fails to attach with a missing-cublasLt64_13.dll style error. Either install the CUDA 13 runtime alongside 12 (the majors coexist), pin ONNX Runtime 1.26 in your own project, or use DirectML on Windows — details in the GPU package README.

Local / offline models

To pin exact model files and guarantee no download is ever attempted, point the service at ONNX files on disk. Local paths bypass the registry entirely — nothing is downloaded and no checksum is applied (the file is trusted as-is):

await using var ocr = new PaddleOcrService(new PaddleOcrServiceOptions
{
    DetectionModelPath        = "/models/PP-OCRv5_server_det.onnx",
    RecognitionModelPath      = "/models/PP-OCRv5_server_rec.onnx",
    RecognitionDictionaryPath = "/models/ppocrv5_dict.txt",   // pair with a custom-trained model
    Download = { Offline = true },   // any *other* model that would need a download fails fast instead
});

RecognitionModelPath replaces the default (ch/en/ja) recognizer; per-script language packs are unaffected — for a fully offline multilingual setup, pre-seed the model cache (or point PADDLEOCRNET_MODEL_BASE_URL at an internal mirror) and set Download.Offline = true.

Output formats

OcrResult exports to plain text, JSON, hOCR, ALTO XML, and TSV; documents export to Markdown, HTML, JSON, Word (.docx), and Excel (.xlsx) (with native tables / merged cells). The ToDocx(image) / ToHtml(image) overloads embed figure/chart/seal regions as real pixels, and DOCX formulas render as native Word equations (OMML). Multi-page Markdown can be stitched with ConcatenateMarkdownPages; PDFs can be re-emitted as searchable PDFs. All exporters are AOT-safe via a source-generated JSON context.

Markdown rendering reproduces Python PP-StructureV3's converter and is tunable via MarkdownRenderOptions: by default page furniture (headers, footers, page numbers, footnotes, margin notes) is omitted, numbered titles map to heading levels, tables emit as <table border="1">, and ConcatenateMarkdownPages joins pages by paragraph continuation — a page ending mid-paragraph flows straight into the next — instead of inserting a --- rule. Pass new MarkdownRenderOptions { IgnoredBlockTypes = Array.Empty<StructureBlockType>() } to render every block, PrettyTables = false for bare <table> fragments, or PageSeparator = StructureMarkdownExtensions.PageSeparator to restore the pre-2.1 horizontal-rule page breaks.

JSON output is written with a Unicode-permissive encoder, so recognized text in Cyrillic, Greek, Arabic, Hebrew, CJK and every other script appears verbatim instead of as \uXXXX escapes; HTML-sensitive characters (< > & ' +) are still escaped. Pass your own JsonSerializerOptions to override any of it:

using System.Text.Json;
using System.Text.Encodings.Web;

string pretty  = result.ToJson(new JsonSerializerOptions { WriteIndented = true });
string escaped = doc.ToJson(new JsonSerializerOptions { Encoder = JavaScriptEncoder.Default });  // pre-2.0.2 escaping

Document intelligence (LLM-backed KIE & Q&A)

The PaddleOcrNet.Intelligence layer adds key-information extraction and document Q&A on top of OCR/structure analysis — provider-agnostic. Plug in any LLM by implementing IChatModel, or use the built-in OpenAI-compatible adapter, which targets OpenAI, Azure OpenAI, Ollama, vLLM, LM Studio, Groq, Together, DeepSeek, Mistral, and any other OpenAI-style /chat/completions endpoint.

using PaddleOcrNet.Intelligence;

// Pick any provider — here OpenAI; swap for .AzureOpenAi(...), .Ollama(...), or .Generic(...).
var chat = new OpenAiCompatibleChatModel(OpenAiCompatibleOptions.OpenAi(apiKey, "gpt-4o-mini"));
var docs = new DocumentIntelligenceEngine(ocrService, chat);

// Key-information extraction (returns a JSON-grounded key → value result).
var info = await docs.ExtractKeyInformationAsync("invoice.png", new[] { "Invoice Number", "Vendor", "Total" });
Console.WriteLine(info["Total"]);

// Document question-answering.
var answer = await docs.AskAsync("contract.pdf", "What is the termination notice period?");
Console.WriteLine(answer.Answer);

DI: services.AddOpenAiCompatibleChatModel(...) (or AddChatModel(myModel)) + AddPaddleOcrDocumentIntelligence(). The model is grounded on the parsed document Markdown by default; set DocumentIntelligenceOptions.UseVision to also attach the page image when the model is multimodal.

Chart → data (vision-LLM, PP-Chart2Table equivalent)

ParseChartsAsync detects chart/plot regions in a document and reconstructs the data behind each one as a GitHub-flavored Markdown table — the provider-agnostic equivalent of PaddleOCR's PP-Chart2Table. It crops each detected chart region and sends only those pixels to a vision-capable model, so it works with any vision provider (OpenAI gpt-4o, Azure, or a local Ollama qwen2.5-vl / llama3.2-vision) and needs no local GPU — the provider performs the vision inference.

using PaddleOcrNet.Intelligence;

// Reconstruct the data behind every chart in a document (needs a vision-capable model).
ChartParseResult charts = await docs.ParseChartsAsync("report.png");
foreach (ParsedChart chart in charts.Charts)
{
    Console.WriteLine($"{chart.ChartType}: {chart.Title}");
    Console.WriteLine(chart.DataMarkdown);   // a Markdown table of the chart's data
}

With the built-in OpenAiCompatibleChatModel, vision is on by default (OpenAiCompatibleOptions.SupportsVision defaults to true); if the configured model isn't vision-capable and the document has charts, the call throws NotSupportedException. Customize the extraction prompt via DocumentIntelligenceOptions.ChartExtractionSystemPromptOverride. This is the vision-LLM path — there is no bundled offline chart ONNX model.

Offline (non-LLM) KIE

IOfflineKeyInformationExtractor is the offline alternative to LLM-backed ExtractKeyInformationAsync — use it when you can't or don't want to call a model. It's a heuristic, geometry-based extractor (no LLM, no network, CPU-only): for each key it finds the label in the OCR text and reads the value inline (Key: value), to the right (same row), or below. It returns the same KeyInformationResult (with Usage / Model / RawJson left null). Best-effort — it works best on clearly labeled forms and invoices.

using PaddleOcrNet.Intelligence.Offline;

var extractor = new OfflineKeyInformationExtractor(ocrService);

OcrResult ocr = await ocrService.ExtractTextFromImage("invoice.png");
KeyInformationResult result = extractor.Extract(ocr, new[] { "Invoice Number", "Total" });
Console.WriteLine(result["Total"]);

// Or OCR + extract in one call (uses OcrLanguage.Auto):
result = await extractor.ExtractAsync("invoice.png", new[] { "Invoice Number", "Total" });

DI: services.AddPaddleOcrOfflineKie(); (requires AddPaddleOcrNet()), then inject IOfflineKeyInformationExtractor.


Models & licensing

PaddleOcrNet ships no weights — on first use it downloads PP-OCRv5 / PP-StructureV3 ONNX models and their dictionaries (SHA-256 verified) to the local cache. The models are derived from PaddleOCR (Apache-2.0, © PaddlePaddle/Baidu); the formula model is RapidLaTeXOCR (MIT). See NOTICE for attribution. The library itself is MIT — see LICENSE.

Note on formula recognition: PaddleOCR's PP-FormulaNet cannot be exported to ONNX, so PaddleOcrNet uses the equivalent LaTeX-OCR model for formula → LaTeX.


Roadmap

Already shipped: detection, recognition (multilingual + auto-detect), orientation, unwarp, layout, tables, formulas, reading order, Markdown/HTML/JSON/DOCX/XLSX export (with embedded figure/chart/seal pixels and native Word equations via OMML), the PDF pipeline, LLM-backed document intelligence (key-information extraction, Q&A, and chart-to-data parsing — the PP-Chart2Table-equivalent vision-LLM path), and a heuristic, layout-based offline KIE extractor as the non-LLM alternative. Under consideration:

  • Optional RT-DETR table-cell detector path for table recognition (SLANeXt already recovers cells from its own location head, so this is an accuracy enhancement rather than a gap)
  • A model-based on-device (ONNX VI-LayoutXLM) KIE path to complement the current heuristic offline extractor
  • PP-OCRv6 model line
  • Additional per-language recognizer packs

Why PaddleOcrNet?

  • vs. Python PaddleOCR — same models and accuracy, but no Python runtime, no paddlepaddle native dependency, and no server process. Ships as a single NuGet package with first-class .NET ergonomics (DI, health checks, AOT) and adds automatic language detection.
  • vs. cloud OCR APIs — runs entirely in-process and offline; no per-page fees, no data leaving your infrastructure.
  • vs. EasyOCR-based libraries — PP-OCRv5 is materially stronger on dense documents, tables, rotated scans, handwriting, and CJK, and adds full document-structure understanding.

Contributing

Contributions are welcome! Accuracy improvements, performance tuning, bug fixes, additional language/model coverage, documentation, and tests are all appreciated.

  • Found a bug? Open an issue with a minimal repro (image/PDF + the code and options you used).
  • Have an idea or feature request? Open an issue to discuss it first, then send a PR.
  • Sending a PR? Read CONTRIBUTING.md — it covers the build, the two test suites (CI only gates the unit ones, so accuracy regressions need a local integration run), the pinned model checksums, and the hand-synced structure engine.

Participation is governed by the Code of Conduct. For security problems, please follow SECURITY.md rather than opening a public issue.


Support

If PaddleOcrNet saves you time, consider supporting development:

  • PayPalpaypal.me/FarhanLodi
  • UPI (India)farhanlodi5@oksbi
  • Bank transfer (USD) — details below

<details> <summary><b>USD bank transfer details (Wise)</b></summary>

<br>

USD account details for Farhan Lodi on Wise. Sending from a bank in the US? Use these details for a domestic transfer. Sending from anywhere else? Make an international SWIFT transfer.

Field Value
Name Farhan Lodi
Account type Deposit
Routing number (wire and ACH) 084009519
Account number 420927686563885
SWIFT/BIC TRWIUS35XXX
Bank address Wise US Inc, 108 W 13th St, Wilmington, DE, 19801, United States

Use the routing and account numbers when sending from the US, and the SWIFT/BIC when sending from outside the US.

</details>

Need more details, a different payment method, or have a question? Email farhanlodi31@gmail.com. See Donation.md for the full list.


Contact

For work inquiries, collaboration, feature requests, or any questions, reach out to:

Farhan Lodifarhanlodi31@gmail.com


License

MIT © PaddleOcrNet contributors. Downloaded models are Apache-2.0 / MIT and attributed to their authors (see NOTICE).

Every runtime dependency is permissively licensed, with no revenue threshold, commercial tier or licence key anywhere in the stack: imaging runs on EasyImageSharp (MIT), inference on ONNX Runtime (MIT), PDF rasterization on Docnet.Core (MIT) and polygon offsetting on Clipper2 (BSL-1.0).

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PaddleOcrNet:

Package Downloads
PaddleOcrNet.Gpu

CUDA GPU execution provider for PaddleOcrNet. Adds Microsoft.ML.OnnxRuntime.Gpu so detection, classification and recognition run on NVIDIA GPUs.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0 41 9/4/2026
2.0.4 69 9/1/2026
2.0.3 69 9/1/2026
2.0.2 99 8/28/2026
2.0.1 105 8/27/2026
2.0.0 100 8/26/2026
1.0.0 382 6/21/2026