strikethrough-ocr 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. strikethrough_ocr-0.3.3/.gitignore +10 -0
  2. strikethrough_ocr-0.3.3/Makefile +18 -0
  3. strikethrough_ocr-0.3.3/PKG-INFO +179 -0
  4. strikethrough_ocr-0.3.3/README.md +157 -0
  5. strikethrough_ocr-0.3.3/___dev/.gitignore +0 -0
  6. strikethrough_ocr-0.3.3/___dev/.notes +2 -0
  7. strikethrough_ocr-0.3.3/___dev/COMMIT_CHANGES.sh +31 -0
  8. strikethrough_ocr-0.3.3/___dev/NOTES.md +25 -0
  9. strikethrough_ocr-0.3.3/demo.sh +18 -0
  10. strikethrough_ocr-0.3.3/examples/image01.png +0 -0
  11. strikethrough_ocr-0.3.3/examples/image02.png +0 -0
  12. strikethrough_ocr-0.3.3/examples/image03.png +0 -0
  13. strikethrough_ocr-0.3.3/examples/image04.png +0 -0
  14. strikethrough_ocr-0.3.3/examples/image05.png +0 -0
  15. strikethrough_ocr-0.3.3/examples/image06.png +0 -0
  16. strikethrough_ocr-0.3.3/examples/image07.png +0 -0
  17. strikethrough_ocr-0.3.3/examples/image08.png +0 -0
  18. strikethrough_ocr-0.3.3/examples/image09.png +0 -0
  19. strikethrough_ocr-0.3.3/examples/image10.png +0 -0
  20. strikethrough_ocr-0.3.3/examples/image11.png +0 -0
  21. strikethrough_ocr-0.3.3/examples/image12.png +0 -0
  22. strikethrough_ocr-0.3.3/examples/image13.png +0 -0
  23. strikethrough_ocr-0.3.3/examples/image14.png +0 -0
  24. strikethrough_ocr-0.3.3/examples/image15.png +0 -0
  25. strikethrough_ocr-0.3.3/pyproject.toml +43 -0
  26. strikethrough_ocr-0.3.3/src/strikethrough_ocr/__init__.py +42 -0
  27. strikethrough_ocr-0.3.3/src/strikethrough_ocr/markdown.py +973 -0
  28. strikethrough_ocr-0.3.3/src/strikethrough_ocr/scan.py +1225 -0
  29. strikethrough_ocr-0.3.3/src/strikethrough_ocr/strikethrough_ocr.py +139 -0
  30. strikethrough_ocr-0.3.3/tests/conftest.py +122 -0
  31. strikethrough_ocr-0.3.3/tests/fixtures/page.overlay.html +93 -0
  32. strikethrough_ocr-0.3.3/tests/test_cli.py +63 -0
  33. strikethrough_ocr-0.3.3/tests/test_document.py +147 -0
  34. strikethrough_ocr-0.3.3/tests/test_golden.py +41 -0
  35. strikethrough_ocr-0.3.3/tests/test_integration.py +37 -0
  36. strikethrough_ocr-0.3.3/tests/test_scan_geometry.py +62 -0
  37. strikethrough_ocr-0.3.3/tests/test_scan_ink.py +217 -0
  38. strikethrough_ocr-0.3.3/uv.lock +370 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .DS_Store
8
+ *.scan.*
9
+ pages/
10
+ out/
@@ -0,0 +1,18 @@
1
+ .PHONY: all
2
+ all: build test
3
+
4
+ .PHONY: build
5
+ build:
6
+ uv build
7
+
8
+ .PHONY: test
9
+ test:
10
+ uv run pytest
11
+
12
+ # uv publish uploads every file in dist/, including builds of old
13
+ # versions; publish only the artifacts of the current version.
14
+ VERSION := $(shell sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml)
15
+
16
+ .PHONY: publish
17
+ publish: build test
18
+ uv publish dist/strikethrough_ocr-$(VERSION)-py3-none-any.whl dist/strikethrough_ocr-$(VERSION).tar.gz
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.5
2
+ Name: strikethrough-ocr
3
+ Version: 0.3.3
4
+ Summary: OCR for scanned pages that preserves styling: bold, colour, underline and strike-through.
5
+ Author-email: Frank Eickhoff <info@frankeickhoff.de>
6
+ Keywords: contracts,ocr,opencv,scanned-documents,strike-through,strikethrough,tesseract
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Scanners
15
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
16
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: numpy>=2
19
+ Requires-Dist: opencv-python-headless>=4.11
20
+ Requires-Dist: pytesseract>=0.3.13
21
+ Description-Content-Type: text/markdown
22
+
23
+ # strikethrough-ocr
24
+
25
+ Most OCR pipelines silently drop text decorations like color, underline, strike-through.
26
+
27
+ StrikethroughOCR tries to preserves them.
28
+
29
+ ## Installation
30
+
31
+ ```sh
32
+ uv tool install strikethrough-ocr # or: pip install strikethrough-ocr
33
+ ```
34
+
35
+ Tesseract itself must be on `PATH`:
36
+
37
+ ```sh
38
+ brew install tesseract # macOS
39
+ apt install tesseract-ocr # Debian/Ubuntu
40
+ ```
41
+
42
+ ## Command line
43
+
44
+ ```
45
+ strikethrough-ocr [OPTIONS] INPUT.png
46
+ ```
47
+
48
+ Without options it writes the HTML overlay next to the image:
49
+
50
+ ```sh
51
+ strikethrough-ocr page.png # -> page.scan-preview.html (overlay)
52
+ strikethrough-ocr -to html page.png # -> page.scan.html (clean document)
53
+ strikethrough-ocr -to md page.png # -> page.scan.md
54
+ strikethrough-ocr page.png -o out/ # choose the output directory
55
+ strikethrough-ocr page.png --stdout # print instead of writing
56
+ strikethrough-ocr page.png --lang deu+eng # Tesseract language(s)
57
+ strikethrough-ocr page.png --json # also write page.scan.json
58
+ strikethrough-ocr page.png --debug # also write page.debug.png
59
+ strikethrough-ocr page.png -v # log second-pass OCR decisions
60
+ ```
61
+
62
+ | option | effect |
63
+ |---|---|
64
+ | `-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` |
65
+ | `-o`, `--out-dir` | output directory (default: next to the image) |
66
+ | `--stdout` | print the output instead of writing a file |
67
+ | `--lang` | Tesseract language(s), e.g. `deu+eng` (default `eng`) |
68
+ | `--psm` | Tesseract page segmentation mode (default 3) |
69
+ | `--bold-factor` | stroke width relative to regular text that counts as bold (default 1.2) |
70
+ | `--json` | also write `<name>.scan.json` with the page model as records |
71
+ | `--debug` | also write `<name>.debug.png` with the detections drawn on the scan |
72
+ | `--min-conf` | `-to html`, `-to md`: drop words below this OCR confidence |
73
+ | `--no-color` | `-to html`, `-to md`: do not emit colour spans |
74
+ | `--no-headings` | `-to html`, `-to md`: large text stays a paragraph |
75
+ | `-v`, `--verbose` | log second-pass OCR and layout decisions |
76
+
77
+ The HTML overlay references the scan by relative path for its slider, so
78
+ keep the two files together or the slider shows nothing.
79
+
80
+ ## Library
81
+
82
+ ```python
83
+ from strikethrough_ocr import process, render_html, html_to_markdown, \
84
+ html_to_document_html
85
+
86
+ model = process("page.png", lang="eng") # run the OCR pipeline
87
+ overlay = render_html(model, "page", "page.png") # styled HTML overlay
88
+ md = html_to_markdown(overlay) # Markdown in reading order
89
+ doc = html_to_document_html(overlay) # clean document HTML
90
+ ```
91
+
92
+ `process()` returns a `PageModel`: words (text, box, baseline, font size,
93
+ colour, bold, underline, strike, decoration colour, confidence), table
94
+ rules, decoration strokes and graphics. `model_to_json()` turns it into
95
+ plain records; `draw_debug()` paints the detections onto the scan image.
96
+
97
+ ## Development
98
+
99
+ ```sh
100
+ uv sync --group dev # install with the test dependencies
101
+ uv run pytest # unit tests and golden files
102
+ UPDATE_GOLDENS=1 uv run pytest # rewrite tests/golden and the overlay fixture
103
+ ```
104
+
105
+ The golden tests pin all three renderings of one synthetic fixture page
106
+ (`tests/conftest.py` builds it): the overlay it renders must equal
107
+ `tests/fixtures/page.overlay.html`, and the Markdown and document HTML
108
+ produced from that fixture must equal the files under `tests/golden/`.
109
+ After a deliberate change to a renderer, regenerate them and review the
110
+ diff. One end-to-end test runs the real pipeline and is skipped when no
111
+ `tesseract` binary is on PATH.
112
+
113
+ ## How it works
114
+
115
+ 1. **Ink mask.** The page is thresholded on each pixel's *distance from
116
+ white*, `255 − min(B, G, R)`, so black and coloured ink are both caught.
117
+ The glyph size measured on this mask sets the working scale: a scan
118
+ whose print is smaller than the 300 dpi reference is enlarged (up to 4x)
119
+ before anything else, because Tesseract and every stroke measure need
120
+ the pixels. Highlighter bands pass the ink threshold too; they are
121
+ recognised as *bright, broad* fills — every print ink is dark, and no
122
+ stroke of print is as tall as a marker band — and whitened away, so OCR
123
+ sees paper while the letters on top of them stay.
124
+ 2. **Straight strokes.** Morphological opening finds long horizontal and
125
+ vertical strokes. A stroke with clear paper on both sides is a **table
126
+ rule**; every other horizontal stroke is a **decoration candidate**
127
+ (underline or strike-through). The paper test follows the stroke's ink
128
+ column by column, so a slightly slanted strike-through does not pass
129
+ for a rule. A stroke crossed by vertical rules belongs to a table
130
+ lattice: it is split at the crossings, and only a piece whose glyphs
131
+ pass through it (a strike drawn flush onto a row boundary fuses with
132
+ it into one line) stays a decoration. Length is deliberately no
133
+ criterion — a struck-through paragraph line is as long as a rule.
134
+ 3. **OCR.** Strikes are removed in two ways (Telea inpainting and
135
+ column-wise erasure, which fail on different glyphs) and Tesseract reads
136
+ the cleaned page as hOCR, which carries a baseline and font size per row.
137
+ Weak lines are cut out and read again in isolation.
138
+ 4. **Per-word style.** Colour is the median of the *darkest* ink in the
139
+ word box (darkest by luminance, so the fringe where a glyph blends into
140
+ a marker or the paper does not vote); a decoration's colour comes from
141
+ the stroke's own pixels, or the glyphs a red line crosses would outvote
142
+ the line. Bold compares the measured stroke width (2 × area / perimeter of the
143
+ word's core ink) against a regular-weight model fitted to the page.
144
+ Decoration strokes are assigned to the words they span: through the
145
+ x-height it is a strike, at the baseline an underline. Struck lines are
146
+ re-read on both cleaned variants and the most confident reading wins.
147
+ 5. **Graphics.** Oversized coloured lettering (logos) and ink no confident
148
+ word claims are cropped from the scan and embedded as PNG data URIs.
149
+
150
+ In the Markdown output, connected rules become tables (merged cells hold
151
+ their content in the top-left grid cell), large text becomes headings,
152
+ bold is `**text**`, strike-through `~~text~~`, underline `<u>text</u>`
153
+ and colour `<span style="color:#rrggbb">…</span>`; colour and underline
154
+ have no Markdown syntax, so they are inline HTML. The document HTML
155
+ output shares the same layout analysis and writes it the way pandoc
156
+ would: `<p>`, `<h1..h3>` with an id, `<table>` with a `<colgroup>`,
157
+ `<strong>`/`<del>`/`<u>`/`<span>` inline, and consecutive paragraphs
158
+ labelled `(vi)`, `(b)` or `3.` folded into `<ol start type>` lists —
159
+ unless the label itself is struck through, because deleted text keeps
160
+ its literal label.
161
+
162
+ ## Limits and assumptions
163
+
164
+ * Geometry constants are set for a 300 dpi scan and scaled to the page's
165
+ *measured* glyph size; a scan with smaller print is enlarged internally
166
+ (up to 4x) first. Below roughly 8 px of x-height there is not enough
167
+ signal left even so, and the bold measure goes first.
168
+ * The page is assumed to be deskewed; a rotated page breaks the rule
169
+ detection.
170
+ * Bold is relative: a page set entirely in bold has no regular reference
171
+ and renders as regular. Italic is not detected.
172
+ * Underline and strike-through are found as straight strokes at least
173
+ 40 px long (at 300 dpi); a wavy or hand-drawn line is missed, and a
174
+ heavy or doubled strike hides too much of the glyphs for the text to be
175
+ recovered. A marker stroke as tall as the text it covers is taken for a
176
+ highlight, not a strike.
177
+ * The HTML overlay is a *reproduction of the page*, not a document: words
178
+ are absolutely positioned and the reading order is Tesseract's. The
179
+ document HTML and Markdown outputs are the document view.
@@ -0,0 +1,157 @@
1
+ # strikethrough-ocr
2
+
3
+ Most OCR pipelines silently drop text decorations like color, underline, strike-through.
4
+
5
+ StrikethroughOCR tries to preserves them.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ uv tool install strikethrough-ocr # or: pip install strikethrough-ocr
11
+ ```
12
+
13
+ Tesseract itself must be on `PATH`:
14
+
15
+ ```sh
16
+ brew install tesseract # macOS
17
+ apt install tesseract-ocr # Debian/Ubuntu
18
+ ```
19
+
20
+ ## Command line
21
+
22
+ ```
23
+ strikethrough-ocr [OPTIONS] INPUT.png
24
+ ```
25
+
26
+ Without options it writes the HTML overlay next to the image:
27
+
28
+ ```sh
29
+ strikethrough-ocr page.png # -> page.scan-preview.html (overlay)
30
+ strikethrough-ocr -to html page.png # -> page.scan.html (clean document)
31
+ strikethrough-ocr -to md page.png # -> page.scan.md
32
+ strikethrough-ocr page.png -o out/ # choose the output directory
33
+ strikethrough-ocr page.png --stdout # print instead of writing
34
+ strikethrough-ocr page.png --lang deu+eng # Tesseract language(s)
35
+ strikethrough-ocr page.png --json # also write page.scan.json
36
+ strikethrough-ocr page.png --debug # also write page.debug.png
37
+ strikethrough-ocr page.png -v # log second-pass OCR decisions
38
+ ```
39
+
40
+ | option | effect |
41
+ |---|---|
42
+ | `-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` |
43
+ | `-o`, `--out-dir` | output directory (default: next to the image) |
44
+ | `--stdout` | print the output instead of writing a file |
45
+ | `--lang` | Tesseract language(s), e.g. `deu+eng` (default `eng`) |
46
+ | `--psm` | Tesseract page segmentation mode (default 3) |
47
+ | `--bold-factor` | stroke width relative to regular text that counts as bold (default 1.2) |
48
+ | `--json` | also write `<name>.scan.json` with the page model as records |
49
+ | `--debug` | also write `<name>.debug.png` with the detections drawn on the scan |
50
+ | `--min-conf` | `-to html`, `-to md`: drop words below this OCR confidence |
51
+ | `--no-color` | `-to html`, `-to md`: do not emit colour spans |
52
+ | `--no-headings` | `-to html`, `-to md`: large text stays a paragraph |
53
+ | `-v`, `--verbose` | log second-pass OCR and layout decisions |
54
+
55
+ The HTML overlay references the scan by relative path for its slider, so
56
+ keep the two files together or the slider shows nothing.
57
+
58
+ ## Library
59
+
60
+ ```python
61
+ from strikethrough_ocr import process, render_html, html_to_markdown, \
62
+ html_to_document_html
63
+
64
+ model = process("page.png", lang="eng") # run the OCR pipeline
65
+ overlay = render_html(model, "page", "page.png") # styled HTML overlay
66
+ md = html_to_markdown(overlay) # Markdown in reading order
67
+ doc = html_to_document_html(overlay) # clean document HTML
68
+ ```
69
+
70
+ `process()` returns a `PageModel`: words (text, box, baseline, font size,
71
+ colour, bold, underline, strike, decoration colour, confidence), table
72
+ rules, decoration strokes and graphics. `model_to_json()` turns it into
73
+ plain records; `draw_debug()` paints the detections onto the scan image.
74
+
75
+ ## Development
76
+
77
+ ```sh
78
+ uv sync --group dev # install with the test dependencies
79
+ uv run pytest # unit tests and golden files
80
+ UPDATE_GOLDENS=1 uv run pytest # rewrite tests/golden and the overlay fixture
81
+ ```
82
+
83
+ The golden tests pin all three renderings of one synthetic fixture page
84
+ (`tests/conftest.py` builds it): the overlay it renders must equal
85
+ `tests/fixtures/page.overlay.html`, and the Markdown and document HTML
86
+ produced from that fixture must equal the files under `tests/golden/`.
87
+ After a deliberate change to a renderer, regenerate them and review the
88
+ diff. One end-to-end test runs the real pipeline and is skipped when no
89
+ `tesseract` binary is on PATH.
90
+
91
+ ## How it works
92
+
93
+ 1. **Ink mask.** The page is thresholded on each pixel's *distance from
94
+ white*, `255 − min(B, G, R)`, so black and coloured ink are both caught.
95
+ The glyph size measured on this mask sets the working scale: a scan
96
+ whose print is smaller than the 300 dpi reference is enlarged (up to 4x)
97
+ before anything else, because Tesseract and every stroke measure need
98
+ the pixels. Highlighter bands pass the ink threshold too; they are
99
+ recognised as *bright, broad* fills — every print ink is dark, and no
100
+ stroke of print is as tall as a marker band — and whitened away, so OCR
101
+ sees paper while the letters on top of them stay.
102
+ 2. **Straight strokes.** Morphological opening finds long horizontal and
103
+ vertical strokes. A stroke with clear paper on both sides is a **table
104
+ rule**; every other horizontal stroke is a **decoration candidate**
105
+ (underline or strike-through). The paper test follows the stroke's ink
106
+ column by column, so a slightly slanted strike-through does not pass
107
+ for a rule. A stroke crossed by vertical rules belongs to a table
108
+ lattice: it is split at the crossings, and only a piece whose glyphs
109
+ pass through it (a strike drawn flush onto a row boundary fuses with
110
+ it into one line) stays a decoration. Length is deliberately no
111
+ criterion — a struck-through paragraph line is as long as a rule.
112
+ 3. **OCR.** Strikes are removed in two ways (Telea inpainting and
113
+ column-wise erasure, which fail on different glyphs) and Tesseract reads
114
+ the cleaned page as hOCR, which carries a baseline and font size per row.
115
+ Weak lines are cut out and read again in isolation.
116
+ 4. **Per-word style.** Colour is the median of the *darkest* ink in the
117
+ word box (darkest by luminance, so the fringe where a glyph blends into
118
+ a marker or the paper does not vote); a decoration's colour comes from
119
+ the stroke's own pixels, or the glyphs a red line crosses would outvote
120
+ the line. Bold compares the measured stroke width (2 × area / perimeter of the
121
+ word's core ink) against a regular-weight model fitted to the page.
122
+ Decoration strokes are assigned to the words they span: through the
123
+ x-height it is a strike, at the baseline an underline. Struck lines are
124
+ re-read on both cleaned variants and the most confident reading wins.
125
+ 5. **Graphics.** Oversized coloured lettering (logos) and ink no confident
126
+ word claims are cropped from the scan and embedded as PNG data URIs.
127
+
128
+ In the Markdown output, connected rules become tables (merged cells hold
129
+ their content in the top-left grid cell), large text becomes headings,
130
+ bold is `**text**`, strike-through `~~text~~`, underline `<u>text</u>`
131
+ and colour `<span style="color:#rrggbb">…</span>`; colour and underline
132
+ have no Markdown syntax, so they are inline HTML. The document HTML
133
+ output shares the same layout analysis and writes it the way pandoc
134
+ would: `<p>`, `<h1..h3>` with an id, `<table>` with a `<colgroup>`,
135
+ `<strong>`/`<del>`/`<u>`/`<span>` inline, and consecutive paragraphs
136
+ labelled `(vi)`, `(b)` or `3.` folded into `<ol start type>` lists —
137
+ unless the label itself is struck through, because deleted text keeps
138
+ its literal label.
139
+
140
+ ## Limits and assumptions
141
+
142
+ * Geometry constants are set for a 300 dpi scan and scaled to the page's
143
+ *measured* glyph size; a scan with smaller print is enlarged internally
144
+ (up to 4x) first. Below roughly 8 px of x-height there is not enough
145
+ signal left even so, and the bold measure goes first.
146
+ * The page is assumed to be deskewed; a rotated page breaks the rule
147
+ detection.
148
+ * Bold is relative: a page set entirely in bold has no regular reference
149
+ and renders as regular. Italic is not detected.
150
+ * Underline and strike-through are found as straight strokes at least
151
+ 40 px long (at 300 dpi); a wavy or hand-drawn line is missed, and a
152
+ heavy or doubled strike hides too much of the glyphs for the text to be
153
+ recovered. A marker stroke as tall as the text it covers is taken for a
154
+ highlight, not a strike.
155
+ * The HTML overlay is a *reproduction of the page*, not a document: words
156
+ are absolutely positioned and the reading order is Tesseract's. The
157
+ document HTML and Markdown outputs are the document view.
File without changes
@@ -0,0 +1,2 @@
1
+ registered_at="2026-09-23 10:12:48"
2
+ source_path="/Users/frankeickhoff/dev/contract-dsl/strikethrough-ocr"
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # Commit the remaining working tree of the strikethrough-ocr repository.
3
+ set -euo pipefail
4
+
5
+ cd "$(dirname "$0")/.."
6
+
7
+ git add src/strikethrough_ocr/strikethrough_ocr.py
8
+ git commit -m "fix: add the renamed CLI module
9
+
10
+ The console script entry point already refers to
11
+ strikethrough_ocr.strikethrough_ocr, but the renamed module file itself
12
+ was never committed."
13
+
14
+ git add tests/conftest.py
15
+ git add tests/test_golden.py
16
+ git add tests/test_scan_ink.py
17
+ git add tests/test_scan_geometry.py
18
+ git add tests/test_document.py
19
+ git add tests/test_cli.py
20
+ git add tests/test_integration.py
21
+ git add tests/fixtures/page.overlay.html
22
+ # the golden files match the *.scan.* ignore pattern, so they are added explicitly
23
+ git add -f tests/golden/page.scan.md
24
+ git add -f tests/golden/page.scan.html
25
+ git commit -m "test: add unit tests, fixtures and golden files
26
+
27
+ A synthetic fixture page pins all three renderings: the overlay must
28
+ match tests/fixtures and the Markdown and document HTML must match
29
+ tests/golden. Unit tests cover ink analysis, stroke classification,
30
+ decoration assignment and the CLI; an end-to-end test skips without
31
+ tesseract."
@@ -0,0 +1,25 @@
1
+
2
+ These are my personal notes.
3
+ The ___dev/ folder is entirely for development.
4
+ For commits or generated documentation, never mention any planning or investigation details as described in ___dev/.
5
+
6
+ 🤖 Please ignore everything below the next line.
7
+
8
+
9
+ ---
10
+
11
+ install from local
12
+
13
+ uv tool install . 2>&1 | tail -3 && which strikethrough-ocr && strikethrough-ocr --version
14
+
15
+ uv tool install --force .
16
+
17
+ ---
18
+
19
+ cd strikethrough-ocr
20
+
21
+ uv build
22
+
23
+ uv publish --token pypi-<your-api-token> # token from pypi.org/manage/account/token
24
+
25
+ ~/.pypirc
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+
3
+ mkdir -p ./out
4
+ rm -rf ./out/*
5
+
6
+ for INPUT in $(find ./pages -type f \( -name '*.png' -o -name '*.jpg' \) | sort); do
7
+ echo $INPUT
8
+ strikethrough-ocr ${INPUT} --debug --json -o ./out
9
+ strikethrough-ocr ${INPUT} -to html -o ./out
10
+ strikethrough-ocr ${INPUT} -to md -o ./out
11
+ done
12
+
13
+ for INPUT in $(find ./examples -type f \( -name '*.png' -o -name '*.jpg' \) | sort); do
14
+ echo $INPUT
15
+ strikethrough-ocr ${INPUT} --debug --json -o ./out
16
+ strikethrough-ocr ${INPUT} -to html -o ./out
17
+ strikethrough-ocr ${INPUT} -to md -o ./out
18
+ done
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "strikethrough-ocr"
3
+ version = "0.3.3"
4
+ description = "OCR for scanned pages that preserves styling: bold, colour, underline and strike-through."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ authors = [
8
+ { name = "Frank Eickhoff", email = "info@frankeickhoff.de" },
9
+ ]
10
+ keywords = ["ocr", "strikethrough", "strike-through", "tesseract", "opencv", "scanned-documents", "contracts"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Multimedia :: Graphics :: Capture :: Scanners",
20
+ "Topic :: Scientific/Engineering :: Image Recognition",
21
+ "Topic :: Text Processing :: Markup :: Markdown",
22
+ ]
23
+ dependencies = [
24
+ "opencv-python-headless>=4.11",
25
+ "numpy>=2",
26
+ "pytesseract>=0.3.13",
27
+ ]
28
+
29
+ [project.scripts]
30
+ strikethrough-ocr = "strikethrough_ocr.strikethrough_ocr:main"
31
+
32
+ [dependency-groups]
33
+ dev = ["pytest>=8"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
37
+
38
+ [build-system]
39
+ requires = ["hatchling"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/strikethrough_ocr"]
@@ -0,0 +1,42 @@
1
+ """strikethrough-ocr -- OCR for scanned pages that preserves styling.
2
+
3
+ Library use:
4
+
5
+ from strikethrough_ocr import process, render_html, html_to_markdown
6
+
7
+ model = process("page.png", lang="eng") # run the OCR pipeline
8
+ overlay = render_html(model, "page", "page.png") # styled HTML overlay
9
+ md = html_to_markdown(overlay) # Markdown in reading order
10
+ doc = html_to_document_html(overlay) # clean document HTML
11
+ """
12
+
13
+ __version__ = "0.3.3"
14
+
15
+ from .scan import (
16
+ PageModel,
17
+ draw_debug,
18
+ model_to_json,
19
+ process,
20
+ render_html,
21
+ )
22
+ from .markdown import (
23
+ html_to_document_html,
24
+ html_to_markdown,
25
+ page_to_html,
26
+ page_to_markdown,
27
+ parse_page,
28
+ )
29
+
30
+ __all__ = [
31
+ "__version__",
32
+ "PageModel",
33
+ "draw_debug",
34
+ "model_to_json",
35
+ "process",
36
+ "render_html",
37
+ "html_to_document_html",
38
+ "html_to_markdown",
39
+ "page_to_html",
40
+ "page_to_markdown",
41
+ "parse_page",
42
+ ]