newspaper-ocr 0.1.0__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 (42) hide show
  1. newspaper_ocr-0.1.0/LICENSE +21 -0
  2. newspaper_ocr-0.1.0/PKG-INFO +243 -0
  3. newspaper_ocr-0.1.0/README.md +201 -0
  4. newspaper_ocr-0.1.0/pyproject.toml +52 -0
  5. newspaper_ocr-0.1.0/setup.cfg +4 -0
  6. newspaper_ocr-0.1.0/src/newspaper_ocr/__init__.py +5 -0
  7. newspaper_ocr-0.1.0/src/newspaper_ocr/cli.py +87 -0
  8. newspaper_ocr-0.1.0/src/newspaper_ocr/detectors/__init__.py +12 -0
  9. newspaper_ocr-0.1.0/src/newspaper_ocr/detectors/as_yolo.py +502 -0
  10. newspaper_ocr-0.1.0/src/newspaper_ocr/detectors/base.py +10 -0
  11. newspaper_ocr-0.1.0/src/newspaper_ocr/detectors/paddlex.py +59 -0
  12. newspaper_ocr-0.1.0/src/newspaper_ocr/formatters/__init__.py +10 -0
  13. newspaper_ocr-0.1.0/src/newspaper_ocr/formatters/base.py +9 -0
  14. newspaper_ocr-0.1.0/src/newspaper_ocr/formatters/hocr.py +29 -0
  15. newspaper_ocr-0.1.0/src/newspaper_ocr/formatters/json_fmt.py +29 -0
  16. newspaper_ocr-0.1.0/src/newspaper_ocr/formatters/text.py +7 -0
  17. newspaper_ocr-0.1.0/src/newspaper_ocr/layout_processor.py +504 -0
  18. newspaper_ocr-0.1.0/src/newspaper_ocr/models.py +53 -0
  19. newspaper_ocr-0.1.0/src/newspaper_ocr/pipeline.py +147 -0
  20. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/__init__.py +28 -0
  21. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/base.py +19 -0
  22. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/effocr.py +47 -0
  23. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/glm_ocr.py +198 -0
  24. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/tesseract.py +72 -0
  25. newspaper_ocr-0.1.0/src/newspaper_ocr/recognizers/tesserocr_backend.py +100 -0
  26. newspaper_ocr-0.1.0/src/newspaper_ocr/registry.py +19 -0
  27. newspaper_ocr-0.1.0/src/newspaper_ocr/spell_checker.py +171 -0
  28. newspaper_ocr-0.1.0/src/newspaper_ocr/text_cleaner.py +181 -0
  29. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/PKG-INFO +243 -0
  30. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/SOURCES.txt +40 -0
  31. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/dependency_links.txt +1 -0
  32. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/entry_points.txt +2 -0
  33. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/requires.txt +25 -0
  34. newspaper_ocr-0.1.0/src/newspaper_ocr.egg-info/top_level.txt +1 -0
  35. newspaper_ocr-0.1.0/tests/test_cli.py +16 -0
  36. newspaper_ocr-0.1.0/tests/test_layout_processor.py +340 -0
  37. newspaper_ocr-0.1.0/tests/test_models.py +44 -0
  38. newspaper_ocr-0.1.0/tests/test_pipeline.py +208 -0
  39. newspaper_ocr-0.1.0/tests/test_pipeline_factory.py +28 -0
  40. newspaper_ocr-0.1.0/tests/test_registry.py +21 -0
  41. newspaper_ocr-0.1.0/tests/test_spell_checker.py +246 -0
  42. newspaper_ocr-0.1.0/tests/test_text_cleaner.py +298 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Neal Caren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: newspaper-ocr
3
+ Version: 0.1.0
4
+ Summary: Modular OCR pipeline for historical newspaper scans
5
+ Author-email: Neal Caren <neal.caren@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nealcaren/newspaper-ocr
8
+ Project-URL: Repository, https://github.com/nealcaren/newspaper-ocr
9
+ Project-URL: Issues, https://github.com/nealcaren/newspaper-ocr/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
17
+ Classifier: Topic :: Text Processing :: General
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: pillow>=10.0
22
+ Requires-Dist: opencv-python-headless>=4.8
23
+ Requires-Dist: onnxruntime>=1.16
24
+ Requires-Dist: numpy>=1.24
25
+ Requires-Dist: torch>=2.0
26
+ Requires-Dist: torchvision>=0.15
27
+ Requires-Dist: click>=8.0
28
+ Requires-Dist: huggingface-hub>=0.20
29
+ Provides-Extra: tesseract
30
+ Provides-Extra: paddlex
31
+ Requires-Dist: paddlepaddle; extra == "paddlex"
32
+ Requires-Dist: paddlex; extra == "paddlex"
33
+ Provides-Extra: glm-ocr
34
+ Requires-Dist: transformers>=5.1; extra == "glm-ocr"
35
+ Requires-Dist: torch; extra == "glm-ocr"
36
+ Provides-Extra: all
37
+ Requires-Dist: newspaper-ocr[glm-ocr,paddlex,tesseract]; extra == "all"
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=7.0; extra == "dev"
40
+ Requires-Dist: pytest-cov; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # newspaper-ocr
44
+
45
+ Modular OCR pipeline for historical newspaper scans. Three-phase architecture with swappable backends at every stage.
46
+
47
+ ## Pipeline
48
+
49
+ ```
50
+ Phase 1 Phase 2 Phase 3
51
+ LAYOUT OCR POST-PROCESSING
52
+
53
+ Image ──→ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
54
+ │ Detection │ │ Recognition │ │ Text Cleaning │
55
+ JP2 │ (AS YOLO or │──→│ (Tesseract, │──→│ (dehyphenation, │──→ Output
56
+ JPG │ PP-DocLayout) │ │ tesserocr, │ │ line joining) │ text
57
+ PNG │ │ │ EffOCR) │ │ │ json
58
+ │ Layout Proc. │ │ │ │ Spell Check │ hOCR
59
+ │ (reading order, │ │ │ │ (SymSpell) │
60
+ │ dedup, merge) │ │ │ │ │
61
+ └─────────────────┘ └──────────────┘ └─────────────────┘
62
+ ```
63
+
64
+ **Phase 1 — Layout:** Detect regions (articles, headlines, ads) and text lines. Reorder into newspaper reading order (columns left-to-right, top-to-bottom). Deduplicate overlapping detections, fill gaps.
65
+
66
+ **Phase 2 — OCR:** Recognize text in each detected line or region. Swappable backends with different speed/accuracy tradeoffs.
67
+
68
+ **Phase 3 — Post-Processing:** Reconstruct continuous text from OCR'd lines. Rejoin hyphenated words across line breaks. Join continuation lines into paragraphs. Optional spell correction.
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ pip install newspaper-ocr
74
+
75
+ # Tesseract (requires system install):
76
+ # macOS: brew install tesseract
77
+ # Ubuntu: apt install tesseract-ocr
78
+
79
+ # Optional backends:
80
+ pip install "newspaper-ocr[glm-ocr]" # GLM-OCR vision-language model
81
+ pip install "newspaper-ocr[paddlex]" # PP-DocLayout detector
82
+
83
+ # EfficientOCR (installed separately from fork):
84
+ pip install git+https://github.com/nealcaren/efficient_ocr.git
85
+ ```
86
+
87
+ ## Quick Start
88
+
89
+ ### Python
90
+
91
+ ```python
92
+ from newspaper_ocr import Pipeline
93
+
94
+ # Defaults: AS YOLO detection + Tesseract recognition
95
+ pipe = Pipeline()
96
+ text = pipe.ocr("page.jp2")
97
+
98
+ # Fast mode (tesserocr C API, ~4x faster)
99
+ pipe = Pipeline(recognizer="tesserocr")
100
+
101
+ # With spell correction
102
+ pipe = Pipeline(recognizer="tesserocr", spell_check=True)
103
+
104
+ # JSON output with bounding boxes and confidence scores
105
+ pipe = Pipeline(output="json")
106
+ result = pipe.ocr("page.jp2")
107
+
108
+ # Fine-tuned Tesseract model
109
+ from newspaper_ocr.recognizers.tesseract import TesseractRecognizer
110
+ rec = TesseractRecognizer(model="news_gold_v2", tessdata_dir="/path/to/models")
111
+ pipe = Pipeline(recognizer=rec)
112
+
113
+ # Disable layout post-processing (for non-newspaper documents)
114
+ pipe = Pipeline(layout_processing=False)
115
+
116
+ # Batch processing
117
+ results = pipe.ocr_batch(["page1.jp2", "page2.jp2", "page3.jp2"])
118
+ ```
119
+
120
+ ### Command Line
121
+
122
+ ```bash
123
+ # Basic OCR
124
+ newspaper-ocr page.jp2
125
+
126
+ # Fast mode with JSON output
127
+ newspaper-ocr page.jp2 --backend tesserocr --output json
128
+
129
+ # With spell correction
130
+ newspaper-ocr page.jp2 --backend tesserocr --spell-check
131
+
132
+ # Batch processing to files
133
+ newspaper-ocr *.jp2 --outdir results/ --output text
134
+
135
+ # Fine-tuned model
136
+ newspaper-ocr page.jp2 --model news_gold_v2.traineddata
137
+
138
+ # Disable post-processing
139
+ newspaper-ocr page.jp2 --no-layout-processing --no-text-cleaning
140
+ ```
141
+
142
+ ## Phase 1: Layout
143
+
144
+ Two detection backends, plus battle-tested newspaper layout post-processing.
145
+
146
+ ### Detectors
147
+
148
+ | Detector | What it finds | Speed | Best for |
149
+ |----------|--------------|-------|----------|
150
+ | `as_yolo` (default) | Regions + lines | ~8s/page | Line-level OCR (Tesseract, EffOCR) |
151
+ | `paddlex` | Regions only (20 categories) | varies | Region-level OCR, detailed layout analysis |
152
+
153
+ ### Layout Processing
154
+
155
+ Ported from the [Dangerous Press](https://dangerouspress.org) production pipeline. Applied automatically after detection:
156
+
157
+ 1. **Filter** low-confidence detections
158
+ 2. **Rescue** missed regions in gaps between accepted detections
159
+ 3. **Deduplicate** overlapping regions (three-pass: contained, title-text, near-duplicate)
160
+ 4. **Fill column gaps** using geometric column detection
161
+ 5. **Reading order** — column-aware sorting (full-width headers first, then column-by-column)
162
+ 6. **Merge** vertically adjacent blocks into coherent regions
163
+
164
+ Disable with `layout_processing=False`.
165
+
166
+ ## Phase 2: OCR
167
+
168
+ Three recognition backends with different speed/accuracy tradeoffs.
169
+
170
+ | Backend | Mode | Speed | CER* | How it works |
171
+ |---------|------|-------|------|-------------|
172
+ | `tesseract` | line | ~106s | 3.2% | Subprocess per line, LSTM sequence model |
173
+ | `tesseract` | region | ~38s | — | Subprocess per region, Tesseract's own line segmentation |
174
+ | `tesserocr` | line | ~26s | 3.2% | C API bindings, no subprocess overhead |
175
+ | `tesserocr` | region | ~25s | — | C API, region-level |
176
+ | `effocr` | line | ~50s | 11.2% | Contrastive char/word matching, ONNX |
177
+
178
+ *CER measured against LLM gold-standard labels with fine-tuned `news_gold_v2` model. Baseline Tesseract (eng) is ~8-11% CER. Times on a single newspaper page (~1,100 lines).
179
+
180
+ ### Fine-Tuned Models
181
+
182
+ The pipeline includes infrastructure for fine-tuning Tesseract on historical newspaper text using LLM-verified gold-standard labels. See `dangerouspress-ocr-finetune` for the training pipeline.
183
+
184
+ ## Phase 3: Post-Processing
185
+
186
+ ### Text Cleaning
187
+
188
+ Reconstructs continuous text from OCR'd lines:
189
+
190
+ - **Dehyphenation**: `"com-" + "plete"` → `"complete"` (when next line starts lowercase)
191
+ - **Line joining**: Continuation lines joined with spaces
192
+ - **Paragraph breaks**: Detected via vertical gaps, column shifts, or terminal punctuation + uppercase
193
+ - **Semantic dashes preserved**: Em-dashes and spaced dashes kept intact
194
+
195
+ Disable with `text_cleaning=False` or `--no-text-cleaning`.
196
+
197
+ ### Spell Correction
198
+
199
+ Optional SymSpell-based correction (`spell_check=True`):
200
+
201
+ - Corrects words not found in dictionary (edit distance ≤ 2)
202
+ - Preserves capitalization, punctuation, numbers, abbreviations
203
+ - Supports custom frequency dictionaries for corpus-specific vocabulary
204
+ - Logs all corrections for review
205
+
206
+ ```python
207
+ pipe = Pipeline(spell_check=True)
208
+
209
+ # With corpus-specific dictionary
210
+ from newspaper_ocr.spell_checker import SpellChecker
211
+ checker = SpellChecker(dictionary_path="my_newspaper_words.txt")
212
+ ```
213
+
214
+ ## Output Formats
215
+
216
+ | Format | Flag | Content |
217
+ |--------|------|---------|
218
+ | `text` | `--output text` | Plain text, paragraphs separated by blank lines |
219
+ | `json` | `--output json` | Structured: regions, lines, bounding boxes, confidence |
220
+ | `hocr` | `--output hocr` | HTML with spatial coordinates (for text overlay on images) |
221
+
222
+ ## Architecture
223
+
224
+ Every stage is a swappable component behind an abstract interface. Adding a new backend = one file + one registry entry.
225
+
226
+ ```python
227
+ # Custom detector
228
+ from newspaper_ocr.detectors.base import Detector
229
+ class MyDetector(Detector):
230
+ def detect(self, image) -> PageLayout: ...
231
+
232
+ # Custom recognizer
233
+ from newspaper_ocr.recognizers.base import LineRecognizer
234
+ class MyRecognizer(LineRecognizer):
235
+ def recognize(self, line) -> Line: ...
236
+
237
+ # Plug into pipeline
238
+ pipe = Pipeline(detector=MyDetector(), recognizer=MyRecognizer())
239
+ ```
240
+
241
+ ## License
242
+
243
+ MIT
@@ -0,0 +1,201 @@
1
+ # newspaper-ocr
2
+
3
+ Modular OCR pipeline for historical newspaper scans. Three-phase architecture with swappable backends at every stage.
4
+
5
+ ## Pipeline
6
+
7
+ ```
8
+ Phase 1 Phase 2 Phase 3
9
+ LAYOUT OCR POST-PROCESSING
10
+
11
+ Image ──→ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
12
+ │ Detection │ │ Recognition │ │ Text Cleaning │
13
+ JP2 │ (AS YOLO or │──→│ (Tesseract, │──→│ (dehyphenation, │──→ Output
14
+ JPG │ PP-DocLayout) │ │ tesserocr, │ │ line joining) │ text
15
+ PNG │ │ │ EffOCR) │ │ │ json
16
+ │ Layout Proc. │ │ │ │ Spell Check │ hOCR
17
+ │ (reading order, │ │ │ │ (SymSpell) │
18
+ │ dedup, merge) │ │ │ │ │
19
+ └─────────────────┘ └──────────────┘ └─────────────────┘
20
+ ```
21
+
22
+ **Phase 1 — Layout:** Detect regions (articles, headlines, ads) and text lines. Reorder into newspaper reading order (columns left-to-right, top-to-bottom). Deduplicate overlapping detections, fill gaps.
23
+
24
+ **Phase 2 — OCR:** Recognize text in each detected line or region. Swappable backends with different speed/accuracy tradeoffs.
25
+
26
+ **Phase 3 — Post-Processing:** Reconstruct continuous text from OCR'd lines. Rejoin hyphenated words across line breaks. Join continuation lines into paragraphs. Optional spell correction.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install newspaper-ocr
32
+
33
+ # Tesseract (requires system install):
34
+ # macOS: brew install tesseract
35
+ # Ubuntu: apt install tesseract-ocr
36
+
37
+ # Optional backends:
38
+ pip install "newspaper-ocr[glm-ocr]" # GLM-OCR vision-language model
39
+ pip install "newspaper-ocr[paddlex]" # PP-DocLayout detector
40
+
41
+ # EfficientOCR (installed separately from fork):
42
+ pip install git+https://github.com/nealcaren/efficient_ocr.git
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### Python
48
+
49
+ ```python
50
+ from newspaper_ocr import Pipeline
51
+
52
+ # Defaults: AS YOLO detection + Tesseract recognition
53
+ pipe = Pipeline()
54
+ text = pipe.ocr("page.jp2")
55
+
56
+ # Fast mode (tesserocr C API, ~4x faster)
57
+ pipe = Pipeline(recognizer="tesserocr")
58
+
59
+ # With spell correction
60
+ pipe = Pipeline(recognizer="tesserocr", spell_check=True)
61
+
62
+ # JSON output with bounding boxes and confidence scores
63
+ pipe = Pipeline(output="json")
64
+ result = pipe.ocr("page.jp2")
65
+
66
+ # Fine-tuned Tesseract model
67
+ from newspaper_ocr.recognizers.tesseract import TesseractRecognizer
68
+ rec = TesseractRecognizer(model="news_gold_v2", tessdata_dir="/path/to/models")
69
+ pipe = Pipeline(recognizer=rec)
70
+
71
+ # Disable layout post-processing (for non-newspaper documents)
72
+ pipe = Pipeline(layout_processing=False)
73
+
74
+ # Batch processing
75
+ results = pipe.ocr_batch(["page1.jp2", "page2.jp2", "page3.jp2"])
76
+ ```
77
+
78
+ ### Command Line
79
+
80
+ ```bash
81
+ # Basic OCR
82
+ newspaper-ocr page.jp2
83
+
84
+ # Fast mode with JSON output
85
+ newspaper-ocr page.jp2 --backend tesserocr --output json
86
+
87
+ # With spell correction
88
+ newspaper-ocr page.jp2 --backend tesserocr --spell-check
89
+
90
+ # Batch processing to files
91
+ newspaper-ocr *.jp2 --outdir results/ --output text
92
+
93
+ # Fine-tuned model
94
+ newspaper-ocr page.jp2 --model news_gold_v2.traineddata
95
+
96
+ # Disable post-processing
97
+ newspaper-ocr page.jp2 --no-layout-processing --no-text-cleaning
98
+ ```
99
+
100
+ ## Phase 1: Layout
101
+
102
+ Two detection backends, plus battle-tested newspaper layout post-processing.
103
+
104
+ ### Detectors
105
+
106
+ | Detector | What it finds | Speed | Best for |
107
+ |----------|--------------|-------|----------|
108
+ | `as_yolo` (default) | Regions + lines | ~8s/page | Line-level OCR (Tesseract, EffOCR) |
109
+ | `paddlex` | Regions only (20 categories) | varies | Region-level OCR, detailed layout analysis |
110
+
111
+ ### Layout Processing
112
+
113
+ Ported from the [Dangerous Press](https://dangerouspress.org) production pipeline. Applied automatically after detection:
114
+
115
+ 1. **Filter** low-confidence detections
116
+ 2. **Rescue** missed regions in gaps between accepted detections
117
+ 3. **Deduplicate** overlapping regions (three-pass: contained, title-text, near-duplicate)
118
+ 4. **Fill column gaps** using geometric column detection
119
+ 5. **Reading order** — column-aware sorting (full-width headers first, then column-by-column)
120
+ 6. **Merge** vertically adjacent blocks into coherent regions
121
+
122
+ Disable with `layout_processing=False`.
123
+
124
+ ## Phase 2: OCR
125
+
126
+ Three recognition backends with different speed/accuracy tradeoffs.
127
+
128
+ | Backend | Mode | Speed | CER* | How it works |
129
+ |---------|------|-------|------|-------------|
130
+ | `tesseract` | line | ~106s | 3.2% | Subprocess per line, LSTM sequence model |
131
+ | `tesseract` | region | ~38s | — | Subprocess per region, Tesseract's own line segmentation |
132
+ | `tesserocr` | line | ~26s | 3.2% | C API bindings, no subprocess overhead |
133
+ | `tesserocr` | region | ~25s | — | C API, region-level |
134
+ | `effocr` | line | ~50s | 11.2% | Contrastive char/word matching, ONNX |
135
+
136
+ *CER measured against LLM gold-standard labels with fine-tuned `news_gold_v2` model. Baseline Tesseract (eng) is ~8-11% CER. Times on a single newspaper page (~1,100 lines).
137
+
138
+ ### Fine-Tuned Models
139
+
140
+ The pipeline includes infrastructure for fine-tuning Tesseract on historical newspaper text using LLM-verified gold-standard labels. See `dangerouspress-ocr-finetune` for the training pipeline.
141
+
142
+ ## Phase 3: Post-Processing
143
+
144
+ ### Text Cleaning
145
+
146
+ Reconstructs continuous text from OCR'd lines:
147
+
148
+ - **Dehyphenation**: `"com-" + "plete"` → `"complete"` (when next line starts lowercase)
149
+ - **Line joining**: Continuation lines joined with spaces
150
+ - **Paragraph breaks**: Detected via vertical gaps, column shifts, or terminal punctuation + uppercase
151
+ - **Semantic dashes preserved**: Em-dashes and spaced dashes kept intact
152
+
153
+ Disable with `text_cleaning=False` or `--no-text-cleaning`.
154
+
155
+ ### Spell Correction
156
+
157
+ Optional SymSpell-based correction (`spell_check=True`):
158
+
159
+ - Corrects words not found in dictionary (edit distance ≤ 2)
160
+ - Preserves capitalization, punctuation, numbers, abbreviations
161
+ - Supports custom frequency dictionaries for corpus-specific vocabulary
162
+ - Logs all corrections for review
163
+
164
+ ```python
165
+ pipe = Pipeline(spell_check=True)
166
+
167
+ # With corpus-specific dictionary
168
+ from newspaper_ocr.spell_checker import SpellChecker
169
+ checker = SpellChecker(dictionary_path="my_newspaper_words.txt")
170
+ ```
171
+
172
+ ## Output Formats
173
+
174
+ | Format | Flag | Content |
175
+ |--------|------|---------|
176
+ | `text` | `--output text` | Plain text, paragraphs separated by blank lines |
177
+ | `json` | `--output json` | Structured: regions, lines, bounding boxes, confidence |
178
+ | `hocr` | `--output hocr` | HTML with spatial coordinates (for text overlay on images) |
179
+
180
+ ## Architecture
181
+
182
+ Every stage is a swappable component behind an abstract interface. Adding a new backend = one file + one registry entry.
183
+
184
+ ```python
185
+ # Custom detector
186
+ from newspaper_ocr.detectors.base import Detector
187
+ class MyDetector(Detector):
188
+ def detect(self, image) -> PageLayout: ...
189
+
190
+ # Custom recognizer
191
+ from newspaper_ocr.recognizers.base import LineRecognizer
192
+ class MyRecognizer(LineRecognizer):
193
+ def recognize(self, line) -> Line: ...
194
+
195
+ # Plug into pipeline
196
+ pipe = Pipeline(detector=MyDetector(), recognizer=MyRecognizer())
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "newspaper-ocr"
7
+ version = "0.1.0"
8
+ description = "Modular OCR pipeline for historical newspaper scans"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ {name = "Neal Caren", email = "neal.caren@gmail.com"},
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Scientific/Engineering :: Image Recognition",
23
+ "Topic :: Text Processing :: General",
24
+ ]
25
+ dependencies = [
26
+ "pillow>=10.0",
27
+ "opencv-python-headless>=4.8",
28
+ "onnxruntime>=1.16",
29
+ "numpy>=1.24",
30
+ "torch>=2.0",
31
+ "torchvision>=0.15",
32
+ "click>=8.0",
33
+ "huggingface-hub>=0.20",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/nealcaren/newspaper-ocr"
38
+ Repository = "https://github.com/nealcaren/newspaper-ocr"
39
+ Issues = "https://github.com/nealcaren/newspaper-ocr/issues"
40
+
41
+ [project.optional-dependencies]
42
+ tesseract = []
43
+ paddlex = ["paddlepaddle", "paddlex"]
44
+ glm-ocr = ["transformers>=5.1", "torch"]
45
+ all = ["newspaper-ocr[tesseract,paddlex,glm-ocr]"]
46
+ dev = ["pytest>=7.0", "pytest-cov"]
47
+
48
+ [project.scripts]
49
+ newspaper-ocr = "newspaper_ocr.cli:main"
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """newspaper-ocr: Modular OCR pipeline for historical newspaper scans."""
2
+ from newspaper_ocr.models import BBox, Line, Region, PageLayout
3
+ from newspaper_ocr.pipeline import Pipeline
4
+
5
+ __all__ = ["Pipeline", "BBox", "Line", "Region", "PageLayout"]
@@ -0,0 +1,87 @@
1
+ """CLI entry point for newspaper-ocr."""
2
+ from __future__ import annotations
3
+ import sys
4
+ from pathlib import Path
5
+ import click
6
+
7
+
8
+ @click.command()
9
+ @click.argument("images", nargs=-1, required=True, type=click.Path(exists=True))
10
+ @click.option("--backend", "-b", default="tesseract",
11
+ help="Recognition backend: tesseract, tesserocr, effocr")
12
+ @click.option("--detector", "-d", default="as_yolo",
13
+ help="Detection backend: as_yolo, paddlex")
14
+ @click.option("--output", "-o", default="text",
15
+ help="Output format: text, json, hocr")
16
+ @click.option("--model", "-m", default=None,
17
+ help="Custom model path (e.g., traineddata for Tesseract)")
18
+ @click.option("--model-dir", default=None,
19
+ help="Model cache directory")
20
+ @click.option("--mode", default="region",
21
+ help="Recognition mode: line or region (default: region)")
22
+ @click.option("--no-layout-processing", is_flag=True,
23
+ help="Disable reading order post-processing")
24
+ @click.option("--no-text-cleaning", is_flag=True,
25
+ help="Disable dehyphenation and line-joining post-processing")
26
+ @click.option("--spell-check", is_flag=True, default=False,
27
+ help="Enable SymSpell spell correction post-processing (off by default)")
28
+ @click.option("--fallback", default=None,
29
+ help="Fallback recognizer for low-confidence lines (e.g. glm-ocr)")
30
+ @click.option("--fallback-threshold", default=70, type=float,
31
+ help="Confidence threshold (0-100) below which fallback is used (default: 70)")
32
+ @click.option("--outdir", default=None,
33
+ help="Output directory (default: stdout)")
34
+ def main(images, backend, detector, output, model, model_dir, mode, no_layout_processing, no_text_cleaning, spell_check, fallback, fallback_threshold, outdir):
35
+ """OCR historical newspaper scans.
36
+
37
+ Examples:
38
+ newspaper-ocr page.jp2
39
+ newspaper-ocr page.jp2 --backend tesserocr --output json
40
+ newspaper-ocr *.jp2 --outdir results/ --output text
41
+ newspaper-ocr page.jp2 --model news_gold_v2.traineddata
42
+ """
43
+ from newspaper_ocr import Pipeline
44
+
45
+ # Build recognizer with mode
46
+ from newspaper_ocr.recognizers import RECOGNIZERS
47
+ import inspect
48
+ rec_cls = RECOGNIZERS.get(backend)
49
+ rec_kwargs = {}
50
+ params = inspect.signature(rec_cls.__init__).parameters
51
+ if "mode" in params:
52
+ rec_kwargs["mode"] = mode
53
+ if model:
54
+ # Route model arg to the right param
55
+ if "model" in params:
56
+ rec_kwargs["model"] = model
57
+ elif "model_dir" in params:
58
+ rec_kwargs["model_dir"] = model
59
+
60
+ recognizer = rec_cls(**rec_kwargs)
61
+
62
+ pipe = Pipeline(
63
+ detector=detector,
64
+ recognizer=recognizer,
65
+ output=output,
66
+ model_cache_dir=model_dir,
67
+ layout_processing=not no_layout_processing,
68
+ text_cleaning=not no_text_cleaning,
69
+ spell_check=spell_check,
70
+ fallback=fallback,
71
+ fallback_threshold=fallback_threshold,
72
+ )
73
+
74
+ for image_path in images:
75
+ result = pipe.ocr(image_path)
76
+
77
+ if outdir:
78
+ out_path = Path(outdir) / (Path(image_path).stem + _ext(output))
79
+ Path(outdir).mkdir(parents=True, exist_ok=True)
80
+ out_path.write_text(result)
81
+ click.echo(f"Saved: {out_path}", err=True)
82
+ else:
83
+ click.echo(result)
84
+
85
+
86
+ def _ext(fmt: str) -> str:
87
+ return {"text": ".txt", "json": ".json", "hocr": ".hocr"}.get(fmt, ".txt")
@@ -0,0 +1,12 @@
1
+ from newspaper_ocr.detectors.base import Detector
2
+ from newspaper_ocr.detectors.as_yolo import AsYoloDetector
3
+ from newspaper_ocr.registry import Registry
4
+
5
+ DETECTORS = Registry("detector")
6
+ DETECTORS.register("as_yolo", AsYoloDetector)
7
+
8
+ try:
9
+ from newspaper_ocr.detectors.paddlex import PaddleXDetector
10
+ DETECTORS.register("paddlex", PaddleXDetector)
11
+ except ImportError:
12
+ pass