How to run OpenThai 2.0 OCR Text and OCR Markdown on Windows, Mac and Linux
Today we released two Thai OCR models as free downloads: OpenThai 2.0 OCR Text and OpenThai 2.0 OCR Markdown. This guide shows how to run each of them on your own computer, on Windows, macOS or Linux, from installing Python to turning a PDF into text or Markdown. Your documents stay on your machine.
Which model to use
| OpenThai 2.0 OCR Text | OpenThai 2.0 OCR Markdown | |
|---|---|---|
| What it writes | The page's text in reading order, with a box and a confidence for every line | Markdown, with tables as HTML (rowspan and colspan), and a box and a class for every block |
| Download size | 4 MB | 2.5 GB |
| Runs on | Any CPU; no GPU needed | An NVIDIA GPU (fastest), a Mac with Apple silicon, or a CPU |
| Speed | About 0.5 s a page on a Mac with M3 Max | 1.6 s a page on one NVIDIA H100; 15 to 27 s a page on a Mac with M3 Max |
| Licence | Apache-2.0 | OpenMDW-1.1 |
Use OCR Text when you need the words: search, copying text out of scans, or feeding a chatbot. Use OCR Markdown when the layout matters, above all tables, whose figures must stay in their rows and columns.
The Mac figures were measured for this guide on a MacBook Pro with M3 Max, on a Thai government letter and a table page; the H100 figure is from the model card.
Without writing code: Chinda Bot
Chinda Bot, our free desktop assistant, already has OCR Text built in. Attach a PDF with the paperclip, or drag it onto the chat, and every page is read on your computer; click the file's name to see and copy the text that was read. Chinda Bot runs on a Mac with Apple silicon, on Windows PCs (Intel or AMD, and Windows on Arm) and on Linux arm64.
Chinda Bot reads text lines only. For tables kept as tables, use OCR Markdown, below.
Before you start: Python
Both models run from Python 3.10 or later. We tested this guide with Python 3.13 on macOS and Python 3.12 on Ubuntu 24.04.
- Windows
- Mac
- Linux
Download Python 3.13 from python.org and run the installer. On its first screen, tick Add python.exe to PATH. Then open Command Prompt (press the Windows key and type cmd). Every Windows command below is for Command Prompt.
Install Python 3.13 from python.org, or with Homebrew: brew install python@3.13. Then open Terminal.
On Ubuntu or Debian:
sudo apt install python3 python3-venv python3-pip
OpenThai 2.0 OCR Text, on any computer
1. Make a folder and a Python environment
- Windows
- Mac
- Linux
mkdir thai-ocr
cd thai-ocr
py -m venv .venv
.venv\Scripts\activate
mkdir thai-ocr && cd thai-ocr
python3 -m venv .venv
source .venv/bin/activate
mkdir thai-ocr && cd thai-ocr
python3 -m venv .venv
source .venv/bin/activate
The environment keeps these packages apart from the rest of your computer. Activate it again (the last line) whenever you open a new window.
2. Install the packages and download the model
The same two commands on every system:
pip install onnxruntime opencv-python-headless pyclipper numpy pillow pypdfium2 huggingface_hub
hf download iapp/OpenThai2.0-OCR-Text --local-dir OpenThai2.0-OCR-Text
The model is two files of 1.8 MB and 2.4 MB, with its reading code, in the folder OpenThai2.0-OCR-Text.
Windows on Arm (RTX Spark and other Arm PCs): two of the packages, opencv-python-headless and pyclipper, do not yet publish versions for Windows on Arm, so the pip install line fails there. Either install the x64 version of Python from python.org, which Windows runs under emulation, more slowly; or use Chinda Bot, which runs OCR Text natively on Windows on Arm.
3. Save the script
Save this as ocr_text.py in the thai-ocr folder:
"""Read Thai and English text from pictures and PDFs with OpenThai 2.0 OCR Text.
Usage: python ocr_text.py document.pdf [more files ...]
Prints the text and saves it next to each file as <file>.txt.
"""
import sys
import time
from pathlib import Path
MODEL_DIR = Path(__file__).resolve().parent / "OpenThai2.0-OCR-Text"
sys.path.insert(0, str(MODEL_DIR))
from runtime.line_engine import LineOCR # noqa: E402
def pages(path):
"""Each page of a PDF as a picture (drawn at 200 dpi), or the picture itself."""
if path.suffix.lower() == ".pdf":
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(str(path))
for i in range(len(pdf)):
yield pdf[i].render(scale=200 / 72).to_pil()
else:
yield str(path)
ocr = LineOCR(str(MODEL_DIR), threads=2)
for name in sys.argv[1:]:
path = Path(name)
texts = []
for number, page in enumerate(pages(path), start=1):
start = time.perf_counter()
result = ocr.read(page)
seconds = time.perf_counter() - start
print(f"{path.name} page {number}: {len(result['lines'])} lines in {seconds:.2f} s", file=sys.stderr)
texts.append(result["text"])
text = "\n\n".join(texts)
path.with_name(path.name + ".txt").write_text(text, encoding="utf-8")
print(text)
4. Read a document
python ocr_text.py document.pdf
It prints the text and saves it as document.pdf.txt. It reads PDFs page by page, and pictures (PNG, JPG and the other common formats) as they are; give it several files at once if you like. Each page takes about half a second on a recent computer.
Two notes:
- Scanned PDFs and PDFs with text both work, because every page is drawn as a picture and read.
- For thousands of pages, change
threads=2tothreads=1and run one copy of the script per CPU core, each on its own share of the files. In one process, more threads stop helping after about two cores. The model card measures about 950 pages a minute on 8 server cores.
OpenThai 2.0 OCR Markdown
This model is 2.5 GB, so it is best on a GPU. We run it on Linux with NVIDIA H100 GPUs, and we tested this guide on a Mac with M3 Max. Pick your system:
- Windows
- Mac
- Linux
With an NVIDIA GPU: use WSL 2, which runs Ubuntu inside Windows and gives it the GPU through your normal NVIDIA driver. In PowerShell opened as administrator:
wsl --install -d Ubuntu-24.04
Restart when asked, open Ubuntu from the Start menu, and follow the Linux steps on this page inside it. We have not yet tested OCR Markdown on Windows ourselves.
Without an NVIDIA GPU: use the OCR Markdown API below, or Chinda Bot for plain text.
A Mac with Apple silicon (M1 or later) runs the model on its own GPU. Make the environment as in step 1 of OCR Text (a new folder is fine), then:
pip install huggingface_hub pypdfium2
hf download iapp/OpenThai2.0-OCR-Markdown --local-dir OpenThai2.0-OCR-Markdown
pip install -e ./OpenThai2.0-OCR-Markdown
The last line installs the model's own reading package, with PyTorch. Then save the script below and run:
python ocr_markdown.py document.pdf
On our M3 Max a table page took 15.5 s and a full page of dense text 26.7 s. The scores we publish were measured with vLLM on an NVIDIA GPU; on a Mac the same weights run through PyTorch instead, so a page can come out slightly differently.
With an NVIDIA GPU, serve the model with vLLM, the way our own API runs it. Make the environment as in step 1 of OCR Text, then:
pip install huggingface_hub pypdfium2
hf download iapp/OpenThai2.0-OCR-Markdown --local-dir OpenThai2.0-OCR-Markdown
pip install vllm==0.26.0
pip install -e ./OpenThai2.0-OCR-Markdown
vllm serve ./OpenThai2.0-OCR-Markdown --served-model-name nemotron-parse --trust-remote-code
Leave the server running. In a second terminal, in the same folder, activate the environment, save the script below and run:
python ocr_markdown.py document.pdf --server http://localhost:8000/v1
On one H100, a page takes about 1.6 s, and the server reads many pages at once if you send them in parallel. Our hosted API runs the model in about 8 GB of GPU memory. We have tested it on H100 GPUs only; by default vLLM takes 90% of the card's memory, and on a smaller card you may need to lower --max-num-seqs.
Without an NVIDIA GPU, leave out the vllm lines and run the script without --server. It then runs the model on the CPU, which is much slower.
Save this as ocr_markdown.py, in the folder that holds OpenThai2.0-OCR-Markdown:
"""Read pictures and PDFs into Markdown with OpenThai 2.0 OCR Markdown.
python ocr_markdown.py document.pdf
runs the model on this computer: an NVIDIA GPU, a Mac's GPU, or the CPU
python ocr_markdown.py document.pdf --server http://localhost:8000/v1
sends the pages to a vLLM server instead (the fastest way)
Saves the Markdown next to each file as <file>.md. Tables come out as HTML.
"""
import argparse
import sys
import time
from pathlib import Path
from PIL import Image
from nemotron_parse.inference import DocumentParser, GenerationError, GenerationSettings, TransformersBackend
MODEL_DIR = Path(__file__).resolve().parent / "OpenThai2.0-OCR-Markdown"
def pages(path):
"""Each page of a PDF as a picture (drawn at 200 dpi), or the picture itself."""
if path.suffix.lower() == ".pdf":
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(str(path))
for i in range(len(pdf)):
yield pdf[i].render(scale=200 / 72).to_pil()
else:
yield Image.open(path)
def local_parser():
import torch
from transformers import AutoModel, AutoProcessor
if torch.cuda.is_available():
device, dtype = "cuda", torch.bfloat16
elif torch.backends.mps.is_available():
device, dtype = "mps", torch.float32
else:
device, dtype = "cpu", torch.float32
model = AutoModel.from_pretrained(MODEL_DIR, trust_remote_code=True, dtype=dtype).to(device).eval()
processor = AutoProcessor.from_pretrained(MODEL_DIR, trust_remote_code=True)
class Backend(TransformersBackend):
# a vLLM server reads the prompt after a new line; send the same tokens here
def __call__(self, image, *, prompt, **kwargs):
return super().__call__(image, prompt="\n" + prompt, **kwargs)
print(f"Running the model on {device}", file=sys.stderr)
# read greedily (temperature 0); with sampling, a Mac's GPU garbled pages in our test
return DocumentParser(backend=Backend(model, processor), fullpage=GenerationSettings(temperature=0))
args = argparse.ArgumentParser()
args.add_argument("files", nargs="+")
args.add_argument("--server", help="a vLLM server, for example http://localhost:8000/v1")
args = args.parse_args()
parser = DocumentParser(base_url=args.server, model="nemotron-parse") if args.server else local_parser()
with parser:
for name in args.files:
path = Path(name)
parts = []
for number, page in enumerate(pages(path), start=1):
start = time.perf_counter()
try:
parts.append(parser.parse(page, mode="single")["markdown"])
except GenerationError:
parts.append(f"<!-- page {number} could not be read completely -->")
print(f"{path.name} page {number}: {time.perf_counter() - start:.1f} s", file=sys.stderr)
markdown = "\n\n".join(parts)
path.with_name(path.name + ".md").write_text(markdown, encoding="utf-8")
print(markdown)
It writes document.pdf.md, with each table as an HTML <table>; Markdown viewers and most language models read those as they are. If a page is ever cut off at the length limit, the script notes it in the file and goes on with the next page.
No GPU? Use the API
Both models also run as hosted APIs, with nothing to install:
- OpenThai 2.0 OCR Markdown API: PDF or picture in, Markdown and tables out, 0.15 IC a page.
- OpenThai 2.0 OCR Text API: plain text, Word, PowerPoint, Excel or a searchable PDF, from 0.049 IC a page.
Links
- Model pages: OpenThai 2.0 OCR Text and OpenThai 2.0 OCR Markdown, with benchmark results
- Weights: iapp/OpenThai2.0-OCR-Text and iapp/OpenThai2.0-OCR-Markdown on Hugging Face
- The launch post: OpenThai 2.0 OCR Markdown
If a Thai document does not read well, write to us at sale@iapp.co.th with a sample that holds no personal data; those pages help us improve the next version.