ocr-harness 1.0.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.
- ocr_harness-1.0.3/LICENSE +21 -0
- ocr_harness-1.0.3/PKG-INFO +322 -0
- ocr_harness-1.0.3/README.md +291 -0
- ocr_harness-1.0.3/consts.py +5 -0
- ocr_harness-1.0.3/evaluation/__init__.py +13 -0
- ocr_harness-1.0.3/evaluation/evaluation_analysis.py +237 -0
- ocr_harness-1.0.3/evaluation/evaluation_pipeline.py +483 -0
- ocr_harness-1.0.3/evaluation/metrics.py +265 -0
- ocr_harness-1.0.3/evaluation/ocr_ground_truth.py +42 -0
- ocr_harness-1.0.3/ocr_backbone/__init__.py +16 -0
- ocr_harness-1.0.3/ocr_backbone/bounding_box.py +74 -0
- ocr_harness-1.0.3/ocr_backbone/image_preprocessing.py +181 -0
- ocr_harness-1.0.3/ocr_backbone/input_image.py +24 -0
- ocr_harness-1.0.3/ocr_backbone/ocr_abstract.py +212 -0
- ocr_harness-1.0.3/ocr_backbone/ocr_config.py +218 -0
- ocr_harness-1.0.3/ocr_backbone/ocr_result.py +44 -0
- ocr_harness-1.0.3/ocr_backbone/polygon.py +92 -0
- ocr_harness-1.0.3/ocr_harness.egg-info/PKG-INFO +322 -0
- ocr_harness-1.0.3/ocr_harness.egg-info/SOURCES.txt +44 -0
- ocr_harness-1.0.3/ocr_harness.egg-info/dependency_links.txt +1 -0
- ocr_harness-1.0.3/ocr_harness.egg-info/requires.txt +24 -0
- ocr_harness-1.0.3/ocr_harness.egg-info/top_level.txt +7 -0
- ocr_harness-1.0.3/ocr_modules/__init__.py +40 -0
- ocr_harness-1.0.3/ocr_modules/easyocr_module.py +64 -0
- ocr_harness-1.0.3/ocr_modules/paddleocr_module.py +137 -0
- ocr_harness-1.0.3/ocr_modules/pytesseract_module.py +75 -0
- ocr_harness-1.0.3/pyproject.toml +88 -0
- ocr_harness-1.0.3/scripts/__init__.py +1 -0
- ocr_harness-1.0.3/scripts/draw_bboxes.py +107 -0
- ocr_harness-1.0.3/scripts/run_evaluation.py +120 -0
- ocr_harness-1.0.3/setup.cfg +4 -0
- ocr_harness-1.0.3/tagging_tool/__init__.py +0 -0
- ocr_harness-1.0.3/tagging_tool/__main__.py +58 -0
- ocr_harness-1.0.3/tagging_tool/app.py +271 -0
- ocr_harness-1.0.3/tests/tests_runner.py +20 -0
- ocr_harness-1.0.3/utils/__init__.py +1 -0
- ocr_harness-1.0.3/utils/binarize.py +15 -0
- ocr_harness-1.0.3/utils/callable_descriptors.py +121 -0
- ocr_harness-1.0.3/utils/dataset_utils.py +271 -0
- ocr_harness-1.0.3/utils/image_utils.py +49 -0
- ocr_harness-1.0.3/utils/json_utils.py +30 -0
- ocr_harness-1.0.3/utils/lazy_import.py +36 -0
- ocr_harness-1.0.3/utils/logging_config.py +21 -0
- ocr_harness-1.0.3/utils/serialize_utils.py +211 -0
- ocr_harness-1.0.3/utils/statistics.py +93 -0
- ocr_harness-1.0.3/utils/text_utils.py +27 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Royee Yosibash
|
|
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,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ocr-harness
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: A unified toolkit for integrating, benchmarking, and comparing OCR engines with modular preprocessing and evaluation.
|
|
5
|
+
Author: Royee Yosibash
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: datasets==4.5.0
|
|
11
|
+
Requires-Dist: matplotlib>=3.10.0
|
|
12
|
+
Requires-Dist: numpy==2.4.2
|
|
13
|
+
Requires-Dist: opencv-python-headless==4.13.0.92
|
|
14
|
+
Requires-Dist: pillow==12.1.0
|
|
15
|
+
Requires-Dist: scipy>=1.17.0
|
|
16
|
+
Provides-Extra: ocrs
|
|
17
|
+
Requires-Dist: easyocr==1.7.2; extra == "ocrs"
|
|
18
|
+
Requires-Dist: paddleocr>=3.4.0; extra == "ocrs"
|
|
19
|
+
Requires-Dist: paddlepaddle==3.2.0; extra == "ocrs"
|
|
20
|
+
Requires-Dist: pytesseract>=0.3.10; extra == "ocrs"
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff>=0.15.0; extra == "dev"
|
|
25
|
+
Provides-Extra: dataset-tagging-app
|
|
26
|
+
Requires-Dist: flask>=3.0; extra == "dataset-tagging-app"
|
|
27
|
+
Requires-Dist: pytest-playwright>=0.7.0; extra == "dataset-tagging-app"
|
|
28
|
+
Provides-Extra: full
|
|
29
|
+
Requires-Dist: ocr-harness[dataset-tagging-app,dev,ocrs]; extra == "full"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# OCR-Toolkit
|
|
33
|
+
|
|
34
|
+
This toolkit aims to create a unified "one-stop-shop" framework for integrating, benchmarking, and comparing OCR
|
|
35
|
+
engines. It aims to provide a seamless and clean interface to integrate multiple OCR engines for common and uncommon
|
|
36
|
+
OCR tasks. The OCR-Toolkit cleanly decouples the OCR engine, preprocessing pipeline, and the evaluation metrics into interchangeable
|
|
37
|
+
components allowing easy tinkering and experimentation for OCr configuration and engine evaluation. Using the toolkit
|
|
38
|
+
Integrating a new OCR engine requires one subclass with one method and the framework handles plugging image
|
|
39
|
+
preprocessing, coordinate remapping, metric computation, and result persistence automatically.
|
|
40
|
+
|
|
41
|
+
## Introduction
|
|
42
|
+
|
|
43
|
+
Selecting the optimal OCR engine and configuration for a given input type is
|
|
44
|
+
largely trial and error experiment. Different engines, preprocessing pipelines, and
|
|
45
|
+
parameter choices can yield vastly different results, yet there is no
|
|
46
|
+
standardized method for quantifying those differences across a dataset.
|
|
47
|
+
OCR-Toolkit replaces intuition with data: execute the pipeline, examine the
|
|
48
|
+
metrics, and let confidence intervals, per-tag breakdowns, and side-by-side
|
|
49
|
+
plots identify the optimal combination. It then allows copying the successful configuration
|
|
50
|
+
to production with no additional harness and a lightweight installation.
|
|
51
|
+
|
|
52
|
+
## Use Cases
|
|
53
|
+
|
|
54
|
+
- Compare multiple OCR engines on the same dataset under identical conditions.
|
|
55
|
+
- Measure the impact of a preprocessing step (e.g. contour splitting vs. grid
|
|
56
|
+
splitting) on recognition accuracy.
|
|
57
|
+
- Integrate a new OCR engine with minimal boilerplate and immediately benchmark
|
|
58
|
+
it against existing solutions.
|
|
59
|
+
- Create and maintain ground-truth annotations using the included tagging tool.
|
|
60
|
+
- Make data-driven decisions about OCR configuration rather than relying on
|
|
61
|
+
manual inspection.
|
|
62
|
+
|
|
63
|
+
## Example: Compare OCR Configurations in One Command
|
|
64
|
+
|
|
65
|
+
The fastest way to see what OCR-Toolkit can do: **two JSON files, one command,
|
|
66
|
+
and a data-driven answer.** The `examples/` directory ships a ready-to-run
|
|
67
|
+
comparison that asks a typical question -- *does splitting an image into a 2x2
|
|
68
|
+
grid improve PaddleOCR?* -- and answers it across 70 real-world images.
|
|
69
|
+
|
|
70
|
+
### Compare configurations
|
|
71
|
+
|
|
72
|
+
Start with the baseline -- `examples/paddleocr_baseline.json` runs PaddleOCR with
|
|
73
|
+
its stock pipeline:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"alias": "Paddle Baseline",
|
|
78
|
+
"model_name": "PaddleOCRModule",
|
|
79
|
+
"model_params": {
|
|
80
|
+
"lang": "en",
|
|
81
|
+
"use_doc_orientation_classify": false,
|
|
82
|
+
"use_doc_unwarping": false,
|
|
83
|
+
"use_textline_orientation": false
|
|
84
|
+
},
|
|
85
|
+
"preprocess_methods": []
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Now compare it to a configuration that is *exactly the same, plus one key* --
|
|
90
|
+
`examples/paddleocr_split_image.json` adds a single `preprocess_methods` entry to
|
|
91
|
+
split every image into a 2x2 grid before recognition. The diff is just:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"alias": "Paddle 2x2 Grid Split",
|
|
96
|
+
...
|
|
97
|
+
"preprocess_methods": [
|
|
98
|
+
{"name": "grid_split_image", "kwargs": {"grid": [2, 2]}}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The toolkit handles the rest -- running each cell through the engine and
|
|
104
|
+
remapping the detections back to the original image coordinates automatically.
|
|
105
|
+
Testing any other preprocessing idea (contour splitting, binarization, custom
|
|
106
|
+
steps) is the same one-line change -- no engine or pipeline code to touch.
|
|
107
|
+
|
|
108
|
+
### Running the comparison
|
|
109
|
+
|
|
110
|
+
Run both configurations against the 70-image dataset in one shot:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -m scripts.run_evaluation \
|
|
114
|
+
--config examples \
|
|
115
|
+
--dataset dataset \
|
|
116
|
+
--output-dir scripts/results
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The pipeline computes character accuracy, word accuracy, word recall, word
|
|
120
|
+
precision, and word count ratio -- with per-image results, confidence
|
|
121
|
+
intervals, and comparison plots for every configuration in the directory.
|
|
122
|
+
|
|
123
|
+
### Automatic Report Generation
|
|
124
|
+
|
|
125
|
+
In this example the baseline wins on average across all images: grid-splitting reduces accuracy and
|
|
126
|
+
precision while detecting more (spurious) words. On natural-scene text the
|
|
127
|
+
picture flips -- the grid-split recovers words the baseline misses, lifting
|
|
128
|
+
word recall roughly 9 points at essentially no precision cost:
|
|
129
|
+
|
|
130
|
+
<table>
|
|
131
|
+
<tr>
|
|
132
|
+
<td align="center">
|
|
133
|
+
<img src="examples/radar_pad_base_vs_2x2.png" alt="PaddleOCR baseline vs 2x2 grid split" width="420"/>
|
|
134
|
+
<br />
|
|
135
|
+
<em>All images: baseline is more accurate</em>
|
|
136
|
+
</td>
|
|
137
|
+
<td align="center">
|
|
138
|
+
<img src="examples/radar_pad_base_vs_2x2_natural_scene.png" alt="PaddleOCR baseline vs 2x2 grid split (natural scene)" width="420"/>
|
|
139
|
+
<br />
|
|
140
|
+
<em>Natural scenes: grid split recovers more words</em>
|
|
141
|
+
</td>
|
|
142
|
+
</tr>
|
|
143
|
+
</table>
|
|
144
|
+
|
|
145
|
+
One command, two configs, and the trade-off is quantified.
|
|
146
|
+
|
|
147
|
+
## Features
|
|
148
|
+
|
|
149
|
+
- **Unified OCR interface** -- integrate any OCR engine by subclassing
|
|
150
|
+
`OCRAbstract`. Ships optionally with EasyOCR, PaddleOCR, and pytesseract
|
|
151
|
+
example implementations.
|
|
152
|
+
- **Modular preprocessing** -- composable preprocessing pipeline supporting
|
|
153
|
+
grid splitting, contour-based segmentation, binarization (adaptive and Otsu),
|
|
154
|
+
and user-supplied steps. Combine stages via configuration or code to build the
|
|
155
|
+
pipeline that fits your input data.
|
|
156
|
+
- **Evaluation pipeline** -- compute character accuracy, word accuracy, word
|
|
157
|
+
recall, word precision, and word count ratio against ground-truth
|
|
158
|
+
annotations, with per-image and aggregate results including confidence
|
|
159
|
+
intervals and supporting visualizations
|
|
160
|
+
- **Tagging tool** -- browser-based Flask UI for creating and editing
|
|
161
|
+
ground-truth bounding-box annotations.
|
|
162
|
+
|
|
163
|
+
## Versioning
|
|
164
|
+
|
|
165
|
+
Current version: **1.0.3** (released 2026-09-22).
|
|
166
|
+
|
|
167
|
+
## Installation
|
|
168
|
+
|
|
169
|
+
Requires Python 3.12+.
|
|
170
|
+
|
|
171
|
+
The project ships three independent optional dependency groups on top of the
|
|
172
|
+
always-installed base. Pick any subset, combine them, or use `full` for
|
|
173
|
+
everything.
|
|
174
|
+
|
|
175
|
+
**Base** (core abstractions, evaluation primitives, no engines, no app):
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pip install -e .
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**OCRs** (all OCR engines: EasyOCR, PaddleOCR, PaddlePaddle, pytesseract):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pip install -e ".[ocrs]"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
> Note: `pytesseract` also requires the system `tesseract` binary, e.g.
|
|
188
|
+
> `sudo apt-get install -y tesseract-ocr` on Debian/Ubuntu.
|
|
189
|
+
|
|
190
|
+
**Dataset Tagging App** (Flask UI plus its end-to-end test deps):
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pip install -e ".[dataset-tagging-app]"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Developer** (ruff, pytest, pre-commit):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
pip install -e ".[dev]"
|
|
200
|
+
pre-commit install
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Full** (all groups above):
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pip install -e ".[full]"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Combinations are supported, e.g. `pip install -e ".[ocrs,dev]"`.
|
|
210
|
+
|
|
211
|
+
All code style is enforced by [Ruff](https://docs.astral.sh/ruff/) via
|
|
212
|
+
pre-commit hooks. After `pre-commit install`, every commit is automatically
|
|
213
|
+
checked (lint + format). The ruff configuration lives in `pyproject.toml`
|
|
214
|
+
under `[tool.ruff]`.
|
|
215
|
+
|
|
216
|
+
## Project Structure
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
ocr_backbone/ Core abstractions: OCRAbstract, OCRConfig, OCRResult, BoundingBox, InputImage
|
|
220
|
+
ocr_modules/ Concrete OCR engine implementations (EasyOCR, PaddleOCR, pytesseract)
|
|
221
|
+
evaluation/ Evaluation pipeline, metrics, ground truth, and plotting utilities
|
|
222
|
+
utils/ Shared utilities (JSON I/O, dataset loading, statistics, serialization)
|
|
223
|
+
scripts/ CLI tools: run_evaluation, draw_bboxes, tagging tool
|
|
224
|
+
dataset/ Local dataset with images/ and ground_truth/ subdirectories
|
|
225
|
+
tests/ Unit, regression, and application tests
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage
|
|
229
|
+
|
|
230
|
+
### Run Evaluation
|
|
231
|
+
|
|
232
|
+
Execute all registered OCR modules against the dataset and generate metrics
|
|
233
|
+
and plots:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
python -m scripts.run_evaluation [--config config.json] [--dataset path/to/dataset] [--output-dir scripts/results]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- `--config` accepts a single JSON config file or a directory of JSON configs.
|
|
240
|
+
Without it, all registered OCR modules are evaluated with default settings.
|
|
241
|
+
- `--dataset` defaults to the built-in `dataset/` directory. The directory must
|
|
242
|
+
contain `images/` and `ground_truth/` subdirectories with matching stems.
|
|
243
|
+
- `--output-dir` defaults to `scripts/results`.
|
|
244
|
+
- `--overwrite` re-runs OCR even when cached per-image results already exist.
|
|
245
|
+
|
|
246
|
+
Results are saved as JSON files and PNG plots (bar chart, radar, range plot).
|
|
247
|
+
|
|
248
|
+
### Tagging Tool
|
|
249
|
+
|
|
250
|
+
Launch the browser-based annotation UI:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
python -m tagging_tool [--port 5000]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Opens `http://localhost:5000` in the default browser.
|
|
257
|
+
|
|
258
|
+
### Draw Bounding Boxes
|
|
259
|
+
|
|
260
|
+
Overlay persisted OCR results on an image:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
python -m scripts.draw_bboxes <image_path> <results_json>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Produces `<image_stem>_with_bounding_box.<ext>` in the same directory.
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
## Configuration
|
|
270
|
+
|
|
271
|
+
OCR runs are configured via `OCRConfig`, which can be loaded from a JSON file.
|
|
272
|
+
A minimal configuration requires only `model_name` and `model_params`:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"model_name": "EasyOCRModule",
|
|
277
|
+
"model_params": {}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
A full configuration can include preprocessing steps and a bounding-box
|
|
282
|
+
validator:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"model_name": "EasyOCRModule",
|
|
287
|
+
"model_params": {"languages": ["en"]},
|
|
288
|
+
"preprocess_methods": [
|
|
289
|
+
{"name": "contour_split_image"}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Preprocessing method names are resolved from `ocr_backbone.image_preprocessing`
|
|
295
|
+
by default. Dotted paths (e.g. `"my_package.module.func"`) are dynamically
|
|
296
|
+
imported. Optional `"kwargs"` are bound via `functools.partial`.
|
|
297
|
+
|
|
298
|
+
## Adding a New OCR Module
|
|
299
|
+
|
|
300
|
+
Subclass `OCRAbstract` and implement `_run_single`:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from ocr_backbone.ocr_abstract import OCRAbstract
|
|
304
|
+
from ocr_backbone.ocr_result import OCRResult
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class MyOCRModule(OCRAbstract):
|
|
308
|
+
def __init__(self, config):
|
|
309
|
+
super().__init__(config)
|
|
310
|
+
# initialize your engine
|
|
311
|
+
|
|
312
|
+
def _run_single(self, image, single_run_model_params):
|
|
313
|
+
# run inference, return OCRResult
|
|
314
|
+
...
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The subclass is auto-registered by class name and becomes available to
|
|
318
|
+
`OCRAbstract.from_config` and the evaluation pipeline.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# OCR-Toolkit
|
|
2
|
+
|
|
3
|
+
This toolkit aims to create a unified "one-stop-shop" framework for integrating, benchmarking, and comparing OCR
|
|
4
|
+
engines. It aims to provide a seamless and clean interface to integrate multiple OCR engines for common and uncommon
|
|
5
|
+
OCR tasks. The OCR-Toolkit cleanly decouples the OCR engine, preprocessing pipeline, and the evaluation metrics into interchangeable
|
|
6
|
+
components allowing easy tinkering and experimentation for OCr configuration and engine evaluation. Using the toolkit
|
|
7
|
+
Integrating a new OCR engine requires one subclass with one method and the framework handles plugging image
|
|
8
|
+
preprocessing, coordinate remapping, metric computation, and result persistence automatically.
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
Selecting the optimal OCR engine and configuration for a given input type is
|
|
13
|
+
largely trial and error experiment. Different engines, preprocessing pipelines, and
|
|
14
|
+
parameter choices can yield vastly different results, yet there is no
|
|
15
|
+
standardized method for quantifying those differences across a dataset.
|
|
16
|
+
OCR-Toolkit replaces intuition with data: execute the pipeline, examine the
|
|
17
|
+
metrics, and let confidence intervals, per-tag breakdowns, and side-by-side
|
|
18
|
+
plots identify the optimal combination. It then allows copying the successful configuration
|
|
19
|
+
to production with no additional harness and a lightweight installation.
|
|
20
|
+
|
|
21
|
+
## Use Cases
|
|
22
|
+
|
|
23
|
+
- Compare multiple OCR engines on the same dataset under identical conditions.
|
|
24
|
+
- Measure the impact of a preprocessing step (e.g. contour splitting vs. grid
|
|
25
|
+
splitting) on recognition accuracy.
|
|
26
|
+
- Integrate a new OCR engine with minimal boilerplate and immediately benchmark
|
|
27
|
+
it against existing solutions.
|
|
28
|
+
- Create and maintain ground-truth annotations using the included tagging tool.
|
|
29
|
+
- Make data-driven decisions about OCR configuration rather than relying on
|
|
30
|
+
manual inspection.
|
|
31
|
+
|
|
32
|
+
## Example: Compare OCR Configurations in One Command
|
|
33
|
+
|
|
34
|
+
The fastest way to see what OCR-Toolkit can do: **two JSON files, one command,
|
|
35
|
+
and a data-driven answer.** The `examples/` directory ships a ready-to-run
|
|
36
|
+
comparison that asks a typical question -- *does splitting an image into a 2x2
|
|
37
|
+
grid improve PaddleOCR?* -- and answers it across 70 real-world images.
|
|
38
|
+
|
|
39
|
+
### Compare configurations
|
|
40
|
+
|
|
41
|
+
Start with the baseline -- `examples/paddleocr_baseline.json` runs PaddleOCR with
|
|
42
|
+
its stock pipeline:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"alias": "Paddle Baseline",
|
|
47
|
+
"model_name": "PaddleOCRModule",
|
|
48
|
+
"model_params": {
|
|
49
|
+
"lang": "en",
|
|
50
|
+
"use_doc_orientation_classify": false,
|
|
51
|
+
"use_doc_unwarping": false,
|
|
52
|
+
"use_textline_orientation": false
|
|
53
|
+
},
|
|
54
|
+
"preprocess_methods": []
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Now compare it to a configuration that is *exactly the same, plus one key* --
|
|
59
|
+
`examples/paddleocr_split_image.json` adds a single `preprocess_methods` entry to
|
|
60
|
+
split every image into a 2x2 grid before recognition. The diff is just:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"alias": "Paddle 2x2 Grid Split",
|
|
65
|
+
...
|
|
66
|
+
"preprocess_methods": [
|
|
67
|
+
{"name": "grid_split_image", "kwargs": {"grid": [2, 2]}}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The toolkit handles the rest -- running each cell through the engine and
|
|
73
|
+
remapping the detections back to the original image coordinates automatically.
|
|
74
|
+
Testing any other preprocessing idea (contour splitting, binarization, custom
|
|
75
|
+
steps) is the same one-line change -- no engine or pipeline code to touch.
|
|
76
|
+
|
|
77
|
+
### Running the comparison
|
|
78
|
+
|
|
79
|
+
Run both configurations against the 70-image dataset in one shot:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m scripts.run_evaluation \
|
|
83
|
+
--config examples \
|
|
84
|
+
--dataset dataset \
|
|
85
|
+
--output-dir scripts/results
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The pipeline computes character accuracy, word accuracy, word recall, word
|
|
89
|
+
precision, and word count ratio -- with per-image results, confidence
|
|
90
|
+
intervals, and comparison plots for every configuration in the directory.
|
|
91
|
+
|
|
92
|
+
### Automatic Report Generation
|
|
93
|
+
|
|
94
|
+
In this example the baseline wins on average across all images: grid-splitting reduces accuracy and
|
|
95
|
+
precision while detecting more (spurious) words. On natural-scene text the
|
|
96
|
+
picture flips -- the grid-split recovers words the baseline misses, lifting
|
|
97
|
+
word recall roughly 9 points at essentially no precision cost:
|
|
98
|
+
|
|
99
|
+
<table>
|
|
100
|
+
<tr>
|
|
101
|
+
<td align="center">
|
|
102
|
+
<img src="examples/radar_pad_base_vs_2x2.png" alt="PaddleOCR baseline vs 2x2 grid split" width="420"/>
|
|
103
|
+
<br />
|
|
104
|
+
<em>All images: baseline is more accurate</em>
|
|
105
|
+
</td>
|
|
106
|
+
<td align="center">
|
|
107
|
+
<img src="examples/radar_pad_base_vs_2x2_natural_scene.png" alt="PaddleOCR baseline vs 2x2 grid split (natural scene)" width="420"/>
|
|
108
|
+
<br />
|
|
109
|
+
<em>Natural scenes: grid split recovers more words</em>
|
|
110
|
+
</td>
|
|
111
|
+
</tr>
|
|
112
|
+
</table>
|
|
113
|
+
|
|
114
|
+
One command, two configs, and the trade-off is quantified.
|
|
115
|
+
|
|
116
|
+
## Features
|
|
117
|
+
|
|
118
|
+
- **Unified OCR interface** -- integrate any OCR engine by subclassing
|
|
119
|
+
`OCRAbstract`. Ships optionally with EasyOCR, PaddleOCR, and pytesseract
|
|
120
|
+
example implementations.
|
|
121
|
+
- **Modular preprocessing** -- composable preprocessing pipeline supporting
|
|
122
|
+
grid splitting, contour-based segmentation, binarization (adaptive and Otsu),
|
|
123
|
+
and user-supplied steps. Combine stages via configuration or code to build the
|
|
124
|
+
pipeline that fits your input data.
|
|
125
|
+
- **Evaluation pipeline** -- compute character accuracy, word accuracy, word
|
|
126
|
+
recall, word precision, and word count ratio against ground-truth
|
|
127
|
+
annotations, with per-image and aggregate results including confidence
|
|
128
|
+
intervals and supporting visualizations
|
|
129
|
+
- **Tagging tool** -- browser-based Flask UI for creating and editing
|
|
130
|
+
ground-truth bounding-box annotations.
|
|
131
|
+
|
|
132
|
+
## Versioning
|
|
133
|
+
|
|
134
|
+
Current version: **1.0.3** (released 2026-09-22).
|
|
135
|
+
|
|
136
|
+
## Installation
|
|
137
|
+
|
|
138
|
+
Requires Python 3.12+.
|
|
139
|
+
|
|
140
|
+
The project ships three independent optional dependency groups on top of the
|
|
141
|
+
always-installed base. Pick any subset, combine them, or use `full` for
|
|
142
|
+
everything.
|
|
143
|
+
|
|
144
|
+
**Base** (core abstractions, evaluation primitives, no engines, no app):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install -e .
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**OCRs** (all OCR engines: EasyOCR, PaddleOCR, PaddlePaddle, pytesseract):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[ocrs]"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> Note: `pytesseract` also requires the system `tesseract` binary, e.g.
|
|
157
|
+
> `sudo apt-get install -y tesseract-ocr` on Debian/Ubuntu.
|
|
158
|
+
|
|
159
|
+
**Dataset Tagging App** (Flask UI plus its end-to-end test deps):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pip install -e ".[dataset-tagging-app]"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Developer** (ruff, pytest, pre-commit):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pip install -e ".[dev]"
|
|
169
|
+
pre-commit install
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Full** (all groups above):
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
pip install -e ".[full]"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Combinations are supported, e.g. `pip install -e ".[ocrs,dev]"`.
|
|
179
|
+
|
|
180
|
+
All code style is enforced by [Ruff](https://docs.astral.sh/ruff/) via
|
|
181
|
+
pre-commit hooks. After `pre-commit install`, every commit is automatically
|
|
182
|
+
checked (lint + format). The ruff configuration lives in `pyproject.toml`
|
|
183
|
+
under `[tool.ruff]`.
|
|
184
|
+
|
|
185
|
+
## Project Structure
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
ocr_backbone/ Core abstractions: OCRAbstract, OCRConfig, OCRResult, BoundingBox, InputImage
|
|
189
|
+
ocr_modules/ Concrete OCR engine implementations (EasyOCR, PaddleOCR, pytesseract)
|
|
190
|
+
evaluation/ Evaluation pipeline, metrics, ground truth, and plotting utilities
|
|
191
|
+
utils/ Shared utilities (JSON I/O, dataset loading, statistics, serialization)
|
|
192
|
+
scripts/ CLI tools: run_evaluation, draw_bboxes, tagging tool
|
|
193
|
+
dataset/ Local dataset with images/ and ground_truth/ subdirectories
|
|
194
|
+
tests/ Unit, regression, and application tests
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Usage
|
|
198
|
+
|
|
199
|
+
### Run Evaluation
|
|
200
|
+
|
|
201
|
+
Execute all registered OCR modules against the dataset and generate metrics
|
|
202
|
+
and plots:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
python -m scripts.run_evaluation [--config config.json] [--dataset path/to/dataset] [--output-dir scripts/results]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- `--config` accepts a single JSON config file or a directory of JSON configs.
|
|
209
|
+
Without it, all registered OCR modules are evaluated with default settings.
|
|
210
|
+
- `--dataset` defaults to the built-in `dataset/` directory. The directory must
|
|
211
|
+
contain `images/` and `ground_truth/` subdirectories with matching stems.
|
|
212
|
+
- `--output-dir` defaults to `scripts/results`.
|
|
213
|
+
- `--overwrite` re-runs OCR even when cached per-image results already exist.
|
|
214
|
+
|
|
215
|
+
Results are saved as JSON files and PNG plots (bar chart, radar, range plot).
|
|
216
|
+
|
|
217
|
+
### Tagging Tool
|
|
218
|
+
|
|
219
|
+
Launch the browser-based annotation UI:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
python -m tagging_tool [--port 5000]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Opens `http://localhost:5000` in the default browser.
|
|
226
|
+
|
|
227
|
+
### Draw Bounding Boxes
|
|
228
|
+
|
|
229
|
+
Overlay persisted OCR results on an image:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
python -m scripts.draw_bboxes <image_path> <results_json>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Produces `<image_stem>_with_bounding_box.<ext>` in the same directory.
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
## Configuration
|
|
239
|
+
|
|
240
|
+
OCR runs are configured via `OCRConfig`, which can be loaded from a JSON file.
|
|
241
|
+
A minimal configuration requires only `model_name` and `model_params`:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"model_name": "EasyOCRModule",
|
|
246
|
+
"model_params": {}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
A full configuration can include preprocessing steps and a bounding-box
|
|
251
|
+
validator:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"model_name": "EasyOCRModule",
|
|
256
|
+
"model_params": {"languages": ["en"]},
|
|
257
|
+
"preprocess_methods": [
|
|
258
|
+
{"name": "contour_split_image"}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Preprocessing method names are resolved from `ocr_backbone.image_preprocessing`
|
|
264
|
+
by default. Dotted paths (e.g. `"my_package.module.func"`) are dynamically
|
|
265
|
+
imported. Optional `"kwargs"` are bound via `functools.partial`.
|
|
266
|
+
|
|
267
|
+
## Adding a New OCR Module
|
|
268
|
+
|
|
269
|
+
Subclass `OCRAbstract` and implement `_run_single`:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from ocr_backbone.ocr_abstract import OCRAbstract
|
|
273
|
+
from ocr_backbone.ocr_result import OCRResult
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class MyOCRModule(OCRAbstract):
|
|
277
|
+
def __init__(self, config):
|
|
278
|
+
super().__init__(config)
|
|
279
|
+
# initialize your engine
|
|
280
|
+
|
|
281
|
+
def _run_single(self, image, single_run_model_params):
|
|
282
|
+
# run inference, return OCRResult
|
|
283
|
+
...
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The subclass is auto-registered by class name and becomes available to
|
|
287
|
+
`OCRAbstract.from_config` and the evaluation pipeline.
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Evaluation pipeline, metrics, and ground-truth types.
|
|
2
|
+
|
|
3
|
+
The re-export below exposes the package's public ground-truth type
|
|
4
|
+
and, as a necessary side effect, ensures every ``SerializableClass``
|
|
5
|
+
subclass in this package registers itself in
|
|
6
|
+
``SerializableClass._registry`` (via ``__init_subclass__``) the moment
|
|
7
|
+
any consumer touches the package. Add a line here when introducing a
|
|
8
|
+
new ``SerializableClass`` subclass under ``evaluation/``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from evaluation.ocr_ground_truth import OCRGroundTruth
|
|
12
|
+
|
|
13
|
+
__all__ = ["OCRGroundTruth"]
|