Skip to main content

TitanOCR

A standalone, general-purpose OCR and document-structure engine. Give it any PDF or image — born-digital, scanned, or phone-photographed; printed, hybrid, or handwritten — and it returns faithful text and structure with pixel-level provenance: every value carries its page, bounding box, origin and confidence. Deterministic, auditable, and CPU-only on the primary path.

Scope: a product in its own right, not a component of any particular domain pipeline. Consumers take the Document IR (or its Markdown projection) and do their own downstream processing.


The split this project is built on

Sending document images to a vision LLM collapses two different problems into one opaque call:

Problem Right tool Cost profile Verifiable?
Transcription — what characters are on this page, where OCR (detection + recognition + layout + tables) ~₹0.00X/page, CPU yes — bboxes + per-line confidence
Understanding — what the text means for a given consumer the caller's own logic/LLM, on text caller's choice caller's problem — now grounded in auditable text

A vision-LLM-only approach pays frontier rates on every page, with no bounding box, no calibrated confidence, and no way to tell a correct answer from a fluent hallucination. This engine makes transcription deterministic, nearly free, and verifiable — and keeps a VLM only as a budgeted escalation for what CPU OCR cannot yet read confidently (today: handwriting). The roadmap (phase 17) is to read handwriting on CPU too, with a deterministic model, shrinking escalation to a safety net.

Design invariants

These are load-bearing. Changing one is an ADR, not a refactor.

  1. OCR always runs. Every raster page, no pre-classification gate. It is cheap enough that running it unconditionally is free insurance — and its confidence output is the triage signal. This is why the handwritten/printed split never needs to be known up front.
  2. The VLM is an escalation, never the default path. It fires on a measured signal, on the narrowest possible unit (field > region > page > document), and always with OCR output supplied as grounding context.
  3. Every emitted value is traceable to pixels. Page, bbox, source stage, confidence. If you can't point at where a value came from, it doesn't ship.
  4. No accuracy claim without the golden set. Route mix, cost/page and per-stratum accuracy are measured artifacts, not vibes.
  5. A fallback that fires often is not a fallback. Escalation rate has an explicit budget and alerts when breached.
  6. Every stage is a swappable implementation behind an interface, wired by config, pure over the IR, with no module-level state. "Modular" is enforced by the dependency graph and a shared contract test suite, not by intent. → 08
  7. Nothing blocks the event loop. CPU work runs in a process pool; the async front door stays responsive under saturation so health probes never fail mid-document. → 07
  8. Abstention over guessing. Below-threshold content escalates or is flagged — never emitted as if certain.

Layout

new/
├── README.md                  ← you are here
├── implementation_plan.md     build-level plan (titanocr + API + demo UI)
├── plans/                     numbered phase plans + master tracker (execution source of truth)
├── docs/
│   ├── 01-architecture.md     pipeline, components, service topology, stack + licences
│   ├── 02-document-ir.md      ⭐ the Document IR schema — the central contract
│   ├── 03-triage-and-escalation.md   confidence signals, routing policy, budget
│   ├── 04-handwriting-roadmap.md  the end-state: deterministic CPU handwriting (the roadmap)
│   ├── 05-eval-harness.md     golden set, metrics, release gates
│   ├── 06-deployment-and-operations.md  container, probes, sizing, operational behaviour
│   ├── 07-concurrency-and-scaling.md  execution models, thread/memory arithmetic, backpressure
│   ├── 08-modularity-and-interfaces.md  stage protocols, registry, module boundaries
│   ├── 09-success-criteria.md ⭐ what "good" means, how it's proven, kill criteria
│   └── adr/                   architecture decision records (0001-…, append-only)
├── src/titanocr/            ONE Python project (3.13) — ir/ (the contract), stages/, engines/,
│                              render_md/, service code, demo UI static/
├── pipelines/ · fixtures/ · models/ · scripts/ · tests/
├── packaging/pypi-placeholder/   the `titanocr` PyPI name reservation (phase 18)
└── eval/                    ⭐ the accuracy-proof layer (phase 16)
    ├── gates.yaml             release gates — `target: null` means "not measured yet"
    ├── golden/                golden sets: manifest + per-document truth (smoke · hard)
    ├── harness/               run · score · diff · report · thresholds
    └── scripts/               confidence-histogram batch tooling (threshold bootstrapping)

v1 is a single project (ADR 0003): the IR, stage interfaces and engines live as modules inside titanocr, with dependency direction (ir → stages → engines → service) enforced by import-linter. Extracting ir/ into a standalone package is the recorded revert path if the contract ever gains a second consumer.

Status

Area State
Design docs 01–09 drafted and maintained as law (adr/ records decisions)
Document IR signed off — ir_version 1.8 (phase 02 signed off 1.0, 2026-08-13; minor bumps ADR 0004: table blocks may carry their recognised lines, ADR 0005: table_failed degrade flag, ADR 0006: quality.ink_ratio; ADR 0007 fixes what escalation may claim; later minors add the block types figure/signature without lines ADR 0010, rule ADR 0011, connector ADR 0012 + its label anchors ADR 0016, and formula ADR 0018)
titanocr service released v0.1.0 (2026-08-13) — pipeline stages 0–5 feature-complete and containerised: digital fast path (exact, ₹0), spawn pool with thread pinning + warm-up-gated readiness, PP-OCRv5 det/rec chosen accuracy-first by fixture bake-off (scan CER 0.15%, photo 0.30%), layout + reading order, geometric table reconstruction with ML fallback (cells exact on every fixture), triage confidence-v1 + reject path + signals JSONL, VLM escalation (cropped, OCR-grounded, additive merge, caps + breaker; live-provider smoke blocked on an endpoint/key), demo UI at /demo, 976 MB linux/amd64 image starting to ready offline. Full per-phase detail: plans/
Accuracy still no accuracy claim. The phase-16 harness is built and in CI (make eval — stratified CER, cell accuracy at (row,col), detection recall, route mix, ₹/page, abstention honesty, determinism, 14 release gates, clickable HTML report), and it enforces the honesty rules rather than stating them: a model-produced label is refused, a synthetic set reports unclaimable, an unset target can never report pass. The golden set — real, human-labelled documents — does not exist yet, so the verdict is incomplete by construction and phases 17/18/21 stay gated. First run's smoke baseline and the real defect it found: note
Packaging the PyPI name titanocr is reserved (0.0.1 placeholder, no OCR code — make pypi-placeholder). Release automation is in place (Releasing to PyPI). Shipping the engine itself as pip install titanocr is phase 18, gated on the accuracy proof (ADR 0009)
Structure fidelity phases 19–20 done (2026-08-26) — the engine no longer asserts structure a page does not have (a stacked formula is not a grid; an equation number stays in its own y-band), the born-digital route types its regions with the layout stage in a pool worker (ADR 0017, ≈0.26 s/page), and mathematics is a block type excluded from structure-guessing by type (ADR 0018). Renderer 1.12
Roadmap phase 16 (accuracy proof) → phase 17 (handwritten OCR on CPU, deterministic), phase 18 (pip install titanocr) and phase 21 (formula → LaTeX), all gated on it — see plans/

Conventions

  • Non-obvious or reversible-at-cost decisions get an ADR in docs/adr/. Numbered, append-only, superseded rather than edited.
  • The Document IR is versioned. Breaking changes bump the major and require a migration note.
  • Anything measured (thresholds, costs, route mix) cites the run that produced it. No folk numbers.

Releasing to PyPI

Releases are published by .github/workflows/release.yml when a v* tag is pushed. It uses PyPI trusted publishing (OIDC), so no API token is stored in the repository or its secrets.

Gated: publishing the engine is phase 18, and phase 18 waits on phase 16's accuracy proof (hard rule 14). The pipeline is ready; pushing a real version tag is the release decision.

One-time setup

  1. On pypi.org: titanocr → Settings → Publishing → Add a new publisher → GitHub with owner Phantom-IN, repository titanocr, workflow release.yml, environment pypi.
  2. Optional, for dry runs: the same on test.pypi.org with environment testpypi (the name must be registered there too).
  3. On GitHub: Settings → Environments → create pypi and testpypi. Adding yourself as a required reviewer on pypi makes every release wait for a manual approval before upload.

Cutting a release

uv version 0.2.0                      # bump the version in pyproject.toml
git commit -am "release 0.2.0"
git tag v0.2.0
git push origin main v0.2.0           # the tag push starts the release

The workflow then runs, in order — and nothing is uploaded unless every step passes:

  1. the full CI gate (ci.yml: lint, typecheck, tests, real-engine contract suites, eval, container);
  2. a check that the tag matches the pyproject.toml version;
  3. uv build (sdist + wheel) and twine check --strict;
  4. a clean-venv install and import of the wheel on Linux and macOS;
  5. upload to PyPI (after approval, if the pypi environment requires it).

Dry run: Actions → release → Run workflow runs the same steps and publishes to TestPyPI only, never PyPI.

Rules

  • PyPI never accepts the same version twice — even after a deletion. A failed or wrong release is fixed by publishing the next version, not by re-tagging.
  • Keep versions above the published ones (the placeholder is 0.0.1; the engine is at 0.1.0).
  • Models are never in the distribution; they are fetched and checksummed (make models).

Read order

New to the project: this file → 01-architecture → 02-document-ir. Those three are the whole idea.

Before writing service code, also read 07 and 08 — they constrain how the code is structured, and retrofitting either is expensive.

Reviewing the project: this file → 09-success-criteria. That doc carries the targets, how each claim gets proven, and the conditions under which we stop.

Metadata

Release files for titanocr 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for titanocr 0.1.2
File Size Uploaded
titanocr-0.1.2.tar.gz 538.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for titanocr 0.1.2
File Interpreter ABI Platform
titanocr-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 1.1 MB

Release files / titanocr-0.1.2.tar.gz

Download URL titanocr-0.1.2.tar.gz
Size 538.2 kB
Tags Source
SHA-256 checksum
How to use checksums
2e53dde01d5a9c58ea01cac55655ae2f26d18aa786eeddfc6787bd5a153fcc14
BLAKE2b-256 checksum
How to use checksums
feb066a7cc7273186bfce1fdd9e6033604210e5ae30e1b394f48abd18d964d79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / titanocr-0.1.2-py3-none-any.whl

Download URL titanocr-0.1.2-py3-none-any.whl
Size 561.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b12cddc81f81782748b8f1ffa397a65da1dffe43940b80da45b2dd903087b97f
BLAKE2b-256 checksum
How to use checksums
915bd5b21281fae49c2c89728fa2bd63c507b26fed92dde993313f2d0eb71a9f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.3

2 release files

This release

0.1.2 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page