← Alle Playbooks
Playbook· lokal

Reading documents offline when the scan must not be uploaded

Medical letter, contract, personnel file. Ten steps to text recognition that stays on your machine, including the distinction between a tool that misreads characters and one that makes up whole facts.

There is a stack of scanned documents on your desk and you are supposed to do something with it. Sort last quarter's invoices, pull the notice periods out of forty contracts, make a personnel file searchable. The obvious move would be to upload the stack to a cloud service. With exactly this kind of material, that is often the move you are not allowed to make.

This playbook shows the route that works without uploading. It starts with a distinction that hardly anyone explains and that decides everything else: there are two completely different tools for what looks like one task from the outside. Both make mistakes, but they make completely different ones, and if you do not separate them you end up checking the wrong place.

One thing up front, because it otherwise gets lost: the tools computing locally does not by itself keep the files in the building. The folder they sit in and the program you click for the job have a say too. Both are in step 10.

1. First separate the two tasks

Reading a document falls into two steps, and each has its own kind of tool.

The first step is recognising characters. An image of paper becomes text. That is done by text recognition, OCR for short. It understands nothing of what it reads, it maps shapes to letters. Its particular character follows from that: it does not reason. It will not infer a clause that is not on the paper, and it does not compute an invoice item. That does not make it error-free. It can misread characters and whole words, drop them or add them, and it can split the page wrongly so that an amount lands in the wrong column. A 3 becomes an 8, a capital I becomes a 1, and there it sits, inconspicuously, in the line. Tesseract can output a confidence value for every word. What that does not give you is an error rate: you only get that by holding the output against a transcript you have checked by hand.

The second step is understanding. What kind of document is this, where is the notice period, which amount is the total and which is the VAT. That is done by a language model. It is considerably cleverer and has a different failure mode: it hallucinates at the level of meaning. Faced with a barely legible amount it does not produce garbled characters but a number that fits the context, and potentially a whole justification to go with it.

So the difference is not "error-free versus error-prone" but the kind of error. Text recognition goes wrong while reading, and what comes out of it you can hold against the paper. The language model goes wrong while interpreting, and what comes out of that reads like the result.

Tip: This one distinction is the reason for this whole playbook. For anything where a wrong digit gets expensive, you want to be able to see the step that read the number. And you still want to check every decision-relevant number against the original.

2. Check whether you need text recognition at all

Before you install anything, open the PDF and try to select a sentence with the mouse. If it can be selected and copied, the PDF has a text layer and you may not need text recognition at all. All you need then is a tool that pulls the text out.

Careful here, this is the trap: selectable text proves that a text layer exists, not that it is correct. A scanned document can consist of the page image plus an invisible, faulty text layer that somebody put underneath at some point. Tesseract itself produces exactly those searchable PDFs. So copy a paragraph out once and compare it with what is visible on the page. If it matches, the layer is usable. If it does not, treat the file like a scan.

One point that many guides quietly skip over: Tesseract does not read PDF. Its input is image files, so TIFF, PNG, JPEG and relatives. For scanned PDFs you therefore need an intermediate step.

The convenient route is OCRmyPDF. It takes that detour internally and hands you back a searchable PDF. On a file with a faulty text layer, the case from just now, it stops of its own accord, with PriorOcrFoundError: page already has text!. There is a dedicated switch for that:

ocrmypdf --redo-ocr -l deu scan.pdf scan-new.pdf

--redo-ocr looks for the hidden text layer, throws it away and reads afresh, without rasterising the page. There is also --force-ocr, which turns every page into an image and then reads it. That is the sledgehammer for cases where --redo-ocr does not get through, and it costs you real vector text and image quality. Keep the original file in both cases.

The other route is to render the pages to images yourself, if you want to carry on with Tesseract directly. There is a trap built into this one that otherwise costs you ten minutes:

pdftoppm -r 300 -png scan.pdf page

pdftoppm is part of the Poppler tools. Both options are needed, because without -png it writes PPM files and without -r it renders at 150 dots per inch, half of what step 6 gives as the lower bound. For TIFF use -tiff instead of -png. And now the trap: what the files end up being called depends on how many pages the PDF has. With two pages you get page-1.png, with twelve pages page-01.png, because Poppler pads the number to the width of the total. A hard-typed filename therefore misses on every other stack. Either work with a pattern like page-*.png, or fetch exactly one page under a name you know for a single test:

pdftoppm -f 1 -l 1 -singlefile -r 300 -png scan.pdf page-1

Note also that this rasterises in any case: a page of text becomes an image, and fine print loses in the process.

Tip: A mixed stack is the normal case. Sort it once into "has usable text" and "is an image" before you start. The first pile is done in minutes.

3. Take Tesseract for character recognition

The established open standard is called Tesseract. It is maintained on GitHub under tesseract-ocr/tesseract, is licensed under Apache 2.0 and runs entirely on your machine without sending anything anywhere.

Pretrained language data exists for well over a hundred languages. That does not mean any of it is installed: engine and language data are two separate parts, and depending on the package you get several languages, English only or none at all. What is actually on your machine is what tesseract --list-langs tells you.

Apache 2.0, like MIT, is one of the uncomplicated licences. You may work with it commercially and build it into your own workflows, and for plain in-house use it asks nothing of you. Obligations only arise once you distribute it yourself: then you include a copy of the licence, leave the existing attributions in place, mark changed files, and pass on a NOTICE file only if the work comes with one. And the licence only says what you may do with the software anyway. Whether you may process these particular documents with it in a law firm or a practice is a different question, answered by data protection law, professional rules and your own internal approvals, not by Apache 2.0.

Tip: If you do not want to work in the terminal, look for a graphical interface with Tesseract inside it. There are several, and they take the assembling of commands off your hands.

4. Install the German language pack, otherwise it is missing

The most common stumbling block right after installation: often only English is there. With a German document you then get a result that looks as if somebody typed with their elbow, and you conclude that the tool is bad.

The language files are kept separately and are called traineddata. For German you need deu. On Debian and Ubuntu the package for it is called tesseract-ocr-deu, and the three tools of this playbook together are tesseract-ocr, tesseract-ocr-deu, poppler-utils and ocrmypdf. The official language files are maintained in the tesseract-ocr/tessdata repository, also under Apache 2.0, and were trained at Google.

Whether German is really there is answered by one command, and you should run it before you try anything else:

tesseract --list-langs

If deu is not in the list, the package is missing, and every call with -l deu aborts with Failed loading language 'deu'. The same goes for OCRmyPDF: -l deu requires the same installed language file.

And now the place where most people get stuck: installing alone switches nothing over. Without an instruction Tesseract takes eng, no matter what else is on your machine. The language belongs in the call:

tesseract page-1.png stdout -l deu

stdout writes the result into the terminal. If you give a name instead, that is not a place next to the image but an output path, and a relative one lands in the folder you are currently standing in. That is more than tidiness: the scan can sit in your protected working folder and the extracted plain text still end up in your home directory or in a synchronised folder. So write the target path out, for example tesseract page-1.png /work/scans/page-1 -l deu.

For a document with a German and an English part you append the second language with a plus, so -l deu+eng.

Tip: Only load what actually occurs in the paper. The selection and its order change both runtime and output, and whether an additional language helps or hurts you only shows on your own material. Try it on five pages instead of following a fixed rule.

5. Choose the right data set, there are three

Here is an adjustment screw many people never find. Of the language files there are three official sets with different priorities.

tessdata_fast is the fastest and the least accurate. tessdata sits in the middle and additionally supports the older recognition engine. tessdata_best is the slowest and the most accurate, and it is the only set suitable as a basis for your own fine-tuning.

For a stack you let run through once overnight, take tessdata_best. Speed does not matter then, and every error avoided saves you correction work in the morning.

The same applies here: knowing about it is not enough. Whatever your package manager brought along stays in use until you expressly point somewhere else. Download the deu.traineddata you want from the tessdata_best repository into a folder of its own and name it in the call:

tesseract page-1.png stdout -l deu --tessdata-dir ./tessdata_best

One detail about it that bites in practice: --tessdata-dir does not layer over your system installation, it replaces it. Tesseract then looks in that folder only. So anyone who learned -l deu+eng above and puts only deu.traineddata in here gets Failed loading language 'eng' and an abort. Every language you ask for has to be in the same folder, and you can check with tesseract --list-langs --tessdata-dir ./tessdata_best.

Tip: Most guides on the web do not mention this distinction and you end up with the standard set. If your results are just short of usable, switching to tessdata_best is the first thing to try before you give up on the tool.

6. The original matters more than the setting

Text recognition is merciless towards bad scans, and no data set compensates for that. A sheet fed in crooked, a shadow across the middle, a photo taken with the phone at an angle from above: all of that costs more accuracy than any setting brings back.

Scan straight, with sufficient resolution and in greyscale rather than colour. As a rule of thumb from scanning practice, 300 dots per inch applies for ordinary body text, more for very small print. If you photograph paper, lay it flat and hold the camera parallel above it.

Tip: If a single document refuses to be recognised while the rest of the stack works, it is almost always the original and almost never the setting. Rescan that one sheet instead of fiddling with parameters.

7. For old documents there is a language file of its own

A detail that comes up surprisingly often in German-speaking countries: anything printed before the middle of the twentieth century is frequently set in Fraktur, and the regular German language file fails at it reliably. There is a file of its own for that, and it is called deu_latf.

Watch the name here, because almost everywhere on the web the old one is still given. The file used to be called frk, was renamed because the code was never ISO compliant, and the old name is explicitly deprecated. In the current sets, tessdata_best included, you will find deu_latf.traineddata. So anyone who follows a guide from 2019 and asks for frk gets, in the worst case, an error about missing language data and looks for it in the wrong place.

The difference is the one between "unreadable" and "usable" for land register extracts, old deeds, parish records and company archives. Anyone who has done genealogy or digitised a file from the thirties knows the problem.

Tip: Do not expect clean results from deu_latf on handwritten passages either. Handwriting is a different discipline, and Tesseract is built for print.

8. Only once understanding is needed does a language model join in

Now you have text, but no answer yet to "which contracts expire in March". For that you need the second step, and it belongs to a local language model.

For the route in this playbook a plain text model is enough. It gets the text Tesseract has read and never has to see the paper. That is the normal case and the gentler option, because a text model still runs on modest equipment. Which size suits your machine is covered by Which local model fits your hardware.

Models with image understanding, recognisable by being labelled a vision variant or by VL in the name, are a different route: they get the page image directly and do reading and understanding in one go. That can be practical for forms and tables, where the arrangement on the page carries meaning. It costs considerably more memory, and it undoes the separation from step one. Which families and sizes exist right now changes fast, which is why there is deliberately no recommendation here that would be wrong in three months.

Tip: Start with the text model. You take the image route only once you notice that the plain text version loses the structure of the page, and then deliberately and for that one kind of document.

9. Combine the two in the right order

The safest workflow for anything where numbers matter looks like this. Tesseract reads the characters. The text thus gained goes to the language model, not the image. The model sorts, summarises and answers questions, but it does not read the amounts off the paper itself.

The reason is the division of labour from step one. If you give the model the image, it does both in one go, and afterwards you can no longer tell whether a number was read or supplied. If you separate the steps, you have an intermediate result you can look at.

The confidence values do not come for free with that. The calls above deliver plain text and nothing else. If you want to see how sure the recognition was per word, ask for the table output:

tesseract page-1.png stdout -l deu tsv

That gives you a table with a conf column and the recognised word next to it. Low values show you where to look first.

Tip: Use the confidence values as a sorting aid, not as a free pass. A high value means the recognition was confident, not that it was right.

10. Automate the workflow and think about deletion

A folder the scans go into, a small script that runs through everything in it and puts the text files next to them. That turns the experiment into a routine you will still be using in six months.

Before you rely on it, run ten documents through and compare the extracted details by hand with the original. That is a pilot, not an acceptance test. If you draw ten out of forty documents at random and exactly one of them is faulty, you catch it with a probability of one in four. As verification that is worthless, as a first impression it is useful, and anyone taking "the first ten" or "a few typical ones" instead does not even have that number. So for live use the harder rule applies: every number and every deadline you base a decision on gets seen on the original. The workflow saves you the typing, not the checking.

And now the part that decides the promise of this playbook. A tool computing locally does not mean the file stays local.

The folder is the first question. Desktop and Documents are backed up by OneDrive on Windows and by iCloud Drive on macOS, in each case when the feature is switched on, and it often is without anybody having consciously set it. A scan that lands there goes along at the next sync. So put the working folder outside the synchronised areas and look in the settings of OneDrive, iCloud and Dropbox to see what is really being backed up. Think of the backup program running in the background, and of the intermediate files that sit between scan and text file.

The program is the second. A graphical interface with Tesseract inside can still check for updates, send usage data or offer individual features through a service. Disconnect the machine from the network once and let a stack run through. If text comes out, you know the workflow does not need a connection. That is a function test, not a proof: a program can also hold a transfer back and catch up the next time it connects. If it really matters, block the program from outgoing connections permanently or keep the whole workflow disconnected.

That the documents do not leave your machine is a genuine advantage, because transmission to a third party does not happen. It does not automatically make the processing lawful. You are still processing personal data and still need a legal basis and deletion periods, and you should check whether the processing belongs in your record of processing activities. For personnel files and health data that is the normal case, and further requirements come on top.

Tip: Build the deletion period into the workflow right away, and for everything that arises in it: scans, text files, rendered intermediate images, temporary files. A folder of scanned ID cards that nobody needs any more and everybody has forgotten is exactly the kind of legacy that gets uncomfortable in an audit. The clean separation between "stays in the house" and "is therefore allowed" is in Local AI and the GDPR.

What next

The twin to this playbook is Transcribing conversations offline, the same idea for audio instead of paper. If you do not have a local model running at all yet, start with Your first local AI model in 30 minutes. How cloud models process images and why a cropped section works better there than a 4K screenshot is explained in Why your screenshot shrinks. And for evaluating the numbers you have gained, Evaluating numbers and tables with AI takes it further.

Sources

Licence, language and data set details come from the official Tesseract documentation, as of August 2026. The resolution recommendation is a rule of thumb from scanning practice and not a specification of the project.

  • Tesseract, main repository and licence: https://github.com/tesseract-ocr/tesseract
  • Tesseract, supported input formats: https://tesseract-ocr.github.io/tessdoc/InputFormats.html
  • Tesseract, command line including confidence output and searchable PDF: https://tesseract-ocr.github.io/tessdoc/Command-Line-Usage.html
  • Tesseract, error rates from the UNLV benchmark, measured against a checked reference transcript: https://tesseract-ocr.github.io/tessdoc/UNLV-Testing-of-Tesseract.html
  • Tesseract, how recognition errors arise: https://tesseract-ocr.github.io/tessdoc/ImproveQuality.html
  • tessdata, official language files: https://github.com/tesseract-ocr/tessdata
  • Renaming of frk to deu_latf: https://github.com/tesseract-ocr/tessdata_fast
  • Overview of the three data sets and language codes: https://tesseract-ocr.github.io/tessdoc/Data-Files.html
  • OCRmyPDF, replacing an existing text layer: https://ocrmypdf.readthedocs.io/en/latest/cookbook.html
  • pdftoppm, format and resolution options: https://manpages.debian.org/testing/poppler-utils/pdftoppm.1.en.html
Reading documents offline when the scan must not be uploaded — StudioMeyer Academy