ocracy
ocracy — one facade over many OCR engines, plus a ledger to choose between them.
OCR (“read the text in this image”) is solved a dozen different ways: local engines (Tesseract, EasyOCR, PaddleOCR), cloud APIs (Google Vision, AWS Textract, Azure), and VLM-based readers — each with its own install, API, pricing, language coverage, and quirks. ocracy gives you:
A uniform facade. Call
ocr()and get the sameOcrResultback no matter which backend ran:import ocracy result = ocracy.ocr("scan.png") # default (first installed) backend print(result) # -> the recognized text result = ocracy.ocr("scan.png", backend="easyocr", languages=["en", "fr"])
Convenience:
read_text()returns just the string.A ledger / gallery of every engine we researched — not only the ones with a working facade — so you can choose with eyes open:
ocracy.catalog # the whole ledger ocracy.find(is_local=True, open_source=True) # filter it ocracy.find(handwriting="yes", is_remote=True) ocracy.catalog.to_dataframe() # browse as a table
The ledger lives in data (
ocracy/data/backends.json), not code.Tools to build new facades. The catalog is large; ocracy ships a facade for a curated subset and gives you the machinery (and a SKILL) to add any other one in minutes:
from ocracy.make_backend import scaffold_backend, validate_adapter scaffold_backend("mathpix") # generate a backend package from the ledger validate_adapter("tesseract") # smoke-test an adapter end to end
Three tiers of access, from simplest to most powerful:
ocracy.ocr(img) # facade, default backend
ocracy.services.tesseract.read(img, lang="fra") # pick a backend
ocracy.services.tesseract.adapter # raw engine adapter
- class ocracy.BBox(x0: float, y0: float, x1: float, y1: float, polygon: Tuple[Tuple[float, float], ...] | None = None)[source]
An axis-aligned bounding box in pixel coordinates (origin = top-left).
polygonoptionally carries the original (possibly rotated) vertices as a sequence of(x, y)points; the axis-alignedx0/y0/x1/y1are always populated (derived from the polygon if a backend only gives one).- property as_tuple: Tuple[float, float, float, float]
(x0, y0, x1, y1)— the convention PIL’scropexpects.
- classmethod from_polygon(points: Sequence) BBox[source]
Build a box from polygon vertices
[(x, y), ...].The axis-aligned extent is computed from the vertices and the original polygon is preserved for callers that care about rotation.
- property xywh: Tuple[float, float, float, float]
(x, y, width, height)— the convention many drawing libs expect.
- class ocracy.BackendInfo(record: dict, *, implemented: bool = False)[source]
One backend’s ledger entry — a read-only, attribute-and-dict accessible record.
Wraps the raw record dict so that new fields added to the JSON are available immediately (via attribute or key access) without code changes, while a few commonly used fields get typed properties for convenience and discoverability.
- class ocracy.BaseOcrAdapter(config: dict)[source]
Optional base class for backend adapters.
Stores the config, builds a kwarg translator from
config['param_map'], and implementsreadas: translate normalized kwargs -> native kwargs ->_read(). Subclasses implement_read()and return anOcrResult.Adapters are not required to subclass this — the registry only needs an
Adapterclass with aread(image, **kwargs)method — but doing so removes the boilerplate.
- class ocracy.Catalog(path: str | Path | None = None, *, _records: List[dict] | None = None)[source]
A filterable, dict-like collection of
BackendInfo, keyed by id.Loaded lazily from
DEFAULT_LEDGER_PATH(override via thepathargument or theOCRACY_LEDGERenvironment variable).filter()returns a newCatalogover the matching subset, so filters compose:catalog.filter(is_remote=True).filter(pricing_model="free_tier_then_paid")
- can(capability: str) Catalog[source]
Backends with a non-plain-text
capability(e.g."math","tables").Matches either the
beyond_textlist or a<capability>field set to"yes"(e.g.handwriting,tables,math_formula).
- compare(ids: Iterable[str] | None = None, *, fields: Iterable[str] = ('name', 'is_local', 'is_remote', 'open_source', 'pricing_model', 'price_note', 'accuracy_tier', 'languages_count', 'handwriting', 'math_formula', 'tables', 'best_for')) List[dict][source]
A trimmed, side-by-side view of selected backends and fields.
- filter(*, predicate: Callable[[BackendInfo], bool] | None = None, implemented: bool | None = None, **criteria: Any) Catalog[source]
Return a new
Catalogof backends matching every criterion.- Parameters:
predicate – An arbitrary
BackendInfo -> boolcallable.implemented – If set, keep only (un)implemented backends.
**criteria –
field=valueconstraints.valuemay be a set/list/tuple meaning “one of”. Free-text fields (languages_note,beyond_text,output_formats) use a case-insensitive substring match.
Example:
catalog.filter(is_local=True, open_source=True, handwriting="yes")
- class ocracy.OcrResult(text: str, blocks: List[TextBlock] = <factory>, backend: str = '', raw: Any = None, meta: dict = <factory>)[source]
The normalized result of reading an image with any backend.
textis the headline payload: the full recognized text in reading order.blockscarries the structured units (with boxes/confidences) when the backend provides them.rawis the untouched backend output.metaholds cross-cutting extras (languages, page count, a Markdown rendering, timing, …).Progressive disclosure:
result = ocracy.ocr("scan.png") print(result) # -> the text result.text # -> the same string for line in result: # -> iterate TextBlocks (lines by default) print(line.text, line.confidence) result.words # -> only word-level blocks result.mean_confidence # -> average confidence, if available result.raw # -> engine-specific structure
- filter_confidence(min_confidence: float) OcrResult[source]
Return a copy keeping only blocks at or above
min_confidence.Blocks without a confidence are dropped.
textis rebuilt from the surviving blocks (joined by newline).
- classmethod from_blocks(blocks: List[TextBlock], *, backend: str = '', raw: Any = None, text: str | None = None, joiner: str = '\n', **meta: Any) OcrResult[source]
Build a result from structured blocks.
If
textis not given it is synthesized by joining the blocks’ text in their given order withjoiner(callers should pass blocks already in reading order, or pre-join and passtextexplicitly).
- classmethod from_text(text: str, *, backend: str = '', raw: Any = None, **meta: Any) OcrResult[source]
Build a minimal result from just a text string (no geometry).
- property markdown: str | None
Markdown rendering if the backend produced one (else
None).
- property mean_confidence: float | None
Mean confidence over blocks that report one, or
None.
- class ocracy.Requirements(backend_id: str, implemented: bool, available: bool, is_local: bool, is_remote: bool, pip_command: str, extra: str | None = None, system: List[str] = <factory>, system_note: str | None = None, gpu: str | None = None, weights: str | None = None, heavy: bool = False, alternative: str | None = None, credentials: List[str] = <factory>, notes: List[str] = <factory>)[source]
What a backend needs to run — structured for an agent to act on.
- class ocracy.ServiceCollection[source]
Lazy mapping of backend ids ->
ServiceHandle.Supports dict-style (
services['tesseract']) and attribute-style (services.tesseract) access.
- class ocracy.TextBlock(text: str, bbox: BBox | None = None, confidence: float | None = None, level: str = 'line', language: str | None = None, meta: dict = <factory>)[source]
One recognized unit of text.
- text
The recognized string for this unit.
- Type:
str
- bbox
Where it was found (pixel coordinates), if the backend reports it.
- Type:
ocracy.base.BBox | None
- confidence
Recognition confidence in
[0, 1](normalized by ocracy from whatever scale the backend used), if available.- Type:
float | None
- level
Granularity — one of
LEVELS(“word”, “line”, …).- Type:
str
- language
Detected/declared language code for this unit, if any.
- Type:
str | None
- meta
Backend-specific extras (font size, style, page index, …).
- Type:
dict
- ocracy.available_backends() List[str][source]
Implemented backends whose dependency is importable right now.
- ocracy.backend_ids(level: str = 'all', *, info: Dict[str, dict] | None = None, test_image: Any = None) List[str][source]
Sorted backend ids at a readiness
level(one ofLEVELS).Pass a precomputed
info(frombackend_info()) to avoid recomputation — important for"tested", which otherwise re-runs OCR.
- ocracy.backend_info(ids: Iterable[str] | None = None, *, run_tests=False, test_image: Any = None) Dict[str, dict][source]
Per-backend status: every ledger field plus
implemented/set_up/tested.- Parameters:
ids – Which backends (default: every ledger id).
run_tests – Which set-up backends to actually OCR-test.
True= all set-up (real API calls for remotes!);False= none (testedisNone); or an iterable of ids to limit testing to those.test_image – Image to test with (default: a generated one, shared across all).
- Returns:
{id: {..., "name", "website", "implemented", "set_up", "tested"}}wheretestedisTrue/False/None(None = not attempted).
- ocracy.check(backend_id: str) bool[source]
Is
backend_idimportable / usable right now? (no install, no network).
- ocracy.doctor() dict[source]
Report which implemented backends are usable now and what the rest need.
Returns
{"available": [...], "missing": {id: one-line install hint}}.
- ocracy.find(**criteria) Catalog[source]
Filter the ledger; shorthand for
ocracy.catalog.Catalog.filter().Example:
ocracy.find(is_local=True, open_source=True, handwriting="yes") ocracy.find(implemented=True) # only backends ocracy can run now
- ocracy.get_config(backend_id: str) dict[source]
A backend’s
BACKEND_CONFIGwithout loading its adapter.
- ocracy.get_default_backend(capability: str = 'read', *, require_available: bool = True) str[source]
Pick a sensible default backend id for a capability.
Strategy (OCR-tuned): prefer a backend explicitly flagged
default_forthe capability and whose dependency is importable; then any importable backend for the capability; then — ifrequire_availableis False or nothing is installed — the first registered candidate (using it will raise a helpful install error).
- ocracy.install(backend_id: str, *, yes: bool = False, gpu: bool = False, verify: bool = True, upgrade: bool = False) dict[source]
Plan (and optionally run) the pip install for a backend.
With
yes=False(default) this is a dry run: it returns the plan without changing anything — callresult['requirements'].instructions()to show it. Withyes=Trueit runspip installfor the backend’s extra in the current interpreter, then (ifverify) checks importability.System dependencies and GPU wheels are surfaced, never run automatically (they need sudo/brew or an environment-specific CUDA choice) — run those yourself from
result['requirements'].system/.gpu.
- ocracy.is_set_up(backend_id: str) bool[source]
Ready to run here & now.
Requires the engine/client to be importable (local and remote), and — for a remote backend — its (primary) credential env var to resolve. So a remote whose key is set but whose client library isn’t installed is not set up (it couldn’t actually run). Returns False for ledger-only backends.
- ocracy.is_tested(backend_id: str, *, image: Any = None) bool[source]
Actually run OCR on
image(a generated one if None); True iff it worked.For remote backends this performs a real API call. Returns False if the backend isn’t set up or the run fails.
- ocracy.list_backends(capability: str | None = None) List[str][source]
Sorted ids of implemented backends, optionally filtered by capability.
A backend matches
capabilityif it appears in the backend’scapabilitieslist (the primary"read"is implied for all).
- ocracy.make_block(text: str, *, bbox: Any = None, confidence: float | None = None, conf_scale: float = 1.0, level: str = 'word', language: str | None = None, **meta: Any) TextBlock[source]
Build a normalized
TextBlock.bboxmay be any shape accepted byas_bbox().confidenceis normalized to[0, 1]viaconf_scale(e.g.conf_scale=100for percent-scale engines).
- ocracy.names_with_sites(ids: Iterable[str], *, info: Dict[str, dict] | None = None) str[source]
"Name (website), Name2 (website2), ..."forids(name, not id).
- ocracy.ocr(image: str | Path | bytes | PILImage | NDArray, *, backend: str = None, **kwargs) OcrResult[source]
Read text from an image with any backend, returning a normalized result.
- Parameters:
image – A path,
http(s)URL,bytes, PIL image, or numpy array.backend – Backend id (see
list_backends()). Defaults to the first installed implemented backend (seeget_default_backend()).**kwargs – Normalized, backend-translated options (e.g.
languages). Unknown options for the chosen backend are warned about and dropped.
- Returns:
An
OcrResult(str(result)is the text).
- ocracy.read_text(image: str | Path | bytes | PILImage | NDArray, *, backend: str = None, **kwargs) str[source]
Like
ocr()but returns just the recognized text string.
- ocracy.register_backend(backend_id: str, config: dict, adapter: Any = None) None[source]
Register a backend at runtime (for third-party plugins).
- Parameters:
backend_id – Unique identifier.
config –
BACKEND_CONFIG-shaped dict (needs at leastname).adapter – Optional pre-instantiated adapter; else loaded lazily from
config['module_path']when first used.
- ocracy.requirements(backend_id: str, *, gpu: bool = False) Requirements[source]
Return structured install
Requirementsforbackend_id.Works for both implemented backends (uses the
ocracy[extra]install and the recipe) and ledger-only backends (falls back to the ledger’spython_installstring). Passgpu=Trueto surface GPU wheel guidance.
- ocracy.scaffold_backend(backend_id: str, *, dest: str | Path | None = None, overwrite: bool = False, ledger: Any = None, extra_overrides: dict | None = None) Path[source]
Create a new
ocracy/backends/<id>/package from the template.Pre-fills
config.pyfrom the backend’s ledger entry (if any) so you only have to flesh outparam_mapand implementadapter.py’s_read.- Parameters:
backend_id – The ledger id (e.g.
"easyocr","google-vision"). The on-disk module name uses underscores; the configidkeeps the id verbatim.dest – Target directory (defaults to
ocracy/backends/<id_underscored>).overwrite – Allow writing into an existing non-empty directory.
ledger – A
Catalogto read the record from (defaults to the shipped catalog).extra_overrides – Extra config-key overrides applied on top of the record.
- Returns:
The path to the created backend package.
- ocracy.services = <ServiceCollection backends=['aws-textract', 'azure-document-intelligence', 'claude-vision', 'easyocr', 'google-vision', 'gpt-4o-vision', 'mathpix', 'mistral-ocr', 'ocr-space', 'ocrmac', 'paddleocr', 'pix2tex-latex-ocr', 'rapidocr', 'tesseract', 'trocr-handwritten']>
Singleton service collection for per-backend access (
services.tesseract).
- ocracy.status_table(ids: Iterable[str] | None = None, *, info: Dict[str, dict] | None = None, columns=None, run_tests=False, test_image: Any = None) str[source]
Render an aligned Markdown table of backend status (also plain-text readable).
Rows are ordered tested → set-up → implemented → listed, then by name, so the “live” backends float to the top. Pass a precomputed
infoto avoid re-running tests; otherwiserun_testscontrols which set-up backends get OCR-tested.
- ocracy.validate_adapter(backend_id: str, *, image: Any = None, expect_text: str | None = None) dict[source]
Smoke-test a backend adapter end to end, returning a report dict.
Loads the adapter (reporting unavailability instead of raising), runs
readon a generated (or supplied) image, and checks the contract: anOcrResultcame back with text and/or blocks. Never raises on a recognition mismatch — it returns what happened so callers can decide.