Fast PDF text and image/table extraction using PyMuPDF + YOLOv8 DocLayNet ONNX
Project description
SARAL Docling
Fast PDF text and image/table extraction.
Powered by PyMuPDF + YOLOv8 DocLayNet (bundled) — no model file needed.
pip install saraldocling
saraldocling paper.pdf
License: This package bundles
yolov8n-doclaynet.onnxwhich is released under AGPL-3.0. The entiresaraldoclingpackage is therefore also distributed under AGPL-3.0.
Full text: https://www.gnu.org/licenses/agpl-3.0.html
No OCR. Only PDFs with a native text layer are supported.
For scanned PDFs, pre-process withocrmypdffirst (see Limitations).
Table of contents
- How it works
- Installation
- CLI usage
- Python API
- Output structure
- FastAPI / server deployment
- Configuration reference
- Limitations
How it works
| Stage | What happens |
|---|---|
| 1 — Text | PyMuPDF extracts the native text layer from every page |
| 2 — Images/Tables | YOLOv8 DocLayNet (bundled ONNX) detects Picture and Table regions, re-renders those regions at 300 DPI, and saves them as PNGs |
The ONNX model (yolov8n-doclaynet.onnx) is included inside the wheel — there
is no separate download step.
Installation
onnxruntimeis included by default (CPU). For GPU, use[gpu]instead. Do not install both — the twoonnxruntimebuilds conflict.
Default (CPU) — works everywhere, no GPU required
pip install saraldocling
GPU — NVIDIA CUDA
Requires CUDA 11.8+ and cuDNN on the host.
Compatibility: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html
pip install "saraldocling[gpu]"
Apple Silicon — CoreML acceleration (M1/M2/M3/M4)
The default install works fine on ARM. For ANE-accelerated inference:
pip install saraldocling
pip install onnxruntime-silicon # replaces the cpu wheel
Linux / Debian server — one-time system packages
sudo apt-get install -y libgl1 libglib2.0-0
pip install saraldocling
From source (development)
git clone https://github.com/yourname/saraldocling
cd saraldocling
pip install -e ".[dev]"
CLI usage
The bundled model runs automatically. No --model flag needed.
# Full pipeline: text + image/table crops (default)
saraldocling paper.pdf
# Text only — skip YOLO, much faster
saraldocling paper.pdf --no-images
# Custom output directory
saraldocling paper.pdf -o ./results
# Preview extracted text in the terminal
saraldocling paper.pdf --preview 800
# One .txt file per page
saraldocling paper.pdf --pages
# GPU (CUDA) — needs saraldocling[gpu]
saraldocling paper.pdf --gpu
# Apple Silicon CoreML
saraldocling paper.pdf --coreml
# Debug: see every step logged
saraldocling paper.pdf -v
# All flags
saraldocling --help
What gets written
paper_out/
├── extracted_text.txt ← all pages joined
├── manifest.json ← JSON summary of the run
├── pages/ ← only with --pages
│ ├── page_001.txt
│ └── page_002.txt
└── extracted_images/ ← Picture + Table crops at 300 DPI
├── p0_Picture_231.png
└── p2_Table_445.png
Output directory is auto-named <pdf_stem>_out/ next to the PDF unless you
pass -o.
Python API
One-call pipeline (recommended)
from saraldocling import parse_pdf, ParseConfig
# Full pipeline — bundled model used automatically
result = parse_pdf(ParseConfig(
pdf_path="paper.pdf",
output_dir="./out",
))
print(result.num_pages) # int
print(result.text[:500]) # full extracted text
print(result.image_paths) # list[str] of saved PNG paths
# Text only — skip YOLO
result = parse_pdf(ParseConfig(
pdf_path="paper.pdf",
output_dir="./out",
extract_images=False,
))
Lower-level: text extraction only
from saraldocling import PDFProcessor
with PDFProcessor("paper.pdf") as proc:
full_text = proc.extract_text()
page_text = proc.extract_text_by_page(1) # 0-indexed
page_image = proc.extract_page_image(0, dpi=150) # PIL.Image
Lower-level: YOLO extraction only
from saraldocling import ImageExtractor
# CPU (default)
with ImageExtractor() as ext:
paths = ext.extract_images_from_pdf("paper.pdf", "./out")
# GPU
with ImageExtractor(providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) as ext:
paths = ext.extract_images_from_pdf("paper.pdf", "./out")
# Apple Silicon
with ImageExtractor(providers=["CoreMLExecutionProvider", "CPUExecutionProvider"]) as ext:
paths = ext.extract_images_from_pdf("paper.pdf", "./out")
Using a custom model (advanced)
result = parse_pdf(ParseConfig(
pdf_path="paper.pdf",
output_dir="./out",
model_path="/path/to/custom.onnx", # overrides the bundled model
))
Output structure
manifest.json example:
{
"pdf": "/abs/path/to/paper.pdf",
"pages": 12,
"text_chars": 38201,
"text_file": "/abs/path/to/paper_out/extracted_text.txt",
"per_page_files": [],
"image_paths": [
"/abs/path/to/paper_out/extracted_images/p0_Picture_231.png",
"/abs/path/to/paper_out/extracted_images/p3_Table_112.png"
]
}
Crop filenames follow the pattern p<page>_<Label>_<x>.png where <Label>
is either Picture or Table.
FastAPI / server deployment
saraldocling is a plain pip package — drop it into any FastAPI app on Debian
with no special system configuration beyond the apt packages above.
Minimal endpoint
# main.py
import shutil, uuid
from pathlib import Path
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
from saraldocling import parse_pdf, ParseConfig
OUTPUT_ROOT = Path("/tmp/saraldocling")
app = FastAPI()
@app.post("/parse")
async def parse(file: UploadFile = File(...)):
job_dir = OUTPUT_ROOT / str(uuid.uuid4())
job_dir.mkdir(parents=True, exist_ok=True)
pdf_path = job_dir / file.filename
with open(pdf_path, "wb") as f:
shutil.copyfileobj(file.file, f)
result = parse_pdf(ParseConfig(
pdf_path=str(pdf_path),
output_dir=str(job_dir),
))
return JSONResponse({
"pages": result.num_pages,
"text": result.text,
"image_paths": result.image_paths,
})
pip install saraldocling fastapi uvicorn python-multipart
uvicorn main:app --host 0.0.0.0 --port 8000
Pre-load the ONNX session at startup
Creating an ImageExtractor loads and optimises the ONNX graph (~300 ms).
Do this once at startup and reuse it — session.run() is thread-safe:
from contextlib import asynccontextmanager
from fastapi import FastAPI
from saraldocling import ImageExtractor
extractor: ImageExtractor | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global extractor
extractor = ImageExtractor() # loads bundled model
yield
extractor.close()
app = FastAPI(lifespan=lifespan)
# then in your endpoint, call extractor.extract_images_from_pdf() directly
Configuration reference (ParseConfig)
| Field | Default | Description |
|---|---|---|
pdf_path |
required | Path to the source PDF |
output_dir |
required | Root directory for all output files |
extract_images |
True |
Run YOLO stage. False = text only |
model_path |
None |
Custom .onnx path. None = use bundled model |
conf_threshold |
0.30 |
YOLO confidence cutoff |
nms_threshold |
0.45 |
NMS IoU cutoff |
min_box_size |
30 |
Minimum crop side-length in pixels |
num_workers |
cpu_count |
Thread-pool size for page processing |
onnx_providers |
["CPUExecutionProvider"] |
ONNX Runtime execution providers |
DocLayNet labels
The model detects 11 document element types.
Only Picture and Table regions are saved as crops.
Caption Footnote Formula List-item Page-footer
Page-header Picture Section-header Table Text Title
Limitations
Not OCR — requires a native text layer in the PDF. For scanned documents:
pip install ocrmypdf
ocrmypdf scanned.pdf scanned_ocr.pdf
saraldocling scanned_ocr.pdf
Thread safety — both PDFProcessor and ImageExtractor use internal
mutex locks (mirroring the Go SafeDocument). Safe for concurrent use.
License
This package is distributed under AGPL-3.0 because it bundles
yolov8n-doclaynet.onnx, which carries that licence.
What this means in practice:
- Free to use for any purpose, including commercial.
- If you modify the source and run it as a network service, you must make your modified source available under AGPL-3.0.
- The full licence text is at https://www.gnu.org/licenses/agpl-3.0.html
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file saraldocling-1.0.1.tar.gz.
File metadata
- Download URL: saraldocling-1.0.1.tar.gz
- Upload date:
- Size: 10.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d073702377d3f92c1fa66d82c8af61e980ada8b28d3a56970fc6106af7488c1
|
|
| MD5 |
3a0bf9fea79f1c2f21711c6cc47d7946
|
|
| BLAKE2b-256 |
2f5601201496aef8c8cc8ab7fa9f3da7335950bf0383401c87043c9b74cce48e
|
File details
Details for the file saraldocling-1.0.1-py3-none-any.whl.
File metadata
- Download URL: saraldocling-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29be1c403b72e4e215fe58a81a48de8791c648dc47897a3413107522536584bd
|
|
| MD5 |
6bc7aa030d417b724b54ac08619bdb74
|
|
| BLAKE2b-256 |
aab99b08fc69a5ce15077f056bd4a5067c3c26642ab8f49a8142fcb12451f79f
|