docparse: a very small document parser

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 (paper, code) 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+):

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 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

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
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 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

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.
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Space using Kedhareswer/docparse 1

Paper for Kedhareswer/docparse