paradox2
Fast, simple document extraction — a from-scratch rewrite of paradox-pdf, built around one idea: most PDFs are digital and don't need a GPU.
import paradox2 as pdx
pages = pdx.extract("invoice.pdf")
Why this rewrite exists
paradox-pdf grew into a 2,783-line facade doing routing, backend resolution, and overlay rendering all in one file. paradox2 starts over with a hard rule: digital pages never pay for OCR or vision models. Every PDF page is classified independently — a digital page (has a text layer) goes through a zero-ML fast path (PyMuPDF text + vector-line table detection); a scanned page routes to the GPU OCR pipeline. A single mixed document is handled correctly per-page, automatically.
Install
pip install paradox2
Everything above works with just that — digital PDFs, text, vector-line tables, key-value fields. Heavier features are opt-in extras so the base install stays small:
| Extra | Adds | When you need it |
|---|---|---|
paradox2[gpu] |
PaddleOCR + PP-DocLayoutV2 | Scanned/photographed pages |
paradox2[rapidai] |
TableStructureRec (RapidAI), ONNX-only | Mid-tier table structure for scanned pages the OCR-grid heuristic rejects — no torch/torchvision, CPU-only, ~5.9s/table |
paradox2[yolo] |
YOLO26-document-layout | Independent table-bbox detector — crops the page before RapidAI/VLM instead of feeding them the whole page. Needed for the router's full accuracy; see below |
paradox2[formats] |
docx/xlsx/pptx/msg/rtf/7z/rar/odf readers | Non-PDF documents |
paradox2[specialists] |
torch + transformers (handwriting, signatures, formulas) | Handwriting (TrOCR), signature detection (Conditional-DETR), formula-to-LaTeX (pix2tex) |
pip install "paradox2[gpu,rapidai,formats]"
Use a clean virtual environment for [gpu]. Installing into a shared/base
environment (e.g. Anaconda's base) that already has an older paddleocr
or paddlepaddle from a prior project can leave an incompatible version in
place — pip does not always resolve this cleanly against pre-existing
packages in a polluted environment. python -m venv .venv && source .venv/bin/activate (or conda create -n paradox2 python=3.12) before
installing avoids this.
What it does
import paradox2 as pdx
# Every page, as structured JSON — digital pages via the fast path,
# scanned pages via GPU OCR, decided per page.
pages = pdx.extract("document.pdf")
# Just the text
text = pdx.extract_text("document.pdf")
# Just the tables (vector-line detection on digital pages,
# OCR-grid heuristic + optional RapidAI/VLM fallback on scanned pages)
tables = pdx.extract_tables("document.pdf")
# Key-value pairs from an invoice/form-like page (digital only)
fields = pdx.extract_kie("invoice.pdf")
# Any non-PDF format too — same call, same output shape
data = pdx.extract("spreadsheet.xlsx")
data = pdx.extract("scan.docx")
Selecting pages, features, and output format
pdx.extract("report.pdf", pages="1-3,7") # specific pages
pdx.extract("report.pdf", feature="tables") # just one feature, still full extract() under the hood
pdx.extract("report.pdf", output_format="markdown") # rendered markdown instead of the raw IR
pdx.extract("report.pdf", fields=True) # turn on key-value extraction inline
Optional specialists (all off by default, all lazy-loaded)
pdx.extract("form.pdf", handwriting=True) # TrOCR on handwritten blocks
pdx.extract("contract.pdf", signatures=True) # Conditional-DETR signature boxes
pdx.extract("paper.pdf", formulas=True) # inline math -> LaTeX
None of these import their heavy dependencies unless the flag is set —
import paradox2 alone never touches torch, paddle, or transformers.
The scanned-table router
Scanned pages route tables through three tiers, each opt-in past the first:
OCR-grid heuristic (free, always on)
| rejects merged/borderless/dense tables by design,
| or under-reads a real table (low_confidence flag)
v
YOLO26 bbox (PARADOX2_YOLO_DETECT=1) -- crop the page to the detected
| table region before handing it to the tiers below. A
| borderline-confidence detection (<0.85) skips RapidAI
| entirely and goes straight to the VLM tier - VLM tolerates
| a loose/imprecise crop better than RapidAI's structure model
v
RapidAI mid-tier (PARADOX2_RAPIDAI_TABLES=1)
| ONNX wired/wireless classifier + structure model. Its own
| result is cross-checked against OCR line density in the same
| crop (table_rapidai.py's low_confidence) - implausibly few
| OR implausibly many rows both re-route to the VLM tier
v
VLM fallback (PARADOX2_VLM_TABLES=1)
| PaddleOCR-VL single-pass, reserved for tables that look
| genuinely complex - not run on everything RapidAI touches
v
best available result
Every tier is measured end-to-end (paradox2.extract(), not the isolated
engine wrapper) on the same 20 real full-page tables (OmniDocBench), not
simulated:
| config | exact row match | within +/-3 rows | mean time/page |
|---|---|---|---|
| heuristic only (pre-router) | 5% | 15% | 0.8s |
| + RapidAI, full page (bug, fixed) | 15% | 50% | 6.0s |
| + RapidAI, cropped to heuristic's own bbox (bug, fixed - made 3/20 pages worse) | 15% | 40% | 4.3s |
| + RapidAI, cropped to YOLO26 bbox | 25% | 75% | 5.5s |
| + selective VLM escalation (low YOLO confidence or implausible RapidAI row count) | 35% | 80% | 6.4s |
The isolated RapidAI engine alone scores 65% exact on tight ground-truth crops — the gap to the router's end-to-end number is the YOLO26 crop's margin versus a perfect crop, not a RapidAI accuracy problem. Cropping to the heuristic's own bbox instead of an independent detector actively hurts: that bbox only spans the rows the heuristic already found, so it bakes its own under-read into the pixels before the next tier ever sees them. A same-crop self-consistency check (RapidAI's row count vs. OCR line density) can't catch a loose crop itself, since both numbers inflate together on the same imprecise crop — that's what the YOLO-confidence trigger (<0.85) is for instead.
The heuristic never gets replaced by a worse result — a lower tier's output is kept unless a higher tier actually produces something.
export PARADOX2_YOLO_DETECT=1
export PARADOX2_RAPIDAI_TABLES=1
export PARADOX2_VLM_TABLES=1
Health check
from paradox2 import doctor
doctor() # {"cuda": True/False, "pymupdf": "1.24.x", ...} - never silently hides a backend mismatch
Design principles
- Digital-first: a PDF with a text layer never pays for OCR, vision layout models, or GPU inference — verified per-page, not per-document.
- Facade stays thin:
paradox2/api.pyis dispatch only; business logic lives inpipeline/,tables/,engines/,formats/. If the facade creeps toward 100+ lines, that's a signal something leaked out of place. - Specialists are lazy and swallow their own failures: an optional
engine (handwriting, signatures, formulas, RapidAI, VLM) that fails to
load or errors mid-call returns
[]/None— it never takes down the base extraction path. - No simulated benchmarks: every accuracy/speed number in this README and in the codebase's docstrings comes from a real run against real documents, with the script path noted alongside it.
Status
Early-stage rewrite, private while the API and table router stabilize. See
docs/PLAN.md for the phase breakdown and docs/TASKS.md for current work.
Release files for paradox2 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| paradox2-0.2.0.tar.gz | 276.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| paradox2-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 419.2 kB
Release files / paradox2-0.2.0.tar.gz
| Download URL | paradox2-0.2.0.tar.gz |
|---|---|
| Size | 276.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e50762eca121c1c6331912c59eddc82947853e387a5feb418b895cc83259e85e
|
|
BLAKE2b-256 checksum How to use checksums |
77e70a0d04fe396caf6228c238bc8a74f8063215d8a818ca77df6665503fbdf3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.18
|
Release files / paradox2-0.2.0-py3-none-any.whl
| Download URL | paradox2-0.2.0-py3-none-any.whl |
|---|---|
| Size | 143.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc71d28358d3203d408f3359e8e4fc246d1f351bcaa8f9e4cbf649046a1551f3
|
|
BLAKE2b-256 checksum How to use checksums |
454750cfd71ed5c3b03c8cfd22d98e119cc2f796d427a6981d2d7edcfff35355
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.18
|