Compact CRNN OCR for printed text (Portuguese charset) — pure PyTorch, CPU-friendly, ~360k parameters
Project description
Legere
Compact CRNN OCR for printed text (Portuguese charset with accents, digits, punctuation), built from scratch with PyTorch and trained entirely on synthetic pages generated with PIL. No pretrained models, no OCR libraries.
- ~360k parameters (hard limit enforced: 1M) — bundled weights are 1.4 MB
- CRNN: MobileNet-style depthwise separable CNN + 1 BiGRU(128) + linear head, CTC loss
- Classic (non-neural) deskew + projection-based line segmentation
- CPU-friendly: full-HD page in well under a second; INT8/TorchScript exports
- Trained weights ship inside the package — install and read pages immediately
Install
pip install legere # inference only
pip install legere[pdf] # + PDF input support (pypdfium2)
pip install legere[train] # + training extras (rich, psutil)
From a checkout:
pip install -e .[train]
Library usage
from legere import Legere
ocr = Legere() # bundled weights, CPU
result = ocr.read("page.png") # path, or a numpy array (gray or BGR)
print(result.text) # full text in reading order
print(result.skew_angle) # estimated page skew (degrees)
for line in result.lines:
print(line.text, line.bbox) # per-line text + (x0, y0, x1, y1)
Options: Legere(model="checkpoints/best.pt") for your own checkpoint,
Legere(model="exports/model_int8.ts.pt", torchscript=True) for the INT8
export, beam_width=8 for CTC beam search, threads=N to cap CPU threads.
PDFs (with legere[pdf] installed):
results = ocr.read_pdf("document.pdf", dpi=200) # one PageResult per page
from legere.pdf import pdf_to_pngs
pdf_to_pngs("document.pdf", out_dir="pages/") # just convert to PNGs
CLI
legere read page.png # text on stdout
legere page.png # same (shortcut)
legere read page.png --json out.json # + per-line text and bboxes
legere read page.png --beam 8 # CTC beam search
legere read page.png --model exports/model_int8.ts.pt --torchscript
legere read document.pdf # OCR every PDF page (legere[pdf])
legere pdf document.pdf --out-dir pages # convert PDF pages to PNGs (no OCR)
legere train # train on synthetic pages
legere export # TorchScript fp32 + INT8 exports
legere benchmark # CPU latency + page CER report
Pipeline of read: fit to 1920x1080 → adaptive binarization + deskew → line
segmentation by horizontal projection (rules removed via morphology) →
height-32 line batch through the CRNN → CTC greedy decode → text in reading
order.
Project layout
src/legere/
├── charset.py # character set + encode/decode helpers
├── model.py # CompactCRNN, parameter budget, CTC greedy/beam decode
├── segment.py # classic deskew + projection line segmentation
├── lines.py # line-crop normalization and batching
├── inference.py # Legere engine + full-page pipeline
├── pdf.py # PDF page rendering / PNG conversion (legere[pdf])
├── metrics.py # edit distance, CER, WER
├── cli.py # `legere` command
├── data/model.pt # bundled trained weights (state dict + charset)
└── training/ # synthetic pagegen, dataset stream, train/export/benchmark
Training
Training data is generated on the fly: synthetic pages with headers,
paragraphs, key/value lines, tables, Portuguese-like words, dates, monetary
values and CPF/CNPJ, degraded with noise/blur/JPEG artifacts. Fonts are read
from C:\Windows\Fonts (training currently expects Windows).
legere train # live Rich dashboard
legere train --no-dashboard # plain log lines
legere train --resume checkpoints/last.pt
Checkpoints go to checkpoints/ (best.pt by validation CER, last.pt
every eval); metrics append to runs/metrics.jsonl. Training stops early
once CER < 1% holds with no further improvement, or on a clear plateau.
Useful flags: --threads N, --width-budget N (batch size via padded-width
budget; lower it to reduce RAM).
After training, refresh the packaged weights:
legere export --checkpoint checkpoints/best.pt --bundle src/legere/data/model.pt
Export & benchmark
legere export # writes exports/model_fp32.ts.pt and exports/model_int8.ts.pt
legere benchmark # line/page CPU latency + full-page CER per variant
Results
Bundled weights: validation CER 0.044% (line level, 2000-line fixed set) at
step 26k. legere benchmark reproduces line/page latency and full-page CER
on synthetic pages.
License
MIT
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 legere-0.2.0.tar.gz.
File metadata
- Download URL: legere-0.2.0.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35e6f456c03b393198217e0913657f5d3fbd803f3d7a84536a0b4a6802b16c70
|
|
| MD5 |
55f91d69a009539891f40982aeff6c17
|
|
| BLAKE2b-256 |
6400e6de26af2ce33870e629fc233d9e0bce1fa1b01d6346a1e21e4336cf1836
|
File details
Details for the file legere-0.2.0-py3-none-any.whl.
File metadata
- Download URL: legere-0.2.0-py3-none-any.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3b12bdbd39fbbb1f41d36c090f3f6a8912406cea8920654ead7d2eece8800b8
|
|
| MD5 |
5d45fdae6e79c2fb7fdfc95a78acbcc2
|
|
| BLAKE2b-256 |
940f2a767634bf846ea2e719282d23dd0d375fdc18a132d8226d55d738d3c005
|