tinydoc 0.1.1__tar.gz → 0.2.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.
- tinydoc-0.2.0/MANIFEST.in +3 -0
- tinydoc-0.2.0/PKG-INFO +199 -0
- tinydoc-0.2.0/README.md +144 -0
- tinydoc-0.2.0/setup.py +68 -0
- tinydoc-0.2.0/tinydoc/__init__.py +28 -0
- tinydoc-0.2.0/tinydoc/cli.py +105 -0
- tinydoc-0.2.0/tinydoc/evidence.py +277 -0
- tinydoc-0.2.0/tinydoc/overlay.py +117 -0
- tinydoc-0.2.0/tinydoc/pipeline.py +780 -0
- tinydoc-0.2.0/tinydoc/schemas/sroie_receipt.schema.json +49 -0
- tinydoc-0.2.0/tinydoc.egg-info/PKG-INFO +199 -0
- {tinydoc-0.1.1 → tinydoc-0.2.0}/tinydoc.egg-info/SOURCES.txt +7 -1
- tinydoc-0.2.0/tinydoc.egg-info/entry_points.txt +2 -0
- tinydoc-0.2.0/tinydoc.egg-info/requires.txt +27 -0
- tinydoc-0.1.1/MANIFEST.in +0 -1
- tinydoc-0.1.1/PKG-INFO +0 -65
- tinydoc-0.1.1/README.md +0 -24
- tinydoc-0.1.1/setup.py +0 -40
- tinydoc-0.1.1/tinydoc/__init__.py +0 -4
- tinydoc-0.1.1/tinydoc.egg-info/PKG-INFO +0 -65
- tinydoc-0.1.1/tinydoc.egg-info/requires.txt +0 -10
- {tinydoc-0.1.1 → tinydoc-0.2.0}/setup.cfg +0 -0
- {tinydoc-0.1.1 → tinydoc-0.2.0}/tinydoc/extractor.py +0 -0
- {tinydoc-0.1.1 → tinydoc-0.2.0}/tinydoc/models.py +0 -0
- {tinydoc-0.1.1 → tinydoc-0.2.0}/tinydoc.egg-info/dependency_links.txt +0 -0
- {tinydoc-0.1.1 → tinydoc-0.2.0}/tinydoc.egg-info/top_level.txt +0 -0
tinydoc-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinydoc
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local-first grounded document extraction SDK — schema, evidence, confidence. Free Ollama/Qwen engine by default; no API key.
|
|
5
|
+
Home-page: https://github.com/eulogik/TinyDoc-VLM
|
|
6
|
+
Author: eulogik
|
|
7
|
+
Author-email: hello@eulogik.com
|
|
8
|
+
Project-URL: Model status, https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/v3_plan.md
|
|
9
|
+
Project-URL: HuggingFace Space, https://huggingface.co/spaces/eulogik/TinyDoc-VLM
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/eulogik/TinyDoc-VLM/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/eulogik/TinyDoc-VLM#readme
|
|
12
|
+
Project-URL: Benchmarks, https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/BENCHMARKS.md
|
|
13
|
+
Project-URL: Website, https://eulogik.github.io/TinyDoc-VLM/
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: pydantic>=2.8.0
|
|
23
|
+
Requires-Dist: pillow>=10.0.0
|
|
24
|
+
Requires-Dist: jsonschema>=4.20.0
|
|
25
|
+
Provides-Extra: ocr
|
|
26
|
+
Requires-Dist: pytesseract>=0.3.10; extra == "ocr"
|
|
27
|
+
Provides-Extra: ocr-pp
|
|
28
|
+
Requires-Dist: rapidocr-onnxruntime>=1.3.0; extra == "ocr-pp"
|
|
29
|
+
Provides-Extra: smolvlm2
|
|
30
|
+
Requires-Dist: torch>=2.2.0; extra == "smolvlm2"
|
|
31
|
+
Requires-Dist: transformers>=4.48.0; extra == "smolvlm2"
|
|
32
|
+
Requires-Dist: numpy>=1.26.0; extra == "smolvlm2"
|
|
33
|
+
Requires-Dist: sentencepiece>=0.2.0; extra == "smolvlm2"
|
|
34
|
+
Provides-Extra: onnx
|
|
35
|
+
Requires-Dist: onnxruntime>=1.19.0; extra == "onnx"
|
|
36
|
+
Requires-Dist: optimum>=1.22.0; extra == "onnx"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: pytesseract>=0.3.10; extra == "all"
|
|
39
|
+
Requires-Dist: rapidocr-onnxruntime>=1.3.0; extra == "all"
|
|
40
|
+
Requires-Dist: torch>=2.2.0; extra == "all"
|
|
41
|
+
Requires-Dist: transformers>=4.48.0; extra == "all"
|
|
42
|
+
Requires-Dist: numpy>=1.26.0; extra == "all"
|
|
43
|
+
Requires-Dist: sentencepiece>=0.2.0; extra == "all"
|
|
44
|
+
Dynamic: author
|
|
45
|
+
Dynamic: author-email
|
|
46
|
+
Dynamic: classifier
|
|
47
|
+
Dynamic: description
|
|
48
|
+
Dynamic: description-content-type
|
|
49
|
+
Dynamic: home-page
|
|
50
|
+
Dynamic: project-url
|
|
51
|
+
Dynamic: provides-extra
|
|
52
|
+
Dynamic: requires-dist
|
|
53
|
+
Dynamic: requires-python
|
|
54
|
+
Dynamic: summary
|
|
55
|
+
|
|
56
|
+
# TinyDoc 0.2.0 — local-first grounded document extraction
|
|
57
|
+
|
|
58
|
+
Extract receipt/invoice fields as **schema-validated JSON** with per-field
|
|
59
|
+
**evidence** (quote + OCR bbox) and **confidence**. Runs locally with Ollama;
|
|
60
|
+
no API key, no cloud, no telemetry.
|
|
61
|
+
|
|
62
|
+
> **Status (2026-11-06):** the SDK and evaluation harness are measured. The
|
|
63
|
+
> first-party 256M model is retired (OCRBench 0.0%) and the SmolVLM2-2.2B SFT
|
|
64
|
+
> track is closed (no gain over base). The working product is the engine +
|
|
65
|
+
> trust layer, not the old checkpoint. Full analysis:
|
|
66
|
+
> [docs/v3_plan.md](https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/v3_plan.md).
|
|
67
|
+
|
|
68
|
+
## Measured results (SROIE public-train-derived clean-100, n=100)
|
|
69
|
+
|
|
70
|
+
| Engine | Field F1 | Address | Schema | Median latency | Errors |
|
|
71
|
+
|---|---:|---:|---:|---:|---:|
|
|
72
|
+
| **Qwen2.5-VL-3B** (`ollama:qwen2.5vl:3b`) | **0.9475** | 0.86 | 1.00 | 14.1 s | 0 |
|
|
73
|
+
| **Qwen3-VL-2B-Instruct** (`ollama:qwen3-vl:2b-instruct`) | **0.9200** | 0.84 | 1.00 | 7.0 s | 0 |
|
|
74
|
+
| Hybrid v2 first-party (`hybrid_2p2b`) | 0.603 | 0.37 | 0.93 | ~8 s | — |
|
|
75
|
+
| PP-OCR + heuristics | 0.376 | 0.03 | — | — | — |
|
|
76
|
+
| Tesseract + regex | 0.227 | — | 0.60 | — | — |
|
|
77
|
+
|
|
78
|
+
> **Caveat:** these are **not external benchmark numbers**. The 100-doc set is a
|
|
79
|
+
> duplicate-filtered split of the public SROIE **train** pool; Qwen may have seen
|
|
80
|
+
> it during pretraining. The original 100-doc set is 60% leaked. Use the numbers
|
|
81
|
+
> for same-document engine comparisons, not as a generalization claim. The next
|
|
82
|
+
> eval adds CORD/FUNSD/invoices and a private wild set.
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
From PyPI (use this once 0.2.0 is published):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install tinydoc
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
From the repo:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install ./sdk
|
|
96
|
+
# editable for development:
|
|
97
|
+
pip install -e ./sdk
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If `pip install tinydoc` resolves to **0.1.2**, that is the legacy 256M API; use
|
|
101
|
+
the source install above until 0.2.0 is uploaded. Core deps: `pydantic`,
|
|
102
|
+
`pillow`, `jsonschema`. Optional extras:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install './sdk[ocr]' # pytesseract for evidence + ocr_regex
|
|
106
|
+
pip install './sdk[ocr-pp]' # RapidOCR ONNX evidence
|
|
107
|
+
pip install './sdk[smolvlm2]' # optional local HF SmolVLM2 engine
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For the default engine, install [Ollama](https://ollama.com) and pull a base:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
ollama pull qwen2.5vl:3b # quality default
|
|
114
|
+
ollama pull qwen3-vl:2b-instruct # fast tier (use -instruct, not the thinking tag)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
System `tesseract` is needed for OCR-span evidence (`brew install tesseract` on macOS).
|
|
118
|
+
|
|
119
|
+
## Quick start
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from tinydoc import ReceiptPipeline
|
|
123
|
+
|
|
124
|
+
# Quality default: Qwen2.5-VL-3B
|
|
125
|
+
pipe = ReceiptPipeline("ollama:qwen2.5vl:3b")
|
|
126
|
+
|
|
127
|
+
# Fast tier: Qwen3-VL-2B-Instruct, ~2x faster
|
|
128
|
+
# pipe = ReceiptPipeline("ollama:qwen3-vl:2b-instruct")
|
|
129
|
+
|
|
130
|
+
result = pipe.extract("receipt.jpg")
|
|
131
|
+
print(result.fields) # {"company": ..., "date": ..., "address": ..., "total": ...}
|
|
132
|
+
print(result.schema_valid) # True/False (JSON Schema)
|
|
133
|
+
print(result.confidence) # 0..1 aggregate
|
|
134
|
+
for field in result.field_results:
|
|
135
|
+
print(field.name, field.value, field.confidence, field.evidence)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
CLI:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
tinydoc extract ./receipts/ --engine ollama:qwen2.5vl:3b \
|
|
142
|
+
--out results.jsonl --overlay overlays/
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Overlay PNGs draw OCR-span evidence boxes and field labels (`tinydoc.draw_overlay`).
|
|
146
|
+
|
|
147
|
+
## Engines
|
|
148
|
+
|
|
149
|
+
| Engine id | What it does |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `ollama` / `ollama:qwen2.5vl:3b` | Quality default; free, local |
|
|
152
|
+
| `ollama:qwen3-vl:2b-instruct` | Fast tier; 1.9 GB Q4, ~2x faster |
|
|
153
|
+
| `auto` | Ollama if reachable; OCR floor if the schema would fail |
|
|
154
|
+
| `ocr_regex` | Offline floor (Tesseract + rules) |
|
|
155
|
+
| `mlx_2p2b` | First-party v2 edge engine (MLX SmolVLM2-2.2B 4-bit) |
|
|
156
|
+
| `hybrid_2p2b` | v2 product path: MLX fields + frozen F3 OCR address |
|
|
157
|
+
|
|
158
|
+
**Do not** set Ollama `format: "json"` — constrained decoding truncates long
|
|
159
|
+
addresses and can drop `total`. The SDK uses unconstrained prompts, JSON salvage,
|
|
160
|
+
and prompt-v1.
|
|
161
|
+
|
|
162
|
+
### Environment
|
|
163
|
+
|
|
164
|
+
- `TINYDOC_OLLAMA_TIMEOUT` (default 180 s) — raise to 600 for slow local runs.
|
|
165
|
+
- `TINYDOC_OLLAMA_NUM_CTX` (default 8192) — Ollama context window. The implicit
|
|
166
|
+
4096 rejected 2481×3508 scans with HTTP 400; keep 8192 for full pages.
|
|
167
|
+
|
|
168
|
+
## Evidence and honesty
|
|
169
|
+
|
|
170
|
+
- Evidence kind is always `ocr_span` (RapidOCR word boxes), **not** VLM-predicted
|
|
171
|
+
boxes. Treat it as provenance, not as model grounding. A 100-box probe measured
|
|
172
|
+
VLM bbox hit@0.5 of **0.20** (2B) and **0.04** (3B), below the 0.70 product gate,
|
|
173
|
+
so OCR spans remain the shipped path.
|
|
174
|
+
- The schema is packaged (`tinydoc/schemas/sroie_receipt.schema.json`); validation
|
|
175
|
+
uses `jsonschema`.
|
|
176
|
+
- Confidence is a heuristic, not a probability. Evidence-off runs are constant
|
|
177
|
+
(0.4025; field ECE 0.52–0.55). The default evidence-on path measures field ECE
|
|
178
|
+
**0.115–0.135** raw and **0.005–0.014** after document-grouped isotonic OOF;
|
|
179
|
+
document-level confidence is **not** calibrated (ECE 0.09–0.10). Use field-level
|
|
180
|
+
scores for review routing only.
|
|
181
|
+
- Every published number must recompute from `evaluation/phase0/results/`:
|
|
182
|
+
`python3 evaluation/phase0/recompute_scores.py` must exit 0.
|
|
183
|
+
- The 256M model card and the v2 decision are kept for reproducibility; neither is
|
|
184
|
+
a current performance claim.
|
|
185
|
+
|
|
186
|
+
## Layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
sdk/tinydoc/
|
|
190
|
+
pipeline.py # engines, schema, sanitize, confidence, ReceiptPipeline
|
|
191
|
+
evidence.py # OCR-span evidence
|
|
192
|
+
overlay.py # draw bboxes + labels
|
|
193
|
+
cli.py # tinydoc extract
|
|
194
|
+
schemas/ # JSON Schema per doc type
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
Apache 2.0
|
tinydoc-0.2.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# TinyDoc 0.2.0 — local-first grounded document extraction
|
|
2
|
+
|
|
3
|
+
Extract receipt/invoice fields as **schema-validated JSON** with per-field
|
|
4
|
+
**evidence** (quote + OCR bbox) and **confidence**. Runs locally with Ollama;
|
|
5
|
+
no API key, no cloud, no telemetry.
|
|
6
|
+
|
|
7
|
+
> **Status (2026-11-06):** the SDK and evaluation harness are measured. The
|
|
8
|
+
> first-party 256M model is retired (OCRBench 0.0%) and the SmolVLM2-2.2B SFT
|
|
9
|
+
> track is closed (no gain over base). The working product is the engine +
|
|
10
|
+
> trust layer, not the old checkpoint. Full analysis:
|
|
11
|
+
> [docs/v3_plan.md](https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/v3_plan.md).
|
|
12
|
+
|
|
13
|
+
## Measured results (SROIE public-train-derived clean-100, n=100)
|
|
14
|
+
|
|
15
|
+
| Engine | Field F1 | Address | Schema | Median latency | Errors |
|
|
16
|
+
|---|---:|---:|---:|---:|---:|
|
|
17
|
+
| **Qwen2.5-VL-3B** (`ollama:qwen2.5vl:3b`) | **0.9475** | 0.86 | 1.00 | 14.1 s | 0 |
|
|
18
|
+
| **Qwen3-VL-2B-Instruct** (`ollama:qwen3-vl:2b-instruct`) | **0.9200** | 0.84 | 1.00 | 7.0 s | 0 |
|
|
19
|
+
| Hybrid v2 first-party (`hybrid_2p2b`) | 0.603 | 0.37 | 0.93 | ~8 s | — |
|
|
20
|
+
| PP-OCR + heuristics | 0.376 | 0.03 | — | — | — |
|
|
21
|
+
| Tesseract + regex | 0.227 | — | 0.60 | — | — |
|
|
22
|
+
|
|
23
|
+
> **Caveat:** these are **not external benchmark numbers**. The 100-doc set is a
|
|
24
|
+
> duplicate-filtered split of the public SROIE **train** pool; Qwen may have seen
|
|
25
|
+
> it during pretraining. The original 100-doc set is 60% leaked. Use the numbers
|
|
26
|
+
> for same-document engine comparisons, not as a generalization claim. The next
|
|
27
|
+
> eval adds CORD/FUNSD/invoices and a private wild set.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
From PyPI (use this once 0.2.0 is published):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install tinydoc
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
From the repo:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install ./sdk
|
|
41
|
+
# editable for development:
|
|
42
|
+
pip install -e ./sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If `pip install tinydoc` resolves to **0.1.2**, that is the legacy 256M API; use
|
|
46
|
+
the source install above until 0.2.0 is uploaded. Core deps: `pydantic`,
|
|
47
|
+
`pillow`, `jsonschema`. Optional extras:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install './sdk[ocr]' # pytesseract for evidence + ocr_regex
|
|
51
|
+
pip install './sdk[ocr-pp]' # RapidOCR ONNX evidence
|
|
52
|
+
pip install './sdk[smolvlm2]' # optional local HF SmolVLM2 engine
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For the default engine, install [Ollama](https://ollama.com) and pull a base:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ollama pull qwen2.5vl:3b # quality default
|
|
59
|
+
ollama pull qwen3-vl:2b-instruct # fast tier (use -instruct, not the thinking tag)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
System `tesseract` is needed for OCR-span evidence (`brew install tesseract` on macOS).
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from tinydoc import ReceiptPipeline
|
|
68
|
+
|
|
69
|
+
# Quality default: Qwen2.5-VL-3B
|
|
70
|
+
pipe = ReceiptPipeline("ollama:qwen2.5vl:3b")
|
|
71
|
+
|
|
72
|
+
# Fast tier: Qwen3-VL-2B-Instruct, ~2x faster
|
|
73
|
+
# pipe = ReceiptPipeline("ollama:qwen3-vl:2b-instruct")
|
|
74
|
+
|
|
75
|
+
result = pipe.extract("receipt.jpg")
|
|
76
|
+
print(result.fields) # {"company": ..., "date": ..., "address": ..., "total": ...}
|
|
77
|
+
print(result.schema_valid) # True/False (JSON Schema)
|
|
78
|
+
print(result.confidence) # 0..1 aggregate
|
|
79
|
+
for field in result.field_results:
|
|
80
|
+
print(field.name, field.value, field.confidence, field.evidence)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
CLI:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
tinydoc extract ./receipts/ --engine ollama:qwen2.5vl:3b \
|
|
87
|
+
--out results.jsonl --overlay overlays/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Overlay PNGs draw OCR-span evidence boxes and field labels (`tinydoc.draw_overlay`).
|
|
91
|
+
|
|
92
|
+
## Engines
|
|
93
|
+
|
|
94
|
+
| Engine id | What it does |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `ollama` / `ollama:qwen2.5vl:3b` | Quality default; free, local |
|
|
97
|
+
| `ollama:qwen3-vl:2b-instruct` | Fast tier; 1.9 GB Q4, ~2x faster |
|
|
98
|
+
| `auto` | Ollama if reachable; OCR floor if the schema would fail |
|
|
99
|
+
| `ocr_regex` | Offline floor (Tesseract + rules) |
|
|
100
|
+
| `mlx_2p2b` | First-party v2 edge engine (MLX SmolVLM2-2.2B 4-bit) |
|
|
101
|
+
| `hybrid_2p2b` | v2 product path: MLX fields + frozen F3 OCR address |
|
|
102
|
+
|
|
103
|
+
**Do not** set Ollama `format: "json"` — constrained decoding truncates long
|
|
104
|
+
addresses and can drop `total`. The SDK uses unconstrained prompts, JSON salvage,
|
|
105
|
+
and prompt-v1.
|
|
106
|
+
|
|
107
|
+
### Environment
|
|
108
|
+
|
|
109
|
+
- `TINYDOC_OLLAMA_TIMEOUT` (default 180 s) — raise to 600 for slow local runs.
|
|
110
|
+
- `TINYDOC_OLLAMA_NUM_CTX` (default 8192) — Ollama context window. The implicit
|
|
111
|
+
4096 rejected 2481×3508 scans with HTTP 400; keep 8192 for full pages.
|
|
112
|
+
|
|
113
|
+
## Evidence and honesty
|
|
114
|
+
|
|
115
|
+
- Evidence kind is always `ocr_span` (RapidOCR word boxes), **not** VLM-predicted
|
|
116
|
+
boxes. Treat it as provenance, not as model grounding. A 100-box probe measured
|
|
117
|
+
VLM bbox hit@0.5 of **0.20** (2B) and **0.04** (3B), below the 0.70 product gate,
|
|
118
|
+
so OCR spans remain the shipped path.
|
|
119
|
+
- The schema is packaged (`tinydoc/schemas/sroie_receipt.schema.json`); validation
|
|
120
|
+
uses `jsonschema`.
|
|
121
|
+
- Confidence is a heuristic, not a probability. Evidence-off runs are constant
|
|
122
|
+
(0.4025; field ECE 0.52–0.55). The default evidence-on path measures field ECE
|
|
123
|
+
**0.115–0.135** raw and **0.005–0.014** after document-grouped isotonic OOF;
|
|
124
|
+
document-level confidence is **not** calibrated (ECE 0.09–0.10). Use field-level
|
|
125
|
+
scores for review routing only.
|
|
126
|
+
- Every published number must recompute from `evaluation/phase0/results/`:
|
|
127
|
+
`python3 evaluation/phase0/recompute_scores.py` must exit 0.
|
|
128
|
+
- The 256M model card and the v2 decision are kept for reproducibility; neither is
|
|
129
|
+
a current performance claim.
|
|
130
|
+
|
|
131
|
+
## Layout
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
sdk/tinydoc/
|
|
135
|
+
pipeline.py # engines, schema, sanitize, confidence, ReceiptPipeline
|
|
136
|
+
evidence.py # OCR-span evidence
|
|
137
|
+
overlay.py # draw bboxes + labels
|
|
138
|
+
cli.py # tinydoc extract
|
|
139
|
+
schemas/ # JSON Schema per doc type
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
Apache 2.0
|
tinydoc-0.2.0/setup.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from setuptools import setup, find_packages
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name="tinydoc",
|
|
7
|
+
version="0.2.0",
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
include_package_data=True,
|
|
10
|
+
package_data={
|
|
11
|
+
"tinydoc": ["schemas/*.json"],
|
|
12
|
+
},
|
|
13
|
+
install_requires=[
|
|
14
|
+
"pydantic>=2.8.0",
|
|
15
|
+
"pillow>=10.0.0",
|
|
16
|
+
"jsonschema>=4.20.0",
|
|
17
|
+
],
|
|
18
|
+
extras_require={
|
|
19
|
+
"ocr": ["pytesseract>=0.3.10"],
|
|
20
|
+
"ocr-pp": ["rapidocr-onnxruntime>=1.3.0"],
|
|
21
|
+
"smolvlm2": [
|
|
22
|
+
"torch>=2.2.0",
|
|
23
|
+
"transformers>=4.48.0",
|
|
24
|
+
"numpy>=1.26.0",
|
|
25
|
+
"sentencepiece>=0.2.0",
|
|
26
|
+
],
|
|
27
|
+
"onnx": ["onnxruntime>=1.19.0", "optimum>=1.22.0"],
|
|
28
|
+
"all": [
|
|
29
|
+
"pytesseract>=0.3.10",
|
|
30
|
+
"rapidocr-onnxruntime>=1.3.0",
|
|
31
|
+
"torch>=2.2.0",
|
|
32
|
+
"transformers>=4.48.0",
|
|
33
|
+
"numpy>=1.26.0",
|
|
34
|
+
"sentencepiece>=0.2.0",
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
entry_points={
|
|
38
|
+
"console_scripts": [
|
|
39
|
+
"tinydoc=tinydoc.cli:main",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
author="eulogik",
|
|
43
|
+
author_email="hello@eulogik.com",
|
|
44
|
+
description=(
|
|
45
|
+
"Local-first grounded document extraction SDK — schema, evidence, "
|
|
46
|
+
"confidence. Free Ollama/Qwen engine by default; no API key."
|
|
47
|
+
),
|
|
48
|
+
long_description=(Path(__file__).parent / "README.md").read_text(encoding="utf-8"),
|
|
49
|
+
long_description_content_type="text/markdown",
|
|
50
|
+
url="https://github.com/eulogik/TinyDoc-VLM",
|
|
51
|
+
project_urls={
|
|
52
|
+
"Model status": "https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/v3_plan.md",
|
|
53
|
+
"HuggingFace Space": "https://huggingface.co/spaces/eulogik/TinyDoc-VLM",
|
|
54
|
+
"Bug Tracker": "https://github.com/eulogik/TinyDoc-VLM/issues",
|
|
55
|
+
"Documentation": "https://github.com/eulogik/TinyDoc-VLM#readme",
|
|
56
|
+
"Benchmarks": "https://github.com/eulogik/TinyDoc-VLM/blob/main/docs/BENCHMARKS.md",
|
|
57
|
+
"Website": "https://eulogik.github.io/TinyDoc-VLM/",
|
|
58
|
+
},
|
|
59
|
+
classifiers=[
|
|
60
|
+
"Development Status :: 4 - Beta",
|
|
61
|
+
"Programming Language :: Python :: 3",
|
|
62
|
+
"License :: OSI Approved :: Apache Software License",
|
|
63
|
+
"Operating System :: OS Independent",
|
|
64
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
65
|
+
"Intended Audience :: Developers",
|
|
66
|
+
],
|
|
67
|
+
python_requires=">=3.9",
|
|
68
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .models import ExtractionResult, QAResult, TableResult
|
|
2
|
+
from .pipeline import ReceiptPipeline, DocumentResult, FieldResult, build_engine
|
|
3
|
+
from .evidence import Evidence, attach_evidence
|
|
4
|
+
from .overlay import draw_overlay
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"ExtractionResult",
|
|
8
|
+
"QAResult",
|
|
9
|
+
"TableResult",
|
|
10
|
+
"ReceiptPipeline",
|
|
11
|
+
"DocumentResult",
|
|
12
|
+
"FieldResult",
|
|
13
|
+
"build_engine",
|
|
14
|
+
"Evidence",
|
|
15
|
+
"attach_evidence",
|
|
16
|
+
"draw_overlay",
|
|
17
|
+
# legacy name kept for import compatibility
|
|
18
|
+
"TinyDocExtractor",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def __getattr__(name: str):
|
|
23
|
+
# Lazy legacy extractor (256M path) so pipeline imports do not load torch models
|
|
24
|
+
if name == "TinyDocExtractor":
|
|
25
|
+
from .extractor import TinyDocExtractor
|
|
26
|
+
|
|
27
|
+
return TinyDocExtractor
|
|
28
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""TinyDoc Phase-1 CLI: folder → schema-valid JSON + evidence + confidence.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m tinydoc.cli extract <folder_or_image> [--engine auto|ollama|ocr_regex|smolvlm2]
|
|
6
|
+
[--out results.jsonl] [--limit N] [--no-evidence]
|
|
7
|
+
|
|
8
|
+
Writes JSONL (one doc per line) and prints a summary table.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> int:
|
|
20
|
+
# allow running from repo without install
|
|
21
|
+
sdk_root = Path(__file__).absolute().parent.parent
|
|
22
|
+
if str(sdk_root) not in sys.path:
|
|
23
|
+
sys.path.insert(0, str(sdk_root))
|
|
24
|
+
|
|
25
|
+
from tinydoc.pipeline import ReceiptPipeline # noqa: E402
|
|
26
|
+
|
|
27
|
+
ap = argparse.ArgumentParser(prog="tinydoc", description=__doc__)
|
|
28
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
29
|
+
|
|
30
|
+
ex = sub.add_parser("extract", help="Extract fields from an image or folder")
|
|
31
|
+
ex.add_argument("path", help="Image file or directory")
|
|
32
|
+
ex.add_argument("--engine", default="auto")
|
|
33
|
+
ex.add_argument("--out", default=None, help="Write JSONL here")
|
|
34
|
+
ex.add_argument("--limit", type=int, default=None)
|
|
35
|
+
ex.add_argument("--no-evidence", action="store_true")
|
|
36
|
+
ex.add_argument("--model", default=None, help="Engine-specific model id")
|
|
37
|
+
ex.add_argument(
|
|
38
|
+
"--overlay",
|
|
39
|
+
default=None,
|
|
40
|
+
metavar="DIR",
|
|
41
|
+
help="Write evidence-overlay PNGs into DIR (red/blue boxes + labels)",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
args = ap.parse_args(argv)
|
|
45
|
+
|
|
46
|
+
p = Path(args.path)
|
|
47
|
+
if not p.exists():
|
|
48
|
+
print(f"path not found: {p}", file=sys.stderr)
|
|
49
|
+
return 2
|
|
50
|
+
kwargs = {}
|
|
51
|
+
if args.model:
|
|
52
|
+
kwargs["model"] = args.model
|
|
53
|
+
pipe = ReceiptPipeline(args.engine, **kwargs)
|
|
54
|
+
with_ev = not args.no_evidence
|
|
55
|
+
|
|
56
|
+
if p.is_dir():
|
|
57
|
+
results = pipe.extract_folder(p, limit=args.limit, with_evidence=with_ev)
|
|
58
|
+
else:
|
|
59
|
+
results = [pipe.extract(p, with_evidence=with_ev)]
|
|
60
|
+
|
|
61
|
+
out_path = Path(args.out) if args.out else None
|
|
62
|
+
if out_path:
|
|
63
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
with out_path.open("w") as f:
|
|
65
|
+
for r in results:
|
|
66
|
+
f.write(json.dumps(r.to_dict()) + "\n")
|
|
67
|
+
|
|
68
|
+
if args.overlay:
|
|
69
|
+
from tinydoc.overlay import draw_overlay
|
|
70
|
+
|
|
71
|
+
odir = Path(args.overlay)
|
|
72
|
+
odir.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
for r in results:
|
|
74
|
+
name = Path(r.image_path).stem + "_overlay.png"
|
|
75
|
+
try:
|
|
76
|
+
draw_overlay(r.image_path, r.field_results, odir / name)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f" overlay failed for {r.image_path}: {e}")
|
|
79
|
+
print(f"wrote overlays → {odir}")
|
|
80
|
+
|
|
81
|
+
n = len(results)
|
|
82
|
+
if n == 0:
|
|
83
|
+
print("no images found")
|
|
84
|
+
return 1
|
|
85
|
+
schema_rate = sum(r.schema_valid for r in results) / n
|
|
86
|
+
mean_conf = sum(r.confidence for r in results) / n
|
|
87
|
+
ev_rate = sum(r.evidence_coverage for r in results) / n
|
|
88
|
+
mean_lat = sum(r.latency_ms for r in results) / n
|
|
89
|
+
print(
|
|
90
|
+
f"engine={pipe.engine.name} n={n} "
|
|
91
|
+
f"schema={schema_rate:.3f} conf={mean_conf:.3f} "
|
|
92
|
+
f"evidence={ev_rate:.3f} latency_ms={mean_lat:.0f}"
|
|
93
|
+
)
|
|
94
|
+
for r in results[:5]:
|
|
95
|
+
print(
|
|
96
|
+
f" {Path(r.image_path).name}: schema={r.schema_valid} "
|
|
97
|
+
f"conf={r.confidence:.2f} fields={r.fields}"
|
|
98
|
+
)
|
|
99
|
+
if out_path:
|
|
100
|
+
print(f"wrote {out_path}")
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
raise SystemExit(main())
|