Skip to main content

strikethrough-ocr

Most OCR pipelines silently drop text decorations like color, underline, strike-through.

StrikethroughOCR tries to preserves them.

Installation

uv tool install strikethrough-ocr    # or: pip install strikethrough-ocr

Tesseract itself must be on PATH:

brew install tesseract               # macOS
apt install tesseract-ocr            # Debian/Ubuntu

Command line

strikethrough-ocr [OPTIONS] INPUT.png

Without options it writes the HTML overlay next to the image:

strikethrough-ocr page.png                 # -> page.scan-preview.html (overlay)
strikethrough-ocr -to html page.png        # -> page.scan.html (clean document)
strikethrough-ocr -to md page.png          # -> page.scan.md
strikethrough-ocr page.png -o out/         # choose the output directory
strikethrough-ocr page.png --stdout        # print instead of writing
strikethrough-ocr page.png --lang deu+eng  # Tesseract language(s)
strikethrough-ocr page.png --json          # also write page.scan.json
strikethrough-ocr page.png --debug         # also write page.debug.png
strikethrough-ocr page.png -v              # log second-pass OCR decisions
option effect
-to {preview,html,md} output format; preview (default) writes the overlay as <name>.scan-preview.html, html writes the clean document as <name>.scan.html, md writes <name>.scan.md
-o, --out-dir output directory (default: next to the image)
--stdout print the output instead of writing a file
--lang Tesseract language(s), e.g. deu+eng (default eng)
--psm Tesseract page segmentation mode (default 3)
--bold-factor stroke width relative to regular text that counts as bold (default 1.2)
--json also write <name>.scan.json with the page model as records
--debug also write <name>.debug.png with the detections drawn on the scan
--min-conf -to html, -to md: drop words below this OCR confidence
--no-color -to html, -to md: do not emit colour spans
--no-headings -to html, -to md: large text stays a paragraph
-v, --verbose log second-pass OCR and layout decisions

The HTML overlay references the scan by relative path for its slider, so keep the two files together or the slider shows nothing.

Library

from strikethrough_ocr import process, render_html, html_to_markdown, \
    html_to_document_html

model = process("page.png", lang="eng")           # run the OCR pipeline
overlay = render_html(model, "page", "page.png")  # styled HTML overlay
md = html_to_markdown(overlay)                    # Markdown in reading order
doc = html_to_document_html(overlay)              # clean document HTML

process() returns a PageModel: words (text, box, baseline, font size, colour, bold, underline, strike, decoration colour, confidence), table rules, decoration strokes and graphics. model_to_json() turns it into plain records; draw_debug() paints the detections onto the scan image.

Development

uv sync --group dev          # install with the test dependencies
uv run pytest                # unit tests and golden files
UPDATE_GOLDENS=1 uv run pytest   # rewrite tests/golden and the overlay fixture

The golden tests pin all three renderings of one synthetic fixture page (tests/conftest.py builds it): the overlay it renders must equal tests/fixtures/page.overlay.html, and the Markdown and document HTML produced from that fixture must equal the files under tests/golden/. After a deliberate change to a renderer, regenerate them and review the diff. One end-to-end test runs the real pipeline and is skipped when no tesseract binary is on PATH.

How it works

  1. Ink mask. The page is thresholded on each pixel's distance from white, 255 − min(B, G, R), so black and coloured ink are both caught. The glyph size measured on this mask sets the working scale: a scan whose print is smaller than the 300 dpi reference is enlarged (up to 4x) before anything else, because Tesseract and every stroke measure need the pixels. Highlighter bands pass the ink threshold too; they are recognised as bright, broad fills — every print ink is dark, and no stroke of print is as tall as a marker band — and whitened away, so OCR sees paper while the letters on top of them stay.
  2. Straight strokes. Morphological opening finds long horizontal and vertical strokes. A stroke with clear paper on both sides is a table rule; every other horizontal stroke is a decoration candidate (underline or strike-through). The paper test follows the stroke's ink column by column, so a slightly slanted strike-through does not pass for a rule. A stroke crossed by vertical rules belongs to a table lattice: it is split at the crossings, and only a piece whose glyphs pass through it (a strike drawn flush onto a row boundary fuses with it into one line) stays a decoration. Length is deliberately no criterion — a struck-through paragraph line is as long as a rule.
  3. OCR. Strikes are removed in two ways (Telea inpainting and column-wise erasure, which fail on different glyphs) and Tesseract reads the cleaned page as hOCR, which carries a baseline and font size per row. Weak lines are cut out and read again in isolation.
  4. Per-word style. Colour is the median of the darkest ink in the word box (darkest by luminance, so the fringe where a glyph blends into a marker or the paper does not vote); a decoration's colour comes from the stroke's own pixels, or the glyphs a red line crosses would outvote the line. Bold compares the measured stroke width (2 × area / perimeter of the word's core ink) against a regular-weight model fitted to the page. Decoration strokes are assigned to the words they span: through the x-height it is a strike, at the baseline an underline. Struck lines are re-read on both cleaned variants and the most confident reading wins.
  5. Graphics. Oversized coloured lettering (logos) and ink no confident word claims are cropped from the scan and embedded as PNG data URIs.

In the Markdown output, connected rules become tables (merged cells hold their content in the top-left grid cell), large text becomes headings, bold is **text**, strike-through ~~text~~, underline <u>text</u> and colour <span style="color:#rrggbb">…</span>; colour and underline have no Markdown syntax, so they are inline HTML. The document HTML output shares the same layout analysis and writes it the way pandoc would: <p>, <h1..h3> with an id, <table> with a <colgroup>, <strong>/<del>/<u>/<span> inline, and consecutive paragraphs labelled (vi), (b) or 3. folded into <ol start type> lists — unless the label itself is struck through, because deleted text keeps its literal label.

Limits and assumptions

  • Geometry constants are set for a 300 dpi scan and scaled to the page's measured glyph size; a scan with smaller print is enlarged internally (up to 4x) first. Below roughly 8 px of x-height there is not enough signal left even so, and the bold measure goes first.
  • The page is assumed to be deskewed; a rotated page breaks the rule detection.
  • Bold is relative: a page set entirely in bold has no regular reference and renders as regular. Italic is not detected.
  • Underline and strike-through are found as straight strokes at least 40 px long (at 300 dpi); a wavy or hand-drawn line is missed, and a heavy or doubled strike hides too much of the glyphs for the text to be recovered. A marker stroke as tall as the text it covers is taken for a highlight, not a strike.
  • The HTML overlay is a reproduction of the page, not a document: words are absolutely positioned and the reading order is Tesseract's. The document HTML and Markdown outputs are the document view.

Release files for strikethrough-ocr 0.3.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for strikethrough-ocr 0.3.4
File Size Uploaded
strikethrough_ocr-0.3.4.tar.gz 4.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for strikethrough-ocr 0.3.4
File Interpreter ABI Platform
strikethrough_ocr-0.3.4-py3-none-any.whl Python 3 none any Details

Total release size: 4.2 MB

Release files / strikethrough_ocr-0.3.4.tar.gz

Download URL strikethrough_ocr-0.3.4.tar.gz
Size 4.1 MB
Tags Source
SHA-256 checksum
How to use checksums
e049575010c9123ffc950b465d1d35fe8efe8adeaa4085ab5b2ed5c74a98e8e5
BLAKE2b-256 checksum
How to use checksums
616e920c00beb6c5e7ad91a77d2b579dab285044aa0421adfee75f06a30332f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / strikethrough_ocr-0.3.4-py3-none-any.whl

Download URL strikethrough_ocr-0.3.4-py3-none-any.whl
Size 36.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a0d6010aad4d834315795cf7cd0e72b4d9c14680388a83b231b6f388531e9c3
BLAKE2b-256 checksum
How to use checksums
e2c9f26c530c7a240fedda7c01c1e90c3b4d98c361b7082b01108c1d266e25c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.3.5

2 release files

This release

0.3.4 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page