The call fails before OCR reads a single character: the wrapper raises TesseractError with status 1, the message says Error opening data file, and it names jpn.traineddata and TESSDATA_PREFIX. Tesseract itself is installed and starts; the language data is simply not in the directory this runtime searches. That is a path problem, not a broken installation, and it has a five-minute fix. This guide is for engineers adding OCR to a vision pipeline in Python on a shop PC running Windows, Linux or WSL. You will find the tessdata directory the failing process actually uses, prove the language file sits in it, point TESSDATA_PREFIX at that one directory, and add a two-line existence test so the error cannot reach a running line.
What the error is telling you
Tesseract ships with no language models of its own. Each language you can request is one file, named like jpn.traineddata after the language code, and the engine loads it once at start-up from exactly one directory. That directory is chosen in a fixed order: the --tessdata-dir option if your call passes one, then the TESSDATA_PREFIX environment variable, then the default folder compiled into the installed build.
The error names the file it tried to open, so read that path first: it tells you which of the three levels your runtime ended at. Failed loading language followed by Could not initialize tesseract means the file was missing or unreadable at that location. Your image, your threshold and the rest of the code have not run yet; the process never got past loading.
The message's own instruction is one sentence: point TESSDATA_PREFIX at your tessdata directory. The rest of this guide is about making that the directory the failing process really searches, and proving it before the next failed decode.
Why the command line works but Python fails
In the case behind this guide, tesseract 48.png output_48 -l jpn decoded fine in a terminal while the Python call on the same image raised TesseractError status 1. That pair is diagnostic, not contradictory. The command-line run uses the default data directory of its own installed build and finds jpn.traineddata there. The script does not: it assigns the Windows directory C:\Program Files\Tesseract-OCR\tessdata to TESSDATA_PREFIX, then calls the engine from Python 3.8.10 inside WSL Ubuntu 20.04.6 LTS. Under Linux that Windows path does not exist. WSL exposes Windows drives under /mnt, which is why the same script opens its test image from a /mnt/c path, so the Linux engine searched a C:\ directory that no Linux process can see and gave up with status 1.
The decision follows directly. When the command line reads the language, the traineddata file exists on that machine. Your script's override is then the whole bug, and deleting the TESSDATA_PREFIX assignment is the fastest correct fix.
Set the tessdata path in five steps
- Read the directory out of the error. The path after Error opening data file is the directory your runtime used. On the Windows installer it is the tessdata folder inside the install directory, most often C:\Program Files\Tesseract-OCR\tessdata, with 32-bit installs under Program Files (x86). On Ubuntu's package the data sits under /usr/share/tesseract-ocr; the 20.04 build keeps it in /usr/share/tesseract-ocr/4.00/tessdata, and other distributions use layouts such as /usr/share/tesseract/4/tessdata.
- Confirm the file is on disk before you edit any code. List the directory and look for jpn.traineddata, or eng, chi_sim or heb, whichever code your call passes. If the file is missing, install the matching language package for your platform or copy the model file from the language data repository the Tesseract project publishes on its own site, which carries the models for every supported language. A real model file is a plain data file in the megabyte range; a zero-byte stub or a partially copied file will fail the same way.
- Set the variable in the failing process, before the first OCR call: os.environ["TESSDATA_PREFIX"] = TESSDATA_PATH, placed with the OCR code so every entry point into that code gets the same value.
- Aim at the tessdata folder itself, not the install root. The variable must name the directory that contains the .traineddata files. Setup notes written for Tesseract 3 point one level higher, and on current builds that reproduces this exact error with the files sitting one folder below.
- Prefer absolute paths everywhere, including the --tessdata-dir option the command line accepts. A relative value such as tessdata/ resolves against the current working directory, which moves the moment a scheduler or service starts the job somewhere else. An absolute path also sidesteps the old Windows behavior that tied the lookup to the working directory's drive.
The two-line test that proves the path
Put the test before the call. It prints True when the engine will initialize and False while you are still pointed at the wrong folder, which you learn in one line instead of one failed decode.
Windows Python:
import os
# Raw strings keep the backslashes literal
TESSERACT_PATH = r"C:\Program Files\Tesseract-OCR"
TESSDATA_PATH = r"C:\Program Files\Tesseract-OCR\tessdata"
os.environ["PATH"] += os.pathsep + TESSERACT_PATH
os.environ["TESSDATA_PREFIX"] = TESSDATA_PATH
# True means the call below will initialize
print(os.path.isfile(os.path.join(TESSDATA_PATH, "jpn.traineddata")))
The same script under WSL Ubuntu 20.04:
import os
# Linux path: the package's own data directory
TESSDATA_PATH = "/usr/share/tesseract-ocr/4.00/tessdata"
os.environ["TESSDATA_PREFIX"] = TESSDATA_PATH
print(os.path.isfile(os.path.join(TESSDATA_PATH, "jpn.traineddata")))
TESSERACT_PATH on PATH is Windows housekeeping: the wrapper shells out to the tesseract executable, which is why the original script appended it with os.pathsep. The OCR call itself never changes. In the failing case it reads:
tools = pyocr.get_available_tools()
tool = tools[0]
builder = pyocr.builders.TextBuilder()
result = tool.image_to_string(img_org, lang="jpn", builder=builder)
With the directory right and the printout True, that call returns text instead of a TesseractError traceback.
Worked example: jpn OCR under WSL Ubuntu 20.04
The inputs, all from the failing case: Python 3.8.10 under WSL Ubuntu 20.04.6 LTS, the pyocr wrapper, requested language jpn, a script that sets TESSDATA_PREFIX to the Windows directory C:\Program Files\Tesseract-OCR\tessdata, and a terminal where tesseract 48.png output_48 -l jpn succeeds. Ubuntu 20.04's package keeps its data in /usr/share/tesseract-ocr/4.00/tessdata.
The rule in words: the engine joins its chosen data directory, a separator, the language code and .traineddata, and refuses to start unless that exact file opens.
Script run (variable set to the Windows folder):
C:\Program Files\Tesseract-OCR\tessdata + /jpn.traineddata
= C:\Program Files\Tesseract-OCR\tessdata/jpn.traineddata
exists under Ubuntu: no -> status 1, Failed loading language 'jpn'
Terminal run (no variable set, build default used):
/usr/share/tesseract-ocr/4.00/tessdata + /jpn.traineddata
= /usr/share/tesseract-ocr/4.00/tessdata/jpn.traineddata
exists: yes -> the decode you already saw succeed
The mixed slashes in the first result, backslashes from your string plus the forward slash the engine adds, appear verbatim in the traceback; that is the signature of this failure. The language file was installed the whole time. The script pointed a Linux engine at a Windows-only directory, so initialization failed before any image was touched.
The decision, with its threshold: if tesseract -l jpn decodes in the same shell, delete the TESSDATA_PREFIX lines first; that alone fixes this case because both runs then share the working default. Set the variable only when your data lives outside the default directory, and set it to a directory that exists in the operating system the script actually runs in.
One runtime, one path style
The original script carried both worlds in a few lines: Windows tessdata constants plus a test image under /mnt/c. Pick the operating system the file will run in, then keep one constant block for it: raw strings on Windows so the backslashes stay literal, POSIX style on Linux. The Python image file path errors across operating systems guide covers the separator and escape rules in depth, and the GenTL CTI file path errors guide shows the same trap in escaping form. When the symptom is an installed component invisible to one interpreter while another sees it fine, that is an environment mismatch, the same root cause as the GenICam Python package not found case; a path fixed for the Windows Python will not serve the WSL Python on the same machine.
Run the OCR on the device instead
Every step above exists because the engine and its language data live on a PC you maintain. A fixed code reader or a smart camera moves the job onto the device: the camera reads the text inside its own inspection job and hands your PLC or PC a finished string, so there is no tessdata folder, no interpreter and no language file on the line PC to mistype or lose. The OCR on a fixed code reader guide shows several text strings read in one image, and the smart camera OCR string output over TCP guide covers passing that text to your controller.
Two examples from the shop. The MV-ID3013PM-12M-WBN is a compact industrial code reader with a 1.3 MP sensor, a 12 mm lens and a Fast Ethernet interface. The MV-SC5060XM-08M-WBN pairs a 6 MP mono sensor with an 8 mm M12-mount lens over GigE.
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Shutter
- Global
- Lens mount
- M12
- Interface
- GigE
- Resolution
- 6-10 MP
- Sensor format
- 1/2.4"
- Shutter
- Rolling
Both families sit in the cameras finder; on-device reading turns the whole path setup above into a firmware detail you never touch.
Common mistakes that recreate the error
- A variable pointing into the other operating system: the exact failure above, a Windows directory assigned for a script that runs under WSL.
- A one-letter typo in the value. The same case also saved the folder name as tessdate in the system environment; the OS stores what you type without complaint and the engine just reports the file missing.
- The variable aimed at the install root, C:\Program Files\Tesseract-OCR, instead of its tessdata subfolder.
- A language that was never installed: code strings beyond the base set, such as chi_sim or heb on a fresh install, need their own package or a copied file before any path can help.
- A rebuilt station PC: the OCR package comes back with default languages but the extra traineddata files and the path setup do not. Treat both as part of the deployment, the same discipline as the machine vision model file paths guide.
Once it loads, reading accuracy is its own job
A correct tessdata path gets the engine initialized; it says nothing about how much text comes back right. From there the usual limits are optical: character height in pixels has a hard floor, commonly quoted around 20 px minimum and comfortably readable from roughly 30 px upward on a well-focused image, and the minimum font size for OCR guide gives the numbers and the fixes; focus, contrast and skew follow. When the strings are date codes that must also be graded, the date code print quality inspection guide walks through verifying the string and grading the print.
If you are specifying a new station, decide early which text belongs on the device and which needs your Python pipeline. For hands-on help matching a reader or smart camera to your labels, send the sample details through the contact page. The code-reading topic page collects the rest of this family of problems.
Questions
Where does Tesseract look for language data when TESSDATA_PREFIX is not set?
It falls back to the default folder compiled into that build. On the Windows installer that is the tessdata folder inside the install directory; on Ubuntu's package it is a directory under /usr/share/tesseract-ocr. The --tessdata-dir option overrides the variable and the variable overrides the default, so the path printed in the error tells you which level your call ended at. The default is fixed at build time, so two machines on different distribution versions can disagree.
Can I keep my traineddata files in a project folder instead of the install directory?
Yes, and it is the more robust layout for a station PC. Copy each .traineddata file you use into a folder inside your project, point TESSDATA_PREFIX at that folder in the start-up code, and keep the existence test from this guide in place. A rebuilt PC then restores the language models with the rest of the repository instead of needing a reinstall of the OCR package.
Does the TESSDATA_PREFIX I set in Windows reach a script running under WSL?
No. Environment variables belong to one operating system: a value saved in Windows applies to Windows processes, while WSL runs its own Linux environment with its own variables. Set TESSDATA_PREFIX inside the Python process, in the same script that makes the OCR call, and give it the Linux directory that exists under WSL.
Which file must exist for the language string my call passes?
The one named after it: lang='jpn' loads jpn.traineddata, eng loads eng.traineddata, and the file name must match the language string character for character. The error prints the file it tried, so the pair is quick to check. When one call requests several languages, every one of those files must exist or the engine refuses to initialize at all.
The path error is gone but the results are still poor. What do I check next?
Treat it as two problems solved in order. The path fix only lets the engine start; recognition quality then comes from the image itself: character height, focus, contrast, skew and print quality. Small type fails for optical reasons before software ones, so check character height first, then lighting and focus on the label. Grading a printed string is its own workflow, covered by the minimum font size and date code inspection guides.

