paradox-pdf 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 (50) hide show
  1. paradox_pdf-0.1.0/PKG-INFO +375 -0
  2. paradox_pdf-0.1.0/README.md +340 -0
  3. paradox_pdf-0.1.0/paradox_pdf.egg-info/PKG-INFO +375 -0
  4. paradox_pdf-0.1.0/paradox_pdf.egg-info/SOURCES.txt +48 -0
  5. paradox_pdf-0.1.0/paradox_pdf.egg-info/dependency_links.txt +1 -0
  6. paradox_pdf-0.1.0/paradox_pdf.egg-info/entry_points.txt +2 -0
  7. paradox_pdf-0.1.0/paradox_pdf.egg-info/requires.txt +13 -0
  8. paradox_pdf-0.1.0/paradox_pdf.egg-info/top_level.txt +3 -0
  9. paradox_pdf-0.1.0/pdf_grid/__init__.py +26 -0
  10. paradox_pdf-0.1.0/pdf_grid/borderless_tables.py +798 -0
  11. paradox_pdf-0.1.0/pdf_grid/cv_tables.py +153 -0
  12. paradox_pdf-0.1.0/pdf_grid/extractor.py +235 -0
  13. paradox_pdf-0.1.0/pdf_grid/geometry.py +72 -0
  14. paradox_pdf-0.1.0/pdf_grid/line_tables.py +756 -0
  15. paradox_pdf-0.1.0/pdf_grid/renderers.py +218 -0
  16. paradox_pdf-0.1.0/pdf_grid/text_layout.py +97 -0
  17. paradox_pdf-0.1.0/pdf_grid/types.py +23 -0
  18. paradox_pdf-0.1.0/pdf_tagger/__init__.py +1 -0
  19. paradox_pdf-0.1.0/pdf_tagger/camscanner.py +288 -0
  20. paradox_pdf-0.1.0/pdf_tagger/catalog.py +117 -0
  21. paradox_pdf-0.1.0/pdf_tagger/cluster_cells.py +506 -0
  22. paradox_pdf-0.1.0/pdf_tagger/config.py +131 -0
  23. paradox_pdf-0.1.0/pdf_tagger/font_classifier.py +560 -0
  24. paradox_pdf-0.1.0/pdf_tagger/marks_vision.py +304 -0
  25. paradox_pdf-0.1.0/pdf_tagger/scan_detector.py +35 -0
  26. paradox_pdf-0.1.0/pdf_tagger/table_compare.py +325 -0
  27. paradox_pdf-0.1.0/pdf_tagger/table_vision.py +705 -0
  28. paradox_pdf-0.1.0/pdf_tagger/tagger.py +416 -0
  29. paradox_pdf-0.1.0/pdf_tagger/tagger_json.py +1347 -0
  30. paradox_pdf-0.1.0/pdf_tagger/textar_model/__init__.py +1 -0
  31. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/__init__.py +0 -0
  32. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/augment.py +123 -0
  33. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/cait_models.py +479 -0
  34. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/datasets.py +109 -0
  35. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/engine.py +112 -0
  36. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/hubconf.py +8 -0
  37. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/losses.py +70 -0
  38. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/main.py +489 -0
  39. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/models.py +179 -0
  40. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/models_v2.py +493 -0
  41. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/patchconvnet_models.py +545 -0
  42. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/resmlp_models.py +197 -0
  43. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/run_with_submitit.py +126 -0
  44. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/samplers.py +64 -0
  45. paradox_pdf-0.1.0/pdf_tagger/textar_model/deit/utils.py +238 -0
  46. paradox_pdf-0.1.0/pdf_tagger/textar_model/textar.py +235 -0
  47. paradox_pdf-0.1.0/pdf_tagger/vision_layout.py +926 -0
  48. paradox_pdf-0.1.0/pyproject.toml +53 -0
  49. paradox_pdf-0.1.0/scripts/convert.py +278 -0
  50. paradox_pdf-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,375 @@
1
+ Metadata-Version: 2.4
2
+ Name: paradox-pdf
3
+ Version: 0.1.0
4
+ Summary: Structured text extraction framework for digital and scanned PDFs with inline formatting preservation
5
+ Author-email: CreAI <feliperodriguez@creai.mx>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/CreAI-mx/DocumentExtractor
8
+ Project-URL: Repository, https://github.com/CreAI-mx/DocumentExtractor
9
+ Keywords: pdf,extraction,ocr,nlp,document,parsing,table,structured
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Text Processing :: Markup
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: PyMuPDF>=1.23.0
23
+ Requires-Dist: opencv-python>=4.8.0
24
+ Requires-Dist: Pillow>=10.0.0
25
+ Requires-Dist: torch>=2.0
26
+ Requires-Dist: torchvision>=0.15
27
+ Requires-Dist: timm>=0.9
28
+ Requires-Dist: doclayout-yolo
29
+ Requires-Dist: huggingface_hub
30
+ Requires-Dist: rapidocr-onnxruntime
31
+ Requires-Dist: numpy>=1.24
32
+ Requires-Dist: scikit-learn>=1.3
33
+ Requires-Dist: scipy
34
+ Requires-Dist: transformers>=4.30
35
+
36
+ # Paradox
37
+
38
+ > Structured text extraction framework for digital and scanned PDFs with inline formatting preservation.
39
+
40
+ Paradox is a dual-pipeline framework that extracts semantically typed, hierarchically structured content from PDF documents. It detects whether each page is digital or scanned and routes it to the optimal extraction strategy, producing a single unified JSON output regardless of source quality.
41
+
42
+ ## Key Features
43
+
44
+ - **Dual pipeline**: heuristic (digital PDFs) + vision (scanned/photographed), routed per page
45
+ - **68+ element types**: headings, paragraphs, tables, lists, amendments, signatures, metadata, and more
46
+ - **6 inline marks**: **bold**, *italic*, ++underline++, ~~strikethrough~~, ^superscript^, `monospace`
47
+ - **Table extraction** with merged cell detection (colspan/rowspan) via vector analysis and OpenCV
48
+ - **Hierarchical JSON output** with traceable page refs `(pX,lY):(pX,lY)`
49
+ - **Ensemble table detection**: HDBSCAN clustering + Table Transformer + OpenCV, scored and arbitrated
50
+ - **Configurable via 30+ parameters** (dataclass + environment variable overrides)
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ python scripts/convert.py document.pdf
62
+ ```
63
+
64
+ That's it. Output goes to `output/document.json`.
65
+
66
+ ### Options
67
+
68
+ | Flag | Description |
69
+ |---|---|
70
+ | `-o PATH` | Custom output path (file or directory) |
71
+ | `-w N` | Parallel workers (default: 20) |
72
+ | `--pages 1-5` | Extract specific pages only |
73
+ | `--force-vision` | Force vision pipeline on all pages |
74
+ | `--force-heuristic` | Force heuristic pipeline on all pages |
75
+ | `--compare PAGE` | Generate visual QA PNGs for table on page |
76
+ | `--no-images` | Skip embedded image extraction |
77
+ | `--compact` | Compact JSON (no indentation) |
78
+
79
+ ```bash
80
+ python scripts/convert.py contract.pdf -o result.json # Custom output
81
+ python scripts/convert.py docs/ -o extracted/ -w 8 # Batch folder, 8 workers
82
+ python scripts/convert.py scan.pdf --force-vision # Force OCR pipeline
83
+ python scripts/convert.py contract.pdf --compare all # Visual QA for all tables
84
+ ```
85
+
86
+ ## How It Works
87
+
88
+ ![Pipeline Overview](docs/images/pipeline_overview.png)
89
+
90
+ ### Digital Pipeline — Real Example
91
+
92
+ ![Digital Pipeline Example](docs/images/example_digital_pipeline.png)
93
+
94
+ Extracted output (abbreviated):
95
+ ```jsonc
96
+ {
97
+ "elements": [
98
+ {"type": "TITLE", "marks": ["BOLD"], "text": "**Annual Report — Q4 2025**"},
99
+ {"type": "PARAGRAPH", "text": "Revenue increased by **12.3%**...driven by *international expansion*..."},
100
+ {"type": "H1", "marks": ["BOLD"], "text": "**1. Financial Summary**",
101
+ "children": [
102
+ {"type": "TABLE", "shape": [5, 4], "cells": [
103
+ {"p": [0,0], "t": "Category"}, {"p": [0,1], "t": "Q3 2025"}, ...
104
+ ]}
105
+ ]},
106
+ {"type": "H1", "marks": ["BOLD"], "text": "**2. Notes**",
107
+ "children": [
108
+ {"type": "PARAGRAPH", "text": "All figures reported in USD. See *Appendix A*."}
109
+ ]}
110
+ ]
111
+ }
112
+ ```
113
+
114
+ ### Vision Pipeline — Real Example
115
+
116
+ ![Vision Pipeline Example](docs/images/example_vision_pipeline.png)
117
+
118
+ The key insight: digital PDFs contain rich font metadata (bold flags, font names, vector drawings) that enable near-perfect extraction. Scanned PDFs have none of this -- they are just images. Rather than forcing one approach on both, Paradox routes each **page** independently to the optimal pipeline, then produces an identical output format. A single document can have digital contract pages interleaved with scanned signed exhibits, and every page gets the best available extraction.
119
+
120
+ ### Digital Path (Heuristic)
121
+
122
+ The heuristic pipeline leverages PyMuPDF to extract text spans with full font metadata. Each span carries flags for bold, italic, superscript, and monospace. Underline and strikethrough are detected geometrically by finding horizontal vector lines drawn across text baselines -- this is necessary because most PDF producers draw these as separate line objects rather than setting a font flag.
123
+
124
+ Block classification uses a priority ladder with 10 levels, progressing from document metadata through titles and headings down to body content. Font size, weight, and position on the page all contribute to the classification decision.
125
+
126
+ **Speed**: ~250 pages/second. No GPU required.
127
+
128
+ ### Vision Path (YOLO + OCR + TexTAR)
129
+
130
+ The vision pipeline renders each page to an image and processes it through three stages:
131
+
132
+ 1. **DocLayout-YOLO** detects page regions (title, text, table, figure, caption, etc.)
133
+ 2. **RapidOCR** (ONNX runtime) extracts word-level text from each detected region
134
+ 3. **TexTAR** (Vision Transformer, ICDAR 2025) classifies inline marks per word: bold, italic, underline, and strikethrough
135
+
136
+ Strikethrough detection uses an OpenCV fallback because TexTAR's accuracy on strikethrough alone is poor. Superscript is detected via a bounding-box height heuristic (word height below 60% of the line median). Monospace cannot be detected from images and is unavailable in the vision path.
137
+
138
+ **Speed**: ~12 pages/second (GPU), ~0.5 pages/second (CPU-only).
139
+
140
+ ### Table Extraction Strategy
141
+
142
+ ![Table Extraction Ensemble](docs/images/table_extraction.png)
143
+
144
+ Scanned table regions detected by YOLO are processed through two parallel structure-extraction strategies:
145
+
146
+ - **HDBSCAN clustering** (`cluster_cells.py`): a purely geometric approach that clusters OCR bounding boxes into rows and columns. Warp-invariant and effective on borderless or loosely formatted tables.
147
+ - **Table Transformer + OpenCV** (`table_vision.py`): a semantic approach that detects row and column separators. Better for tables with clear borders.
148
+
149
+ A scoring function (`_score_struct()`) evaluates both results and picks the winner based on cell fill rate, a merge penalty (linear: `merge_ratio * 1.5`), and word-count compatibility. Source priority provides tie-breaking: vectorial (+0.3) > vision table (+0.1) > HDBSCAN (0.0).
150
+
151
+ When both the vectorial pipeline (for digital content) and the vision pipeline detect the same table, a deduplication step (`_dedupe_tables()`) groups candidates by bounding-box IoU >= 0.5 and keeps only the highest-scoring result per group.
152
+
153
+ ## Output Format
154
+
155
+ Both pipelines produce **identical JSON**. The output is a hierarchical tree where headings contain their children (paragraphs, tables, lists), enabling semantic navigation of the document.
156
+
157
+ ```jsonc
158
+ {
159
+ "source": "contract.pdf", // 📄 Source filename
160
+ "total_pages": 228, // 📊 Document stats
161
+ "total_elements": 544,
162
+ "type_summary": { // 📋 Element count by type
163
+ "PARAGRAPH": 200,
164
+ "H1": 50,
165
+ "TABLE": 12
166
+ },
167
+
168
+ // 🌳 HIERARCHICAL TREE — headings nest their children by depth
169
+ // TITLE > SUBTITLE > H1 > H2 > H3 > H4
170
+ // Each heading "owns" everything until the next heading of equal or higher level
171
+ "elements": [
172
+ {
173
+ "type": "H1", // 🏷️ Element type (68+ types available)
174
+ "marks": ["BOLD"], // ✨ Detected formatting: BOLD, ITALIC, UNDERLINE,
175
+ // STRIKETHROUGH, SUPERSCRIPT, MONOSPACE
176
+ "text": "**ARTICLE 13. MINIMUM COMPENSATION**",
177
+ // 📝 Text with inline markers:
178
+ // **bold** *italic* ++underline++
179
+ // ~~strike~~ ^super^ `mono`
180
+ "ref": "(p91,l1):(p95,l12)", // 📍 Traceable location in source PDF
181
+ // (page 91, element 1) to (page 95, element 12)
182
+
183
+ // 🌿 CHILDREN — everything under this H1 until the next H1
184
+ "children": [
185
+ {
186
+ "type": "TABLE",
187
+ "marks": [],
188
+ "shape": [14, 7], // 📐 Table dimensions: 14 rows × 7 columns
189
+ "cells": [
190
+ // 🔗 Merged cells: "p": [row, col, rowspan, colspan]
191
+ {"p": [0, 0, 2, 2], "t": "HIGH BUDGET"}, // spans 2 rows × 2 cols
192
+ {"p": [0, 2, 1, 5], "t": "EFFECTIVE"}, // spans 1 row × 5 cols
193
+
194
+ // 📦 Normal cells: "p": [row, col]
195
+ {"p": [2, 1], "t": "Screenplay, including treatment"},
196
+ {"p": [2, 2], "t": "$126,089"}
197
+ ]
198
+ },
199
+ {
200
+ // ✂️ Strikethrough — deleted text preserved with ~~markers~~
201
+ "type": "PARAGRAPH",
202
+ "marks": ["STRIKETHROUGH"],
203
+ "text": "~~The term of this Agreement shall be for a period commencing July 1, 2017~~",
204
+ "ref": "(p91,l5):(p91,l5)"
205
+ }
206
+ ]
207
+ }
208
+ ]
209
+ }
210
+ ```
211
+
212
+ ### Ref format
213
+
214
+ ```
215
+ "ref": "(p1,l3):(p6,l2)"
216
+ | | | |
217
+ | | | +-- element 2 on that page
218
+ | | +----- page 6
219
+ | +----------- element 3 on that page
220
+ +-------------- page 1
221
+ ```
222
+
223
+ Both `p` (page) and `l` (element index) are 1-based.
224
+
225
+ ### Table cell format
226
+
227
+ - `"p": [r, c]` -- normal cell at row `r`, column `c`
228
+ - `"p": [r, c, rs, cs]` -- merged cell spanning `rs` rows and `cs` columns
229
+ - `"t"` -- cell text, with `\n` for internal line breaks
230
+
231
+ ### Inline markers
232
+
233
+ | Mark | Syntax | Example |
234
+ |------|--------|---------|
235
+ | BOLD | `**text**` | `**ARTICLE 1**` |
236
+ | ITALIC | `*text*` | `*See exhibit*` |
237
+ | BOLD+ITALIC | `***text***` | `***IMPORTANT***` |
238
+ | STRIKETHROUGH | `~~text~~` | `~~Deleted~~` |
239
+ | UNDERLINE | `++text++` | `++Underlined++` |
240
+ | SUPERSCRIPT | `^text^` | `^1^` |
241
+ | MONOSPACE | `` `text` `` | `` `DocID-123` `` |
242
+
243
+ ## Configuration
244
+
245
+ All pipeline thresholds are centralized in `pdf_tagger/config.py` as a `PipelineConfig` dataclass. Override any value via environment variables with the `PDF_` prefix:
246
+
247
+ ```bash
248
+ PDF_RENDER_DPI=300 python scripts/convert.py input.pdf
249
+ PDF_CV_BORDER_MISSING_THRESHOLD=0.25 python scripts/convert.py input.pdf
250
+ ```
251
+
252
+ | Parameter | Default | Purpose |
253
+ |---|---|---|
254
+ | `PDF_SCAN_TEXT_THRESHOLD` | 50 | Characters below which a page is routed to the vision pipeline |
255
+ | `PDF_RENDER_DPI` | 200 | DPI for rendering pages to images (higher = better OCR, slower) |
256
+ | `PDF_CV_BORDER_MISSING_THRESHOLD` | 0.35 | OpenCV border coverage ratio below which a cell boundary is considered missing (triggers merge) |
257
+ | `PDF_YOLO_CONFIDENCE` | 0.2 | Minimum confidence for YOLO region detections |
258
+ | `PDF_OCR_MIN_CONFIDENCE` | 0.3 | Minimum confidence for OCR word results |
259
+ | `PDF_HDBSCAN_MIN_FILL_RATE` | 0.40 | Minimum cell fill rate to accept an HDBSCAN table structure |
260
+ | `PDF_DEDUP_IOU_THRESHOLD` | 0.5 | IoU threshold for suppressing duplicate table detections |
261
+
262
+ ## Performance
263
+
264
+ Benchmark results (April 2026):
265
+
266
+ | Suite | Shape Match | Cell Accuracy | Text Similarity |
267
+ |---|---|---|---|
268
+ | Digital via Vision | 100% | 88.9% | 100% |
269
+ | Real Scanned | 80% | 67.8% | 86.6% |
270
+ | Stress Tests | 100% | 82.0% | 100% |
271
+
272
+ Shape match measures whether the extracted grid dimensions are correct. Cell accuracy measures exact content match per cell. Text similarity is a fuzzy comparison of all extracted text against ground truth.
273
+
274
+ ## Architecture
275
+
276
+ ```
277
+ paradox/
278
+
279
+ ├── scripts/
280
+ │ └── convert.py 🚀 CLI entry point — single PDF or batch folder
281
+
282
+ ├── models/
283
+ │ └── textar-trained.pt 🧠 TexTAR Vision Transformer weights (64 MB, ICDAR 2025)
284
+
285
+ ├── pdf_tagger/ 📦 Core extraction pipeline
286
+ │ │
287
+ │ ├── tagger_json.py 🎯 Main orchestrator
288
+ │ │ Routes pages (digital/scanned), runs post-processing,
289
+ │ │ deduplicates tables, builds section tree, assigns refs
290
+ │ │
291
+ │ ├── config.py ⚙️ PipelineConfig dataclass — 30+ tunable thresholds
292
+ │ ├── scan_detector.py 🔍 Per-page routing: < 50 chars text → vision path
293
+ │ ├── catalog.py 📋 68+ element types (TITLE, H1, TABLE, PARAGRAPH, ...)
294
+ │ │
295
+ │ │ ── Digital Path ──
296
+ │ ├── font_classifier.py 🔤 PyMuPDF font extraction → block classification
297
+ │ │ Priority ladder: 10 levels, metadata → titles → body
298
+ │ ├── tagger.py 📐 Geometric detection of strikethrough + underline
299
+ │ │ Finds real vector lines drawn across text baselines
300
+ │ │
301
+ │ │ ── Vision Path ──
302
+ │ ├── vision_layout.py 👁️ DocLayout-YOLO region detection + RapidOCR
303
+ │ │ Ensemble scoring: HDBSCAN vs TATR for tables
304
+ │ ├── marks_vision.py 🎨 TexTAR per-word mark classification
305
+ │ │ T1: BOLD/ITALIC · T2: UNDERLINE/STRIKETHROUGH
306
+ │ │ + OpenCV fallback + superscript heuristic
307
+ │ ├── table_vision.py 📊 Table Transformer (DETR) + OpenCV grid detection
308
+ │ │ Merged cell detection via border coverage analysis
309
+ │ ├── cluster_cells.py 🔬 HDBSCAN density clustering for borderless tables
310
+ │ │ Row/column inference from OCR word positions
311
+ │ │
312
+ │ │ ── Shared ──
313
+ │ ├── camscanner.py 📷 Deskew + perspective correction for photos
314
+ │ ├── table_compare.py 🖼️ Visual QA: side-by-side table PNG comparison
315
+ │ └── textar_model/ 🏗️ TexTAR architecture (DeiT backbone + dual heads)
316
+
317
+ ├── pdf_grid/ 📐 Low-level table geometry
318
+ │ ├── line_tables.py ── Vector lines → grid → merged cells (digital PDFs)
319
+ │ ├── cv_tables.py ── OpenCV morphology → border-missing detection
320
+ │ ├── borderless_tables.py ── Column alignment → borderless table detection
321
+ │ ├── geometry.py ── Clustering, coverage, geometric primitives
322
+ │ ├── extractor.py ── Grid extraction coordinator
323
+ │ ├── text_layout.py ── Text-to-cell assignment
324
+ │ └── types.py ── Shared type definitions (Word, BBox)
325
+
326
+ ├── docs/ 📚 Documentation
327
+ │ ├── architecture.md Deep dive: why dual pipeline, design decisions
328
+ │ ├── why-this-approach.md Tradeoffs: vs LLMs, vs Docling/MinerU/Marker
329
+ │ ├── configuration.md All 30+ parameters with types and defaults
330
+ │ ├── api-reference.md Python API, JSON schema, CLI reference
331
+ │ └── research/ 12 research documents (YAML status headers)
332
+
333
+ └── examples/ 💡 Sample PDFs + expected JSON outputs
334
+ ```
335
+
336
+ For a detailed architectural walkthrough, see [`docs/architecture.md`](docs/architecture.md). For design decisions and tradeoffs, see [`docs/why-this-approach.md`](docs/why-this-approach.md).
337
+
338
+ ## Marks Coverage
339
+
340
+ | Mark | Syntax | Digital | Vision | Method (Digital) | Method (Vision) |
341
+ |------|--------|:------:|:------:|-----------------|-----------------|
342
+ | **Bold** | `**text**` | ✅ | ✅ | Font flags + name | TexTAR T1 head |
343
+ | **Italic** | `*text*` | ✅ | ✅ | Font flags | TexTAR T1 head |
344
+ | **Underline** | `++text++` | ✅ | ✅ | Geometric line detection | TexTAR T2 head |
345
+ | **Strikethrough** | `~~text~~` | ✅ | ✅ | Geometric line detection | TexTAR T2 + OpenCV |
346
+ | **Superscript** | `^text^` | ✅ | ✅ | Font flags | BBox height heuristic |
347
+ | **Monospace** | `` `text` `` | ✅ | ❌ | Font name heuristic | Not available |
348
+
349
+ > Inline marks within table cells are not yet extracted in either pipeline.
350
+
351
+ ## Limitations
352
+
353
+ - **MONOSPACE**: only detected in digital PDFs via font name heuristic. No visual equivalent exists for scanned pages because monospace fonts are not reliably distinguishable from proportional fonts at typical scan resolutions.
354
+ - **Multi-column layouts**: reading order may interleave columns on scanned pages. The heuristic pipeline benefits from PyMuPDF's text flow analysis; the vision pipeline relies on YOLO region order, which can fail on complex layouts.
355
+ - **Complex scanned tables**: tables with more than ~8 rows and complex multi-level headers may lose 1-2 rows due to HDBSCAN clustering sensitivity or YOLO region boundary errors.
356
+ - **Table cell marks**: inline formatting (bold, italic, etc.) within individual table cells is not extracted in either pipeline.
357
+ - **GPU recommendation**: the vision pipeline runs on CPU but is approximately 24x slower. A CUDA-capable GPU is strongly recommended for batch processing of scanned documents.
358
+
359
+ ## Development
360
+
361
+ Testing suites and benchmarks live in the `_dev/` directory:
362
+
363
+ ```bash
364
+ python scripts/run_tests.py # All suites combined
365
+ python scripts/test_vision_vs_heuristic.py # Vision vs heuristic on digital PDFs
366
+ python scripts/test_vision_full.py # 3 suites: digital, scanned, stress
367
+ python scripts/test_complex_tables.py # 10 complex tables with merged cells
368
+ python scripts/bench_distortion.py # Robustness under geometric distortions
369
+ ```
370
+
371
+ See `_dev/README.md` for detailed testing instructions and ground-truth format.
372
+
373
+ ## License
374
+
375
+ This project is proprietary. All rights reserved.