Note: docTR 1.1.0 requires python >= 3.11
Note
docTR is now a full document understanding toolkit. On top of text detection and recognition, v1.1.0 adds layout analysis, table structure recognition and a reading-order aware export to Markdown / AsciiDoc / HTML / XML - plus a CLI so you can run the whole pipeline without writing a single line of Python.
What's Changed
Breaking Changes 🛠
- The minimum supported Python version was raised from 3.10 to 3.11
- The
opencv-pythonupper bound was relaxed to<6.0.0
New features
- Layout analysis with the new
LW-DETRarchitecture (lw_detr_s,lw_detr_m) by @felixdittrich92 - Table structure recognition with
TableCenterNetand aStarNetbackbone by @felixdittrich92 - Reading order module and new exporters (Markdown / AsciiDoc / HTML / XML) by @felixdittrich92
- Vocab whitelisting to constrain the recognition model to a known character set by @felixdittrich92
doctr-clito run end-to-end OCR from your shell by @simont2k- Layout masking (
ignore_regions) to skip page furniture before detection/recognition by @felixdittrich92 preserve_original_coordsto map boxes back to the original page afterstraighten_pages=Trueby @saad-rd11- New dataset classes (
LayoutDataset,TableStructureDataset), new metrics (ObjectDetectionMetric,TableCellMetric) and new reference scripts for both tasks
✨ Layout Analysis ✨
Layout analysis localizes and classifies the visual elements of a page - not just the words. The new LW-DETR models are trained on 11 classes (Caption, Footnote, Formula, List-item, Page-footer, Page-header, Picture, Section-header, Table, Text, Title) and, unlike most layout detectors, also work on rotated and skewed documents.
Left: words found by db_resnet50 + crnn_vgg16_bn. Right: the 18 regions returned by lw_detr_s on the same page.
Available architectures:
| Architecture | Input shape | # params | mAP@[.5:.95] | AP@[.5] | AP@[.75] | sec/it (B: 1) - CPU |
|---|---|---|---|---|---|---|
lw_detr_s |
(1024, 1024, 3) | 15.1 M | 66.89 | 82.83 | 73.75 | 0.5 |
lw_detr_m |
(1024, 1024, 3) | 29.5 M | soon | soon | soon | 0.7 |
Standalone usage
import numpy as np
from doctr.models import layout_predictor
model = layout_predictor("lw_detr_s", pretrained=True)
dummy_img = (255 * np.random.rand(800, 600, 3)).astype(np.uint8)
out = model([dummy_img])
# out[0] -> {"class_names": ["Title", "Text", ...], "boxes": array(...), "scores": array(...)}Like the detection predictor, it accepts assume_straight_pages, preserve_aspect_ratio and symmetric_pad:
from doctr.models import layout_predictor
predictor = layout_predictor(
"lw_detr_s", pretrained=True, assume_straight_pages=False, preserve_aspect_ratio=True
)Inside the OCR pipeline
Pass detect_layout=True to the ocr_predictor (or the kie_predictor) and the detected regions are attached to every page, exported alongside the rest of the page and rendered by .show():
from doctr.io import DocumentFile
from doctr.models import ocr_predictor
model = ocr_predictor(pretrained=True, detect_layout=True, layout_arch="lw_detr_s")
doc = DocumentFile.from_images("path/to/your/doc.jpg")
result = model(doc)
# Access the detected layout regions of the first page
for region in result.pages[0].layout:
print(region.type, region.confidence, region.geometry)
# Title 0.97 ((0.13, 0.06), (0.87, 0.11))
# Text 0.95 ((0.11, 0.14), (0.89, 0.42))
# Table 0.93 ((0.12, 0.45), (0.88, 0.79))
# The layout is part of the exported representation
export = result.pages[0].export()
print(export["layout"])
# Overlay both text and layout regions (use display_layout=False to hide the regions)
result.pages[0].show()Ignoring regions
Layout regions can also be used to remove content before it ever reaches the detection and recognition models. Pass the class names you want to drop to ignore_regions — the corresponding areas are masked out (filled with black), which speeds up inference and keeps page furniture out of your results:
from doctr.models import ocr_predictor
# Headers, footers and pictures are masked out before detection & recognition
model = ocr_predictor(pretrained=True, ignore_regions=["Page-header", "Page-footer", "Picture"])✨ Table Structure Recognition ✨
Table structure recognition parses a table into a machine-understandable representation: it localizes every cell (spatial structure) and recovers the rows and columns each cell spans (logical structure). The new TableCenterNet architecture is built on a lightweight StarNet backbone (7.1 M params).
The 20 cells predicted by tablecenternet with their logical (row, column) coordinates, and the dense grid to_grid() builds from them.
| Architecture | Input shape | # params | Recall | Precision | F1 | Structure acc | sec/it (B: 1) |
|---|---|---|---|---|---|---|---|
tablecenternet |
(1024, 1024, 3) | 7.1 M | 82.31 | 96.01 | 88.64 | 77.53 | 0.5 |
Metrics are produced by
references/table/evaluate.pyusing the newTableCellMetric: cell-detection Recall, Precision and F1 (cells matched above an IoU threshold of 0.5), and Structure acc, the share of matched cells whose logical (row/column) coordinates are correctly predicted.
Standalone usage
import numpy as np
from doctr.models import table_predictor
model = table_predictor("tablecenternet", pretrained=True)
table_crop = (255 * np.random.rand(800, 600, 3)).astype(np.uint8)
out = model([table_crop])
# out[0] -> {"cells": [{"geometry": ..., "score": ..., "row_start": 0, "row_end": 0,
# "col_start": 0, "col_end": 0}, ...], "num_rows": ..., "num_cols": ...}Inside the OCR pipeline
Passing detect_tables=True to the ocr_predictor wires layout detection and table structure recognition together: every region the layout model labels as a Table is cropped and passed to the table model (so detect_tables=True implicitly enables the layout model). Words whose center falls inside a detected cell are regrouped into a structured table available under page.tables - and removed from the regular blocks output, so the same text is never returned twice.
from doctr.io import DocumentFile
from doctr.models import ocr_predictor
model = ocr_predictor(pretrained=True, detect_tables=True)
doc = DocumentFile.from_images("invoice_with_table.png")
result = model(doc)
page = result.pages[0]
for i, table in enumerate(page.tables):
print(f"Table {i} ({table.num_rows}x{table.num_cols}):")
print(table.to_grid()) # dense list of lists -> loads straight into pandas
# The remaining (non-table) text is still available as usual
print(page.render())# Table 0 (3x3):
# [['Item', 'Qty', 'Price'],
# ['Coffee', '2', '7.00'],
# ['Tea', '1', '3.50']]
import pandas as pd
grid = page.tables[0].to_grid()
df = pd.DataFrame(grid[1:], columns=grid[0])Tables are included in Page.export() under the tables key, so they survive the JSON export as well.
✨ Reading order & new exporters ✨
Results can now be linearized in reading order and exported to Markdown, AsciiDoc, HTML or XML. The content is ordered column by column, the reading direction is inferred from the recognized text (e.g. right-to-left for Arabic or Hebrew documents), and - when the predictor runs with detect_layout=True - the layout regions are used to render headings, list items and recognized tables, and to place page furniture (headers, footers, footnotes).
from doctr.io import DocumentFile
from doctr.models import ocr_predictor
model = ocr_predictor(pretrained=True, detect_layout=True)
result = model(DocumentFile.from_pdf("path/to/your/doc.pdf"))
markdown_output = result.export_as_markdown()
asciidoc_output = result.export_as_asciidoc()
html_output = result.export_as_html()
xml_output = result.export_as_xml() # hOCR
raw_text_output = result.render() # same as result.export_as("text")
dict_output = result.export() # same as result.export_as("json")export_as is a convenience dispatcher over all formats:
result.export_as("markdown") # or "md"
result.export_as("asciidoc") # or "adoc"
result.export_as("html")
result.export_as("text") # same as render()
result.export_as("json") # same as export()
result.export_as("xml") # same as export_as_xml()Sample Markdown output:
# Invoice 2024-042
## Billing address
Mindee, 54 Rue de Paradis, 75010 Paris
| Item | Qty | Price |
| --- | --- | --- |
| Coffee | 2 | 7.00 |
| Tea | 1 | 3.50 |Every export path shares the same linearization, so render(), export(), export_as_xml() and the Markdown / AsciiDoc / HTML exports all present the content in the same order. The result is memoized on the page, so exporting one page to several formats orders it only once.
If you want the document structure itself in reading order, build the predictor with keep_reading_order=True, which sorts the blocks of every page:
predictor = ocr_predictor(pretrained=True, keep_reading_order=True)✨ Vocab whitelisting ✨
If you only expect text from one or more known languages, you can now whitelist the corresponding vocabs so that the recognition model can no longer predict any character outside of them.
The whitelist is enforced at the model's final projection layer, before the decoding argmax. Because every logit flows through that projection, the constraint also applies inside the autoregressive decoding loop of SAR, MASTER and PARSeq - a forbidden character can never be produced, not even fed back mid-word. It works with every recognition architecture and with any predictor wrapping one (ocr_predictor, kie_predictor, recognition_predictor).
from doctr.datasets import VOCABS
from doctr.io import DocumentFile
from doctr.models import ocr_predictor
from doctr.models.utils import add_whitelist
predictor = ocr_predictor(pretrained=True)
# The recognition model can now only predict Polish/German characters
handle = add_whitelist(predictor, [VOCABS["polish"], VOCABS["german"]])
input_page = DocumentFile.from_images("path/to/your/image.png")
out = predictor(input_page)
# Restore the original, unconstrained decoding
handle.remove()The returned handle also works as a context manager:
with add_whitelist(predictor, VOCABS["german"]):
out = predictor(input_page) # only German characters can be predicted here
# the whitelist is automatically removed outside of the `with` blockTwo strategies are available:
strategy="mask"(default): forbidden logits are set to-inf, so decoding falls back to the highest-scoring allowed character.strategy="nearest": each forbidden character is first folded onto the closest allowed one (ä->a,ł->l), which is handy to normalize accents/diacritics onto a base alphabet.
# Fold any non-ASCII character onto its closest ASCII letter (e.g. é -> e, ł -> l)
handle = add_whitelist(predictor, VOCABS["latin"], strategy="nearest")
out = predictor(input_page)
handle.remove()The mapping is built by transliteration by default; pass mapping="weights" to derive it from the model's own learned confusions, or a {forbidden_char: allowed_char} dict to override specific characters.
Note
A whitelist can only restrict a model to characters it already knows - characters outside the model's own vocabulary are silently ignored. Make sure the model was trained on a vocab that covers your languages (e.g. a multilingual model).
✨ Command Line Interface ✨
The full OCR pipeline can now be run from your shell on images and PDFs, with the results exported to JSON - no Python required.
doctr-cli --input_path path/to/your/document.pdf --output results.json
A few more examples:
# Run OCR on an image
doctr-cli --input_path image.jpg --output ocr_res.json
# Pick your architectures and straighten skewed pages
doctr-cli --input_path doc.pdf --det_arch db_mobilenet_v3_large --reco_arch crnn_vgg16_bn --straighten_pages
# Rotated documents, with orientation and language detection
doctr-cli --input_path doc.pdf --no-assume_straight_pages --detect_orientation --detect_languageThe JSON output follows the usual docTR document model: pages (dimensions & orientation), blocks, lines and words with their confidence scores and bounding boxes.
Documentation: https://mindee.github.io/doctr/latest/using_doctr/using_cli.html
Further improvements
- Keep the original coordinates when straightening pages - with
straighten_pages=True, boxes are remapped onto the original page instead of the rotated one, which is what you want for redaction and annotation:
from doctr.models import ocr_predictor
model = ocr_predictor(pretrained=True, straighten_pages=True, preserve_original_coords=True)- New metrics - a COCO-mAP-like
ObjectDetectionMetricfor layout evaluation and aTableCellMetricfor table structure evaluation - Transforms & augmentations now operate on a unified
Samplecontainer (image,mask,target), which makes them reusable across detection, layout and table tasks PreProcessor/Resizelogic was reworked and rotated post-processing was improved- Detection reference scripts can now use the built-in datasets directly (as recognition already could)
- Reconstitution -
synthesize_pagegot better font scaling, rendering and font fallback - Performance - a
bfloat16issue was fixed, MASTER got noticeably faster, and general memory/latency improvements landed across the pipeline - Rotated layouts are de-skewed before the reading order is computed
- CI/CD jobs were refactored with much better caching
What's Changed
New Features
- [Feat] Add CLI support for OCR by @simont2k in #2058
- [Feat] Add Vit-Det as LW-DETR encoder by @felixdittrich92 in #2063
- [Feat] Add layout detection dataset class by @felixdittrich92 in #2064
- [Feat] Add Layout model LW-DETR by @felixdittrich92 in #2059
- [Feat] Add layout scripts and update augmentations to
Samplestrategy by @felixdittrich92 in #2067 - [Layout] Add layout element & extend vis by @felixdittrich92 in #2074
- [feature] Add whitelist hook by @felixdittrich92 in #2077
- [Layout] Add layout integration by @felixdittrich92 in #2075
- Table metric and dataset by @felixdittrich92 in #2086
- Add starnet model backbone for tablestructure model by @felixdittrich92 in #2087
- [TSR] Add TableCenterNet & table_predictor by @felixdittrich92 in #2092
- [TSR] table predictor integration by @felixdittrich92 in #2096
- [Feat] Add layout masking by @felixdittrich92 in #2109
- [Feat] Add reading order module by @felixdittrich92 in #2110
- [feature] Add preserve_original_coords option for
straighten_pages=Trueby @saad-rd11 in #2108 - [reading order] Improve reading order and add exporters by @felixdittrich92 in #2116
Bug Fixes
- fix style and wrong point order by @felixdittrich92 in #2052
- Ordering fix by @felixdittrich92 in #2054
- Fix api conftest order by @felixdittrich92 in #2055
- fix: handle empty inputs in OrientationPredictor by @badranX in #2069
- fix(geometry): apply inclusive last-index per box in extract_crops by @devteamaegis in #2071
- [Fix] Fix LW-DETR & Minor updates by @felixdittrich92 in #2068
- LWDETR fix amp by @felixdittrich92 in #2073
- [Fix] Remove pad fix by @felixdittrich92 in #2079
- Fix several bugs and increase performance by @felixdittrich92 in #2101
- [Fix] Doc builder arg length & masking by @felixdittrich92 in #2102
- fix(visualization): honor add_labels in visualize_kie_page by @Anai-Guo in #2122
Improvements
- [metrics] Add COCO mAP like ObjectDetection metric by @felixdittrich92 in #2061
- Update PreProcessor and Resize logic by @felixdittrich92 in #2065
- Update augmentations by @felixdittrich92 in #2066
- [references] Detection - Allow built-in datasets usage by @felixdittrich92 in #2081
- Improve synthesize_page and font fallback by @felixdittrich92 in #2098
- Rotated post proc improvements by @felixdittrich92 in #2099
- [misc] Add minor improvements by @felixdittrich92 in #2105
- [CI/CD] Refactor jobs & improve caching by @felixdittrich92 in #2106
- [reading order] de-skew rotated layout geoms by @felixdittrich92 in #2115
- [optimization] Fix bfloat16 issue and speed up master by @felixdittrich92 in #2118
- [misc] minor performance & memory improvements by @felixdittrich92 in #2120
- [reconstitution] Improve font scaling and rendering by @felixdittrich92 in #2121
- [layout] release lw_detr_s checkpoint by @felixdittrich92 in #2125
- [CLI] Update CLI to cover mostly all options & update tests by @felixdittrich92 in #2128
Miscellaneous
- [misc] post release v1.0.1 by @felixdittrich92 in #2041
- Chore/readme by @t2kgrosse-boelting in #2060
- Change support image link to t2k website by @t2kgrosse-boelting in #2091
- [misc] Apply safe guards and minor improvements by @felixdittrich92 in #2104
- Adjust readme and need help image by @t2kgrosse-boelting in #2113
- [build / references] Update opencv upper bound and minor table/layout lr update by @felixdittrich92 in #2114
- Update root logging by @felixdittrich92 in #2129
New Contributors
- @t2kgrosse-boelting made their first contribution in #2060
- @simont2k made their first contribution in #2058
- @badranX made their first contribution in #2069
- @devteamaegis made their first contribution in #2071
- @felixdittrich92 with @Copilot made their first contribution in #2080
- @saad-rd11 made their first contribution in #2108
- @Anai-Guo made their first contribution in #2122
Full Changelog: v1.0.1...v1.1.0
