ocr-postprocess gives every text line one of five categories. A reader of the output then knows which lines can go on to NLP, which need correcting, and which need another OCR pass. This page explains how it decides and where the decision is weak. The complete rules, with every constant, are in the reference; what was measured is on the evaluation page; the dated decisions are in the decision record.
| Category | Meaning | What to do with it |
|---|---|---|
Clear |
Legible. Whatever mistakes it has, nobody needs the page image to read it. | pass it on to NLP |
Noisy |
Easily decipherable through a mistake: a swapped letter, a digit inside a word, a few lost characters. | correct the readable words |
Trash |
Illegible: nobody can read it. | re-process the page with another OCR tool |
Non-text |
Structured but not language: numbers, codes, stamps, dates, measurements. | check it for identifiers of finds or sites |
Empty |
Nothing on the line. | ignore it |
The definition is the data providers': Trash means illegible, however useful the line is, so a correctly
scanned web address is Clear (@david-spacil, 2026-09-22; agreed by @DanaKriv, 2026-10-01). Non-text cannot be
told from Trash without the page image, so hand annotation leaves it out. The program approximates this
definition; Known limits says where it falls short.
A line is judged on its own text, with two models as witnesses. A language identifier (FastText) says which language the line looks like and how sure it is. A small language model (Qwen2.5-0.5B) says how surprising the line is (its perplexity). Neither decides alone. Most lines are decided by a quality score built from nine measurements of the text. A short list of rules decides the cases the score gets wrong, such as an upside-down scan or a web address. In the batch pipeline, a few document passes use the rest of the page to correct isolated mistakes.
- Pre-filter. A blank line is
Empty. Numbers, dates, codes, stamps and very short or symbol-only lines areNon-text. No model runs for either. Two common OCR errors are repaired (poh1ed→pohled). - Language. FastText's label is kept for an expected or trusted language and replaced by the collection's default otherwise. Its confidence, weighted by how expected the language is, is the trust the later steps read.
- Measurements. Symbols and digits inside words, mid-word capitals, repeated and fused letters, vowel share, letters an upside-down scan produces, and the perplexity. The perplexity is capped for lines of one or two words, which give any language model too little context.
- Quality score. A weighted sum of nine of these, between 0 and 1.
- Rules, in order. The first that matches decides: the address rule, three perplexity rules, the upside-down scan, an all-capitals scramble, too many symbols, short garbage, short lines, very low perplexity, and six structural red flags.
- The score decides the rest. Under 0.55 the line is
Trash, unless a rescue applies. Above it, the line isClear, orNoisywhen too few of its words pass the word test or a word is damaged. - Document passes (batch pipeline only): the same text gets the same category throughout a document. A
Noisyline inside fourTrashlines becomesTrash. Very clean and very bad pages correct their outliers. A run of upside-down lines becomesTrash. They change the category, never the score.
Each step, with its constants: the reference.
The stored quality_score is moved into the band of the line's category: Trash < 0.55 ≤ Noisy < 0.80 ≤
Clear. 0.80 is where the Clear band starts; it does not decide between Clear and Noisy. That is
decided by the word test and damaged tokens. The document passes can leave a category that disagrees with its
score. The category, not the score, is the decision.
- Addresses first. Every measurement asks how word-like a string is, and an address is not trying to be a word. Web and e-mail addresses therefore get their category before anything else is read.
- Perplexity alone is not enough. On short lines a language model is often more surprised by real domain vocabulary than by garbage. A perplexity rule therefore convicts only when FastText cannot place the line either, and no perplexity rule reads a line of one or two words.
- Upside-down scans read as plausible letters; they are caught by the share of letters that turn into one another, and by words that are flip images of common Czech words. Any Czech diacritic protects a line.
- Structural red flags need a second witness. A flag on its own (many one-letter tokens, a repeated two-letter run) changes nothing unless the line also shows strong evidence of garbage. Ordinary catalogue lines raise the same flags.
- Rescues turn a
Trashverdict intoNoisyfor a clean entry that trails off in dots, a numbered heading or abbreviation, and a clean reference line.
One function, classify_TEXT.score_line(), decides every line. Three entry points call it and none reimplements
it:
| Caller | Role | Pre-filter | Model values | Document passes |
|---|---|---|---|---|
classify_TEXT.py |
the batch pipeline (Step 4.1) | yes | FastText and the perplexity model, live | yes |
service/text_inference.py |
/process and /score_record |
yes | FastText and the perplexity model, per request | no |
tools/recategorize_from_csv.py |
the offline re-scorer | already applied in the CSV rows | the frozen orig_lang_score / perplex columns |
yes |
- Only the model-derived inputs differ: language, language confidence and perplexity. Every other measurement
is recomputed from the text by the same code. The re-scorer can therefore replay a configuration change over
existing CSVs without a GPU (
tests/test_scoring_single_source.py;tests/test_service_hardening.pypins the service against the batch path line by line). - The document passes need a whole document, so the service answers each line as the scorer gave it. For a
whole document its categories can differ from a batch run's. Its
pages[].quality_scoreis the batchavg_quality_score: the mean of the page'sClearandNoisylines. /score_recordscores the lines of a record another tool wrote (a born-digital record) with the same categories and constants, without reading, splitting or reordering them. A line carrying the converter's decode verdict (Garbage,Inverted) is left as it is, andtextis never written back.
The rules were calibrated on Czech archaeological reports with German and English passages in them. A line in another language meets two things: how its language label is used, and Czech-first heuristics that act on every line whatever its label.
- The label changes only the trust: an expected language (
ces,deu,eng) counts fully, a trusted foreign one (fra,pol,ita,slk) at 0.85, any other at 0.50. A confident wrong label therefore costs only part of its trust. - The word test knows Czech function words only, so a clean English or German sentence can read
Noisy. w,qandxare rare in Czech and count against a word, which hurts German, Polish and English most.- A Czech diacritic, or a Czech function word such as
denoron, makes any line "upright Czech" and protects it from the upside-down-scan rules, also in other languages.
The cases, each pinned by a test, are in the reference.
- Short lines without Czech diacritics. About 26,000 genuine garbage lines over both collections read
Clear(oueussdis one). AClearverdict on a line of one to three tokens without diacritics is less certain than it looks. The rule that would narrow this is built and held for the pilot. - Other languages, as above.
- A mis-scanned address gets the same category as a correct one.
- The service and the batch pipeline can differ on a whole document: only the batch pipeline runs the document passes.
The measurements behind these: the evaluation page.
The category semantics are frozen for the S2 pilot (decision of 2026-09-30, kept in
the decision record); only LOWPPL_CLEAR_MAX moved inside
it (50 → 35, 2026-10-11). After the pilot, a change is measured over the collections and the gold set before it is
adopted (Measuring a change). The settings are listed in
configuration.