Skip to main content

OwistScribe

Openwist AI — building the future, intelligently.

Fast, low-memory, offline OCR and document reading — one pip install owistscribe gives you a CLI, a local web app, and a Python API, all sharing one engine. Built on a patched fork of RapidOCR (ONNX Runtime, bundled PP-OCRv6 "small" weights) — constructing the engine makes zero network calls, ever, because the model weights ship inside the package itself.

pip install owistscribe
owistscribe invoice.pdf

Why

  • Actually offline. No API key, no per-page cost, no network call at inference time. The .onnx model weights are bundled in the wheel.
  • Reads more than images. PDFs and DOCX files with a real embedded text layer are read directly — faster and more accurate than rasterizing and OCR'ing a picture of the text. OCR only runs on pages that are genuinely scanned with no text layer.
  • Output is Markdown, not a text blob. Headings and tables are reconstructed as real Markdown (# headings, | table | cells |) from the OCR geometry — see How extraction works below for what that does and doesn't cover.
  • Streams. A multi-page PDF streams page-by-page; a single image streams region-by-region as text is recognized, instead of a silent wait on a large file.
  • One engine, three front ends. The CLI, the web app, and the Python API all call the same owistscribe.RapidOcrEngine — a fix or a tuning change benefits all three at once.

Install

pip install owistscribe

Requires Python 3.9+. That's it — no second package, no sibling checkout, no post-install download step. (The drag-and-drop desktop app needs one more dependency: pip install "owistscribe[desktop]".)

Quickstart

CLI

owistscribe invoice.png                    # print the extracted text (Markdown)
owistscribe report.pdf --json              # structured JSON instead
owistscribe scan.jpg --min-confidence 0.8  # drop low-confidence text
owistscribe web                            # launch the web app at http://127.0.0.1:8000
owistscribe desktop                        # launch the drag-and-drop desktop app
owistscribe --help                         # every command, with examples

Colored terminal output (headings/tables lightly highlighted) is on automatically for an interactive terminal, off automatically when piped or redirected, and always off with --no-color or the NO_COLOR environment variable.

Web app

owistscribe web

Opens http://127.0.0.1:8000 in your browser. Drag in an image, PDF, or DOCX; results stream in as they're ready and render as formatted Markdown (headings, tables) with a toggle to see the raw text. owistscribe web --port 9000 to use a different port.

Python API

The shape meant for pairing with a chatbot, agent, or any app that needs to read an uploaded file before responding:

from owistscribe import Scribe

owistscribe = Scribe()                      # loads the model once — build this at startup, reuse it
result = owistscribe.read("invoice.pdf")

result.text        # the full extracted text (reading-order corrected, Markdown-formatted)
result.pages        # per-page breakdown: [PageResult(page_number=1, text=..., source=...), ...]
result.headers       # detected repeated header lines, if any
result.footers       # detected repeated footer lines, if any

Progressive results, for a large file or a chat UI that wants to show progress:

for event in owistscribe.stream("report.pdf"):
    if event["type"] == "page":
        print(f"page {event['page_number']} ready: {len(event['text'])} chars")
    elif event["type"] == "done":
        print(f"finished in {event['took_seconds']}s")

One-shot convenience, if you don't want to manage a Scribe instance yourself:

from owistscribe import read
text = read("invoice.pdf").text

Supported files

Images (.png .jpg .jpeg .bmp .tiff .webp), .pdf, .docx.

How extraction works

Reading order & tables. RapidOCR's detection/recognition models find and read individual text boxes — they have no concept of "this is a table" or "read top-to-bottom." That's handled separately, in layout.py, using plain geometry on the returned box coordinates: cluster regions into rows by vertical-center overlap, segment by cell count so one table's structure can't bleed into an unrelated table on the same page, detect a shared column structure, and render it as a Markdown table when one is found — plain reading order otherwise.

Known, tested limitation: a genuine two-column page layout (a newsletter, a two-column résumé) can be geometrically indistinguishable from an actual two-column table — confirmed with an adversarial test where two unrelated columns of ordinary prose were detected as a table and their sentences interleaved row-by-row. Telling the two apart needs a semantic signal (whether the left and right cell's text are topically related) this module deliberately doesn't have. A single column of text, or a table with genuinely data-shaped cells, is unaffected.

Headings. Embedded PDF text carries real font metadata (size, bold) — a bold, larger-than- body line becomes a #/##/### heading. OCR'd text has no font metadata to use, so it isn't guessed at; OCR'd pages get reading-order/table structure only, not heading markup.

Streaming. A multi-page PDF/DOCX streams one event per page as each is ready. A single image streams per detected text region as recognition completes — in arrival order (grouped by aspect ratio for recognition accuracy, not top-to-bottom), with a final corrected, reading-order result once every region is in. See owistscribe.Scribe.stream()'s docstring for the exact event shapes — the web app and the Python API use the same ones.

Speed and accuracy — what was tested, and what changed

The detection resolution cap was costing real accuracy — raised. Profiling a real tall test image (1640×5666) against upstream RapidOCR's 2000px max_side_len cap showed confirmed-wrong text from small label text shrunk past legibility by the forced downscale — not missed detections, wrong characters. Measured at four resolutions:

max_side_len avg time regions found
2000 (upstream default) 4.26s 89
3000 (this project's default) 4.64s (+9%) 94
4000 6.45s (+51%) 98
5666 (full res, no cap) 10.00s (+135%) 101

3000 recovers effectively all of the real accuracy fix for a modest +9% time cost; 4000/5666 mostly add a few marginal tokens at a much steeper cost. Overridable per-instance: Scribe(max_side_len=...) / RapidOcrEngine(max_side_len=...), or via the OWISTSCRIBE_MAX_SIDE_LEN environment variable for the web app.

Tested and genuinely didn't help, said plainly:

  • Forcing onnxruntime's thread count (either 1 or os.cpu_count()) — no measured win over its own -1/auto-detect default; intra_op_num_threads=1 specifically nearly doubled inference time, real proof multi-threading matters, just not worth overriding further.
  • enable_cpu_mem_arena — a ~1.7% difference, indistinguishable from run-to-run noise.
  • Multi-page parallelism (thread pool, process pool) on 2-core test hardware — no measured gain; may look different on a many-core machine, but that's not what was tested.
  • Content-cropping before detection — tested as a speed idea, shipped as an accuracy fix instead (preprocess.py's crop_to_content), because that's what a real padded-canvas test actually showed: unchanged speed, but fewer recognition errors.

Full, dated, itemized record of every patch to the vendored engine (including two real bugs found and fixed during development — a streaming-order bug and a crash on textless images): owistscribe/_vendor/rapidocr/CHANGES.md.

Development

git clone https://github.com/openwist/owistscribe   # replace with this repo's actual URL
cd owistscribe
python -m venv venv && source venv/bin/activate
pip install -e ".[desktop]"
owistscribe web

License

Apache License 2.0 — see LICENSE. This package vendors a fork of RapidOCR (owistscribe/_vendor/rapidocr/, Apache-2.0, Copyright RapidAI) and, for the web app's Markdown rendering, a copy of marked (owistscribe/static/marked.min.js, MIT, Copyright MarkedJS/Christopher Jeffrey). See NOTICE for the full attribution required by Apache-2.0 Section 4.

What's verified and what isn't

Verified by running real files through real code and checking the actual output, not assumed: CLI output for images/PDFs/DOCX, --json output shape, the web app's streaming and non-streaming paths for every supported file type, the Markdown rendering in the browser, a fresh pip install of the built wheel into a clean virtual environment (not the dev tree).

Not verified: a real build of the optional desktop app's packaged executable (PyInstaller doesn't cross-compile, and this was built in a Linux-only sandbox) — said plainly rather than implied. The desktop app itself runs fine from source (owistscribe desktop); only the "package it into a standalone .exe/.app" step is unverified.

Metadata

Release files for owistscribe 1.0.0

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

Source distribution (sdist)

Source distribution for owistscribe 1.0.0
File Size Uploaded
owistscribe-1.0.0.tar.gz 27.3 MB Details

Built distribution (wheel)

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

Total release size: 54.6 MB

Release files / owistscribe-1.0.0.tar.gz

Download URL owistscribe-1.0.0.tar.gz
Size 27.3 MB
Tags Source
SHA-256 checksum
How to use checksums
dbc7624796945c458fc2d75fbd83148ac7c2608700500c30949a9f4f96911708
BLAKE2b-256 checksum
How to use checksums
7334e2af24dbf1703a91021ebbf372c245eabeda938946d9aba4b02feeab7205
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / owistscribe-1.0.0-py3-none-any.whl

Download URL owistscribe-1.0.0-py3-none-any.whl
Size 27.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
9b3d40baa959631b16c0e700b2ced5f2daf06adde2456011f0c5a57d9a6c768a
BLAKE2b-256 checksum
How to use checksums
5e440a494e1834d15622ffe9f03e1a7a9d885c843927b26d8993d426b02a2b02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

1.0.0 This release

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