Code readingTroubleshooting

OCR finds no languages: fix the tessdata path in your environment

OCR lists no languages when the engine runs but cannot see its tessdata files. Fix TESSDATA_PREFIX for your environment, or put the data beside the engine.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01What the empty language list is telling you
  2. 02Why the terminal sees languages and your IDE does not
  3. 03Locate the engine and the data before changing anything
  4. 04The fix: put the data where the engine looks
  5. 05Prove the fix, then spend your effort on pixels
  6. 06Traps that imitate this failure
  7. 07When to stop hosting the OCR environment yourself
  8. 08Checklist: an OCR environment that stays fixed
  9. 09Questions

You ran the standard pyocr probe and it printed Will use tool 'Tesseract (sh)', left the languages line empty, and crashed on langs[0] with an IndexError. The engine is installed and it launches. What it cannot see is its language data, the .traineddata files. This walkthrough is for engineers scripting OCR on Windows from a conda environment and launching it from an IDE: why tesseract --list-langs works in the terminal while Python sees nothing, where conda actually puts the files, the two fixes that hold, and when to stop hosting OCR on a general-purpose computer at all.

What the empty language list is telling you

The tool name is the first clue. Tesseract (sh) means pyocr found an executable and drives it through a shell, so discovery succeeded and the binary resolves. The library's contract for get_available_languages() is blunt: "An array of language codes or an empty array if no languages are present." An empty array is the engine reporting that no .traineddata files are visible where it looked. The IndexError on langs[0] is your script trusting that report.

Two neighboring failures get mistaken for this one. No OCR tool found from get_available_tools() means discovery itself failed, usually a bad executable override. And when the tool loads but a requested language file is missing, the engine exits with Error opening data file ./jpn.traineddata plus the instruction to point TESSDATA_PREFIX at your tessdata directory — a sibling failure with its own walkthrough. Note the ./ in that path: with nothing configured, the engine falls back to the current working directory, so the same script can behave differently depending on where you launch it.

Why the terminal sees languages and your IDE does not

In the activated Anaconda Prompt, tesseract --list-langs prints jpn and jpn_vert. That prompt's process carries the environment's directories on its PATH. Your IDE is a different process tree: it launches python.exe itself, and nothing typed with set in a prompt ever reaches it, because set scopes a variable to that prompt and its children only.

That one scope rule explains the failed attempt exactly. The variable was born in a prompt; the script ran from the IDE; the two processes never met. The value was aimed at the wrong folder as well — the path pointed into the pkgs cache, where conda keeps the package archives it installs from, not the environment the engine runs in.

Locate the engine and the data before changing anything

Run where tesseract in the activated prompt and keep the absolute path it prints. On a conda-forge install the engine lives in the environment's Library\bin folder, and the language data ships in a share\tessdata folder within the package. Two folders, one environment — plus the cache, which is neither.

Then ask the interpreter that actually runs your script, not the terminal:

import os
print(os.environ.get("TESSDATA_PREFIX"))

If that prints None inside the IDE, you have proven the scope gap. Fix where the variable is born, not the engine.

The fix: put the data where the engine looks

The placement that resolved this exact case needs no environment variable:

  1. Open the Library\bin folder that holds tesseract.exe.
  2. Copy the .traineddata files you need — eng, jpn, jpn_vert — from the package's share\tessdata folder into Library\bin itself, beside the executable.
  3. Re-run the script from the IDE. No variable, no PATH edit, nothing to reconstruct on the next machine.

Use that placement when one machine runs one environment. When several engines or versions share a bench, or you cannot write into the environment folder, set TESSDATA_PREFIX instead and make sure the script's own process is born with it:

  1. setx TESSDATA_PREFIX "<path to a tessdata folder inside the environment>" writes a user variable — new processes only, so restart the IDE after setting it.
  2. Or add the variable to the IDE's run-configuration environment fields, per project.
  3. Or set it in-process before the first OCR call, which works because the wrapper starts tesseract.exe as a child that inherits the parent's environment:
import os
from PIL import Image

import pyocr
import pyocr.builders

# str: example path — the folder `where tesseract` leads you to, in your env
tessdata_dir = r"C:\Users\eng\anaconda3\envs\vision\Library\bin\tessdata"
os.environ["TESSDATA_PREFIX"] = tessdata_dir

tools = pyocr.get_available_tools()
tool = tools[0]
langs = tool.get_available_languages()
print("tool:", tool.get_name())   # Tesseract (sh)
print("languages:", langs)        # ['jpn', 'jpn_vert'] in this case
assert len(langs) > 0, "engine still sees no language data"

txt = tool.image_to_string(
    Image.open("test.png"),
    lang="jpn",
    builder=pyocr.builders.TextBuilder(tesseract_layout=6),
)
print(txt)

The assert is the proof, and it runs in the same process your production code will use.

If you override the executable path, aim it at the engine binary. pyocr exposes the override as pyocr.tesseract.TESSERACT_CMD; in the case behind this article, inserting the override line made the tool vanish from discovery even with the engine's absolute path in it. When where tesseract already resolves in the activated prompt, leave the override out entirely — every override is one more path that can go stale.

Prove the fix, then spend your effort on pixels

Once the IDE's own run prints a real language list, the environment is closed and the next limit is optical. Size it once with two lines of arithmetic:

pixels_per_mm = sensor_pixels_across / field_of_view_mm stroke_px = stroke_mm * pixels_per_mm

Worked example, with every input stated: the part carries 6 mm characters with 0.8 mm strokes, the field of view is 180 mm wide, and the camera puts 2448 pixels across it.

  • 2448 px ÷ 180 mm = 13.6 px/mm
  • Stroke: 0.8 mm × 13.6 px/mm = 10.9 px, call it 11 px
  • Character height: 6 mm × 13.6 px/mm = 82 px

Taking 3 px across the narrowest stroke as the working floor, 11 px clears it with nearly four times margin: the optics are not the problem, and the empty list was pure environment. Open the same camera to a 500 mm view and the numbers fall to 2448 ÷ 500 = 4.9 px/mm and a 0.8 mm stroke at 3.9 px — sitting on the floor. Cap that station at a 400 mm view (2448 ÷ 400 = 6.1 px/mm, stroke 4.9 px) or move up in pixels; adding light buys you nothing here.

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

Once words decode but read wrong, the remaining levers are segmentation mode and character whitelists — Tesseract digit accuracy through resolution, psm and a whitelist covers them, and batching OCR across a folder of images is the natural next step at a label station.

Traps that imitate this failure

  • A wrapper named as the engine. pytesseract is a Python package that shells out to the engine; there is no pytesseract.exe to point an override at. The executable is always tesseract.exe.
  • Interpreter mismatch. The run configuration can launch a different environment's python.exe than the prompt you tested in. Open the configuration, read which interpreter it names, and apply the fix in that environment.
  • Assuming English comes first. With only jpn and jpn_vert on disk, langs[0] is jpn. Pass the language to image_to_string explicitly and list order stops mattering.
  • Testing in a terminal, shipping from the IDE. The assert belongs in the process that does the work, or it proves nothing about production.

When to stop hosting the OCR environment yourself

The path problem you just fixed is the strongest argument for knowing when to move up. Three homes, one decision.

While the reading logic is still changing, keep it on the bench machine: full control of the stack, and full ownership of every path defect like this one.

When the station is real, move the host into the cabinet. A vision controller runs the same software stack in fanless industrial packaging with camera ports and light outputs, and the environment becomes one deployment image you build once and freeze.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

If that controller runs embedded Linux, the runtimes and language data you ship are decided when the root filesystem is built, not at run time — selecting embedded Linux languages and the rootfs walks through that choice.

When the deliverable is read rate and a clean PLC handoff, stop hosting OCR yourself. Fixed code readers decode 1D and 2D codes in firmware, and smart cameras run inspection jobs on-device with no computer to patch; language choice becomes a job setting instead of a file path. The same trade appears in barcode work, where hand-rolled decode pipelines lose to commercial readers.

Fixed code readers2560 x 2048 fixed code reader, 30 fps, 60 codes/s, 8 mm lens (LS-CRD-0028)LS-CRD-0028
Interface
Fast Ethernet
Resolution
5 MP
$920.00View part
Smart cameras & vision sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
$1,629.00View part

One safety note belongs here: where the line uses a laser-scanning reader rather than an imaging reader, respect the laser class label and its interlocks — that is a mounting decision, not a software one. For the handoff itself, parsing OCR labels to a PLC over Modbus TCP shows the working pattern, and the camera finder and the system configurator from the camera step size the rest of the chain.

Checklist: an OCR environment that stays fixed

  • One engine, one path: where tesseract in the activated prompt, and the IDE run configuration names that environment's interpreter.
  • Language data beside the engine in Library\bin, or TESSDATA_PREFIX born with the script — run configuration, setx, or in-process before the first call.
  • The assert on the language list passes from the IDE run, not only from a terminal.
  • The language is passed explicitly on every read; nothing depends on langs[0].
  • Windows paths in code are raw strings (r"...") so backslashes survive.
  • Stroke width measures at least 3 px at the working field of view, checked once with the arithmetic above.
  • No configured path points into the pkgs cache.

If the list is still empty after all seven, send the where tesseract output and the assert's message through the contact page — those two lines identify the remaining gap.

Questions

Why does tesseract --list-langs work in the terminal but my script sees no languages?

Because they are different processes. The activated prompt carries the environment's directories on PATH; the IDE run configuration launches python.exe without them, and the IDE's terminal panel is a third environment again, so testing there says nothing about the run. Print TESSDATA_PREFIX from inside the script itself — the interpreter that does the work is the only one whose environment counts.

Should TESSDATA_PREFIX point into the anaconda3 pkgs folder?

No. pkgs is conda's package cache — the archives conda installs from — and conda clean exists to empty it, so a path in there can vanish under you. It also carries the package version in its name, which breaks at the next update. Point the variable at a tessdata folder inside the environment, or place the files beside tesseract.exe in Library\bin and skip the variable.

Is there a pytesseract.exe I should point my wrapper at?

No. pytesseract is a Python package that runs the engine as a subprocess; the executable is tesseract.exe. An override aimed at a non-existent binary does not degrade gracefully — it removes the tool from discovery and you get No OCR tool found instead of an empty list. To test any override, call the wrapper's own probes: get_tesseract_version() and get_languages() both read the engine the wrapper resolves.

The list now shows jpn and jpn_vert but no eng — is the install broken?

No — the list reports exactly what is on disk. A package that ships Japanese data gives you jpn first, so code that assumes English opens the wrong language without failing. Read the list, pass the language explicitly in the image_to_string call, and copy an eng traineddata file in only for parts that actually carry Latin text.

When do I actually need to set the executable override?

When several conda environments each carry their own tesseract.exe and PATH order could resolve the wrong one: point each project at that project's engine binary so the pairing of engine and language data is explicit. Otherwise leave it unset — where tesseract in the activated prompt already resolves to the one engine you use, and every override is one more path that goes stale after an update.

Do code readers and smart cameras need tessdata configured too?

Not on your computer. Readers and smart cameras run their decode and inspection stack in firmware, so language selection is a job setting you configure once, not a file path you place and version. The whole class of failure this article fixes — engine in one folder, data in another, variable in a third — disappears when the OCR host is a device you never image yourself.