arabicocr-khatt 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.
- arabicocr_khatt-0.1.0/LICENSE +21 -0
- arabicocr_khatt-0.1.0/PKG-INFO +203 -0
- arabicocr_khatt-0.1.0/README.md +162 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/__init__.py +7 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/augment.py +147 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/charset_arabic.txt +78 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/compare_runs.py +287 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/dataset.py +63 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/dict_ceiling.py +181 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/eval_val.py +175 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/flag_suspects.py +140 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/metrics.py +90 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/model.py +253 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/monitor.py +737 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/pipeline.py +357 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/preprocess.py +69 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/replay_checkpoint.py +239 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/show_metrics.py +68 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/space_diag.py +126 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/train_crnn_ctc.py +440 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt/webocr.py +904 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/PKG-INFO +203 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/SOURCES.txt +27 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/dependency_links.txt +1 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/entry_points.txt +2 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/requires.txt +16 -0
- arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/top_level.txt +1 -0
- arabicocr_khatt-0.1.0/pyproject.toml +56 -0
- arabicocr_khatt-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Osama Sharaf
|
|
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,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arabicocr-khatt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Arabic handwritten text recognition — CRNN-CTC with Arabic-specific multi-scale vertical encoding, trained on the KHATT dataset
|
|
5
|
+
Author: Osama Sharaf
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FixFips/ArabicOCR_KHATT
|
|
8
|
+
Project-URL: Repository, https://github.com/FixFips/ArabicOCR_KHATT
|
|
9
|
+
Project-URL: Issues, https://github.com/FixFips/ArabicOCR_KHATT/issues
|
|
10
|
+
Keywords: arabic,ocr,htr,handwriting,handwritten-text-recognition,khatt,crnn,ctc,pytorch,rtl
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: Arabic
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: torch>=2.2
|
|
27
|
+
Requires-Dist: numpy>=1.24
|
|
28
|
+
Requires-Dist: pillow>=10.0
|
|
29
|
+
Requires-Dist: opencv-python-headless>=4.8
|
|
30
|
+
Requires-Dist: rapidfuzz>=3.6
|
|
31
|
+
Requires-Dist: huggingface_hub>=0.23
|
|
32
|
+
Provides-Extra: demo
|
|
33
|
+
Requires-Dist: gradio>=4.0; extra == "demo"
|
|
34
|
+
Requires-Dist: pandas>=2.0; extra == "demo"
|
|
35
|
+
Requires-Dist: torchvision>=0.17; extra == "demo"
|
|
36
|
+
Provides-Extra: train
|
|
37
|
+
Requires-Dist: pandas>=2.0; extra == "train"
|
|
38
|
+
Requires-Dist: scikit-learn>=1.3; extra == "train"
|
|
39
|
+
Requires-Dist: torchvision>=0.17; extra == "train"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# ArabicOCR-KHATT — Arabic Handwritten Text Recognition (CRNN-CTC)
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/arabicocr-khatt/)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[](https://huggingface.co/FixFips/arabicocr-khatt)
|
|
47
|
+
|
|
48
|
+
**Arabic handwritten text recognition** — line-level OCR (a line or paragraph
|
|
49
|
+
image → Arabic text), trained on the **KHATT** dataset.
|
|
50
|
+
|
|
51
|
+
What makes it Arabic-specific rather than a generic CRNN:
|
|
52
|
+
|
|
53
|
+
- **3-zone vertical pooling** — preserves *where* dots sit (above vs below the
|
|
54
|
+
baseline), the only difference between ب/ت/ث and ن/ي
|
|
55
|
+
- **Input height 96** — keeps diacritic dots large enough (3–5 px) for 3×3
|
|
56
|
+
convolutions to detect
|
|
57
|
+
- **Dot-safe augmentation** — shear, kashida stretch, mild rotation; erosion /
|
|
58
|
+
dilation / elastic are banned because they destroy 2–4 px dots
|
|
59
|
+
- **Beam-search decoding with an Arabic character bigram LM**
|
|
60
|
+
- **Dot-group CER metric** — tracks errors on dot-differentiated letter groups,
|
|
61
|
+
the #1 Arabic OCR error source
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install arabicocr-khatt
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quickstart
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from arabicocr_khatt import ArabicOCR
|
|
75
|
+
|
|
76
|
+
ocr = ArabicOCR.from_pretrained() # downloads weights from the HF Hub
|
|
77
|
+
text = ocr.recognize("handwritten_page.jpg") # multi-line pages segmented automatically
|
|
78
|
+
print(text)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or from the command line:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
arabicocr handwritten_page.jpg
|
|
85
|
+
arabicocr line.png --no-segment --greedy # single line, fastest decode
|
|
86
|
+
arabicocr scan.jpg --checkpoint runs/exp1/crnn_best.pt # local weights
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Try it in the browser: **[demo Space on Hugging Face](https://huggingface.co/spaces/FixFips/arabicocr-khatt-demo)**.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Architecture
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Input [B, 1, 96, 1536] grayscale
|
|
97
|
+
→ CNN (7 conv layers, full BatchNorm, Dropout2d)
|
|
98
|
+
→ adaptive pool to 3 vertical zones (dot position preserved)
|
|
99
|
+
→ 2-layer BiLSTM(1536 → 384)
|
|
100
|
+
→ FC → CTC loss
|
|
101
|
+
→ greedy or beam-search decode (+ Arabic bigram LM), reversed back to RTL
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Preprocessing (shared between training and inference): grayscale → CLAHE →
|
|
105
|
+
dual-polarity Otsu binarization → LANCZOS resize to H=96 → pad to W=1536.
|
|
106
|
+
|
|
107
|
+
## Project structure
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
ArabicOCR_KHATT/
|
|
111
|
+
├─ arabicocr_khatt/ # the installable package
|
|
112
|
+
│ ├─ pipeline.py # ArabicOCR API + CLI (inference single-source)
|
|
113
|
+
│ ├─ model.py # CRNN + CTC decoders + bigram LM builder
|
|
114
|
+
│ ├─ preprocess.py # CLAHE, binarization, resize, padding
|
|
115
|
+
│ ├─ augment.py # Arabic-safe augmentation
|
|
116
|
+
│ ├─ dataset.py # KHATTDataset
|
|
117
|
+
│ ├─ metrics.py # CER / WER / dot-group CER
|
|
118
|
+
│ ├─ train_crnn_ctc.py # training script
|
|
119
|
+
│ ├─ webocr.py # Gradio test bench (rich debugging UI)
|
|
120
|
+
│ ├─ show_metrics.py, monitor.py, compare_runs.py, eval_val.py, ...
|
|
121
|
+
│ └─ charset_arabic.txt # 75-class charset
|
|
122
|
+
├─ scripts/upload_to_hf.py # publish weights + model card to the HF Hub
|
|
123
|
+
├─ space/ # Hugging Face Space demo app
|
|
124
|
+
├─ archive/ # (gitignored) KHATT images + labels + splits
|
|
125
|
+
├─ runs/ # (gitignored) checkpoints + metrics.csv
|
|
126
|
+
└─ pyproject.toml
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Training your own model
|
|
132
|
+
|
|
133
|
+
Get the KHATT dataset (request access at [khatt.ideas2serve.net](https://khatt.ideas2serve.net/))
|
|
134
|
+
and lay it out as:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
archive/
|
|
138
|
+
├─ images/ *.jpg # line images
|
|
139
|
+
├─ labels/ *.txt # Arabic text (Windows-1256 / UTF-8)
|
|
140
|
+
└─ splits/ # auto-created train/val/test CSVs (80/10/10)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Then, with the training extras installed:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install -e ".[train]"
|
|
147
|
+
python -m arabicocr_khatt.train_crnn_ctc # trains to runs/exp1
|
|
148
|
+
python -m arabicocr_khatt.show_metrics --run ./runs/exp1 # inspect metrics
|
|
149
|
+
python -m arabicocr_khatt.monitor # live training dashboard
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Training uses OneCycleLR, gradient accumulation, early stopping, and logs
|
|
153
|
+
per-epoch CER / WER / WER(norm) / dot-group CER to `runs/<exp>/metrics.csv`.
|
|
154
|
+
Checkpoints are saved as `{"model": state_dict, "vocab": [...], "arch_version": 2}`.
|
|
155
|
+
|
|
156
|
+
## Web test bench (Gradio)
|
|
157
|
+
|
|
158
|
+
A rich debugging UI with crop/rotate, polarity control, decoder comparison,
|
|
159
|
+
confidence heatmaps, char-level diff against ground truth, and batch evaluation:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pip install -e ".[demo]"
|
|
163
|
+
python -m arabicocr_khatt.webocr
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Publishing weights to the Hub
|
|
167
|
+
|
|
168
|
+
After training (on the machine that has `runs/` and `archive/`):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
hf auth login
|
|
172
|
+
python scripts/upload_to_hf.py --run-dir runs/exp1
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This validates the checkpoint, builds the bigram LM from the training split,
|
|
176
|
+
fills the model card with your best-epoch metrics, and uploads everything to
|
|
177
|
+
[FixFips/arabicocr-khatt](https://huggingface.co/FixFips/arabicocr-khatt).
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Notes and limitations
|
|
182
|
+
|
|
183
|
+
- Trained on KHATT: performance is best on handwriting similar to that dataset.
|
|
184
|
+
Printed fonts, very noisy backgrounds, or strongly curved text may fail.
|
|
185
|
+
- Line-level model: pages are segmented into lines with classical morphology;
|
|
186
|
+
complex layouts may segment poorly.
|
|
187
|
+
- Labels use Windows-1256 encoding (KHATT standard); the reader falls back to
|
|
188
|
+
UTF-8 automatically.
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Contributions are very welcome — Arabic OCR is an underserved area and there is
|
|
193
|
+
plenty to do. See [CONTRIBUTING.md](CONTRIBUTING.md) and the
|
|
194
|
+
[good first issues](https://github.com/FixFips/ArabicOCR_KHATT/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT — see [LICENSE](LICENSE).
|
|
199
|
+
|
|
200
|
+
If you use this project in research, please also cite the KHATT dataset:
|
|
201
|
+
|
|
202
|
+
> Mahmoud, S. A., et al. "KHATT: An open Arabic offline handwritten text
|
|
203
|
+
> database." Pattern Recognition 47.3 (2014): 1096–1112.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# ArabicOCR-KHATT — Arabic Handwritten Text Recognition (CRNN-CTC)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/arabicocr-khatt/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://huggingface.co/FixFips/arabicocr-khatt)
|
|
6
|
+
|
|
7
|
+
**Arabic handwritten text recognition** — line-level OCR (a line or paragraph
|
|
8
|
+
image → Arabic text), trained on the **KHATT** dataset.
|
|
9
|
+
|
|
10
|
+
What makes it Arabic-specific rather than a generic CRNN:
|
|
11
|
+
|
|
12
|
+
- **3-zone vertical pooling** — preserves *where* dots sit (above vs below the
|
|
13
|
+
baseline), the only difference between ب/ت/ث and ن/ي
|
|
14
|
+
- **Input height 96** — keeps diacritic dots large enough (3–5 px) for 3×3
|
|
15
|
+
convolutions to detect
|
|
16
|
+
- **Dot-safe augmentation** — shear, kashida stretch, mild rotation; erosion /
|
|
17
|
+
dilation / elastic are banned because they destroy 2–4 px dots
|
|
18
|
+
- **Beam-search decoding with an Arabic character bigram LM**
|
|
19
|
+
- **Dot-group CER metric** — tracks errors on dot-differentiated letter groups,
|
|
20
|
+
the #1 Arabic OCR error source
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install arabicocr-khatt
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from arabicocr_khatt import ArabicOCR
|
|
34
|
+
|
|
35
|
+
ocr = ArabicOCR.from_pretrained() # downloads weights from the HF Hub
|
|
36
|
+
text = ocr.recognize("handwritten_page.jpg") # multi-line pages segmented automatically
|
|
37
|
+
print(text)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or from the command line:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
arabicocr handwritten_page.jpg
|
|
44
|
+
arabicocr line.png --no-segment --greedy # single line, fastest decode
|
|
45
|
+
arabicocr scan.jpg --checkpoint runs/exp1/crnn_best.pt # local weights
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Try it in the browser: **[demo Space on Hugging Face](https://huggingface.co/spaces/FixFips/arabicocr-khatt-demo)**.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Input [B, 1, 96, 1536] grayscale
|
|
56
|
+
→ CNN (7 conv layers, full BatchNorm, Dropout2d)
|
|
57
|
+
→ adaptive pool to 3 vertical zones (dot position preserved)
|
|
58
|
+
→ 2-layer BiLSTM(1536 → 384)
|
|
59
|
+
→ FC → CTC loss
|
|
60
|
+
→ greedy or beam-search decode (+ Arabic bigram LM), reversed back to RTL
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Preprocessing (shared between training and inference): grayscale → CLAHE →
|
|
64
|
+
dual-polarity Otsu binarization → LANCZOS resize to H=96 → pad to W=1536.
|
|
65
|
+
|
|
66
|
+
## Project structure
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
ArabicOCR_KHATT/
|
|
70
|
+
├─ arabicocr_khatt/ # the installable package
|
|
71
|
+
│ ├─ pipeline.py # ArabicOCR API + CLI (inference single-source)
|
|
72
|
+
│ ├─ model.py # CRNN + CTC decoders + bigram LM builder
|
|
73
|
+
│ ├─ preprocess.py # CLAHE, binarization, resize, padding
|
|
74
|
+
│ ├─ augment.py # Arabic-safe augmentation
|
|
75
|
+
│ ├─ dataset.py # KHATTDataset
|
|
76
|
+
│ ├─ metrics.py # CER / WER / dot-group CER
|
|
77
|
+
│ ├─ train_crnn_ctc.py # training script
|
|
78
|
+
│ ├─ webocr.py # Gradio test bench (rich debugging UI)
|
|
79
|
+
│ ├─ show_metrics.py, monitor.py, compare_runs.py, eval_val.py, ...
|
|
80
|
+
│ └─ charset_arabic.txt # 75-class charset
|
|
81
|
+
├─ scripts/upload_to_hf.py # publish weights + model card to the HF Hub
|
|
82
|
+
├─ space/ # Hugging Face Space demo app
|
|
83
|
+
├─ archive/ # (gitignored) KHATT images + labels + splits
|
|
84
|
+
├─ runs/ # (gitignored) checkpoints + metrics.csv
|
|
85
|
+
└─ pyproject.toml
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Training your own model
|
|
91
|
+
|
|
92
|
+
Get the KHATT dataset (request access at [khatt.ideas2serve.net](https://khatt.ideas2serve.net/))
|
|
93
|
+
and lay it out as:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
archive/
|
|
97
|
+
├─ images/ *.jpg # line images
|
|
98
|
+
├─ labels/ *.txt # Arabic text (Windows-1256 / UTF-8)
|
|
99
|
+
└─ splits/ # auto-created train/val/test CSVs (80/10/10)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Then, with the training extras installed:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install -e ".[train]"
|
|
106
|
+
python -m arabicocr_khatt.train_crnn_ctc # trains to runs/exp1
|
|
107
|
+
python -m arabicocr_khatt.show_metrics --run ./runs/exp1 # inspect metrics
|
|
108
|
+
python -m arabicocr_khatt.monitor # live training dashboard
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Training uses OneCycleLR, gradient accumulation, early stopping, and logs
|
|
112
|
+
per-epoch CER / WER / WER(norm) / dot-group CER to `runs/<exp>/metrics.csv`.
|
|
113
|
+
Checkpoints are saved as `{"model": state_dict, "vocab": [...], "arch_version": 2}`.
|
|
114
|
+
|
|
115
|
+
## Web test bench (Gradio)
|
|
116
|
+
|
|
117
|
+
A rich debugging UI with crop/rotate, polarity control, decoder comparison,
|
|
118
|
+
confidence heatmaps, char-level diff against ground truth, and batch evaluation:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install -e ".[demo]"
|
|
122
|
+
python -m arabicocr_khatt.webocr
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Publishing weights to the Hub
|
|
126
|
+
|
|
127
|
+
After training (on the machine that has `runs/` and `archive/`):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
hf auth login
|
|
131
|
+
python scripts/upload_to_hf.py --run-dir runs/exp1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This validates the checkpoint, builds the bigram LM from the training split,
|
|
135
|
+
fills the model card with your best-epoch metrics, and uploads everything to
|
|
136
|
+
[FixFips/arabicocr-khatt](https://huggingface.co/FixFips/arabicocr-khatt).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Notes and limitations
|
|
141
|
+
|
|
142
|
+
- Trained on KHATT: performance is best on handwriting similar to that dataset.
|
|
143
|
+
Printed fonts, very noisy backgrounds, or strongly curved text may fail.
|
|
144
|
+
- Line-level model: pages are segmented into lines with classical morphology;
|
|
145
|
+
complex layouts may segment poorly.
|
|
146
|
+
- Labels use Windows-1256 encoding (KHATT standard); the reader falls back to
|
|
147
|
+
UTF-8 automatically.
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
Contributions are very welcome — Arabic OCR is an underserved area and there is
|
|
152
|
+
plenty to do. See [CONTRIBUTING.md](CONTRIBUTING.md) and the
|
|
153
|
+
[good first issues](https://github.com/FixFips/ArabicOCR_KHATT/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT — see [LICENSE](LICENSE).
|
|
158
|
+
|
|
159
|
+
If you use this project in research, please also cite the KHATT dataset:
|
|
160
|
+
|
|
161
|
+
> Mahmoud, S. A., et al. "KHATT: An open Arabic offline handwritten text
|
|
162
|
+
> database." Pattern Recognition 47.3 (2014): 1096–1112.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# arabicocr_khatt/augment.py
|
|
2
|
+
"""
|
|
3
|
+
Arabic-safe data augmentation for handwriting OCR.
|
|
4
|
+
|
|
5
|
+
DESIGN PRINCIPLE: Arabic letters (ba/ta/tha/nun/ya) share identical base
|
|
6
|
+
strokes and differ ONLY by dots that are 2-4 pixels tall. Every transform
|
|
7
|
+
here is chosen to preserve dots and cursive connections.
|
|
8
|
+
|
|
9
|
+
BANNED transforms and why:
|
|
10
|
+
- Erosion: destroys dots (2-4px) and breaks thin connections
|
|
11
|
+
- Dilation: merges adjacent dots (ta → ba) and fills inter-letter gaps
|
|
12
|
+
- Elastic distortion: shifts dots away from their base letter
|
|
13
|
+
- Random crop/cutout: can remove dots above/below the text line
|
|
14
|
+
- Heavy rotation (>2°): shifts dot laterally to wrong letter position
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import random
|
|
18
|
+
import cv2
|
|
19
|
+
import numpy as np
|
|
20
|
+
from PIL import Image
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArabicAugment:
|
|
24
|
+
"""Apply Arabic-safe augmentations to a binarized grayscale PIL Image."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, training: bool = True):
|
|
27
|
+
self.training = training
|
|
28
|
+
|
|
29
|
+
def __call__(self, img: Image.Image) -> Image.Image:
|
|
30
|
+
if not self.training:
|
|
31
|
+
return img
|
|
32
|
+
|
|
33
|
+
arr = np.array(img, dtype=np.uint8) # binarized grayscale
|
|
34
|
+
|
|
35
|
+
# 1) Affine shear — slants all features equally, dots stay above/below their letter
|
|
36
|
+
if random.random() < 0.4:
|
|
37
|
+
arr = self._shear(arr)
|
|
38
|
+
|
|
39
|
+
# 2) Slight rotation — within ±2° dots keep their letter association
|
|
40
|
+
if random.random() < 0.3:
|
|
41
|
+
arr = self._rotate(arr)
|
|
42
|
+
|
|
43
|
+
# 3) Kashida (tatweel) stretch — uniquely Arabic horizontal elongation
|
|
44
|
+
# Stretches connections between letters; dots sit above/below and are unaffected
|
|
45
|
+
if random.random() < 0.3:
|
|
46
|
+
arr = self._kashida_stretch(arr)
|
|
47
|
+
|
|
48
|
+
# 4) Vertical baseline shift — simulates off-center writing
|
|
49
|
+
if random.random() < 0.2:
|
|
50
|
+
arr = self._vertical_shift(arr)
|
|
51
|
+
|
|
52
|
+
# 5) Brightness/contrast jitter — simulates paper/ink variation
|
|
53
|
+
if random.random() < 0.3:
|
|
54
|
+
arr = self._brightness_jitter(arr)
|
|
55
|
+
|
|
56
|
+
# 6) Mild Gaussian noise — scanner/paper noise at safe levels
|
|
57
|
+
if random.random() < 0.2:
|
|
58
|
+
arr = self._gaussian_noise(arr)
|
|
59
|
+
|
|
60
|
+
# Re-binarize to maintain clean binary format
|
|
61
|
+
arr = (arr > 128).astype(np.uint8) * 255
|
|
62
|
+
|
|
63
|
+
return Image.fromarray(arr, mode="L")
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _shear(arr: np.ndarray) -> np.ndarray:
|
|
67
|
+
h, w = arr.shape[:2]
|
|
68
|
+
shear_x = random.uniform(-0.12, 0.12)
|
|
69
|
+
M = np.array([[1, shear_x, 0], [0, 1, 0]], dtype=np.float32)
|
|
70
|
+
# Shift to keep content centered
|
|
71
|
+
M[0, 2] = -shear_x * h / 2
|
|
72
|
+
return cv2.warpAffine(
|
|
73
|
+
arr, M, (w, h),
|
|
74
|
+
flags=cv2.INTER_LINEAR,
|
|
75
|
+
borderMode=cv2.BORDER_CONSTANT,
|
|
76
|
+
borderValue=255,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _rotate(arr: np.ndarray) -> np.ndarray:
|
|
81
|
+
h, w = arr.shape[:2]
|
|
82
|
+
angle = random.uniform(-2.0, 2.0)
|
|
83
|
+
M = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0)
|
|
84
|
+
return cv2.warpAffine(
|
|
85
|
+
arr, M, (w, h),
|
|
86
|
+
flags=cv2.INTER_LINEAR,
|
|
87
|
+
borderMode=cv2.BORDER_CONSTANT,
|
|
88
|
+
borderValue=255,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _kashida_stretch(arr: np.ndarray) -> np.ndarray:
|
|
93
|
+
"""
|
|
94
|
+
Simulate Arabic kashida (tatweel) by horizontally stretching
|
|
95
|
+
a random column range. This stretches the horizontal connections
|
|
96
|
+
between letters — dots sit above/below and are spatially unaffected.
|
|
97
|
+
"""
|
|
98
|
+
h, w = arr.shape[:2]
|
|
99
|
+
if w < 20:
|
|
100
|
+
return arr
|
|
101
|
+
|
|
102
|
+
# Pick a random stretch region (20-40% of width)
|
|
103
|
+
region_w = random.randint(w // 5, 2 * w // 5)
|
|
104
|
+
start = random.randint(0, w - region_w)
|
|
105
|
+
end = start + region_w
|
|
106
|
+
|
|
107
|
+
stretch_factor = random.uniform(1.0, 1.15)
|
|
108
|
+
new_region_w = int(region_w * stretch_factor)
|
|
109
|
+
|
|
110
|
+
region = arr[:, start:end]
|
|
111
|
+
stretched = cv2.resize(region, (new_region_w, h), interpolation=cv2.INTER_LINEAR)
|
|
112
|
+
|
|
113
|
+
new_w = w - region_w + new_region_w
|
|
114
|
+
result = np.full((h, new_w), 255, dtype=np.uint8)
|
|
115
|
+
result[:, :start] = arr[:, :start]
|
|
116
|
+
result[:, start:start + new_region_w] = stretched
|
|
117
|
+
result[:, start + new_region_w:] = arr[:, end:]
|
|
118
|
+
|
|
119
|
+
# Resize back to original width to maintain consistency
|
|
120
|
+
return cv2.resize(result, (w, h), interpolation=cv2.INTER_LINEAR)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _vertical_shift(arr: np.ndarray) -> np.ndarray:
|
|
124
|
+
shift = random.randint(-2, 2)
|
|
125
|
+
if shift == 0:
|
|
126
|
+
return arr
|
|
127
|
+
M = np.array([[1, 0, 0], [0, 1, shift]], dtype=np.float32)
|
|
128
|
+
return cv2.warpAffine(
|
|
129
|
+
arr, M, (arr.shape[1], arr.shape[0]),
|
|
130
|
+
flags=cv2.INTER_LINEAR,
|
|
131
|
+
borderMode=cv2.BORDER_CONSTANT,
|
|
132
|
+
borderValue=255,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _brightness_jitter(arr: np.ndarray) -> np.ndarray:
|
|
137
|
+
factor = random.uniform(0.85, 1.15)
|
|
138
|
+
offset = random.uniform(-10, 10)
|
|
139
|
+
result = arr.astype(np.float32) * factor + offset
|
|
140
|
+
return np.clip(result, 0, 255).astype(np.uint8)
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def _gaussian_noise(arr: np.ndarray) -> np.ndarray:
|
|
144
|
+
sigma = random.uniform(0, 8)
|
|
145
|
+
noise = np.random.randn(*arr.shape) * sigma
|
|
146
|
+
result = arr.astype(np.float32) + noise
|
|
147
|
+
return np.clip(result, 0, 255).astype(np.uint8)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Digits
|
|
2
|
+
0
|
|
3
|
+
1
|
|
4
|
+
2
|
|
5
|
+
3
|
|
6
|
+
4
|
|
7
|
+
5
|
|
8
|
+
6
|
|
9
|
+
7
|
|
10
|
+
8
|
|
11
|
+
9
|
|
12
|
+
|
|
13
|
+
# Basic punctuation
|
|
14
|
+
|
|
15
|
+
!
|
|
16
|
+
.
|
|
17
|
+
،
|
|
18
|
+
؟
|
|
19
|
+
؛
|
|
20
|
+
"
|
|
21
|
+
'
|
|
22
|
+
-
|
|
23
|
+
:
|
|
24
|
+
#
|
|
25
|
+
(
|
|
26
|
+
)
|
|
27
|
+
,
|
|
28
|
+
%
|
|
29
|
+
/
|
|
30
|
+
|
|
31
|
+
# Arabic letters
|
|
32
|
+
ء
|
|
33
|
+
آ
|
|
34
|
+
أ
|
|
35
|
+
ؤ
|
|
36
|
+
إ
|
|
37
|
+
ئ
|
|
38
|
+
ا
|
|
39
|
+
ب
|
|
40
|
+
ة
|
|
41
|
+
ت
|
|
42
|
+
ث
|
|
43
|
+
ج
|
|
44
|
+
ح
|
|
45
|
+
خ
|
|
46
|
+
د
|
|
47
|
+
ذ
|
|
48
|
+
ر
|
|
49
|
+
ز
|
|
50
|
+
س
|
|
51
|
+
ش
|
|
52
|
+
ص
|
|
53
|
+
ض
|
|
54
|
+
ط
|
|
55
|
+
ظ
|
|
56
|
+
ع
|
|
57
|
+
غ
|
|
58
|
+
ف
|
|
59
|
+
ق
|
|
60
|
+
ك
|
|
61
|
+
ل
|
|
62
|
+
م
|
|
63
|
+
ن
|
|
64
|
+
ه
|
|
65
|
+
و
|
|
66
|
+
ي
|
|
67
|
+
ى
|
|
68
|
+
|
|
69
|
+
# Kashida (tatweel) and diacritics
|
|
70
|
+
ـ
|
|
71
|
+
ً
|
|
72
|
+
ٌ
|
|
73
|
+
ٍ
|
|
74
|
+
َ
|
|
75
|
+
ُ
|
|
76
|
+
ِ
|
|
77
|
+
ّ
|
|
78
|
+
ْ
|