Instructions to use islamsidratul/jonaki-bangla-ocr with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PaddleOCR
How to use islamsidratul/jonaki-bangla-ocr with PaddleOCR:
# Please refer to the document for information on how to use the model. # https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/module_usage/module_overview.html
- Notebooks
- Google Colab
- Kaggle
Request access to Jonaki
Jonaki is free for non-commercial use under CC BY-NC 4.0. Every request is reviewed by the author. Commercial use needs a separate license: email sidrat.saj@gmail.com, or choose "Commercial" below.
Log in or Sign Up to review the conditions and access this model content.
Jonaki (ΰ¦ΰ§ΰ¦¨ΰ¦Ύΰ¦ΰ¦Ώ) β a small, fast Bengali text recognizer
Jonaki is Bengali for firefly: small, quick, and bright in the dark.
Jonaki reads printed Bengali text lines. It is PP-OCRv6_small_rec fine-tuned for Bengali β 3.1 M parameters, a 12 MB file, ~1.4 ms per line on a GPU β and runs in the stock PaddleOCR package. Pair it with the PP-OCRv6 text detector to read whole pages: on the benchmarks below it makes less than half the errors of PaddleOCR-VL 1.6, in about a tenth of the time.
Results
Character error rate (CER, lower is better). Text is NFC-normalised, whitespace collapsed and both danda forms unified before scoring.
Letterpress pages β 3 pages of Rabindranath Tagore's Achalayatan (scans with proofread text from Bengali Wikisource):
| system | CER |
|---|---|
| Jonaki + PP-OCRv6_medium_det (stock PaddleOCR pipeline, as in Usage) | 1.90% |
| Jonaki, running headers removed | 0.80% |
| PaddleOCR-VL 1.6 (0.9B, BF16) | 5.99% |
Scanned books β 185 pages of 37 books Jonaki never saw in training. The reference is an independent digital edition of each book aligned to the page, so edition differences put a floor under every system: compare systems, don't read the numbers as absolute error rates.
| system | CER |
|---|---|
| Jonaki + PP-OCRv6_medium_det, lines put in reading order (snippet below) | 4.6% |
| the same with PaddleOCR's own line order | 25.8% β it interleaves the two pages of a scanned spread |
| PaddleOCR-VL 1.6 (0.9B, BF16) | 24.5% β 4 failed pages, 4 repetition loops |
On the 165 pages where PaddleOCR-VL 1.6 didn't break down (page CER β€ 50%): Jonaki 4.2%, PaddleOCR-VL 9.9%.
Printed lines β 4,565 lines from held-out books: 0.22% CER, 92.4% of lines exactly right.
Speed and memory
RTX 5060 Ti 16 GB / Ryzen 7 7700 (8 threads); PaddlePaddle 3.4 (GPU) and 3.3.1 (CPU), paddleocr 3.7. Full pages: the pipeline in Usage (detector input capped at 1280 px), scanned book pages up to 2,715 px.
| GPU | CPU | |
|---|---|---|
| recognizer, one line (batch 32 / batch 1) | 1.4 / 7.6 ms | 22 / 28 ms (oneDNN) |
recognizer via TextRecognition (batch 8) |
2.7 ms | ~30 ms |
| full page (det + rec) | 0.16β0.18 s | 13β16 s (see CPU note) |
| memory, recognizer alone | < 1 GB VRAM | 0.9 GB RAM (batch 1), 1.3 GB (batch 8) |
| memory, full pipeline | 1.2 GB VRAM | 1.5 GB RAM |
PaddleOCR-VL 1.6 for comparison: 2.8 s per page and 2.4 GB on the same GPU, ~100 s per page on the CPU.
- Detector input size: PaddleOCR runs the detector at full resolution by default. Capped at 1280 px it read every page above as well (scanned books 4.60% vs 4.58%) in far less memory β at full resolution the pipeline needs 4.1 GB of RAM on the CPU and 2.6 GB of VRAM. 960 px needs 1.1 GB of RAM and scored 4.62%. For very large scans (well above 300 dpi) check small print before lowering it.
- GPU memory: Paddle's allocator keeps every block it frees, so a long run keeps growing. Cap it:
export FLAGS_gpu_memory_limit_mb=1000(2500 with the detector at full resolution). - CPU: under PaddlePaddle 3.3 the PP-OCRv6 detector fails with oneDNN, so the full pipeline needs
enable_mkldnn=False, which slows both models. With the detector's ONNX export (PP-OCRv6_medium_det_onnx) on onnxruntime and Jonaki on oneDNN, a page takes about 1.5β2 s on 8 cores.
Usage
Request access above, then:
pip install paddleocr paddlepaddle # GPU: install paddlepaddle-gpu instead, see paddlepaddle.org.cn/install
hf auth login
hf download islamsidratul/jonaki-bangla-ocr --local-dir jonaki
Tested with paddleocr 3.7.0 on PaddlePaddle 3.3.1 and 3.4.0.
Text lines
from paddleocr import TextRecognition
rec = TextRecognition(model_name="PP-OCRv6_small_rec", model_dir="jonaki")
for res in rec.predict(["line.png"], batch_size=8):
print(res["rec_text"], res["rec_score"])
model_name names the architecture, so PaddleOCR knows how to run the files; the weights come from model_dir.
Pages
from paddleocr import PaddleOCR
ocr = PaddleOCR(
text_detection_model_name="PP-OCRv6_medium_det",
text_recognition_model_name="PP-OCRv6_small_rec",
text_recognition_model_dir="jonaki",
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
text_det_limit_type="max",
text_det_limit_side_len=1280, # detector input: same accuracy as full resolution, less than half the memory
# enable_mkldnn=False, # on CPU with PaddlePaddle 3.3 (see above)
)
res = ocr.predict("page.png")[0]
print(page_text(res["rec_polys"], res["rec_texts"], res["doc_preprocessor_res"]["output_img"].shape[1]))
page_text puts the lines in reading order. PaddleOCR's own order runs across both pages of a two-page scan, which
took the scanned-book CER from 4.6% to 25.8%:
import cv2
import numpy as np
def page_text(polys, texts, page_width):
"""PaddleOCR boxes + texts -> page text: split a two-page spread (or two columns) at the gutter, then group the
boxes of each side into printed rows, top to bottom and left to right."""
boxes = [np.asarray(p, dtype=np.float32) for p in polys]
sides = [[" ".join(texts[i] for i in row) for row in rows(boxes, ids)] for ids in columns(boxes, page_width)]
return "\n\n".join("\n".join(side) for side in sides)
def rows(boxes, ids):
"""A box joins a row when its centre is within half a line height of the row's centre."""
if not ids:
return []
h = np.median([min(cv2.minAreaRect(boxes[i])[1]) for i in ids])
out = []
for cy, x, i in sorted((boxes[i][:, 1].mean(), boxes[i][:, 0].min(), i) for i in ids):
if out and abs(cy - np.mean([r[0] for r in out[-1]])) < 0.5 * h:
out[-1].append((cy, x, i))
else:
out.append([(cy, x, i)])
return [[i for _, _, i in sorted(r, key=lambda t: t[1])] for r in out]
def columns(boxes, page_width):
"""The gutter is the x in the middle 30-70% of the page crossed by the fewest boxes (at most 5% of them); both
sides must hold at least a quarter of the boxes and similar heights of text, or the page stays one column."""
ids = list(range(len(boxes)))
if len(boxes) < 8:
return [ids]
x0 = np.array([b[:, 0].min() for b in boxes])
x1 = np.array([b[:, 0].max() for b in boxes])
xs = np.arange(int(0.3 * page_width), int(0.7 * page_width), max(1, page_width // 200))
cover = np.array([((x0 <= x) & (x1 >= x)).sum() for x in xs])
if cover.min() > max(1, 0.05 * len(boxes)):
return [ids]
g = xs[cover.argmin()]
left = [i for i in ids if x0[i] + x1[i] < 2 * g]
right = [i for i in ids if x0[i] + x1[i] >= 2 * g]
if min(len(left), len(right)) < 0.25 * len(boxes):
return [ids]
span = lambda side: np.ptp([boxes[i][:, 1].mean() for i in side])
if min(span(left), span(right)) < 0.5 * max(span(left), span(right)):
return [ids]
return [left, right]
Model
- Architecture: PP-OCRv6 small rec (PP-LCNetV4 backbone, SVTR neck, CTC head), unchanged except for two things: a Bengali character set with its own output layer, and the backbone's final pooling, which keeps W/4 time steps instead of W/8 (Bengali packs several code points into one visible glyph cluster). Both are baked into the exported graph, so stock PaddleOCR runs it as is.
- Input: a text-line image, resized to height 48 with its aspect ratio kept.
- Output: Unicode Bengali text. The character set has 189 symbols: Bengali letters, vowel signs, digits, ΰ§³ and the danda, ZWJ/ZWNJ, Latin letters and digits, and common punctuation and math symbols.
- Training: fine-tuned from PP-OCRv6_small_rec on Bengali text-line images of three kinds β printed book pages in many fonts and typesetting styles, from letterpress to modern DTP; scanned book pages; and synthetic lines rendered in a wide range of Bengali fonts with scan-like degradation. The training data is not distributed.
Limitations
- Bengali script, printed text. Not trained on handwriting; Assamese-only letters (ΰ§°, ΰ§±) are not in the character set.
- English words inside Bengali text are read, but English-only pages were not evaluated. Other scripts (Arabic, Devanagari, β¦) are not supported.
- Horizontal text lines. Mathematical formulas and tables are not a target.
- Page results depend on the detector and the reading order around it: multi-column magazine layouts and footnotes come out in row order only.
License
The Jonaki weights are licensed under CC BY-NC 4.0: free for non-commercial use with attribution. Commercial use needs a separate license from the author β contact sidrat.saj@gmail.com, or choose "Commercial" in the access form.
Jonaki is a derivative of PP-OCRv6_small_rec, Β© PaddlePaddle Authors, licensed under the Apache License 2.0 (copy in
LICENSE-Apache-2.0.txt). Changes: new character set and output layer, the pooling change above, fine-tuned weights.
Citation
Jonaki builds on PP-OCRv6:
@misc{zhang2026ppocrv6,
title={PP-OCRv6: From 1.5M to 34.5M Parameters, Surpassing Billion-Scale VLMs on OCR Tasks},
author={Yubo Zhang and Xueqing Wang and Manhui Lin and Yue Zhang and Penglongyi Deng and Ting Sun and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Changda Zhou and Hongen Liu and Suyin Liang and Cheng Cui and Yi Liu and Dianhai Yu and Yanjun Ma},
year={2026},
eprint={2606.13108},
archivePrefix={arXiv},
primaryClass={cs.CV},
url={https://arxiv.org/abs/2606.13108},
}
- Downloads last month
- 1
Model tree for islamsidratul/jonaki-bangla-ocr
Base model
PaddlePaddle/PP-OCRv6_small_rec