paradox2 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paradox2-0.1.0/.gitignore +20 -0
- paradox2-0.1.0/LICENSE +34 -0
- paradox2-0.1.0/PKG-INFO +227 -0
- paradox2-0.1.0/README.md +172 -0
- paradox2-0.1.0/paradox2/__init__.py +7 -0
- paradox2-0.1.0/paradox2/__main__.py +5 -0
- paradox2-0.1.0/paradox2/api.py +320 -0
- paradox2-0.1.0/paradox2/cli.py +113 -0
- paradox2-0.1.0/paradox2/config.py +33 -0
- paradox2-0.1.0/paradox2/doctor.py +74 -0
- paradox2-0.1.0/paradox2/engines/__init__.py +0 -0
- paradox2-0.1.0/paradox2/engines/_gpu_worker.py +214 -0
- paradox2-0.1.0/paradox2/engines/_html_table.py +68 -0
- paradox2-0.1.0/paradox2/engines/_lazy.py +53 -0
- paradox2-0.1.0/paradox2/engines/_row_confidence.py +166 -0
- paradox2-0.1.0/paradox2/engines/doc_classifier.py +67 -0
- paradox2-0.1.0/paradox2/engines/formula_latex.py +38 -0
- paradox2-0.1.0/paradox2/engines/handwriting_trocr.py +49 -0
- paradox2-0.1.0/paradox2/engines/kie_heuristic.py +366 -0
- paradox2-0.1.0/paradox2/engines/signature_detr.py +43 -0
- paradox2-0.1.0/paradox2/engines/table_azure_di.py +161 -0
- paradox2-0.1.0/paradox2/engines/table_detector.py +130 -0
- paradox2-0.1.0/paradox2/engines/table_ppstructure.py +256 -0
- paradox2-0.1.0/paradox2/engines/table_rapidai.py +178 -0
- paradox2-0.1.0/paradox2/engines/table_vlm.py +207 -0
- paradox2-0.1.0/paradox2/exporters/__init__.py +0 -0
- paradox2-0.1.0/paradox2/exporters/markdown.py +99 -0
- paradox2-0.1.0/paradox2/exporters/structured.py +129 -0
- paradox2-0.1.0/paradox2/formats/__init__.py +0 -0
- paradox2-0.1.0/paradox2/formats/archive_adapter.py +176 -0
- paradox2-0.1.0/paradox2/formats/detector.py +150 -0
- paradox2-0.1.0/paradox2/formats/doc_adapter.py +55 -0
- paradox2-0.1.0/paradox2/formats/docx_adapter.py +38 -0
- paradox2-0.1.0/paradox2/formats/eml_adapter.py +63 -0
- paradox2-0.1.0/paradox2/formats/image_adapter.py +19 -0
- paradox2-0.1.0/paradox2/formats/open_document_adapter.py +71 -0
- paradox2-0.1.0/paradox2/formats/pptx_adapter.py +33 -0
- paradox2-0.1.0/paradox2/formats/render_bundle.py +95 -0
- paradox2-0.1.0/paradox2/formats/rtf_adapter.py +46 -0
- paradox2-0.1.0/paradox2/formats/text_adapters.py +48 -0
- paradox2-0.1.0/paradox2/formats/web_adapter.py +64 -0
- paradox2-0.1.0/paradox2/formats/xls_adapter.py +43 -0
- paradox2-0.1.0/paradox2/formats/xlsb_adapter.py +35 -0
- paradox2-0.1.0/paradox2/formats/xlsx_adapter.py +41 -0
- paradox2-0.1.0/paradox2/ir/__init__.py +0 -0
- paradox2-0.1.0/paradox2/pipeline/__init__.py +0 -0
- paradox2-0.1.0/paradox2/pipeline/annotations.py +192 -0
- paradox2-0.1.0/paradox2/pipeline/classify.py +113 -0
- paradox2-0.1.0/paradox2/pipeline/digital_fast.py +220 -0
- paradox2-0.1.0/paradox2/pipeline/feature_flags.py +78 -0
- paradox2-0.1.0/paradox2/pipeline/language_detect.py +43 -0
- paradox2-0.1.0/paradox2/pipeline/links.py +103 -0
- paradox2-0.1.0/paradox2/pipeline/orientation.py +87 -0
- paradox2-0.1.0/paradox2/pipeline/reading_order.py +124 -0
- paradox2-0.1.0/paradox2/pipeline/scanned.py +488 -0
- paradox2-0.1.0/paradox2/runtime/__init__.py +0 -0
- paradox2-0.1.0/paradox2/runtime/backend_detect.py +49 -0
- paradox2-0.1.0/paradox2/runtime/guardrails.py +28 -0
- paradox2-0.1.0/paradox2/runtime/machine_state.py +380 -0
- paradox2-0.1.0/paradox2/runtime/page_pool.py +159 -0
- paradox2-0.1.0/paradox2/runtime/resource_detect.py +339 -0
- paradox2-0.1.0/paradox2/service/__init__.py +0 -0
- paradox2-0.1.0/paradox2/service/client.py +73 -0
- paradox2-0.1.0/paradox2/service/protocol.py +61 -0
- paradox2-0.1.0/paradox2/service/server.py +81 -0
- paradox2-0.1.0/paradox2/tables/__init__.py +0 -0
- paradox2-0.1.0/paradox2/tables/cv_tables.py +106 -0
- paradox2-0.1.0/paradox2/tables/geometry.py +84 -0
- paradox2-0.1.0/paradox2/tables/line_tables.py +320 -0
- paradox2-0.1.0/paradox2/tables/scanned.py +322 -0
- paradox2-0.1.0/paradox2/tables/scanned_router.py +574 -0
- paradox2-0.1.0/paradox2/tables/structure.py +146 -0
- paradox2-0.1.0/pyproject.toml +171 -0
- paradox2-0.1.0/tests/fixtures/sample.doc +0 -0
- paradox2-0.1.0/tests/fixtures/sample.xls +0 -0
- paradox2-0.1.0/tests/fixtures/sample_digital.pdf +80 -0
- paradox2-0.1.0/tests/fixtures/sample_scanned.pdf +566 -0
- paradox2-0.1.0/tests/fixtures/scanned_tables/fixtures.json +15590 -0
- paradox2-0.1.0/tests/integration/test_service.py +57 -0
- paradox2-0.1.0/tests/test_table_rapidai_bbox.py +55 -0
- paradox2-0.1.0/tests/unit/test_api_encrypted_pdf.py +25 -0
- paradox2-0.1.0/tests/unit/test_api_features.py +48 -0
- paradox2-0.1.0/tests/unit/test_api_page_workers.py +24 -0
- paradox2-0.1.0/tests/unit/test_api_stays_thin.py +26 -0
- paradox2-0.1.0/tests/unit/test_archive_adapter.py +58 -0
- paradox2-0.1.0/tests/unit/test_archive_breadth.py +36 -0
- paradox2-0.1.0/tests/unit/test_backend_detect.py +37 -0
- paradox2-0.1.0/tests/unit/test_classification_projection.py +19 -0
- paradox2-0.1.0/tests/unit/test_classify.py +78 -0
- paradox2-0.1.0/tests/unit/test_config_doctor.py +19 -0
- paradox2-0.1.0/tests/unit/test_digital_fast.py +33 -0
- paradox2-0.1.0/tests/unit/test_doc_classifier.py +31 -0
- paradox2-0.1.0/tests/unit/test_eval_metrics.py +76 -0
- paradox2-0.1.0/tests/unit/test_feature_flags.py +41 -0
- paradox2-0.1.0/tests/unit/test_gpu_worker.py +115 -0
- paradox2-0.1.0/tests/unit/test_line_tables.py +58 -0
- paradox2-0.1.0/tests/unit/test_machine_state.py +185 -0
- paradox2-0.1.0/tests/unit/test_no_duplicate_text_extraction.py +48 -0
- paradox2-0.1.0/tests/unit/test_page_pool.py +74 -0
- paradox2-0.1.0/tests/unit/test_render_bundle.py +36 -0
- paradox2-0.1.0/tests/unit/test_resource_detect.py +86 -0
- paradox2-0.1.0/tests/unit/test_scan_text_threshold.py +43 -0
- paradox2-0.1.0/tests/unit/test_scanned.py +37 -0
- paradox2-0.1.0/tests/unit/test_scanned_dpi.py +27 -0
- paradox2-0.1.0/tests/unit/test_scanned_parallel.py +28 -0
- paradox2-0.1.0/tests/unit/test_scanned_router.py +202 -0
- paradox2-0.1.0/tests/unit/test_scanned_tables.py +220 -0
- paradox2-0.1.0/tests/unit/test_spreadsheet_optional.py +45 -0
- paradox2-0.1.0/tests/unit/test_table_azure_di.py +168 -0
- paradox2-0.1.0/tests/unit/test_table_ppstructure.py +119 -0
- paradox2-0.1.0/tests/unit/test_table_vlm.py +94 -0
- paradox2-0.1.0/tests/unit/test_web_formats.py +24 -0
- paradox2-0.1.0/tests/unit/test_xls_doc_adapters.py +37 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
.venv-*/
|
|
3
|
+
.kilo/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.pyc
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
uv.lock
|
|
8
|
+
.codebase-memory/
|
|
9
|
+
archive_1/
|
|
10
|
+
archive (1).zip
|
|
11
|
+
eval/results/
|
|
12
|
+
|
|
13
|
+
# --- all local table/layout eval artifacts (single folder) ---
|
|
14
|
+
experiments/
|
|
15
|
+
archive*.zip
|
|
16
|
+
docs/golden_set_realtest/
|
|
17
|
+
docs/
|
|
18
|
+
bench/
|
|
19
|
+
.claude/
|
|
20
|
+
comunicationa2a/
|
paradox2-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
paradox2 — Proprietary Software License
|
|
2
|
+
Copyright (c) 2024-2026 CreAI. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This software, including its source code, models, and documentation
|
|
5
|
+
(collectively, the "Software"), is the confidential and proprietary property of
|
|
6
|
+
CreAI ("the Company"). The Software is licensed, not sold, and is NOT open
|
|
7
|
+
source. The publicly distributed package (e.g. via PyPI) is made available for
|
|
8
|
+
use subject to the terms below.
|
|
9
|
+
|
|
10
|
+
1. LICENSE GRANT. Subject to these terms, the Company grants you a
|
|
11
|
+
non-exclusive, non-transferable, revocable license to install and use the
|
|
12
|
+
distributed paradox2 package for your own internal purposes.
|
|
13
|
+
|
|
14
|
+
2. RESTRICTIONS. Except to the extent expressly permitted by applicable law, you
|
|
15
|
+
may not: (a) redistribute, sublicense, sell, rent, or lease the Software;
|
|
16
|
+
(b) reverse engineer, decompile, or disassemble it, or attempt to derive its
|
|
17
|
+
source code; (c) modify or create derivative works of it; or (d) remove or
|
|
18
|
+
alter any proprietary notices.
|
|
19
|
+
|
|
20
|
+
3. BUNDLED THIRD-PARTY COMPONENTS. The Software includes or downloads
|
|
21
|
+
third-party components that remain the property of their respective owners
|
|
22
|
+
and are governed by their own licenses. See the NOTICE file for details;
|
|
23
|
+
those terms control for the respective components.
|
|
24
|
+
|
|
25
|
+
4. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
26
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
27
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. TO
|
|
28
|
+
THE MAXIMUM EXTENT PERMITTED BY LAW, THE COMPANY SHALL NOT BE LIABLE FOR ANY
|
|
29
|
+
CLAIM OR DAMAGES ARISING FROM OR IN CONNECTION WITH THE SOFTWARE.
|
|
30
|
+
|
|
31
|
+
5. COMMERCIAL LICENSING. For commercial use, redistribution, OEM, or any rights
|
|
32
|
+
not expressly granted above, contact feliperodriguez@creai.mx.
|
|
33
|
+
|
|
34
|
+
All rights not expressly granted herein are reserved by CreAI.
|
paradox2-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: paradox2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, simple document extraction — from-scratch rewrite of paradox_pdf
|
|
5
|
+
Author-email: CreAI <feliperodriguez@creai.mx>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: document,extraction,nlp,ocr,parsing,pdf,structured,table
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: numpy<3.0,>=1.24
|
|
19
|
+
Requires-Dist: opencv-python-headless<5.0,>=4.8
|
|
20
|
+
Requires-Dist: pymupdf<2.0,>=1.24
|
|
21
|
+
Provides-Extra: azure
|
|
22
|
+
Requires-Dist: requests<3.0,>=2.31; extra == 'azure'
|
|
23
|
+
Provides-Extra: formats
|
|
24
|
+
Requires-Dist: extract-msg<1.0,>=0.45; extra == 'formats'
|
|
25
|
+
Requires-Dist: odfpy<2.0,>=1.4; extra == 'formats'
|
|
26
|
+
Requires-Dist: openpyxl<4.0,>=3.1; extra == 'formats'
|
|
27
|
+
Requires-Dist: py7zr<1.0,>=0.20; extra == 'formats'
|
|
28
|
+
Requires-Dist: python-docx<2.0,>=1.0; extra == 'formats'
|
|
29
|
+
Requires-Dist: python-pptx<1.0,>=0.6.21; extra == 'formats'
|
|
30
|
+
Requires-Dist: pyxlsb<2.0,>=1.0; extra == 'formats'
|
|
31
|
+
Requires-Dist: rarfile<5.0,>=4.0; extra == 'formats'
|
|
32
|
+
Requires-Dist: striprtf<1.1,>=0.0.26; extra == 'formats'
|
|
33
|
+
Requires-Dist: xlrd<3.0,>=2.0; extra == 'formats'
|
|
34
|
+
Provides-Extra: gpu
|
|
35
|
+
Requires-Dist: paddleocr[doc-parser]>=3.0; extra == 'gpu'
|
|
36
|
+
Requires-Dist: paddlex<4.0,>=3.4; extra == 'gpu'
|
|
37
|
+
Requires-Dist: pillow<12.0,>=10.0; extra == 'gpu'
|
|
38
|
+
Provides-Extra: rapidai
|
|
39
|
+
Requires-Dist: lineless-table-rec<1.0,>=0.1; extra == 'rapidai'
|
|
40
|
+
Requires-Dist: rapidocr-onnxruntime<2.0,>=1.4; extra == 'rapidai'
|
|
41
|
+
Requires-Dist: table-cls<2.0,>=1.2; extra == 'rapidai'
|
|
42
|
+
Requires-Dist: wired-table-rec<2.0,>=1.2; extra == 'rapidai'
|
|
43
|
+
Provides-Extra: specialists
|
|
44
|
+
Requires-Dist: langdetect<2.0,>=1.0; extra == 'specialists'
|
|
45
|
+
Requires-Dist: pillow<12.0,>=10.0; extra == 'specialists'
|
|
46
|
+
Requires-Dist: pix2tex>=0.0.27; extra == 'specialists'
|
|
47
|
+
Requires-Dist: sentencepiece<1.0,>=0.1.99; extra == 'specialists'
|
|
48
|
+
Requires-Dist: torch<3.0,>=2.0; extra == 'specialists'
|
|
49
|
+
Requires-Dist: torchvision<1.0,>=0.15; extra == 'specialists'
|
|
50
|
+
Requires-Dist: transformers<5.0,>=4.30; extra == 'specialists'
|
|
51
|
+
Provides-Extra: yolo
|
|
52
|
+
Requires-Dist: huggingface-hub<1.0,>=0.20; extra == 'yolo'
|
|
53
|
+
Requires-Dist: ultralytics<9.0,>=8.0; extra == 'yolo'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# paradox2
|
|
57
|
+
|
|
58
|
+
Fast, simple document extraction — a from-scratch rewrite of [paradox-pdf](https://pypi.org/project/paradox-pdf/), built around one idea: **most PDFs are digital and don't need a GPU.**
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import paradox2 as pdx
|
|
62
|
+
|
|
63
|
+
pages = pdx.extract("invoice.pdf")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Why this rewrite exists
|
|
67
|
+
|
|
68
|
+
paradox-pdf grew into a 2,783-line facade doing routing, backend resolution,
|
|
69
|
+
and overlay rendering all in one file. paradox2 starts over with a hard rule:
|
|
70
|
+
**digital pages never pay for OCR or vision models.** Every PDF page is
|
|
71
|
+
classified independently — a digital page (has a text layer) goes through a
|
|
72
|
+
zero-ML fast path (PyMuPDF text + vector-line table detection); a scanned
|
|
73
|
+
page routes to the GPU OCR pipeline. A single mixed document is handled
|
|
74
|
+
correctly per-page, automatically.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install paradox2
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Everything above works with just that — digital PDFs, text, vector-line
|
|
83
|
+
tables, key-value fields. Heavier features are opt-in extras so the base
|
|
84
|
+
install stays small:
|
|
85
|
+
|
|
86
|
+
| Extra | Adds | When you need it |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `paradox2[gpu]` | PaddleOCR + PP-DocLayoutV2 | Scanned/photographed pages |
|
|
89
|
+
| `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 |
|
|
90
|
+
| `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 |
|
|
91
|
+
| `paradox2[formats]` | docx/xlsx/pptx/msg/rtf/7z/rar/odf readers | Non-PDF documents |
|
|
92
|
+
| `paradox2[specialists]` | torch + transformers (handwriting, signatures, formulas) | Handwriting (TrOCR), signature detection (Conditional-DETR), formula-to-LaTeX (pix2tex) |
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install "paradox2[gpu,rapidai,formats]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## What it does
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import paradox2 as pdx
|
|
102
|
+
|
|
103
|
+
# Every page, as structured JSON — digital pages via the fast path,
|
|
104
|
+
# scanned pages via GPU OCR, decided per page.
|
|
105
|
+
pages = pdx.extract("document.pdf")
|
|
106
|
+
|
|
107
|
+
# Just the text
|
|
108
|
+
text = pdx.extract_text("document.pdf")
|
|
109
|
+
|
|
110
|
+
# Just the tables (vector-line detection on digital pages,
|
|
111
|
+
# OCR-grid heuristic + optional RapidAI/VLM fallback on scanned pages)
|
|
112
|
+
tables = pdx.extract_tables("document.pdf")
|
|
113
|
+
|
|
114
|
+
# Key-value pairs from an invoice/form-like page (digital only)
|
|
115
|
+
fields = pdx.extract_kie("invoice.pdf")
|
|
116
|
+
|
|
117
|
+
# Any non-PDF format too — same call, same output shape
|
|
118
|
+
data = pdx.extract("spreadsheet.xlsx")
|
|
119
|
+
data = pdx.extract("scan.docx")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Selecting pages, features, and output format
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
pdx.extract("report.pdf", pages="1-3,7") # specific pages
|
|
126
|
+
pdx.extract("report.pdf", feature="tables") # just one feature, still full extract() under the hood
|
|
127
|
+
pdx.extract("report.pdf", output_format="markdown") # rendered markdown instead of the raw IR
|
|
128
|
+
pdx.extract("report.pdf", fields=True) # turn on key-value extraction inline
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Optional specialists (all off by default, all lazy-loaded)
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
pdx.extract("form.pdf", handwriting=True) # TrOCR on handwritten blocks
|
|
135
|
+
pdx.extract("contract.pdf", signatures=True) # Conditional-DETR signature boxes
|
|
136
|
+
pdx.extract("paper.pdf", formulas=True) # inline math -> LaTeX
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
None of these import their heavy dependencies unless the flag is set —
|
|
140
|
+
`import paradox2` alone never touches torch, paddle, or transformers.
|
|
141
|
+
|
|
142
|
+
## The scanned-table router
|
|
143
|
+
|
|
144
|
+
Scanned pages route tables through three tiers, each opt-in past the first:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
OCR-grid heuristic (free, always on)
|
|
148
|
+
| rejects merged/borderless/dense tables by design,
|
|
149
|
+
| or under-reads a real table (low_confidence flag)
|
|
150
|
+
v
|
|
151
|
+
YOLO26 bbox (PARADOX2_YOLO_DETECT=1) -- crop the page to the detected
|
|
152
|
+
| table region before handing it to the tiers below. A
|
|
153
|
+
| borderline-confidence detection (<0.85) skips RapidAI
|
|
154
|
+
| entirely and goes straight to the VLM tier - VLM tolerates
|
|
155
|
+
| a loose/imprecise crop better than RapidAI's structure model
|
|
156
|
+
v
|
|
157
|
+
RapidAI mid-tier (PARADOX2_RAPIDAI_TABLES=1)
|
|
158
|
+
| ONNX wired/wireless classifier + structure model. Its own
|
|
159
|
+
| result is cross-checked against OCR line density in the same
|
|
160
|
+
| crop (table_rapidai.py's low_confidence) - implausibly few
|
|
161
|
+
| OR implausibly many rows both re-route to the VLM tier
|
|
162
|
+
v
|
|
163
|
+
VLM fallback (PARADOX2_VLM_TABLES=1)
|
|
164
|
+
| PaddleOCR-VL single-pass, reserved for tables that look
|
|
165
|
+
| genuinely complex - not run on everything RapidAI touches
|
|
166
|
+
v
|
|
167
|
+
best available result
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Every tier is measured end-to-end (`paradox2.extract()`, not the isolated
|
|
171
|
+
engine wrapper) on the same 20 real full-page tables (OmniDocBench), not
|
|
172
|
+
simulated:
|
|
173
|
+
|
|
174
|
+
| config | exact row match | within +/-3 rows | mean time/page |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| heuristic only (pre-router) | 5% | 15% | 0.8s |
|
|
177
|
+
| + RapidAI, full page (bug, fixed) | 15% | 50% | 6.0s |
|
|
178
|
+
| + RapidAI, cropped to heuristic's own bbox (bug, fixed - made 3/20 pages *worse*) | 15% | 40% | 4.3s |
|
|
179
|
+
| + RapidAI, cropped to YOLO26 bbox | 25% | 75% | 5.5s |
|
|
180
|
+
| + selective VLM escalation (low YOLO confidence or implausible RapidAI row count) | **35%** | **80%** | 6.4s |
|
|
181
|
+
|
|
182
|
+
The isolated RapidAI engine alone scores 65% exact on tight ground-truth
|
|
183
|
+
crops — the gap to the router's end-to-end number is the YOLO26 crop's
|
|
184
|
+
margin versus a perfect crop, not a RapidAI accuracy problem. Cropping to
|
|
185
|
+
the heuristic's *own* bbox instead of an independent detector actively
|
|
186
|
+
hurts: that bbox only spans the rows the heuristic already found, so it
|
|
187
|
+
bakes its own under-read into the pixels before the next tier ever sees
|
|
188
|
+
them. A same-crop self-consistency check (RapidAI's row count vs. OCR
|
|
189
|
+
line density) can't catch a loose crop itself, since both numbers inflate
|
|
190
|
+
together on the same imprecise crop — that's what the YOLO-confidence
|
|
191
|
+
trigger (<0.85) is for instead.
|
|
192
|
+
|
|
193
|
+
The heuristic never gets replaced by a worse result — a lower tier's
|
|
194
|
+
output is kept unless a higher tier actually produces something.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
export PARADOX2_YOLO_DETECT=1
|
|
198
|
+
export PARADOX2_RAPIDAI_TABLES=1
|
|
199
|
+
export PARADOX2_VLM_TABLES=1
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Health check
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from paradox2 import doctor
|
|
206
|
+
doctor() # {"cuda": True/False, "pymupdf": "1.24.x", ...} - never silently hides a backend mismatch
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Design principles
|
|
210
|
+
|
|
211
|
+
- **Digital-first**: a PDF with a text layer never pays for OCR, vision
|
|
212
|
+
layout models, or GPU inference — verified per-page, not per-document.
|
|
213
|
+
- **Facade stays thin**: `paradox2/api.py` is dispatch only; business logic
|
|
214
|
+
lives in `pipeline/`, `tables/`, `engines/`, `formats/`. If the facade
|
|
215
|
+
creeps toward 100+ lines, that's a signal something leaked out of place.
|
|
216
|
+
- **Specialists are lazy and swallow their own failures**: an optional
|
|
217
|
+
engine (handwriting, signatures, formulas, RapidAI, VLM) that fails to
|
|
218
|
+
load or errors mid-call returns `[]`/`None` — it never takes down the
|
|
219
|
+
base extraction path.
|
|
220
|
+
- **No simulated benchmarks**: every accuracy/speed number in this README
|
|
221
|
+
and in the codebase's docstrings comes from a real run against real
|
|
222
|
+
documents, with the script path noted alongside it.
|
|
223
|
+
|
|
224
|
+
## Status
|
|
225
|
+
|
|
226
|
+
Early-stage rewrite, private while the API and table router stabilize. See
|
|
227
|
+
`docs/PLAN.md` for the phase breakdown and `docs/TASKS.md` for current work.
|
paradox2-0.1.0/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# paradox2
|
|
2
|
+
|
|
3
|
+
Fast, simple document extraction — a from-scratch rewrite of [paradox-pdf](https://pypi.org/project/paradox-pdf/), built around one idea: **most PDFs are digital and don't need a GPU.**
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import paradox2 as pdx
|
|
7
|
+
|
|
8
|
+
pages = pdx.extract("invoice.pdf")
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Why this rewrite exists
|
|
12
|
+
|
|
13
|
+
paradox-pdf grew into a 2,783-line facade doing routing, backend resolution,
|
|
14
|
+
and overlay rendering all in one file. paradox2 starts over with a hard rule:
|
|
15
|
+
**digital pages never pay for OCR or vision models.** Every PDF page is
|
|
16
|
+
classified independently — a digital page (has a text layer) goes through a
|
|
17
|
+
zero-ML fast path (PyMuPDF text + vector-line table detection); a scanned
|
|
18
|
+
page routes to the GPU OCR pipeline. A single mixed document is handled
|
|
19
|
+
correctly per-page, automatically.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install paradox2
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Everything above works with just that — digital PDFs, text, vector-line
|
|
28
|
+
tables, key-value fields. Heavier features are opt-in extras so the base
|
|
29
|
+
install stays small:
|
|
30
|
+
|
|
31
|
+
| Extra | Adds | When you need it |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `paradox2[gpu]` | PaddleOCR + PP-DocLayoutV2 | Scanned/photographed pages |
|
|
34
|
+
| `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 |
|
|
35
|
+
| `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 |
|
|
36
|
+
| `paradox2[formats]` | docx/xlsx/pptx/msg/rtf/7z/rar/odf readers | Non-PDF documents |
|
|
37
|
+
| `paradox2[specialists]` | torch + transformers (handwriting, signatures, formulas) | Handwriting (TrOCR), signature detection (Conditional-DETR), formula-to-LaTeX (pix2tex) |
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "paradox2[gpu,rapidai,formats]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import paradox2 as pdx
|
|
47
|
+
|
|
48
|
+
# Every page, as structured JSON — digital pages via the fast path,
|
|
49
|
+
# scanned pages via GPU OCR, decided per page.
|
|
50
|
+
pages = pdx.extract("document.pdf")
|
|
51
|
+
|
|
52
|
+
# Just the text
|
|
53
|
+
text = pdx.extract_text("document.pdf")
|
|
54
|
+
|
|
55
|
+
# Just the tables (vector-line detection on digital pages,
|
|
56
|
+
# OCR-grid heuristic + optional RapidAI/VLM fallback on scanned pages)
|
|
57
|
+
tables = pdx.extract_tables("document.pdf")
|
|
58
|
+
|
|
59
|
+
# Key-value pairs from an invoice/form-like page (digital only)
|
|
60
|
+
fields = pdx.extract_kie("invoice.pdf")
|
|
61
|
+
|
|
62
|
+
# Any non-PDF format too — same call, same output shape
|
|
63
|
+
data = pdx.extract("spreadsheet.xlsx")
|
|
64
|
+
data = pdx.extract("scan.docx")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Selecting pages, features, and output format
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
pdx.extract("report.pdf", pages="1-3,7") # specific pages
|
|
71
|
+
pdx.extract("report.pdf", feature="tables") # just one feature, still full extract() under the hood
|
|
72
|
+
pdx.extract("report.pdf", output_format="markdown") # rendered markdown instead of the raw IR
|
|
73
|
+
pdx.extract("report.pdf", fields=True) # turn on key-value extraction inline
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Optional specialists (all off by default, all lazy-loaded)
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
pdx.extract("form.pdf", handwriting=True) # TrOCR on handwritten blocks
|
|
80
|
+
pdx.extract("contract.pdf", signatures=True) # Conditional-DETR signature boxes
|
|
81
|
+
pdx.extract("paper.pdf", formulas=True) # inline math -> LaTeX
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
None of these import their heavy dependencies unless the flag is set —
|
|
85
|
+
`import paradox2` alone never touches torch, paddle, or transformers.
|
|
86
|
+
|
|
87
|
+
## The scanned-table router
|
|
88
|
+
|
|
89
|
+
Scanned pages route tables through three tiers, each opt-in past the first:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
OCR-grid heuristic (free, always on)
|
|
93
|
+
| rejects merged/borderless/dense tables by design,
|
|
94
|
+
| or under-reads a real table (low_confidence flag)
|
|
95
|
+
v
|
|
96
|
+
YOLO26 bbox (PARADOX2_YOLO_DETECT=1) -- crop the page to the detected
|
|
97
|
+
| table region before handing it to the tiers below. A
|
|
98
|
+
| borderline-confidence detection (<0.85) skips RapidAI
|
|
99
|
+
| entirely and goes straight to the VLM tier - VLM tolerates
|
|
100
|
+
| a loose/imprecise crop better than RapidAI's structure model
|
|
101
|
+
v
|
|
102
|
+
RapidAI mid-tier (PARADOX2_RAPIDAI_TABLES=1)
|
|
103
|
+
| ONNX wired/wireless classifier + structure model. Its own
|
|
104
|
+
| result is cross-checked against OCR line density in the same
|
|
105
|
+
| crop (table_rapidai.py's low_confidence) - implausibly few
|
|
106
|
+
| OR implausibly many rows both re-route to the VLM tier
|
|
107
|
+
v
|
|
108
|
+
VLM fallback (PARADOX2_VLM_TABLES=1)
|
|
109
|
+
| PaddleOCR-VL single-pass, reserved for tables that look
|
|
110
|
+
| genuinely complex - not run on everything RapidAI touches
|
|
111
|
+
v
|
|
112
|
+
best available result
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Every tier is measured end-to-end (`paradox2.extract()`, not the isolated
|
|
116
|
+
engine wrapper) on the same 20 real full-page tables (OmniDocBench), not
|
|
117
|
+
simulated:
|
|
118
|
+
|
|
119
|
+
| config | exact row match | within +/-3 rows | mean time/page |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| heuristic only (pre-router) | 5% | 15% | 0.8s |
|
|
122
|
+
| + RapidAI, full page (bug, fixed) | 15% | 50% | 6.0s |
|
|
123
|
+
| + RapidAI, cropped to heuristic's own bbox (bug, fixed - made 3/20 pages *worse*) | 15% | 40% | 4.3s |
|
|
124
|
+
| + RapidAI, cropped to YOLO26 bbox | 25% | 75% | 5.5s |
|
|
125
|
+
| + selective VLM escalation (low YOLO confidence or implausible RapidAI row count) | **35%** | **80%** | 6.4s |
|
|
126
|
+
|
|
127
|
+
The isolated RapidAI engine alone scores 65% exact on tight ground-truth
|
|
128
|
+
crops — the gap to the router's end-to-end number is the YOLO26 crop's
|
|
129
|
+
margin versus a perfect crop, not a RapidAI accuracy problem. Cropping to
|
|
130
|
+
the heuristic's *own* bbox instead of an independent detector actively
|
|
131
|
+
hurts: that bbox only spans the rows the heuristic already found, so it
|
|
132
|
+
bakes its own under-read into the pixels before the next tier ever sees
|
|
133
|
+
them. A same-crop self-consistency check (RapidAI's row count vs. OCR
|
|
134
|
+
line density) can't catch a loose crop itself, since both numbers inflate
|
|
135
|
+
together on the same imprecise crop — that's what the YOLO-confidence
|
|
136
|
+
trigger (<0.85) is for instead.
|
|
137
|
+
|
|
138
|
+
The heuristic never gets replaced by a worse result — a lower tier's
|
|
139
|
+
output is kept unless a higher tier actually produces something.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
export PARADOX2_YOLO_DETECT=1
|
|
143
|
+
export PARADOX2_RAPIDAI_TABLES=1
|
|
144
|
+
export PARADOX2_VLM_TABLES=1
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Health check
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from paradox2 import doctor
|
|
151
|
+
doctor() # {"cuda": True/False, "pymupdf": "1.24.x", ...} - never silently hides a backend mismatch
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Design principles
|
|
155
|
+
|
|
156
|
+
- **Digital-first**: a PDF with a text layer never pays for OCR, vision
|
|
157
|
+
layout models, or GPU inference — verified per-page, not per-document.
|
|
158
|
+
- **Facade stays thin**: `paradox2/api.py` is dispatch only; business logic
|
|
159
|
+
lives in `pipeline/`, `tables/`, `engines/`, `formats/`. If the facade
|
|
160
|
+
creeps toward 100+ lines, that's a signal something leaked out of place.
|
|
161
|
+
- **Specialists are lazy and swallow their own failures**: an optional
|
|
162
|
+
engine (handwriting, signatures, formulas, RapidAI, VLM) that fails to
|
|
163
|
+
load or errors mid-call returns `[]`/`None` — it never takes down the
|
|
164
|
+
base extraction path.
|
|
165
|
+
- **No simulated benchmarks**: every accuracy/speed number in this README
|
|
166
|
+
and in the codebase's docstrings comes from a real run against real
|
|
167
|
+
documents, with the script path noted alongside it.
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
Early-stage rewrite, private while the API and table router stabilize. See
|
|
172
|
+
`docs/PLAN.md` for the phase breakdown and `docs/TASKS.md` for current work.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Public package surface for the from-scratch extractor."""
|
|
2
|
+
|
|
3
|
+
from paradox2.api import extract, extract_kie, extract_tables, extract_text, read
|
|
4
|
+
from paradox2.doctor import doctor
|
|
5
|
+
from paradox2.runtime.machine_state import warmup
|
|
6
|
+
|
|
7
|
+
__all__ = ["extract", "extract_kie", "extract_tables", "extract_text", "read", "doctor", "warmup"]
|