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.
Files changed (29) hide show
  1. arabicocr_khatt-0.1.0/LICENSE +21 -0
  2. arabicocr_khatt-0.1.0/PKG-INFO +203 -0
  3. arabicocr_khatt-0.1.0/README.md +162 -0
  4. arabicocr_khatt-0.1.0/arabicocr_khatt/__init__.py +7 -0
  5. arabicocr_khatt-0.1.0/arabicocr_khatt/augment.py +147 -0
  6. arabicocr_khatt-0.1.0/arabicocr_khatt/charset_arabic.txt +78 -0
  7. arabicocr_khatt-0.1.0/arabicocr_khatt/compare_runs.py +287 -0
  8. arabicocr_khatt-0.1.0/arabicocr_khatt/dataset.py +63 -0
  9. arabicocr_khatt-0.1.0/arabicocr_khatt/dict_ceiling.py +181 -0
  10. arabicocr_khatt-0.1.0/arabicocr_khatt/eval_val.py +175 -0
  11. arabicocr_khatt-0.1.0/arabicocr_khatt/flag_suspects.py +140 -0
  12. arabicocr_khatt-0.1.0/arabicocr_khatt/metrics.py +90 -0
  13. arabicocr_khatt-0.1.0/arabicocr_khatt/model.py +253 -0
  14. arabicocr_khatt-0.1.0/arabicocr_khatt/monitor.py +737 -0
  15. arabicocr_khatt-0.1.0/arabicocr_khatt/pipeline.py +357 -0
  16. arabicocr_khatt-0.1.0/arabicocr_khatt/preprocess.py +69 -0
  17. arabicocr_khatt-0.1.0/arabicocr_khatt/replay_checkpoint.py +239 -0
  18. arabicocr_khatt-0.1.0/arabicocr_khatt/show_metrics.py +68 -0
  19. arabicocr_khatt-0.1.0/arabicocr_khatt/space_diag.py +126 -0
  20. arabicocr_khatt-0.1.0/arabicocr_khatt/train_crnn_ctc.py +440 -0
  21. arabicocr_khatt-0.1.0/arabicocr_khatt/webocr.py +904 -0
  22. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/PKG-INFO +203 -0
  23. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/SOURCES.txt +27 -0
  24. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/dependency_links.txt +1 -0
  25. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/entry_points.txt +2 -0
  26. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/requires.txt +16 -0
  27. arabicocr_khatt-0.1.0/arabicocr_khatt.egg-info/top_level.txt +1 -0
  28. arabicocr_khatt-0.1.0/pyproject.toml +56 -0
  29. 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
+ [![PyPI](https://img.shields.io/pypi/v/arabicocr-khatt)](https://pypi.org/project/arabicocr-khatt/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
+ [![Model on HF](https://img.shields.io/badge/%F0%9F%A4%97%20Hub-FixFips%2Farabicocr--khatt-blue)](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
+ [![PyPI](https://img.shields.io/pypi/v/arabicocr-khatt)](https://pypi.org/project/arabicocr-khatt/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ [![Model on HF](https://img.shields.io/badge/%F0%9F%A4%97%20Hub-FixFips%2Farabicocr--khatt-blue)](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,7 @@
1
+ """Arabic handwritten text recognition (CRNN-CTC) trained on the KHATT dataset."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .pipeline import ArabicOCR
6
+
7
+ __all__ = ["ArabicOCR", "__version__"]
@@ -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
+ ْ