# Extract Text from PDFs and Images with Local OCR

> Install indra-ocr, run a CPU-only OCR model on an image, pull Markdown from a multi-page PDF, batch files, and find where the weights cache.

- Canonical: https://guides-ai.com/guides/run-ocr-locally/
- Plate 12.14 · Topic: AI media (https://guides-ai.com/topics/media/)
- Published: 19 Oct 2026 · 3 min read
- Source site: guides-ai — https://guides-ai.com/

Scanned PDFs and photos of documents hide their text from search, copy-paste and RAG. A local OCR model turns those pixels back into characters on your own machine, with no upload and no per-page API bill. This guide uses [datalab-to/indra-ocr](https://huggingface.co/datalab-to/indra-ocr): a small, CPU-only model that reads images and PDFs and returns clean Markdown.

## 1. Pick a local OCR model

OCR models split into two camps. Large vision-language models like `baidu/Unlimited-OCR` (about 3B parameters) are accurate but want a CUDA GPU; weigh that against your hardware in [how much RAM or VRAM a model needs](/guides/local-llm-ram-vram-requirements/). `indra-ocr` sits at the other end: it runs on ONNX Runtime on the CPU, needs no PyTorch, and downloads about 76 MB of weights. For a copy-paste pipeline that works on any laptop, start there.

## 2. Install the package

```bash
pip install indra-ocr
```

That pulls the ONNX runtime and the helper code; the model weights download on first use, and Python 3.10 or newer is required.

## 3. OCR a single image

```python
import indra_ocr

conv = indra_ocr.load(threads=4)  # mode="auto" | "ocr" | "text"

for i, page in conv.convert("scan.png"):
    print(page.text)
```

`load()` fetches the weights once and caches them, and `convert()` yields one `page` per input, where `page.text` holds the plain extracted text.

## 4. Extract a multi-page PDF

```python
for i, page in conv.convert("paper.pdf"):
    print(f"--- page {i} ---")
    print(page.markdown)
```

Pass a PDF and the same loop runs page by page; `page.markdown` keeps headings, lists and tables as structured Markdown instead of a flat text dump.

## 5. Batch many files

```python
if __name__ == "__main__":
    for path, result in indra_ocr.convert_many(["a.pdf", "b.pdf"], cores=None):
        print(path, "done")
```

`convert_many` runs one worker per CPU core and returns results in input order; it spawns processes, so the `__main__` guard is mandatory.

## 6. Find the cached weights

On first run the weights download from the Hub and cache alongside your other models; see [where Hugging Face caches files](/guides/hugging-face-cli-download-models/). To pin a fixed copy for an air-gapped box or CI, point `INDRA_OCR_MODEL_DIR` at a folder holding a pre-downloaded copy and nothing hits the network.

## If it fails

- **`ModuleNotFoundError: No module named 'indra_ocr'`** The package installed into a different environment. Activate the right venv and rerun `pip install indra-ocr`, and confirm `python --version` is 3.10 or newer.
- **`RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase`** `convert_many` spawned workers without a guard. Wrap the call in `if __name__ == "__main__":`.
- **First call stalls while "downloading"** It is fetching the model from the Hub. Pre-download once, point `INDRA_OCR_MODEL_DIR` at that folder, and later runs stay offline.

---

Next: [describe and query images with a vision model](/guides/describe-images-with-a-vision-model/) · [run a model with transformers pipeline()](/guides/transformers-pipeline-quickstart/) · [how much RAM or VRAM a model needs](/guides/local-llm-ram-vram-requirements/).
