New
Apps 1,895Makers 1,890This week 1,685
Promote your app−30%
Sceptre screenshot 1
0views in
the last 30 days

Sceptre

Fast OCR library in Rust: CRAFT + CRNN over ONNX

Built with Claude Code

by xberg-io · Unclaimed · from DEGermany · added Oct 29, 2026
no ratings0 comments

Xberg

sceptre

EasyOCR's accuracy in a single Rust binary — CRAFT text detection and gen2 CRNN recognition over ONNX, with no Python runtime.

What and Why?

EasyOCR is accurate, but it is a PyTorch stack: a Python interpreter, a multi-gigabyte runtime, and a heavy process to keep warm. sceptre is a from-scratch Rust reimplementation of the same pipeline — CRAFT text detection then gen2 CRNN recognition with CTC decoding, over ONNX — that keeps the accuracy and drops all of that.

It agrees with EasyOCR's output across the scripts it supports, runs faster — even a cold, one-shot run beats EasyOCR's warm reader (measured figures) — and ships as one self-contained executable with no Python runtime. Models download once, cache locally, and run offline thereafter. Use it as a Rust library, a CLI, or an MCP server.

Every model call goes through one backend seam, so the deployment target is a build-time choice rather than a rewrite: ONNX Runtime for native speed, pure-Rust ONNX for WASM and Android, and candle for GPU devices or a build that links no ONNX Runtime at all.

Features

Feature Description
Parity accuracy Validated against real EasyOCR output across the gen2 scripts — English, Latin, Simplified Chinese, Japanese, Korean, Cyrillic, Telugu, Kannada — on text (word/char-F1) and boxes (IoU), held to a per-image floor rather than character-for-character equality
Faster, warm or cold Higher throughput than EasyOCR warm on the same corpus; even a cold, one-shot CLI run beats EasyOCR's already-loaded reader (measured figures)
No Python, no torch No interpreter, no multi-gigabyte runtime, no process to keep warm; peak memory is dominated by the CRAFT detector both engines share, so it scales with page size — see benchmarks
One binary, no Python A single self-contained executable. Models download once, cache locally, and run offline thereafter — nothing to pip install, no interpreter to ship
Three surfaces The same engine as a Rust library, a CLI (sceptre), and an MCP server for agents
Three backends, one seam ONNX Runtime (ort) for native speed, pure-Rust ONNX (tract) for WASM / Android, and candle for a GPU or a build with no ONNX Runtime at all
Pure-Rust image decoding PNG, JPEG, BMP, GIF, TIFF, WebP, and NetPBM

⭐ Star this repo to show your support — it helps others discover sceptre.

Quick Start

Install

cargo install sceptre-cli --locked

--locked uses the dependency versions tested for that release.

The locked core, ONNX Runtime, and Tract builds support Rust 1.92 or newer. On AArch64 targets with fp16, including Apple Silicon macOS, the optional candle backend requires Rust 1.94 or newer; see Rust version requirements.

Prebuilt release archives for Linux x86_64/ARM64, macOS ARM64, and Windows x86_64 include sceptre mcp, bundled ONNX Runtime, and model downloads. Extract the archive and put sceptre (sceptre.exe on Windows) on PATH.

That default build is self-contained: it links a prebuilt ONNX Runtime fetched at build time (ort-bundled) and enables model download (download), so the binary needs no ORT_DYLIB_PATH and no separately installed libonnxruntime.

The models — CRAFT plus the gen2 recognizers — are fetched from Hugging Face on first use, cached under the standard HF cache, and sha256-verified as they download. Every run after that reads the cached models with no network.

Targets with no prebuilt ONNX Runtime. ort publishes prebuilts for a fixed target list. x86_64-apple-darwin (Intel macOS), every *-unknown-linux-musl (Alpine), armv7-unknown-linux-gnueabihf, riscv64gc-*, *-unknown-freebsd, i686-*, s390x, and powerpc64le have none, so the default install fails at build time with ort-sys's own no prebuilt binaries available for target .... Two remedies:

cargo install sceptre-cli --locked --features ort-dynamic                          # bring your own libonnxruntime
cargo install sceptre-cli --locked --no-default-features --features tract,download  # pure Rust, then --backend tract

--features ort-dynamic is additive, but it still wins over the bundled default: load-dynamic implies ort-sys/disable-linking, which the ort-sys build script checks before its download branch. So switching ONNX Runtime provisioning never needs --no-default-features — only the pure-Rust path does, because it has to drop ort from the graph entirely.

CLI

One image or many in a single warm process:

sceptre run receipt.png --lang english --format json
sceptre run page1.png page2.png page3.png            # batch: models load once
sceptre run sign.jpg --lang english --lang korean    # multi-language
sceptre run page.png --lang english --timings        # per-stage load/detect/recognize breakdown
sceptre run huge-scan.png --canvas-size 1600         # trade some accuracy for lower peak memory

Decodes PNG, JPEG, BMP, GIF, TIFF, WebP, and NetPBM (all pure-Rust; HEIF/AVIF/JPEG-2000 are out of scope — see adrs/0022).

Library

use sceptre::{Reader, ReadOptions};

let reader = Reader::builder().build()?;
for line in reader.readtext("receipt.png".as_ref(), &ReadOptions::default())?.lines {
    println!("{} ({:.2})", line.text, line.confidence);
}
# Ok::<(), sceptre::OcrError>(())

The library ships default = [], so enable a backend and model download: sceptre = { version = "0.8", features = ["ort-bundled", "download"] } (see Feature flags).

WASM and mobile hosts can avoid filesystem and network assumptions by calling model_descriptors, fetching the selected artifacts, constructing a VerifiedModelProvider, and passing it to Reader::builder().model_provider(...).build_warmed().

Android and iOS hosts that package ONNX assets as real files may instead set both model.detector_path and model.recognizer_path; the default provider then bypasses Hugging Face.

MCP server

Expose a readtext tool to an agent. Release archives include the mcp subcommand. When installing from Cargo, enable the mcp feature explicitly:

cargo install sceptre-cli --locked --features mcp
sceptre mcp --lang english
sceptre mcp --lang english --detect-orientation   # whole-page orientation pre-pass for rotated scans
Tool Parameter Type Description
readtext image_path string (required) Filesystem path to the image to run OCR over.
readtext detail bool (optional, default true) When false, only line text is returned (no box/confidence).

--lang, --backend, --threads, and --detect-orientation (plus its --orientation-probe-canvas-size / --orientation-margin knobs) configure the Reader the server is started with — they are not per-call readtext parameters, so every call made to one sceptre mcp process shares them.

Benchmarks

Measured against upstream EasyOCR over a mixed corpus — documents, tables, rotated scans, scene text and receipts, across the English, Latin, Chinese-simplified, Japanese and Korean recognizer groups — on CPU. Both engines are measured identically: each a fresh subprocess per language group under /usr/bin/time, at its native multi-threaded default, loading its model/reader once and processing every image — so peak RSS is a like-for-like whole-process figure (EasyOCR's legitimately includes the torch runtime). Numbers vary with hardware and load, and peak RSS is dominated by the CRAFT detector both engines share, so its ratio narrows toward parity as page size grows — see the benchmarks page for the full methodology.

The table below is generated from benchmarks/published/latest.json by mise run python:publish; CI fails if the two drift apart. Do not edit it by hand.

Engine Throughput (img/s) Peak RSS Mean CER Mean token-F1
EasyOCR (warm/batch) 0.12 4.8 GB 0.569 0.337
sceptre (warm/batch) 0.23 (~1.9×) 4.5 GB (~1.4× lower, per-image median) 0.569 0.358
sceptre (cold CLI run) 0.21 (~1.8×) 4.5 GB 0.569 0.358

Measured with sceptre 0.6.0 (ort/cpu, ONNX Runtime 1.28.0) against EasyOCR 1.7.2 (torch 2.13.0+cu130) on runner-medium (Linux/x86_64), over 40 of 43 corpus entries. Regenerate with mise run python:benchmark then mise run python:publish.

The accuracy columns are the parity check — sceptre and EasyOCR land on the same reading of the corpus; the speedup is the win. Even a cold, one-shot CLI run, which pays model load on every invocation, still beats EasyOCR's already-warm reader. Peak memory is dominated by the CRAFT detector both engines share, so it tracks page size rather than the runtime; sceptre's memory advantage is having no Python/torch process at all, which shows up on smaller pages and in process count rather than as a fixed ratio.

Runtime scope. Every figure above — speed, memory, and the parity claim — was produced on the ort backend running the CPU execution provider, which is what model.accelerator defaults to. The same ONNX graph produces different numeric output on a different provider, so an accelerated run is a different measurement, not a faster one. Select one with --accelerator or model.accelerator; which values a backend accepts differs, because ort reaches hardware through ONNX Runtime execution providers while candle addresses devices directly:

Backend Accelerators Cargo feature
ort coreml, directml, cuda ort-coreml, ort-directml, ort-cuda
tract — (CPU only) —
candle metal, cuda candle-metal, candle-cuda

For ort the linked ONNX Runtime build must also carry the provider. An explicitly requested accelerator that cannot be used is a hard error, not a silent fall back to CPU — only auto is allowed to settle for whatever it finds. The parity figures above cover only the ort backend's CPU execution provider, validated against the golden fixtures under crates/sceptre/tests/data/golden/: every accelerator is unvalidated in CI, which has no GPU runner, so none of the numbers above should be assumed to transfer. candle is a compatibility and deployment option, not a fast one — on a GPU it is still several times slower than ort on the CPU (see ADR 0031). Re-run the parity harness on your own target before relying on them.

cargo bench covers the internal hot paths; the head-to-head harness (mise run python:benchmark) re-measures and writes the gitignored benchmark-results/comparison.{json,md}. Use --group labeled --limit 3 --repeats 1 for a fast inner-loop run, --baseline <prior.json> to see per-image deltas, and --assert for the regression gate (see ADR 0021). mise run python:publish then distils that report into the committed artifact and regenerates the table above; mise run python:publish:check is the drift gate CI runs (see ADR 0030). Parity fixtures live under crates/sceptre/tests/data/golden/.

How it works

Three stages behind one Reader, mirroring EasyOCR's latest pipeline:

  1. Detect — CRAFT produces region/link heat-maps; thresholding, connected components, and min-area boxes become text lines (horizontal and rotated).
  2. Recognize — each line is cropped, normalized, and run through a gen2 CRNN; CTC greedy decoding turns the logits into text and a confidence.
  3. Inference — every model call goes through one backend seam: ort (native ONNX Runtime), tract (pure Rust), or candle (hand-written networks over the same ONNX weights, able to run on Metal or CUDA). config, types, and geometry stay backend-agnostic.

Design decisions live as MADR records under adrs/; conventions live in .ai-rulez/.

Scope

Targets EasyOCR's current models — the CRAFT detector and all eight gen2 (*_g2) recognizers (English, Latin, Simplified Chinese, Japanese, Korean, Cyrillic, Telugu, Kannada). Legacy gen1 models, DBNet, and beam-search decoding are out of scope (see adrs/0002 and adrs/0026). ONNX artifacts are first-party exports, built from EasyOCR's weights and hosted on the xberg-io Hugging Face org (Apache-2.0; see adrs/0025).

Feature flags

Feature Enables Library CLI
ort Native ONNX Runtime backend (desktop / server), provisioning-agnostic ✓ —
ort-bundled ort with a prebuilt runtime fetched at build time (zero-config) ✓ default
ort-dynamic ort loading libonnxruntime at runtime via ORT_DYLIB_PATH (no build-time download) ✓ ✓
tract Pure-Rust ONNX backend (WASM / Android), selected at runtime with --backend tract ✓ ✓
download Runtime model download + cache from Hugging Face ✓ default
mcp MCP (rmcp) server surface ✓ ✓
ort-coreml / ort-directml / ort-cuda Compile in the matching ONNX Runtime execution provider for model.accelerator ✓ ✓
candle Native-tensor backend needing no ONNX Runtime, selected at runtime with --backend candle (see ADR 0031) ✓ ✓
candle-metal / candle-cuda Compile in the matching GPU device for model.accelerator on the candle backend ✓ ✓
bench Exposes the crate's internal hot paths through a bench seam for criterion benchmarking (see ADR 0015) ✓ —

The library ships default = []. The CLI ships default = ["ort-bundled", "download"], so cargo install sceptre-cli --locked produces a self-contained binary; --features ort-dynamic overrides the provisioning strategy without --no-default-features (see Install).

Configuration (OcrConfig) is backend-agnostic. The CLI builds it from OcrConfig::default() plus flags — there is no config-file loader and no SCEPTRE_* environment variable. The CLI does read one env var, EASYOCR_LOG (sets --log-level), a name kept from the project's EasyOCR lineage. cache_dir, registry_owner, detector_path, and recognizer_path are library-only fields with no CLI flag.

Coding-agent plugin and public skills

The Sceptre plugin provides local image OCR instructions and MCP tools for Claude Code, Codex, Cursor, Gemini CLI, Kimi, OpenCode, Factory Droid, Hermes, Agent Plugins and GitHub Copilot CLI. Pi loads the packaged skills and prompts and uses the CLI directly.

Install an MCP-enabled binary before using the server or Hermes bridge:

cargo install sceptre-cli --locked --features mcp

Models download on first use. Set the image language through the MCP configuration; the default is English. Install the Claude marketplace plugin with:

/plugin marketplace add xberg-io/sceptre
/plugin install sceptre@sceptre

For other runtimes, follow the plugin installation guide. Install public skills independently with the skills CLI:

npx skills add xberg-io/sceptre --list
npx skills add xberg-io/sceptre

Select skills and target agents interactively. This installs instructions; the binary and model cache remain separate requirements.

Development

mise run setup  # fetch deps, install git hooks
mise run check  # cargo fmt + clippy -D warnings + test + poly lint
mise run bench  # criterion microbenchmarks (--features bench)

Run mise run python:typecheck to check all Python tooling with Pyrefly. This synchronizes the locked optional export and corpus dependencies, including PyTorch and EasyOCR.

Coding conventions are generated from .ai-rulez/ into CLAUDE.md / AGENTS.md — edit the source, then run ai-rulez generate.

Documentation

Full guides, the configuration reference, backend and accelerator selection, and the benchmark methodology live at docs.sceptre.xberg.io. The Rust API reference is on docs.rs.

Part of Xberg.io

  • Xberg — the open-source content-intelligence engine: text, tables, and metadata from 101 formats (115 file extensions), with OCR, transcription, and code intelligence. MIT.
  • Xberg Pro — a complete self-hosted content-intelligence backend in a single container. Commercial.
  • Xberg Enterprise — the distributed, governed content-intelligence platform, scaled on Kubernetes with team governance and support. Commercial.
  • crawlberg — web crawling and scraping with HTML→Markdown and headless-Chrome fallback.
  • html-to-markdown — fast, lossless HTML→Markdown engine.
  • liter-llm — universal LLM API client with native bindings for 14 languages and 165 providers.
  • tree-sitter-language-pack — tree-sitter grammars and code-intelligence primitives.
  • alef — the polyglot binding generator that produces ev

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#ocr · #rust · #onnx