File size: 16,027 Bytes
4f675c6
 
 
 
 
 
 
 
 
 
 
 
 
 
967ef76
 
 
 
4f675c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
967ef76
 
 
 
 
 
 
 
 
 
 
 
 
4f675c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
---
license: mit
library_name: pytorch
pipeline_tag: image-to-text
tags:
- ocr
- document-parsing
- pdf
- crnn
- ctc
- trained-from-scratch
- cpu
---
# docparse: a very small document parser

- **Try it:** [huggingface.co/spaces/Kedhareswer/docparse](https://huggingface.co/spaces/Kedhareswer/docparse) (drop a PDF, image, Word, Excel, CSV or PowerPoint file)
- **Weights and code:** [huggingface.co/Kedhareswer/docparse](https://huggingface.co/Kedhareswer/docparse): `artifacts/docparse/ocr.pt` (6.0 MB), the `docparse` package, benchmark reports and tests
- **Source:** [github.com/Kedhareswer/ML-AI](https://github.com/Kedhareswer/ML-AI) (`reasoning_harness/`)

`docparse` extracts **everything** from a document (text, headings, paragraphs,
lists, tables, sheets, formulas, headers/footers, metadata and figure positions)
and returns it as JSON, Markdown or plain text, with reading order preserved.

| Input | How it is read | Exact? |
|---|---|---|
| PDF with a text layer | Words and fonts from the PDF; layout analysis groups them into headings, paragraphs, lists, columns and tables | Text is exact |
| Scanned PDF pages (no text layer) | Rendered at 200 dpi, then the OCR pipeline below | OCR |
| Images: PNG, JPG, TIFF (multi-page), BMP, WEBP, GIF | OCR pipeline | OCR |
| Word `.docx` | Document structure: styles → headings/lists, tables (merged cells), text boxes, headers/footers, properties | Exact |
| Excel `.xlsx` | Every sheet: values, formulas, merged ranges, dates, separate tables split on empty rows/columns | Exact |
| CSV / TSV | Delimiter and encoding sniffed | Exact |
| `.doc`, `.rtf`, `.odt`, `.xls`, `.ods`, `.ppt`, `.pptx`, `.odp` | Converted with LibreOffice (if installed), then parsed as above | Exact |

The design choice: **a model is only used where one is needed.** Born-digital
files already contain their text; reading their structure is exact and much
faster than any network. The neural network reads pixels: images, scans and
(optionally, `--ocr-images`) pictures embedded inside documents.

## The model

A CRNN line recognizer trained **from scratch** (no pretrained weights) on a
4-core CPU:

- **1,484,357 parameters, 6.0 MB** (`artifacts/docparse/ocr.pt`): 6 convolution
  layers, a 2-layer bidirectional LSTM and CTC decoding.
- Input: a grayscale text line, 32 px high, any width. Output: the text plus a
  confidence (mean probability of the emitted characters).
- Characters: printable ASCII plus `• € £ ° ©`. Typographic quotes, dashes and
  accented letters are folded to ASCII (`é` → `e`).
- Training: 480,000 synthetic lines (4 rounds of 120,000 freshly rendered
  lines), 27 fonts (DejaVu, Liberation, FreeFont, Inter, JetBrains Mono),
  text from app reviews and medical abstracts in this repository, plus
  generated numbers, prices, dates, emails, URLs, IDs and list items. Lines are
  degraded like scans and screenshots: blur, low resolution, noise, JPEG,
  rotation, uneven light, inverted colours and table-rule remnants. Training
  took 80 minutes.

Everything around the network is classical image processing (numpy only):
adaptive binarization (dark-on-light and light-on-dark), speck removal,
deskew, ruled-table detection from long horizontal/vertical strokes, frame
and outline removal, recursive XY-cut for columns and lines, then grouping
lines into headings (by x-height), paragraphs (by line spacing), list items,
and borderless tables (rows whose gaps line up).

## Results

All numbers are from `docparse bench` (`benchmarks/docparse/report.json`).
CER = character error rate (edit distance / reference length).

**1. Single lines.** Test fonts **Carlito and Caladea were never used in

training** (they are metric clones of Calibri and Cambria, the Office
defaults), and the test sentences come from a separate corpus.

| Test set | Lines | CER | Word error | Lines exactly right |
|---|---:|---:|---:|---:|
| Held-out fonts, clean | 2,000 | **0.27%** | 2.3% | 93.3% |
| Held-out fonts, degraded | 2,000 | **0.93%** | 6.0% | 84.8% |
| Training fonts, unseen text, degraded | 2,000 | 0.24% | 1.2% | 95.6% |
| Untrained network (same architecture), degraded | 500 | 99.7% | 99.8% | 0% |

About 3 ms per line on CPU.

**2. Synthetic A4 pages** (held-out fonts and text; titles, section headings,
paragraphs, bullet lists, ruled tables, key/value tables, two-column sections).
"Scanned" pages are rotated up to ±2°, blurred, noised and JPEG-compressed.

| | Clean (40 pages) | Scanned (40 pages) |
|---|---:|---:|
| Page CER, in reading order (mean / median) | 0.32% / 0.00% | 1.65% / 0.19% |
| Word F1 (order-free) | 98.9% | 97.7% |
| Headings found | 86.7% | 85.7% |
| Tables found with the right rows × columns | 100% | 100% |
| Table cells exactly right | 98.5% | 90.2% |
| Seconds per page (CPU) | 0.9 | 1.3 |

A few hard pages pull the scanned mean up; the median page is almost perfect.
Missed headings are mostly short numbered section titles ("1. Results") read
as list items or paragraphs: headings are detected by letter size, not by
boldness.

**3. Real pages from this repository.** 40 pages of the three PDFs in this
repo (two Jupyter notebook exports and a slide deck) were rendered to
150 dpi images and read **from pixels only**. Each PDF's own text layer is the
reference.

| PDF | Pages | Median CER (reading order) | Median word F1 |
|---|---:|---:|---:|
| Customer Behavior Analysis (notebook) | 15 | 0.7% | 92.5% |
| Credit Card Fraud Detection (notebook) | 11 | 2.3% | 94.8% |
| PPT.pdf (slides with graphics) | 14 | 32.3% | 82.5% |
| **All** | **40** | — | **91.9%** (mean 86.3%) |

Most slide-deck error is reading order: slides scatter text boxes, and the
PDF's own order differs from top-to-bottom reading. Text inside charts (axis
labels, tick numbers) is also read as text. The weakest notebook pages are
mostly plots with a few short labels.

## ParseBench (LlamaIndex)

[ParseBench](https://huggingface.co/datasets/llamaindex/ParseBench)
([paper](https://arxiv.org/abs/2604.08538), [code](https://github.com/run-llama/ParseBench))
is LlamaIndex's public benchmark for document parsers: **2,078 real enterprise

pages** (insurance filings, financial reports, government documents, papers),
1,211 documents and 169,011 human-verified test rules in five dimensions.
Scoring is deterministic rule matching, with no LLM judge.

docparse was run on the **full** dataset with the official harness
(parse-bench 1.0.4) on a 4-core CPU: all 2,078 pages, 0 failures, median
0.5 s per page. The comparison rows are the "Open Source - Local" entries of
ParseBench's published leaderboard.

| Parser | Overall | Tables | Charts | Content faithfulness | Semantic formatting | Visual grounding |
|---|---:|---:|---:|---:|---:|---:|
| PyMuPDF4LLM | 53.5 | 72.0 | 2.2 | 79.3 | 52.0 | 61.9 |
| Warp Ingest | 40.2 | 57.5 | 7.0 | 70.8 | 45.8 | 19.8 |
| LiteParse (no OCR) | 36.9 | 42.4 | 1.8 | 70.0 | 38.5 | 31.8 |
| **docparse** | **36.2** | **42.1** | **1.0** | **72.5** | **35.5** | **30.0** |
| OpenDataLoader | 29.4 | 35.2 | 0.9 | 66.1 | 34.1 | 10.8 |
| pdf-inspector | 26.6 | 26.6 | 5.3 | 56.1 | 35.1 | 9.9 |
| MarkItDown | 18.6 | 15.8 | 2.0 | 64.5 | 0.9 | 9.9 |
| PyMuPDF (text) | 16.0 | 0.0 | 0.0 | 68.3 | 1.0 | 10.9 |
| pypdf | 14.9 | 0.0 | 0.0 | 62.5 | 0.9 | 10.9 |

Overall is the mean of the five dimensions, as on the official leaderboard.
For scale: the top of the full leaderboard (cloud services and multi-billion
parameter vision-language models) reaches 75–90.

**What this says honestly**

- docparse is mid-table among local open-source parsers: ahead of pypdf,
  PyMuPDF, MarkItDown, pdf-inspector and OpenDataLoader; just behind LiteParse
  (36.9); behind Warp Ingest and clearly behind PyMuPDF4LLM.
- Its **content faithfulness (72.5)** is second among local parsers. It is
  strongest on simple (89.0) and multi-column (76.5) pages, and weakest on
  handwriting (27.4), scans (56.1) and non-English text (58.8). That matches the
  OCR model's limits: printed Latin text only.
- **Charts (1.0):** ParseBench wants each chart's data points as a table.
  docparse finds charts and keeps their labels but does not convert them to
  data. No local parser scores above 7.
- **Tables (42.1)** are limited by pdfplumber's ruled-grid detection on
  borderless financial tables; 32.3 on "hard" tables.
- **Formatting (35.5):** bold 29%, italic 23%, superscript 59%, subscript 57%
  of rules pass. Underline, strikeout, highlight, LaTeX and code blocks are not
  produced (0%).
- **Visual grounding (30.0):** paragraph boxes and reading order are decent;
  figure regions and box granularity often differ from the annotation.

**What the benchmark changed in docparse.** The first full run scored 33.66; the final run scored 36.2.
Its failures led to fixes that are now part of docparse itself:

| Change | Effect |
|---|---|
| Bold/italic from PDF font names, `<sup>`/`<sub>` from glyph size and baseline | Semantic formatting 24.0 → 35.5 |
| Running header/footer and printed page-number detection | Header rules 0 → 9.9%, footer rules 0 → 12.2%; content faithfulness 71.8 → 72.5 |
| Rotated margin text (e.g. arXiv stamps) kept out of the reading flow | It no longer turns body text into a fake table |
| Vector charts and diagrams detected as figures, their labels attached | Visual grounding 28.9 → 30.0 |
| Rows stacked in one cell of rule-less tables split into records | Correct rows in insurance rate tables |
| A malformed PDF link annotation no longer fails the whole page | 9 failing pages → 0 |

Reproduce (parse-bench needs Python 3.12+):

```bash

pip install "parse-bench[fast]"

python scripts/parsebench_docparse.py download          # ~600 MB

python scripts/parsebench_docparse.py run docparse --max_concurrent 3

python scripts/parsebench_summary.py output/docparse leaderboard.csv

```

`docparse/parsebench.py` registers the pipeline through ParseBench's public
extension API. Each page is sent as Markdown with HTML tables, page
header/footer fields, and layout boxes in ParseBench's canonical classes.
Aggregate results are in `benchmarks/parsebench/report.json`.

## ExtractBench (LlamaIndex): small local check

[ExtractBench](https://github.com/run-llama/ExtractBench) scores **schema-guided

extraction**: a document plus a JSON Schema in, schema-valid JSON plus evidence
boxes out. A parser cannot do this alone; an LLM fills the schema. So docparse
was tested as the **parse stage** of a two-stage pipeline (as ExtractBench's own
parse → LLM baselines are), against plain pypdf text, with everything else fixed:
the same model (Qwen2.5-3B-Instruct, 4-bit, llama.cpp on 4 CPU cores), the same
prompt and 12,288-token context budget, and the official scorer.

| Parse stage (6-document test set) | Value F1 | Precision | Recall |
|---|---:|---:|---:|
| **docparse** | **35.8** | 63.8 | 32.5 |
| pypdf | 32.9 | 66.3 | 29.8 |

docparse helped most on a scanned document (Value F1 19.6 vs 8.0, from OCR) and
an earnings deck with tables (35.0 vs 25.0). It lost on the 66-page document
(1.4 vs 5.2), where both arms could send only the first few pages.

**Read this carefully.** This is 6 documents, so a 2.9-point gap is not
statistically meaningful. Absolute scores are far below the leaderboard (top
systems score ~96), because a 3B model with a 12k context must cut long documents
(up to 55 of 66 pages dropped) and sometimes even shorten the schema. Grounding is
0 for both arms because this pipeline does not return evidence boxes yet. A real
ExtractBench number needs a strong model over all 370 documents (ExtractBench
estimates $10 to $1,677 per full run, depending on the model). Per-document
details are in `benchmarks/extractbench/report.json`.

Reproduce: start an OpenAI-compatible server (`python -m llama_cpp.server --model

qwen2.5-3b-instruct-q4_k_m.gguf --n_ctx 12288 --port 8088`), then
`DOCPARSE_LLM_URL=http://127.0.0.1:8088 python scripts/extractbench_docparse.py run

docparse_extract_local --test` (and `pypdf_extract_local`).



**Structure parsers** (PDF text layer, DOCX, XLSX, CSV) are tested for exact

output in `tests/test_docparse.py`.

## Use it

```bash

pip install -e '.[docs]'          # torch, numpy, pillow, pdfplumber, pypdfium2, python-docx, openpyxl



docparse extract report.pdf                      # Markdown to stdout

docparse extract scan.png --format json -o scan.json

docparse extract invoice.pdf --ocr always        # OCR every page, even with a text layer

docparse extract deck.docx --ocr-images          # also read text inside embedded pictures

docparse extract *.xlsx --format md -o out/      # many files -> a directory

docparse ui                                      # browser UI on http://127.0.0.1:7862

```

```python

from docparse import extract

from docparse.extract import to_markdown



doc = extract("statement.pdf")          # dict: source, format, metadata, pages, blocks, sheets, warnings

for b in doc["blocks"]:

    print(b["type"], b.get("page"), b.get("text") or b.get("rows"))

print(to_markdown(doc))

```

Each block has a `type` (`heading`, `paragraph`, `list_item`, `table`,
`figure`, `header`, `footer`), a `source` (`text-layer`, `structure` or
`ocr`), and where meaningful a `page`, `bbox`, `level`, `sheet`, `range` and
an OCR `confidence`. Excel sheets also list formulas, merged ranges, charts and
images under `sheets`.

## Online demo

The [Space](https://huggingface.co/spaces/Kedhareswer/docparse) runs the same
`docparse ui` server and weights. Files up to 40 MB, one at a time. LibreOffice
is installed there, so `.doc`, `.rtf`, `.odt`, `.xls`, `.ods`, `.ppt`, `.pptx`
and `.odp` work too. It runs on Hugging Face's free ZeroGPU hardware with all
inference on CPU (the app registers an unused `@spaces.GPU` function because
ZeroGPU will not start without one). Measured there: Word, Excel and CSV files
about 1 s, an 11-page text-layer PDF 3.5 s, a 14-page slide PDF 13 s, a page
image read by OCR 2.5 s. Uploaded files are written to a temporary folder for
the extraction and deleted right after. The Space build is
`scripts/make_hf_docparse.sh`.

## Train or fine-tune your own

```bash

python scripts/build_doc_corpus.py                        # rebuild the text corpora

docparse train-ocr --run runs/ocr --rounds 4              # ~80 min on 4 CPU cores

docparse train-ocr --run runs/ocr --rounds 6 --resume     # continue / extend training

docparse bench --model runs/ocr/ocr.pt --output runs/ocr-bench

```

To specialise it (your fonts, your vocabulary, e.g. invoices in one
typeface), add font files to `TRAIN_FONTS` and text to `docparse/data/corpus_train.txt`
in `docparse/ocr/render.py`, then train or resume. A checkpoint is written
after every round.

## Limits

- **Printed text only.** No handwriting. Latin script only (ASCII plus a few
  symbols); accents are dropped and other scripts are not read.
- Tables: ruled grids and borderless tables with aligned columns are found.
  Cells that span several rows or columns are kept in the first cell. Very
  irregular tables may come out as text.
- Reading order is top-to-bottom, columns left to right. Magazine layouts,
  slides with scattered boxes, sidebars and text running around pictures can
  be read out of order.
- Pictures are reported as `figure` blocks with a position. Charts are not
  converted to data.
- Photos of documents taken at an angle (perspective), curved pages and
  very low resolution (text under about 10 px high) are not handled well.
- Born-digital PDFs are only as good as their text layer: some PDFs embed
  fonts without a Unicode mapping. Use `--ocr always` for those.
- Besides ParseBench, the benchmarks are synthetic pages and three real PDFs from
  this repository. No direct comparison with Tesseract was run here.