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.
Files changed (113) hide show
  1. paradox2-0.1.0/.gitignore +20 -0
  2. paradox2-0.1.0/LICENSE +34 -0
  3. paradox2-0.1.0/PKG-INFO +227 -0
  4. paradox2-0.1.0/README.md +172 -0
  5. paradox2-0.1.0/paradox2/__init__.py +7 -0
  6. paradox2-0.1.0/paradox2/__main__.py +5 -0
  7. paradox2-0.1.0/paradox2/api.py +320 -0
  8. paradox2-0.1.0/paradox2/cli.py +113 -0
  9. paradox2-0.1.0/paradox2/config.py +33 -0
  10. paradox2-0.1.0/paradox2/doctor.py +74 -0
  11. paradox2-0.1.0/paradox2/engines/__init__.py +0 -0
  12. paradox2-0.1.0/paradox2/engines/_gpu_worker.py +214 -0
  13. paradox2-0.1.0/paradox2/engines/_html_table.py +68 -0
  14. paradox2-0.1.0/paradox2/engines/_lazy.py +53 -0
  15. paradox2-0.1.0/paradox2/engines/_row_confidence.py +166 -0
  16. paradox2-0.1.0/paradox2/engines/doc_classifier.py +67 -0
  17. paradox2-0.1.0/paradox2/engines/formula_latex.py +38 -0
  18. paradox2-0.1.0/paradox2/engines/handwriting_trocr.py +49 -0
  19. paradox2-0.1.0/paradox2/engines/kie_heuristic.py +366 -0
  20. paradox2-0.1.0/paradox2/engines/signature_detr.py +43 -0
  21. paradox2-0.1.0/paradox2/engines/table_azure_di.py +161 -0
  22. paradox2-0.1.0/paradox2/engines/table_detector.py +130 -0
  23. paradox2-0.1.0/paradox2/engines/table_ppstructure.py +256 -0
  24. paradox2-0.1.0/paradox2/engines/table_rapidai.py +178 -0
  25. paradox2-0.1.0/paradox2/engines/table_vlm.py +207 -0
  26. paradox2-0.1.0/paradox2/exporters/__init__.py +0 -0
  27. paradox2-0.1.0/paradox2/exporters/markdown.py +99 -0
  28. paradox2-0.1.0/paradox2/exporters/structured.py +129 -0
  29. paradox2-0.1.0/paradox2/formats/__init__.py +0 -0
  30. paradox2-0.1.0/paradox2/formats/archive_adapter.py +176 -0
  31. paradox2-0.1.0/paradox2/formats/detector.py +150 -0
  32. paradox2-0.1.0/paradox2/formats/doc_adapter.py +55 -0
  33. paradox2-0.1.0/paradox2/formats/docx_adapter.py +38 -0
  34. paradox2-0.1.0/paradox2/formats/eml_adapter.py +63 -0
  35. paradox2-0.1.0/paradox2/formats/image_adapter.py +19 -0
  36. paradox2-0.1.0/paradox2/formats/open_document_adapter.py +71 -0
  37. paradox2-0.1.0/paradox2/formats/pptx_adapter.py +33 -0
  38. paradox2-0.1.0/paradox2/formats/render_bundle.py +95 -0
  39. paradox2-0.1.0/paradox2/formats/rtf_adapter.py +46 -0
  40. paradox2-0.1.0/paradox2/formats/text_adapters.py +48 -0
  41. paradox2-0.1.0/paradox2/formats/web_adapter.py +64 -0
  42. paradox2-0.1.0/paradox2/formats/xls_adapter.py +43 -0
  43. paradox2-0.1.0/paradox2/formats/xlsb_adapter.py +35 -0
  44. paradox2-0.1.0/paradox2/formats/xlsx_adapter.py +41 -0
  45. paradox2-0.1.0/paradox2/ir/__init__.py +0 -0
  46. paradox2-0.1.0/paradox2/pipeline/__init__.py +0 -0
  47. paradox2-0.1.0/paradox2/pipeline/annotations.py +192 -0
  48. paradox2-0.1.0/paradox2/pipeline/classify.py +113 -0
  49. paradox2-0.1.0/paradox2/pipeline/digital_fast.py +220 -0
  50. paradox2-0.1.0/paradox2/pipeline/feature_flags.py +78 -0
  51. paradox2-0.1.0/paradox2/pipeline/language_detect.py +43 -0
  52. paradox2-0.1.0/paradox2/pipeline/links.py +103 -0
  53. paradox2-0.1.0/paradox2/pipeline/orientation.py +87 -0
  54. paradox2-0.1.0/paradox2/pipeline/reading_order.py +124 -0
  55. paradox2-0.1.0/paradox2/pipeline/scanned.py +488 -0
  56. paradox2-0.1.0/paradox2/runtime/__init__.py +0 -0
  57. paradox2-0.1.0/paradox2/runtime/backend_detect.py +49 -0
  58. paradox2-0.1.0/paradox2/runtime/guardrails.py +28 -0
  59. paradox2-0.1.0/paradox2/runtime/machine_state.py +380 -0
  60. paradox2-0.1.0/paradox2/runtime/page_pool.py +159 -0
  61. paradox2-0.1.0/paradox2/runtime/resource_detect.py +339 -0
  62. paradox2-0.1.0/paradox2/service/__init__.py +0 -0
  63. paradox2-0.1.0/paradox2/service/client.py +73 -0
  64. paradox2-0.1.0/paradox2/service/protocol.py +61 -0
  65. paradox2-0.1.0/paradox2/service/server.py +81 -0
  66. paradox2-0.1.0/paradox2/tables/__init__.py +0 -0
  67. paradox2-0.1.0/paradox2/tables/cv_tables.py +106 -0
  68. paradox2-0.1.0/paradox2/tables/geometry.py +84 -0
  69. paradox2-0.1.0/paradox2/tables/line_tables.py +320 -0
  70. paradox2-0.1.0/paradox2/tables/scanned.py +322 -0
  71. paradox2-0.1.0/paradox2/tables/scanned_router.py +574 -0
  72. paradox2-0.1.0/paradox2/tables/structure.py +146 -0
  73. paradox2-0.1.0/pyproject.toml +171 -0
  74. paradox2-0.1.0/tests/fixtures/sample.doc +0 -0
  75. paradox2-0.1.0/tests/fixtures/sample.xls +0 -0
  76. paradox2-0.1.0/tests/fixtures/sample_digital.pdf +80 -0
  77. paradox2-0.1.0/tests/fixtures/sample_scanned.pdf +566 -0
  78. paradox2-0.1.0/tests/fixtures/scanned_tables/fixtures.json +15590 -0
  79. paradox2-0.1.0/tests/integration/test_service.py +57 -0
  80. paradox2-0.1.0/tests/test_table_rapidai_bbox.py +55 -0
  81. paradox2-0.1.0/tests/unit/test_api_encrypted_pdf.py +25 -0
  82. paradox2-0.1.0/tests/unit/test_api_features.py +48 -0
  83. paradox2-0.1.0/tests/unit/test_api_page_workers.py +24 -0
  84. paradox2-0.1.0/tests/unit/test_api_stays_thin.py +26 -0
  85. paradox2-0.1.0/tests/unit/test_archive_adapter.py +58 -0
  86. paradox2-0.1.0/tests/unit/test_archive_breadth.py +36 -0
  87. paradox2-0.1.0/tests/unit/test_backend_detect.py +37 -0
  88. paradox2-0.1.0/tests/unit/test_classification_projection.py +19 -0
  89. paradox2-0.1.0/tests/unit/test_classify.py +78 -0
  90. paradox2-0.1.0/tests/unit/test_config_doctor.py +19 -0
  91. paradox2-0.1.0/tests/unit/test_digital_fast.py +33 -0
  92. paradox2-0.1.0/tests/unit/test_doc_classifier.py +31 -0
  93. paradox2-0.1.0/tests/unit/test_eval_metrics.py +76 -0
  94. paradox2-0.1.0/tests/unit/test_feature_flags.py +41 -0
  95. paradox2-0.1.0/tests/unit/test_gpu_worker.py +115 -0
  96. paradox2-0.1.0/tests/unit/test_line_tables.py +58 -0
  97. paradox2-0.1.0/tests/unit/test_machine_state.py +185 -0
  98. paradox2-0.1.0/tests/unit/test_no_duplicate_text_extraction.py +48 -0
  99. paradox2-0.1.0/tests/unit/test_page_pool.py +74 -0
  100. paradox2-0.1.0/tests/unit/test_render_bundle.py +36 -0
  101. paradox2-0.1.0/tests/unit/test_resource_detect.py +86 -0
  102. paradox2-0.1.0/tests/unit/test_scan_text_threshold.py +43 -0
  103. paradox2-0.1.0/tests/unit/test_scanned.py +37 -0
  104. paradox2-0.1.0/tests/unit/test_scanned_dpi.py +27 -0
  105. paradox2-0.1.0/tests/unit/test_scanned_parallel.py +28 -0
  106. paradox2-0.1.0/tests/unit/test_scanned_router.py +202 -0
  107. paradox2-0.1.0/tests/unit/test_scanned_tables.py +220 -0
  108. paradox2-0.1.0/tests/unit/test_spreadsheet_optional.py +45 -0
  109. paradox2-0.1.0/tests/unit/test_table_azure_di.py +168 -0
  110. paradox2-0.1.0/tests/unit/test_table_ppstructure.py +119 -0
  111. paradox2-0.1.0/tests/unit/test_table_vlm.py +94 -0
  112. paradox2-0.1.0/tests/unit/test_web_formats.py +24 -0
  113. 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.
@@ -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.
@@ -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"]
@@ -0,0 +1,5 @@
1
+ """Allow ``python -m paradox2`` as well as the installed console script."""
2
+
3
+ from paradox2.cli import main
4
+
5
+ raise SystemExit(main())