Skip to main content

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. Pages are degraded with a realistic pipeline scaled by severity: paper texture and fiber grain, irregular illumination and vignettes, bleed-through (verso ghosting), stains and coffee rings, printer toner banding/fade/dropout, scanner streaks, noise, blur, JPEG artifacts and a phone-photo preset; line crops additionally get small rotation, perspective and sine-warp distortions (geometry is applied per line so ground-truth boxes stay exact). ~90 Windows fonts are used when available (training currently expects Windows for C:\Windows\Fonts).

legere train                          # defaults, live dashboard
legere train --setup                  # interactive setup first
legere train --finetune bundled       # start from the packaged weights
legere train --finetune ckpt.pt       # start from any checkpoint's weights
legere train --no-dashboard           # plain log lines
legere train --resume checkpoints/last.pt   # continue an interrupted run

legere train --setup opens an interactive setup where every option (mode, style, steps, threads, workers, batch size, ...) is prompted with a sensible default — press Enter to accept, then confirm the summary.

--finetune loads weights only (fresh optimizer, LR schedule, step counter and best-CER — peak LR defaults to 5e-4 instead of 2e-3), so a new-style run converges much faster than from scratch. --resume restores the full training state and is meant for continuing the same interrupted run.

The live dashboard shows the run mode (scratch/fine-tune/resume), loss and CER trend sparklines, per-style CER (document vs report), punctuation CER, throughput, eval history, sample predictions and RAM/CPU gauges.

Resource usage is configurable for bigger machines:

legere train --workers 2              # generate pages in background processes
legere train --threads 12             # default: all physical cores
legere train --width-budget 18432     # batch size knob (default: 9216)
legere train --ram-limit 8192         # MB shown in the dashboard RAM gauge

With --workers 0 (default) pages are generated in-process — lowest RAM. --workers 1-2 overlaps data generation with the model's forward/backward pass, which speeds up long runs when generation is the bottleneck (each worker adds a few hundred MB of RAM).

Two synthetic page styles are available via --style (default mixed): document (free-form headers, paragraphs, key/value lines, tables) and report (bordered technical report forms: gray section bars, label/value grid cells, measurements, serials, gray footer). mixed alternates both.

Training content is deliberately dense in easily-confused punctuation (. , ; : in times, decimals and lists); validation reports a dedicated punctuation CER (val punct CER on the dashboard, punct_cer in runs/metrics.jsonl) alongside overall CER/WER.

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
legere benchmark --style report   # full-page CER on report-style pages

Profiling

Stage-by-stage performance analysis of the real pipeline:

legere benchmark document.pdf                  # profile an image or PDF
legere benchmark page.png --json report.json   # stable JSON for comparisons
legere benchmark doc.pdf --checkpoint exports/model_int8.ts.pt --torchscript
legere train --profile ...                     # per-phase training timings

The inference report breaks the total down into PDF render, resize, deskew, segmentation, normalization, tensor prep, CRNN forward and CTC decode, plus per-line width/latency stats (avg/median/P95), per-batch padding waste, and lines/s & chars/s throughput. legere train --profile prints average data/forward/CTC/backward/optimizer times per step, showing whether the bottleneck is data generation (fix with --workers) or compute.

Results

Bundled weights: validation CER 0.044% (line level, 2000-line fixed set) at step 26k, trained on the document style only — retraining with --style mixed is recommended for report-style pages. legere benchmark reproduces line/page latency and full-page CER on synthetic pages.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

legere-0.8.0.tar.gz (1.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

legere-0.8.0-py3-none-any.whl (1.4 MB view details)

Uploaded Python 3

File details

Details for the file legere-0.8.0.tar.gz.

File metadata

  • Download URL: legere-0.8.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

Hashes for legere-0.8.0.tar.gz
Algorithm Hash digest
SHA256 3ca9cd3332cf6ea6f5490e818dd3a67925c4ef1066a24057d72c67464797dd90
MD5 dc00e1dab275ef9bb57a5aaa7d5dad86
BLAKE2b-256 b029c10f99a6322aac95e22f15833a867d239ebac1f37b96cfda162d2ae6b32a

See more details on using hashes here.

File details

Details for the file legere-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: legere-0.8.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

Hashes for legere-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bb299259350c9a79d09f8745d0339122d9707d90d9674328847493b7408de358
MD5 1d59fb0665c4de67d3b7b6362a6c20ea
BLAKE2b-256 ba32305497b74b94ea060535d7ce2ab1aca89c9321bfd3159332f3b110de646e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page