Doctr Tutorial: Deep Learning OCR Library for Document Understanding

# Tutorial Doctr: Library OCR Deep Learning untuk Pemahaman Dokumen Doctr (Document Text Recognition) adalah library open-source yang dikembangkan oleh Mindee untuk melakukan Optical Character Recogn...

By Ruby Abdullah · · tutorial
DoctrOCRDeep LearningDocument AIPython

Doctr Tutorial: Deep Learning OCR Library for Document Understanding

Doctr (Document Text Recognition) is an open-source library developed by Mindee for performing Optical Character Recognition (OCR) using deep learning models. Unlike traditional OCR libraries such as Tesseract that rely on classical approaches, Doctr is built entirely on modern neural network architectures, resulting in significantly higher accuracy, especially on documents with complex layouts.

The library supports two major deep learning backends: PyTorch and TensorFlow, giving developers the flexibility to choose the framework that best fits their ecosystem. Doctr is specifically designed to process business documents such as invoices, receipts, forms, contracts, and various types of structured documents.

In this tutorial, we will learn how to use Doctr from installation, basic text extraction, to advanced techniques like model fine-tuning and integration into production pipelines.

Why Choose Doctr?

Before diving into implementation, it's important to understand what makes Doctr stand out compared to other OCR solutions:

Two-Stage Pipeline Architecture: Doctr uses a two-stage approach consisting of text detection (finding text locations in images) and text recognition (reading characters from detected areas). Each stage uses independently optimized deep learning models. Pre-trained Models: The library provides various pre-trained models already trained on large-scale document datasets, so they can be used directly without additional training for most use cases. Structured Output: OCR results from Doctr are returned in a hierarchical structured format (Document > Page > Block > Line > Word), making it easy to perform layout analysis and structured information extraction. Multi-Format Support: Doctr can process various input formats including images (PNG, JPG, TIFF) and PDF documents natively.

Installation

Basic Installation with PyTorch Backend

pip install "python-doctr[torch]"

Basic Installation with TensorFlow Backend

pip install "python-doctr[tf]"

Full Installation (All Features)

pip install "python-doctr[torch,viz,html]"

Additional options:

  • viz: For detection and recognition result visualization
  • html: For exporting results to HTML format
  • contrib: For access to community-contributed models

Verify Installation

import doctr

print(f"Doctr version: {doctr.version}")

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

print("Model loaded successfully!")

System Dependencies

To process PDF files, Doctr requires additional system libraries:

# Ubuntu/Debian

sudo apt-get install poppler-utils

macOS

brew install poppler

Windows (via conda)

conda install -c conda-forge poppler

Basic Usage

Loading Documents from Files

The first step is loading the document you want to process. Doctr supports both images and PDFs.

from doctr.io import DocumentFile

Load a PDF file

pdfdoc = DocumentFile.frompdf("sampledocument.pdf")

print(f"Number of pages: {len(pdfdoc)}")

Load an image file

imgdoc = DocumentFile.fromimages("invoice.png")

print(f"Number of images: {len(imgdoc)}")

Load multiple images at once

multidoc = DocumentFile.fromimages([

"page1.png",

"page2.png",

"page3.jpg"

])

print(f"Total images: {len(multidoc)}")

Running the OCR Predictor

The OCR predictor is the main component that combines text detection and text recognition.

from doctr.models import ocrpredictor

Initialize model with pre-trained weights

model = ocrpredictor(pretrained=True)

Run OCR on the document

result = model(pdfdoc)

Display results in JSON format

import json

output = result.export()

print(json.dumps(output, indent=2))

Extracting Text from OCR Results

OCR results are returned in a hierarchical structure. Here's how to access text at each level:

from doctr.models import ocrpredictor

from doctr.io import DocumentFile

model = ocrpredictor(pretrained=True)

doc = DocumentFile.frompdf("invoice.pdf")

result = model(doc)

Extract all text from the document

fulltext = result.render()

print("=== Full Text ===")

print(fulltext)

Access per page

for pageidx, page in enumerate(result.pages):

print(f"\n--- Page {pageidx + 1} ---")

for block in page.blocks:

for line in block.lines:

linetext = " ".join([word.value for word in line.words])

print(f" Line: {linetext}")

Access per word with confidence score

for page in result.pages:

for block in page.blocks:

for line in block.lines:

for word in line.words:

print(f"Word: '{word.value}' | "

f"Confidence: {word.confidence:.4f} | "

f"Bounding Box: {word.geometry}")

Understanding Bounding Boxes

Each detected element has geometry information in the form of a bounding box. Coordinates are normalized between 0 and 1 relative to the page dimensions.

for page in result.pages:

pagedims = page.dimensions # (height, width) in pixels

print(f"Page dimensions: {pagedims}")

for block in page.blocks:

# Geometry format: ((xmin, ymin), (xmax, ymax))

bbox = block.geometry

xmin, ymin = bbox[0]

xmax, ymax = bbox[1]

# Convert to pixels

pxxmin = int(xmin pagedims[1])

pxymin = int(ymin pagedims[0])

pxxmax = int(xmax pagedims[1])

pxymax = int(ymax pagedims[0])

print(f"Block bbox (normalized): {bbox}")

print(f"Block bbox (pixels): ({pxxmin}, {pxymin}) -> ({pxxmax}, {pxymax})")

Model Configuration

Choosing Detection and Recognition Architectures

Doctr provides several model architecture options for detection and recognition. You can choose based on your needs between accuracy and speed.

from doctr.models import ocrpredictor

Default model (balanced accuracy and speed)

modeldefault = ocrpredictor(pretrained=True)

Model with specific architecture

modelcustom = ocrpredictor(

detarch='dbresnet50', # Detection: DBNet with ResNet50

recoarch='crnnvgg16bn', # Recognition: CRNN with VGG16

pretrained=True

)

Lightweight model for speed

modelfast = ocrpredictor(

detarch='dbmobilenetv3large',

recoarch='crnnmobilenetv3small',

pretrained=True

)

Available Detection Architectures

| Architecture | Backbone | Speed | Accuracy |

|-------------|----------|-------|----------|

| dbresnet50 | ResNet-50 | Medium | High |

| dbmobilenetv3large | MobileNetV3-L | Fast | Medium |

| linknetresnet18 | ResNet-18 | Fast | Medium |

| linknetresnet50 | ResNet-50 | Medium | High |

Available Recognition Architectures

| Architecture | Backbone | Speed | Accuracy |

|-------------|----------|-------|----------|

| crnnvgg16bn | VGG-16 BN | Medium | High |

| crnnmobilenetv3small | MobileNetV3-S | Fast | Medium |

| crnnmobilenetv3large | MobileNetV3-L | Medium | High |

| sarresnet31 | ResNet-31 | Slow | Very High |

| master | - | Slow | Very High |

| vitstrsmall | ViT-Small | Medium | High |

Additional Parameter Configuration

model = ocrpredictor(

pretrained=True,

assumestraightpages=True, # Assume pages are already straight (faster)

straightenpages=False, # Disable orientation correction

preserveaspectratio=True, # Preserve aspect ratio during resize

symmetricpad=True, # Symmetric padding

exportasstraightboxes=True, # Export bboxes as straight rectangles

)

Visualizing Results

Visualization with Matplotlib

Doctr provides built-in utilities for visualizing detection results.

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

from doctr.utils.visualization import visualizepage

import matplotlib.pyplot as plt

model = ocrpredictor(pretrained=True)

doc = DocumentFile.fromimages("document.png")

result = model(doc)

Visualize first page

pageresult = result.pages[0]

fig, ax = plt.subplots(1, 1, figsize=(12, 16))

visualizepage(pageresult.export(), doc[0], ax=ax, interactive=False)

plt.tightlayout()

plt.savefig("ocrresult.png", dpi=150)

plt.show()

Drawing Bounding Boxes Manually

import cv2

import numpy as np

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

doc = DocumentFile.fromimages("invoice.png")

result = model(doc)

Load original image

image = cv2.imread("invoice.png")

h, w = image.shape[:2]

for page in result.pages:

for block in page.blocks:

for line in block.lines:

for word in line.words:

# Convert normalized coordinates to pixels

(x1, y1), (x2, y2) = word.geometry

pt1 = (int(x1 w), int(y1 h))

pt2 = (int(x2 w), int(y2 h))

# Draw rectangle

cv2.rectangle(image, pt1, pt2, (0, 255, 0), 2)

# Add text label

cv2.putText(image, word.value,

(pt1[0], pt1[1] - 5),

cv2.FONTHERSHEYSIMPLEX, 0.4,

(0, 0, 255), 1)

cv2.imwrite("annotatedresult.png", image)

print("Result saved to annotatedresult.png")

Export to HTML Format

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

doc = DocumentFile.frompdf("document.pdf")

result = model(doc)

Reconstruct page as synthetic image

syntheticpages = result.synthesize()

for idx, pageimg in enumerate(syntheticpages):

import matplotlib.pyplot as plt

plt.imsave(f"syntheticpage{idx}.png", pageimg)

print(f"Synthetic page {idx} saved")

Advanced Usage

Batch Processing Multiple Documents

When you need to process many documents at once, use batch processing for efficiency.

import os

import json

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

def processdirectory(inputdir, outputdir):

os.makedirs(outputdir, existok=True)

supportedformats = ('.png', '.jpg', '.jpeg', '.tiff', '.pdf')

files = [f for f in os.listdir(inputdir)

if f.lower().endswith(supportedformats)]

results = {}

for filename in files:

filepath = os.path.join(inputdir, filename)

print(f"Processing: {filename}")

if filename.lower().endswith('.pdf'):

doc = DocumentFile.frompdf(filepath)

else:

doc = DocumentFile.fromimages(filepath)

result = model(doc)

# Save full text

text = result.render()

textpath = os.path.join(outputdir, f"{filename}.txt")

with open(textpath, 'w', encoding='utf-8') as f:

f.write(text)

# Save structured results

jsonpath = os.path.join(outputdir, f"{filename}.json")

with open(jsonpath, 'w', encoding='utf-8') as f:

json.dump(result.export(), f, indent=2)

results[filename] = {

'pages': len(result.pages),

'textlength': len(text),

'outputtext': textpath,

'outputjson': jsonpath

}

print(f" -> {len(result.pages)} pages, {len(text)} characters")

return results

Run

summary = processdirectory("./documents", "./ocrresults")

print(f"\nTotal documents processed: {len(summary)}")

Extracting Information from Invoices

A practical example of extracting key fields from invoices using Doctr and simple post-processing.

import re

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

def extractinvoicedata(pdfpath):

doc = DocumentFile.frompdf(pdfpath)

result = model(doc)

fulltext = result.render()

lines = fulltext.split('\n')

invoicedata = {

'invoicenumber': None,

'date': None,

'total': None,

'items': []

}

for line in lines:

line = line.strip()

# Find invoice number

invmatch = re.search(r'(?:Invoice|INV|No)[.\s#:](\S+)', line, re.IGNORECASE)

if invmatch and not invoicedata['invoicenumber']:

invoicedata['invoicenumber'] = invmatch.group(1)

# Find date

datematch = re.search(

r'(\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{1,2}\s+\w+\s+\d{4})',

line

)

if datematch and not invoicedata['date']:

invoicedata['date'] = datematch.group(1)

# Find total amount

totalmatch = re.search(

r'(?:Total|Grand Total|Amount Due)[:\s](?:\$|€|£)?\s([\d.,]+)',

line, re.IGNORECASE

)

if totalmatch:

invoicedata['total'] = totalmatch.group(1)

# Extract structured data per page

for page in result.pages:

for block in page.blocks:

blocktext = ""

for line in block.lines:

linetext = " ".join([w.value for w in line.words])

blocktext += linetext + "\n"

if any(keyword in blocktext.lower()

for keyword in ['item', 'description', 'qty', 'quantity']):

invoicedata['items'].append(blocktext.strip())

return invoicedata

Usage

data = extractinvoicedata("sampleinvoice.pdf")

print(f"Invoice Number: {data['invoicenumber']}")

print(f"Date: {data['date']}")

print(f"Total: {data['total']}")

print(f"Items found: {len(data['items'])}")

Using GPU for Acceleration

import torch

from doctr.models import ocrpredictor

Check GPU availability

device = torch.device("cuda" if torch.cuda.isavailable() else "cpu")

print(f"Using device: {device}")

Model automatically uses GPU if available

model = ocrpredictor(pretrained=True).to(device)

For TensorFlow backend

import tensorflow as tf

gpus = tf.config.listphysicaldevices('GPU')

print(f"GPUs available: {len(gpus)}")

Image Preprocessing for Better Results

import cv2

import numpy as np

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

def preprocessimage(imagepath):

img = cv2.imread(imagepath)

# Convert to grayscale

gray = cv2.cvtColor(img, cv2.COLORBGR2GRAY)

# Adaptive thresholding to handle uneven lighting

thresh = cv2.adaptiveThreshold(

gray, 255, cv2.ADAPTIVETHRESHGAUSSIANC,

cv2.THRESHBINARY, 11, 2

)

# Noise removal

kernel = np.ones((1, 1), np.uint8)

cleaned = cv2.morphologyEx(thresh, cv2.MORPHCLOSE, kernel)

# Deskew (correct skew)

coords = np.columnstack(np.where(cleaned > 0))

angle = cv2.minAreaRect(coords)[-1]

if angle < -45:

angle = -(90 + angle)

else:

angle = -angle

(h, w) = cleaned.shape[:2]

center = (w // 2, h // 2)

M = cv2.getRotationMatrix2D(center, angle, 1.0)

rotated = cv2.warpAffine(

cleaned, M, (w, h),

flags=cv2.INTERCUBIC,

borderMode=cv2.BORDERREPLICATE

)

# Save preprocessed result

outputpath = "preprocessed" + imagepath.split("/")[-1]

cv2.imwrite(outputpath, rotated)

return outputpath

Use preprocessed image

processedpath = preprocessimage("scanneddocument.png")

model = ocrpredictor(pretrained=True)

doc = DocumentFile.fromimages(processedpath)

result = model(doc)

print(result.render())

Integration with FastAPI

Building a REST API for OCR services using FastAPI and Doctr.

from fastapi import FastAPI, UploadFile, File

from fastapi.responses import JSONResponse

import tempfile

import os

from doctr.io import DocumentFile

from doctr.models import ocrpredictor

app = FastAPI(title="Doctr OCR API")

model = ocrpredictor(pretrained=True)

@app.post("/ocr/image")

async def ocrimage(file: UploadFile = File(...)):

with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:

content = await file.read()

tmp.write(content)

tmppath = tmp.name

try:

doc = DocumentFile.fromimages(tmppath)

result = model(doc)

output = {

"filename": file.filename,

"text": result.render(),

"pages": []

}

for pageidx, page in enumerate(result.pages):

pagedata = {

"pagenumber": pageidx + 1,

"dimensions": page.dimensions,

"words": []

}

for block in page.blocks:

for line in block.lines:

for word in line.words:

pagedata["words"].append({

"text": word.value,

"confidence": round(word.confidence, 4),

"bbox": word.geometry

})

output["pages"].append(pagedata)

return JSONResponse(content=output)

finally:

os.unlink(tmppath)

@app.post("/ocr/pdf")

async def ocrpdf(file: UploadFile = File(...)):

with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:

content = await file.read()

tmp.write(content)

tmppath = tmp.name

try:

doc = DocumentFile.frompdf(tmppath)

result = model(doc)

return JSONResponse(content={

"filename": file.filename,

"text": result.render(),

"totalpages": len(result.pages),

"structured": result.export()

})

finally:

os.unlink(tmppath)

Run the server:

uvicorn ocrapi:app --host 0.0.0.0 --port 8000

Test the endpoint:

curl -X POST "http://localhost:8000/ocr/image" \

-F "file=@invoice.png" | python -m json.tool

Fine-tuning the Recognition Model

If the pre-trained model doesn't provide sufficient accuracy for specific document types, you can fine-tune it.

import torch

from doctr.datasets import CORD

from doctr.models import recognition

from doctr.transforms import Resize

from torch.utils.data import DataLoader

Prepare dataset

traindataset = CORD(

train=True,

download=True,

recognitiontask=True,

imgtransforms=Resize((32, 128))

)

valdataset = CORD(

train=False,

download=True,

recognitiontask=True,

imgtransforms=Resize((32, 128))

)

trainloader = DataLoader(traindataset, batchsize=32, shuffle=True)

valloader = DataLoader(valdataset, batchsize=32, shuffle=False)

Initialize model

model = recognition.crnnvgg16bn(pretrained=True)

optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

Training loop

device = torch.device("cuda" if torch.cuda.isavailable() else "cpu")

model = model.to(device)

numepochs = 10

for epoch in range(numepochs):

model.train()

totalloss = 0

for batchidx, (images, targets) in enumerate(trainloader):

images = images.to(device)

output = model(images, targets)

loss = output["loss"]

optimizer.zerograd()

loss.backward()

optimizer.step()

totalloss += loss.item()

if batchidx % 50 == 0:

print(f"Epoch [{epoch+1}/{numepochs}], "

f"Batch [{batchidx}/{len(trainloader)}], "

f"Loss: {loss.item():.4f}")

avgloss = totalloss / len(trainloader)

print(f"Epoch [{epoch+1}/{numepochs}], Average Loss: {avgloss:.4f}")

Save model

torch.save(model.statedict(), "doctrfinetuned.pth")

print("Model saved!")

Using Custom Models

from doctr.models import ocrpredictor, recognition

Load fine-tuned recognition model

customreco = recognition.crnnvgg16bn(pretrained=False)

customreco.loadstatedict(torch.load("doctrfinetuned.pth"))

Use in OCR pipeline

model = ocrpredictor(

detarch='dbresnet50',

recoarch=customreco,

pretrained=False

)

Best Practices

1. Optimize Input Quality

Input image quality significantly affects OCR accuracy. Here are some tips:

  • Ensure minimum resolution of 150 DPI, ideally 300 DPI
  • Avoid images that are too dark or too bright
  • Correct document skew before OCR
  • Remove noise and artifacts from the scanning process

2. Choose the Right Model

  • For standard documents with horizontal text, use assumestraightpages=True to speed up inference
  • For documents with varying orientations, use straightenpages=True
  • For deployment on edge devices, choose MobileNet-based architectures
  • For maximum accuracy, use sarresnet31 or master as the recognition model

3. Memory Management

import torch

from doctr.models import ocrpredictor

model = ocrpredictor(pretrained=True)

Use torch.nograd() during inference

with torch.nograd():

result = model(doc)

Clear GPU cache after processing large batches

if torch.cuda.isavailable():

torch.cuda.emptycache()

4. Post-processing OCR Results

Apply post-processing to improve output quality:

import re

def cleanocrtext(text):

# Join words split by line breaks

text = re.sub(r'(\w)-\n(\w)', r'\1\2', text)

# Normalize whitespace

text = re.sub(r'\s+', ' ', text)

# Common OCR error corrections

corrections = {

'0': 'O', # Contextual: at word start

'l': '1', # Contextual: between digits

'rn': 'm', # Common error

}

# Remove invalid characters

text = re.sub(r'[^\w\s.,;:!?@#$%&()\-+=\[\]{}\'"/\\]', '', text)

return text.strip()

Usage

rawtext = result.render()

cleantext = cleanocrtext(rawtext)

print(cleantext)

5. Confidence Filtering

Filter words with low confidence to improve reliability:

def filterlowconfidence(result, threshold=0.5):

filteredwords = []

lowconfidencewords = []

for page in result.pages:

for block in page.blocks:

for line in block.lines:

for word in line.words:

if word.confidence >= threshold:

filteredwords.append(word.value)

else:

lowconfidencewords.append(

(word.value, word.confidence)

)

print(f"Words with confidence >= {threshold}: {len(filteredwords)}")

print(f"Words with confidence < {threshold}: {len(lowconfidencewords)}")

if lowconfidencewords:

print("\nLow confidence words:")

for word, conf in lowconfidencewords:

print(f" '{word}' -> {conf:.4f}")

return " ".join(filteredwords)

highqualitytext = filterlowconfidence(result, threshold=0.7)

Comparison with Other OCR Libraries

| Feature | Doctr | Tesseract | EasyOCR | PaddleOCR |

|---------|-------|-----------|---------|-----------|

| Backend | PyTorch/TF | C++ | PyTorch | PaddlePaddle |

| Deep Learning | Yes | Partial | Yes | Yes |

| Structured Output | Yes | Limited | Limited | Yes |

| PDF Support | Native | Via wrapper | No | Yes |

| Pre-trained Models | Many | Limited | Many | Many |

| Fine-tuning | Easy | Difficult | Medium | Medium |

| GPU Speed | Fast | N/A | Medium | Fast |

Conclusion

Doctr is an excellent choice for OCR projects that require high accuracy on business documents. With its modern two-stage pipeline architecture, multi-backend support (PyTorch and TensorFlow), and structured output that is easy to process, Doctr offers a good balance between ease of use and flexibility.

Key takeaways to remember:

  • Use pre-trained models to get started quickly
  • Choose model architecture according to your needs: MobileNet for speed, ResNet/ViT for accuracy
  • Good image preprocessing significantly impacts final results
  • Leverage the structured output (Document > Page > Block > Line > Word) for layout analysis
  • Fine-tune when default accuracy is insufficient for your specific document domain
  • Confidence scores are very useful for filtering and quality control

By following the guidelines and best practices in this tutorial, you have a solid foundation for building deep learning-based OCR systems using Doctr, whether for experimentation or production deployment.

Related Articles

Surya OCR Tutorial: Document AI for Text Detection, OCR, and Layout Analysis

Tutorial Surya OCR: Document AI untuk Text Detection, OCR, dan Layout Analysis Surya adalah library Document AI open-sou...

PaddleOCR: High Accuracy Text Extraction from Images and Documents

PaddleOCR: Ekstraksi Teks dari Gambar dan Dokumen dengan Akurasi Tinggi Halo temen-temen, kali ini kita bahas salah satu...

PyTorch Geometric Tutorial: Graph Neural Networks in PyTorch

Memulai dengan PyTorch Geometric: Graph Neural Network di PyTorch Sebagian besar machine learning bekerja pada grid (gam...

OpenCV + Deep Learning Tutorial: Modern Image Processing with Python

OpenCV + Deep Learning: Tutorial Komprehensif Daftar Isi Pendahuluan Prasyarat Dasar-Dasar Preprocessing Gambar [T...