Skip to content
Talk to our solutions team

OCR and text acquisition

The Rules block ingests OCR output. It does not perform OCR.

You run a recogniser yourself, out of band. If it emits hOCR, kis bbox create converts that to a .bbox word-box file; if it already emits word boxes, write the .bbox JSON directly and skip the conversion. Either way the .bbox file is what loads into the document model that extraction, tables and matching operate on.

ComponentRequired forSupplied by
The kis CLIEvery command on this pageThe platform install
The rules pluginkis bbox, kis rulesA separate plugin install
An OCR engine that emits hOCRProducing the input filesYou: entirely your own installation

Nothing else. No tessdata directory, no language pack, no model download, no locale configuration. Text handling is character substitution only. No dictionary, no language model.

your OCR engine ──▶ page.hocr ──▶ kis bbox create ──▶ page.bbox (JSON word boxes)
│
page.json (facts) ───────────────┬────┘
└──▶ kis rules --with-bbox [--mode doc]

kis rules --with-bbox pairs a .bbox file with a same-basename .json facts file and runs rules over the pair, once per pair, or once over all of them with --mode doc. The command is covered in the CLI reference; how the document reaches a rule is covered in Rules over documents.

hOCR is HTML. It is parsed with a real HTML parser, not regex, and the parser is deliberately permissive about which producer emitted the file.

This is the complete accepted vocabulary.

LevelClasses
Pageocr_page
Content area (recursed into)ocr_carea, ocr_photo, ocr_graphic, ocr_separator, ocr_noise
Blockocr_par, ocr_textfloat, ocr_caption, ocr_header, ocr_footer
Lineocr_line, ocrx_line, ocr_textline, ocr_header_line
Wordocrx_word, ocr_word
Characterocrx_cinfo, ocr_cinfo

Any other class beginning with ocr produces an unrecognized_class warning in verbose mode; its text is recovered only by the orphan-text fallback.

<div class="ocr_page" id="page_1" title='image "/tmp/page_1.png"; bbox 0 0 2550 3300; ppageno 0'>
<p class="ocr_par" title="bbox 100 100 400 160">
<span class="ocr_line" title="bbox 100 100 400 160">
<span class="ocrx_word" title="bbox 100 100 400 160; x_wconf 95">HEADER</span>
</span>
</p>
</div>

The page comes out 2550 × 3300; the word lands at {100,100,400,160} with confidence 0.95.

Producers mix structure levels on one page. The parser runs a paragraph pass, then a bare-line pass, then a bare-word pass, each skipping subtrees the earlier pass already consumed, then, only if the page still has zero words, an orphan-text pass. A page containing an ocr_par, a bare ocr_line and a bare ocrx_word side by side yields three words, one paragraph and two lines, with nothing double-counted.

An hOCR title is a ;-separated property list. Splitting honours double-quoted values, and the property name must match as a whole token.

bbox 100 100 200 200 -> {100,100,200,200}
image "/tmp/page_1.png"; bbox 0 0 2550 3300; ppageno 0 -> {0,0,2550,3300}
image "/tmp/scan_bbox.png"; bbox 0 0 2550 3300 -> {0,0,2550,3300}
x_bboxes 10 5 10 10 20 5 30 10; bbox 100 100 200 200 -> {100,100,200,200}
image "a;bbox 9 9 9 9.png"; bbox 1 2 3 4 -> {1,2,3,4}
image "/tmp/page.png"; ppageno 0 -> no bbox
bboxes 1 2 3 4 -> no bbox
bbox a b c d; ppageno 7 -> no bbox

A malformed bbox yields nothing rather than scavenging digits from elsewhere in the title.

PropertyRead asResult
x_wconf NInteger 0–100N / 100
x_conf NFloat; divided by 100 if > 1N or N / 100
Neither present—1.0
Word synthesised from fallback text—0.0

Every word carries confidence in the 0–1 range whatever the producer.

When a word element has no bbox, the parser synthesises the word using the nearest enclosing element’s box, line, paragraph, content area, or page, and sets confidence to 0.0. If no ancestor has a box either, the word gets a zero-size box at the origin, a cluster of words sharing one identical oversized box, and (0,0,0,0) boxes, are both real possible output.

kis bbox create writes a .bbox file: JSON, indented two spaces, holding the serialised document. This is also the format to emit directly if your engine does not produce hOCR.

{
"pages": [
{
"number": 1,
"width": 612,
"height": 792,
"words": [
{
"text": "Borrower",
"box": {"x0": 72, "y0": 100, "x1": 150, "y1": 115},
"confidence": 0.98,
"page": 1
}
]
}
]
}

words[].line_id is optional. lines and paragraphs arrays are optional and hold index ranges into words and lines respectively, see the document model for what those ranges mean.

The extension is a hint, not a contract. The loader dispatches like this:

ExtensionBehaviour
.jsonJSON loader
.hocrhOCR parser
.html, .htmSniffed for ocr_page, ocrx_word or ocr_word; hOCR if found, else JSON
Anything else, including .bboxJSON first, hOCR second

This is why kis bbox subcommands accept a .hocr file even though their -i help string says “path to bbox file”.

Terminal window
kis bbox create -i scan-page1.hocr
kis bbox create: converted 1 file → scan-page1.bbox

With no -o the output path is the input path with its extension replaced by .bbox (page.hocr.html becomes page.bbox). The confirmation line is printed at every log level; the word count needs -l info.

FlagDefaultPurpose
-i, --input—hOCR file, or a directory of hOCR files; files and directories may also be given as arguments
-o, --outputInput locationOutput file (one input) or directory (several inputs, or a path ending in /)
-w, --workers4Parallel workers when there is more than one input
-v, --verbosefalseReport content the parser could not place
--cleanfalseApply the default text-cleanup profile
--clean-formfalseApply the form-aware cleanup profile
--formatautoThe input format: auto or hocr; the output is always .bbox
--incrementalfalseSkip inputs whose .bbox is already present and newer
Terminal window
kis bbox create -i scan-page1.hocr -o out/page1.bbox
kis bbox create -i hocr/ -o bbox/ -w 8 --clean
kis bbox create hocr/*.hocr -o bbox/ --clean-form
kis bbox create -i hocr/ -v

A directory is scanned for *.hocr and *.hocr.html, not recursively; a file named explicitly is converted whatever its extension. Matching nothing at all is an error: the command prints kis bbox create: no input files found to process. with what it looked for, and exits non-zero. Missing output directories are created, and each .bbox is written whole or not at all.

With several inputs, a failing file does not stop the run. Every file is attempted, then the command reports <n> of <m> hocr files failed to convert (first: <path>: <error>) and exits non-zero.

HTML entities are decoded once, by the HTML parser: a page that literally reads &lt;tag&gt; converts to that text.

Warnings are the only visibility you have into content the parser could not place.

TypeMeaning
no_structureNo ocr_page found and fallback extraction found no words
missing_bboxA page or word element has no bbox
no_bbox_fallbackNo box available anywhere; a zero box was used
empty_paragraphParagraph has text but no lines or words were extracted
empty_lineLine has text but no words were extracted
empty_wordWord element has no text content
orphan_textText outside any word element; a fallback word was created
unrecognized_classAn OCR class outside the accepted vocabulary
missed_contentPost-hoc diff of source text against extracted word text

missed_content compares all source text against all extracted word text, lowercased and punctuation-stripped, ignoring single characters, and reports the first 10 missed words.

Each warning renders as [type] element: "text" (context: ...), with text trimmed and truncated to 50 characters. Warnings carrying no text drop the : "text" part.

Cleanup is pure text in, text out. It is character-level only. No dictionary, no language model, no language parameter.

OptionEffect
NormalizeUnicodeUnicode NFC
FixQuotes23 quote, prime and guillemet variants, including backtick and acute, to ASCII ' or "
FixDashes11 dash variants to -
FixLigaturesfi/fl/ff/ffi/ffl/st, Æ/Œ/IJ/ꜳ/ꝏ, and symbol expansions such as No., (P), SM, TM, (R), (C)
RemoveGarbageDeletes 56 glyphs: bullets, arrows, box drawing, geometric shapes, stray diacritics, pilcrow, section sign
NormalizeFormIndicatorsCheckbox and radio glyphs to [ ], [x], ( ), (x)
FixMisreadsFullwidth ASCII to ASCII, Greek and Cyrillic homoglyphs to Latin, Roman numerals expanded, common Unicode space variants to a space, zero-width characters deleted
RemoveStrayPunct16 X patterns to a single space
CleanWhitespaceWhitespace collapse
RemoveControlCharsControl characters removed
AggressiveCleanEverything outside printable ASCII becomes a space; newlines and tabs survive

The two profiles compose these options, applied in the order shown. RemoveGarbage reads PreserveFormIndicators to decide whether checkbox and radio glyphs count as garbage.

OptionDefault profileForm-aware profile
NormalizeUnicode, FixQuotes, FixDashes, FixLigaturesonon
RemoveGarbage, FixMisreads, CleanWhitespace, RemoveControlCharsonon
PreserveFormIndicatorsonon
NormalizeFormIndicatorsoffon
RemoveStrayPunctoffoff
AggressiveCleanoffoff

--clean selects the default profile; --clean-form selects the form-aware profile.

Cleanup is applied to each word’s text, then each line’s text, then each paragraph’s text. Three consequences:

  • Boxes are never adjusted, a word whose text is emptied by garbage-stripping stays in the output as an empty-text box.
  • Word text and line text are cleaned independently. A line’s text was joined from the raw words at parse time, so after cleanup a line’s text is not necessarily its cleaned words rejoined.
  • RemoveStrayPunct matches only a punctuation character with a space on both sides, so it is a no-op on word text, a single word has no spaces, but it does fire on line and paragraph text, where words were joined with spaces.

No surface selects an OCR engine: there is no CLI flag, no file format and no request field that names one. Convert your recogniser’s output to hOCR or to word-box JSON yourself, then supply it the way every other document is supplied, a path to kis rules. These three common shapes map onto word-box JSON, and the notes say what your conversion must handle.

SourceShape it emitsWhat your conversion must do
PaddleOCRFour-corner polygons, per-page result arraysReduce each polygon to an axis-aligned box; drop items with fewer than 4 corners
Tesseract TSVRow records with level, position, size, confKeep only level-5 (word) rows with non-empty text; divide conf by 100
DoclingWord records with bbox [x0,y0,x1,y1] and scoreRequire at least 4 bbox values; score becomes confidence

PaddleOCR and Tesseract report page width and height. Docling does not, carry the page size from your own pipeline, or the page arrives sized 0 × 0.

Page width and height are read from the ocr_page bbox, or from width and height in word-box JSON. If the hOCR has no ocr_page bbox, its page title is malformed, or the JSON page omits the dimensions, both are 0.

Page analysis seeds the statistics from the page declaration, and falls back to the bounding extent of the page’s words when the declaration is zero, flagging that the fallback was used. Percentage-based spatial queries scale by the statistics, not by the raw page record, so they still resolve, the fallback under-estimates the physical page, margins beyond the last word are lost, so percentage regions come out over-scaled. It is a bounded approximation rather than a dead query.

Analysis runs in full for every execution that carries a document, and caches nothing between executions. Every detector, table, field, checkbox, region, runs on every page, and none of them can be turned off.

How many documents are in flight at once is set on the command line:

WhereFlagDefaultEffect
kis bbox create -w-w / --workers4More than one input only; ignored for a single file
kis rules -w-w / --workers1Fully serial; clamped to the number of resolved inputs
kis rules --mode doc——No workers flag; the whole corpus becomes one document
MessageCause
Error: '<command>' is not installed.The rules plugin binary is absent from every search location, or present without an executable bit
failed to stat input pathAn input does not exist
loading hocr failed from given input fileThe path could not be opened, or the HTML parser failed
failed to create output directory-o could not be created
kis bbox create: no input files found to process.No input matched *.hocr or *.hocr.html; exit non-zero
refusing to overwrite input <path>: output path resolves to the same file (pass a different -o)The output would replace the input, as for a .bbox file given as input
unknown --format "<value>" (want auto, hocr)--format names an input format the command does not read
writing json to output file path failedPermissions, or a full disk
<n> of <m> hocr files failed to convert (first: <path>: <error>)One or more inputs failed; the others were converted

The full table is in Error reference; diagnosis steps are in Troubleshooting.