PaddleOcrNet 2.1.0
dotnet add package PaddleOcrNet --version 2.1.0
NuGet\Install-Package PaddleOcrNet -Version 2.1.0
<PackageReference Include="PaddleOcrNet" Version="2.1.0" />
<PackageVersion Include="PaddleOcrNet" Version="2.1.0" />
<PackageReference Include="PaddleOcrNet" />
paket add PaddleOcrNet --version 2.1.0
#r "nuget: PaddleOcrNet, 2.1.0"
#:package PaddleOcrNet@2.1.0
#addin nuget:?package=PaddleOcrNet&version=2.1.0
#tool nuget:?package=PaddleOcrNet&version=2.1.0
<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.Autoand PaddleOcrNet identifies the script and pulls the right model on demand (Python PaddleOCR requires you to name the language up front). - Document understanding —
AnalyzeDocumentAsyncreturns 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
IChatModelor 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 / RecognitionModel — OcrModelVariant.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.TableModel — SlanetPlus (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.LayoutModel — RtDetrL (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.ReadingOrder — Auto (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 — soRecognitionModel = Serveris a no-op for, say, Korean or Arabic rather than an error. - Detection is language-independent, so
DetectionModel = Serverapplies to every language. Setting only the detector toServeris 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.ActiveExecutionProviderandOcrResult.ExecutionProvider/OcrResult.UsedGpureflect the live provider — an accelerator that failed to attach and fell back to CPU reports CPU here, never a falseUsedGpu = true.GpuAccelerationHint(also included inGetRuntimeInfo()) explains why acceleration is off and names the fix. When noILoggeris configured, a provider attach failure is additionally written once per process to stderr so it is never completely invisible; setLogGpuHint = trueto 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
paddlepaddlenative 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:
- PayPal — paypal.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 Lodi — farhanlodi31@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 | Versions 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. |
-
net10.0
- Clipper2 (>= 2.0.0)
- Docnet.Core (>= 2.6.0)
- EasyImageSharp (>= 1.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Microsoft.ML.OnnxRuntime (>= 1.27.0)
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.