urdu-text-eval 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.
- urdu_text_eval-1.0/PKG-INFO +195 -0
- urdu_text_eval-1.0/README.md +179 -0
- urdu_text_eval-1.0/pyproject.toml +43 -0
- urdu_text_eval-1.0/setup.cfg +4 -0
- urdu_text_eval-1.0/src/tranlit_eval/py.typed +0 -0
- urdu_text_eval-1.0/src/urdu_text_eval/__init__.py +16 -0
- urdu_text_eval-1.0/src/urdu_text_eval/evaluate.py +102 -0
- urdu_text_eval-1.0/src/urdu_text_eval/metrics.py +151 -0
- urdu_text_eval-1.0/src/urdu_text_eval/normalize.py +376 -0
- urdu_text_eval-1.0/src/urdu_text_eval/py.typed +0 -0
- urdu_text_eval-1.0/src/urdu_text_eval.egg-info/PKG-INFO +195 -0
- urdu_text_eval-1.0/src/urdu_text_eval.egg-info/SOURCES.txt +13 -0
- urdu_text_eval-1.0/src/urdu_text_eval.egg-info/dependency_links.txt +1 -0
- urdu_text_eval-1.0/src/urdu_text_eval.egg-info/requires.txt +6 -0
- urdu_text_eval-1.0/src/urdu_text_eval.egg-info/top_level.txt +3 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: urdu-text-eval
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Urdu text evaluation & benchmarking library with configurable orthographic normalization (WER, CER, chrF, BLEU).
|
|
5
|
+
Author: urdu-text-eval
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/local/urdu-text-eval
|
|
8
|
+
Keywords: urdu,evaluation,benchmarking,wer,cer,chrf,bleu,nlp,ocr,asr,transliteration,text-evaluation
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: jiwer>=3.0.0
|
|
12
|
+
Requires-Dist: sacrebleu>=2.4.0
|
|
13
|
+
Requires-Dist: rapidfuzz>=3.0.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# urdu-text-eval
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/urdu-text-eval/)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
|
|
23
|
+
**urdu-text-eval** is a Python benchmarking library for **Urdu text evaluation** (OCR, ASR, transliteration, machine translation, LLM text generation, and text processing).
|
|
24
|
+
|
|
25
|
+
It computes standard NLP metrics (**WER, CER, chrF, BLEU, Exact Match, Character Similarity**) with configurable **Urdu orthographic and taxonomy normalization** (handling Arabic lookalike codepoints, zabar/zeer/pesh diacritics, punctuation, numbers, and presentation forms).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Key Features
|
|
30
|
+
|
|
31
|
+
- **Standard Metrics**: Word Error Rate (WER), Character Error Rate (CER), Word/Char Accuracy, chrF, BLEU, Exact Match, and Levenshtein Character Similarity.
|
|
32
|
+
- **Urdu Taxonomy Normalization**: Standardizes Arabic variants (`ك` → `ک`, `ي` → `ی`, `ه` → `ہ`), presentation forms (`ﺍ`, `ﺎ`, `ﺐ`), and combined characters (`ا`+`ٓ` → `آ`).
|
|
33
|
+
- **Fine-Grained Diacritic Controls**: Selective removal of اعراب (**Zabar** `َ`, **Zeer** `ِ`, **Pesh** `ُ`, **Tanween** `ً ٌ ٍ`, **Shadda** `ّ`, **Sukun** `ْ`, Maddah, and Hamza marks).
|
|
34
|
+
- **Flexible Input Formats**: Evaluates simple list-of-dicts `[{"actual": "...", "pred": "..."}]` with support for common key aliases (`ref`, `target`, `gold`, `Urdu`, `hyp`, `prediction`, `output`).
|
|
35
|
+
- **Dual Reporting**: Always reports both **taxonomy-normalized** (primary) and **raw surface** metrics side-by-side.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Install via pip:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install urdu-text-eval
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
*(Dependencies: `jiwer`, `sacrebleu`, `rapidfuzz`)*
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from urdu_text_eval import evaluate
|
|
55
|
+
|
|
56
|
+
pairs = [
|
|
57
|
+
{"actual": "کیا یہ ہے؟", "pred": "كيا يہ ہے؟"},
|
|
58
|
+
{"actual": "آہ جو دل سے", "pred": "آه جو دل سے"},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
result = evaluate(pairs)
|
|
62
|
+
|
|
63
|
+
print(result["summary"])
|
|
64
|
+
print(f"WER: {result['wer']:.4f} | CER: {result['cer']:.4f} | chrF: {result['chrf']:.4f}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Output Structure
|
|
70
|
+
|
|
71
|
+
Calling `evaluate(pairs, per_sample=True)` returns a dictionary containing:
|
|
72
|
+
|
|
73
|
+
| Key | Type | Description |
|
|
74
|
+
|-----|------|-------------|
|
|
75
|
+
| `wer` | `float` | Primary Word Error Rate (0.0 = perfect match) |
|
|
76
|
+
| `cer` | `float` | Primary Character Error Rate |
|
|
77
|
+
| `word_accuracy` | `float` | `1.0 - wer` |
|
|
78
|
+
| `char_accuracy` | `float` | `1.0 - cer` |
|
|
79
|
+
| `normalized_exact_match` | `float` | Ratio of exact matches after normalization |
|
|
80
|
+
| `chrf` | `float` | Character n-gram F-score (sacrebleu) |
|
|
81
|
+
| `bleu` | `float` | Corpus BLEU score |
|
|
82
|
+
| `mean_char_similarity` | `float` | Mean normalized Levenshtein similarity |
|
|
83
|
+
| `raw_wer` / `raw_cer` | `float` | Metrics calculated on raw strings without normalization |
|
|
84
|
+
| `raw_exact_match` | `float` | Exact match ratio on raw strings |
|
|
85
|
+
| `summary` | `str` | Pre-formatted, printable evaluation report |
|
|
86
|
+
| `samples` | `list` | *(Optional, if `per_sample=True`)* List of dicts with per-row scores and normalized strings |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Normalization & Customization
|
|
91
|
+
|
|
92
|
+
Urdu text often varies in orthography (e.g. Arabic vs. Urdu keyboards, presence of diacritics/اعراب, punctuation). You can control normalization behavior precisely:
|
|
93
|
+
|
|
94
|
+
### 1. Master On/Off
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Default: Normalization ON (Fair orthographic evaluation)
|
|
98
|
+
result = evaluate(pairs)
|
|
99
|
+
|
|
100
|
+
# Raw evaluation (No normalization applied)
|
|
101
|
+
result = evaluate(pairs, normalize=False)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Convenience Overrides
|
|
105
|
+
|
|
106
|
+
You can pass boolean flags directly to `evaluate()`:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
result = evaluate(
|
|
110
|
+
pairs,
|
|
111
|
+
taxonomy=True, # Convert Arabic codepoints (ك/ي/ه) to Urdu (ک/ی/ہ)
|
|
112
|
+
remove_diacritics=True, # Remove all اعراب (zabar, zeer, pesh, tanween, etc.)
|
|
113
|
+
remove_zabar=True, # Remove zabar (َ) only
|
|
114
|
+
remove_zeer=True, # Remove zeer (ِ) only
|
|
115
|
+
remove_pesh=True, # Remove pesh (ُ) only
|
|
116
|
+
remove_punctuation=True, # Remove Urdu (؛،؟۔٪) and ASCII punctuation
|
|
117
|
+
remove_digits=False, # Remove ASCII and Urdu digits
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Using `NormalizeConfig` or Presets
|
|
122
|
+
|
|
123
|
+
For reusability across benchmarks, configure a `NormalizeConfig`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from urdu_text_eval import evaluate, NormalizeConfig
|
|
127
|
+
|
|
128
|
+
# Presets
|
|
129
|
+
cfg_default = NormalizeConfig.default() # Taxonomy + diacritics + whitespace
|
|
130
|
+
cfg_raw = NormalizeConfig.none() # Raw string comparison
|
|
131
|
+
cfg_full = NormalizeConfig.full() # Everything on (including punctuation & digit stripping)
|
|
132
|
+
cfg_diac = NormalizeConfig.diacritics_only() # Only remove اعراب
|
|
133
|
+
cfg_punct = NormalizeConfig.punctuation_only() # Only remove punctuation
|
|
134
|
+
cfg_tax = NormalizeConfig.taxonomy_only() # Only taxonomy mapping
|
|
135
|
+
|
|
136
|
+
# Custom Configuration
|
|
137
|
+
cfg = NormalizeConfig(
|
|
138
|
+
enabled=True,
|
|
139
|
+
taxonomy=True, # Map Arabic/presentation characters
|
|
140
|
+
combine_characters=True, # Combine characters (ا + ٓ → آ)
|
|
141
|
+
remove_zabar=True, # Remove zabar
|
|
142
|
+
remove_zeer=True, # Remove zeer
|
|
143
|
+
remove_pesh=True, # Remove pesh
|
|
144
|
+
remove_tanween=True, # Remove tanween
|
|
145
|
+
remove_shadda=True, # Remove shadda
|
|
146
|
+
remove_sukun=True, # Remove sukun
|
|
147
|
+
remove_punctuation=False, # Keep punctuation
|
|
148
|
+
remove_digits=False, # Keep digits
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
result = evaluate(pairs, config=cfg)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Standalone Normalizer Function
|
|
157
|
+
|
|
158
|
+
You can also use the Urdu normalizer directly on individual text strings:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from urdu_text_eval import normalize_urdu, NormalizeConfig
|
|
162
|
+
|
|
163
|
+
# Default normalization
|
|
164
|
+
clean_text = normalize_urdu("كيا يہ ہے؟")
|
|
165
|
+
# Output: "کیا یہ ہے؟"
|
|
166
|
+
|
|
167
|
+
# Remove diacritics (zabar/zeer/pesh) only
|
|
168
|
+
clean_text = normalize_urdu("شیرِ پنجاب", remove_diacritics_all=True)
|
|
169
|
+
# Output: "شیر پنجاب"
|
|
170
|
+
|
|
171
|
+
# Punctuation removal
|
|
172
|
+
clean_text = normalize_urdu("سلام، دنیا!", config=NormalizeConfig.punctuation_only())
|
|
173
|
+
# Output: "سلام دنیا"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## API Summary
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from urdu_text_eval import (
|
|
182
|
+
evaluate, # Core benchmark function: evaluate([{"actual": "...", "pred": "..."}])
|
|
183
|
+
NormalizeConfig, # Configuration dataclass & presets for text normalization
|
|
184
|
+
normalize_urdu, # Single-string Urdu normalization function
|
|
185
|
+
compute_metrics, # Lower-level function: compute_metrics(references, hypotheses)
|
|
186
|
+
format_metrics, # Formats metrics dict into a human-readable text summary
|
|
187
|
+
per_sample_errors, # Returns list of per-item error breakdown dicts
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
Distributed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# urdu-text-eval
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/urdu-text-eval/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
**urdu-text-eval** is a Python benchmarking library for **Urdu text evaluation** (OCR, ASR, transliteration, machine translation, LLM text generation, and text processing).
|
|
8
|
+
|
|
9
|
+
It computes standard NLP metrics (**WER, CER, chrF, BLEU, Exact Match, Character Similarity**) with configurable **Urdu orthographic and taxonomy normalization** (handling Arabic lookalike codepoints, zabar/zeer/pesh diacritics, punctuation, numbers, and presentation forms).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Key Features
|
|
14
|
+
|
|
15
|
+
- **Standard Metrics**: Word Error Rate (WER), Character Error Rate (CER), Word/Char Accuracy, chrF, BLEU, Exact Match, and Levenshtein Character Similarity.
|
|
16
|
+
- **Urdu Taxonomy Normalization**: Standardizes Arabic variants (`ك` → `ک`, `ي` → `ی`, `ه` → `ہ`), presentation forms (`ﺍ`, `ﺎ`, `ﺐ`), and combined characters (`ا`+`ٓ` → `آ`).
|
|
17
|
+
- **Fine-Grained Diacritic Controls**: Selective removal of اعراب (**Zabar** `َ`, **Zeer** `ِ`, **Pesh** `ُ`, **Tanween** `ً ٌ ٍ`, **Shadda** `ّ`, **Sukun** `ْ`, Maddah, and Hamza marks).
|
|
18
|
+
- **Flexible Input Formats**: Evaluates simple list-of-dicts `[{"actual": "...", "pred": "..."}]` with support for common key aliases (`ref`, `target`, `gold`, `Urdu`, `hyp`, `prediction`, `output`).
|
|
19
|
+
- **Dual Reporting**: Always reports both **taxonomy-normalized** (primary) and **raw surface** metrics side-by-side.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install via pip:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install urdu-text-eval
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
*(Dependencies: `jiwer`, `sacrebleu`, `rapidfuzz`)*
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from urdu_text_eval import evaluate
|
|
39
|
+
|
|
40
|
+
pairs = [
|
|
41
|
+
{"actual": "کیا یہ ہے؟", "pred": "كيا يہ ہے؟"},
|
|
42
|
+
{"actual": "آہ جو دل سے", "pred": "آه جو دل سے"},
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
result = evaluate(pairs)
|
|
46
|
+
|
|
47
|
+
print(result["summary"])
|
|
48
|
+
print(f"WER: {result['wer']:.4f} | CER: {result['cer']:.4f} | chrF: {result['chrf']:.4f}")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Output Structure
|
|
54
|
+
|
|
55
|
+
Calling `evaluate(pairs, per_sample=True)` returns a dictionary containing:
|
|
56
|
+
|
|
57
|
+
| Key | Type | Description |
|
|
58
|
+
|-----|------|-------------|
|
|
59
|
+
| `wer` | `float` | Primary Word Error Rate (0.0 = perfect match) |
|
|
60
|
+
| `cer` | `float` | Primary Character Error Rate |
|
|
61
|
+
| `word_accuracy` | `float` | `1.0 - wer` |
|
|
62
|
+
| `char_accuracy` | `float` | `1.0 - cer` |
|
|
63
|
+
| `normalized_exact_match` | `float` | Ratio of exact matches after normalization |
|
|
64
|
+
| `chrf` | `float` | Character n-gram F-score (sacrebleu) |
|
|
65
|
+
| `bleu` | `float` | Corpus BLEU score |
|
|
66
|
+
| `mean_char_similarity` | `float` | Mean normalized Levenshtein similarity |
|
|
67
|
+
| `raw_wer` / `raw_cer` | `float` | Metrics calculated on raw strings without normalization |
|
|
68
|
+
| `raw_exact_match` | `float` | Exact match ratio on raw strings |
|
|
69
|
+
| `summary` | `str` | Pre-formatted, printable evaluation report |
|
|
70
|
+
| `samples` | `list` | *(Optional, if `per_sample=True`)* List of dicts with per-row scores and normalized strings |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Normalization & Customization
|
|
75
|
+
|
|
76
|
+
Urdu text often varies in orthography (e.g. Arabic vs. Urdu keyboards, presence of diacritics/اعراب, punctuation). You can control normalization behavior precisely:
|
|
77
|
+
|
|
78
|
+
### 1. Master On/Off
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Default: Normalization ON (Fair orthographic evaluation)
|
|
82
|
+
result = evaluate(pairs)
|
|
83
|
+
|
|
84
|
+
# Raw evaluation (No normalization applied)
|
|
85
|
+
result = evaluate(pairs, normalize=False)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Convenience Overrides
|
|
89
|
+
|
|
90
|
+
You can pass boolean flags directly to `evaluate()`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
result = evaluate(
|
|
94
|
+
pairs,
|
|
95
|
+
taxonomy=True, # Convert Arabic codepoints (ك/ي/ه) to Urdu (ک/ی/ہ)
|
|
96
|
+
remove_diacritics=True, # Remove all اعراب (zabar, zeer, pesh, tanween, etc.)
|
|
97
|
+
remove_zabar=True, # Remove zabar (َ) only
|
|
98
|
+
remove_zeer=True, # Remove zeer (ِ) only
|
|
99
|
+
remove_pesh=True, # Remove pesh (ُ) only
|
|
100
|
+
remove_punctuation=True, # Remove Urdu (؛،؟۔٪) and ASCII punctuation
|
|
101
|
+
remove_digits=False, # Remove ASCII and Urdu digits
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Using `NormalizeConfig` or Presets
|
|
106
|
+
|
|
107
|
+
For reusability across benchmarks, configure a `NormalizeConfig`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from urdu_text_eval import evaluate, NormalizeConfig
|
|
111
|
+
|
|
112
|
+
# Presets
|
|
113
|
+
cfg_default = NormalizeConfig.default() # Taxonomy + diacritics + whitespace
|
|
114
|
+
cfg_raw = NormalizeConfig.none() # Raw string comparison
|
|
115
|
+
cfg_full = NormalizeConfig.full() # Everything on (including punctuation & digit stripping)
|
|
116
|
+
cfg_diac = NormalizeConfig.diacritics_only() # Only remove اعراب
|
|
117
|
+
cfg_punct = NormalizeConfig.punctuation_only() # Only remove punctuation
|
|
118
|
+
cfg_tax = NormalizeConfig.taxonomy_only() # Only taxonomy mapping
|
|
119
|
+
|
|
120
|
+
# Custom Configuration
|
|
121
|
+
cfg = NormalizeConfig(
|
|
122
|
+
enabled=True,
|
|
123
|
+
taxonomy=True, # Map Arabic/presentation characters
|
|
124
|
+
combine_characters=True, # Combine characters (ا + ٓ → آ)
|
|
125
|
+
remove_zabar=True, # Remove zabar
|
|
126
|
+
remove_zeer=True, # Remove zeer
|
|
127
|
+
remove_pesh=True, # Remove pesh
|
|
128
|
+
remove_tanween=True, # Remove tanween
|
|
129
|
+
remove_shadda=True, # Remove shadda
|
|
130
|
+
remove_sukun=True, # Remove sukun
|
|
131
|
+
remove_punctuation=False, # Keep punctuation
|
|
132
|
+
remove_digits=False, # Keep digits
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = evaluate(pairs, config=cfg)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Standalone Normalizer Function
|
|
141
|
+
|
|
142
|
+
You can also use the Urdu normalizer directly on individual text strings:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from urdu_text_eval import normalize_urdu, NormalizeConfig
|
|
146
|
+
|
|
147
|
+
# Default normalization
|
|
148
|
+
clean_text = normalize_urdu("كيا يہ ہے؟")
|
|
149
|
+
# Output: "کیا یہ ہے؟"
|
|
150
|
+
|
|
151
|
+
# Remove diacritics (zabar/zeer/pesh) only
|
|
152
|
+
clean_text = normalize_urdu("شیرِ پنجاب", remove_diacritics_all=True)
|
|
153
|
+
# Output: "شیر پنجاب"
|
|
154
|
+
|
|
155
|
+
# Punctuation removal
|
|
156
|
+
clean_text = normalize_urdu("سلام، دنیا!", config=NormalizeConfig.punctuation_only())
|
|
157
|
+
# Output: "سلام دنیا"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API Summary
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from urdu_text_eval import (
|
|
166
|
+
evaluate, # Core benchmark function: evaluate([{"actual": "...", "pred": "..."}])
|
|
167
|
+
NormalizeConfig, # Configuration dataclass & presets for text normalization
|
|
168
|
+
normalize_urdu, # Single-string Urdu normalization function
|
|
169
|
+
compute_metrics, # Lower-level function: compute_metrics(references, hypotheses)
|
|
170
|
+
format_metrics, # Formats metrics dict into a human-readable text summary
|
|
171
|
+
per_sample_errors, # Returns list of per-item error breakdown dicts
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
Distributed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "urdu-text-eval"
|
|
7
|
+
version = "1.0"
|
|
8
|
+
description = "Urdu text evaluation & benchmarking library with configurable orthographic normalization (WER, CER, chrF, BLEU)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "urdu-text-eval" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"urdu",
|
|
15
|
+
"evaluation",
|
|
16
|
+
"benchmarking",
|
|
17
|
+
"wer",
|
|
18
|
+
"cer",
|
|
19
|
+
"chrf",
|
|
20
|
+
"bleu",
|
|
21
|
+
"nlp",
|
|
22
|
+
"ocr",
|
|
23
|
+
"asr",
|
|
24
|
+
"transliteration",
|
|
25
|
+
"text-evaluation",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"jiwer>=3.0.0",
|
|
29
|
+
"sacrebleu>=2.4.0",
|
|
30
|
+
"rapidfuzz>=3.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["pytest>=7.0"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/local/urdu-text-eval"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.package-data]
|
|
43
|
+
urdu_text_eval = ["py.typed"]
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""urdu-text-eval: Urdu text evaluation and benchmarking library with orthographic normalization."""
|
|
2
|
+
|
|
3
|
+
from .evaluate import evaluate
|
|
4
|
+
from .metrics import compute_metrics, format_metrics, per_sample_errors
|
|
5
|
+
from .normalize import NormalizeConfig, normalize_urdu
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"evaluate",
|
|
9
|
+
"compute_metrics",
|
|
10
|
+
"format_metrics",
|
|
11
|
+
"per_sample_errors",
|
|
12
|
+
"normalize_urdu",
|
|
13
|
+
"NormalizeConfig",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
__version__ = "1.0"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Public evaluation API for Urdu actual/pred text pairs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Mapping, Sequence
|
|
6
|
+
|
|
7
|
+
from .metrics import compute_metrics, format_metrics, per_sample_errors
|
|
8
|
+
from .normalize import NormalizeConfig
|
|
9
|
+
|
|
10
|
+
_ACTUAL_KEYS = ("actual", "reference", "ref", "Urdu", "urdu", "target", "gold")
|
|
11
|
+
_PRED_KEYS = ("pred", "prediction", "hypothesis", "hyp", "output")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _pick(row: Mapping[str, Any], keys: tuple[str, ...], label: str) -> str:
|
|
15
|
+
for key in keys:
|
|
16
|
+
if key in row and row[key] is not None:
|
|
17
|
+
return str(row[key])
|
|
18
|
+
raise KeyError(
|
|
19
|
+
f"Each item must include an '{label}' field "
|
|
20
|
+
f"(accepted keys: {', '.join(keys)}). Got keys: {sorted(row)}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def evaluate(
|
|
25
|
+
pairs: Sequence[Mapping[str, Any]],
|
|
26
|
+
*,
|
|
27
|
+
normalize: bool = True,
|
|
28
|
+
config: NormalizeConfig | None = None,
|
|
29
|
+
per_sample: bool = False,
|
|
30
|
+
# shortcuts (applied on top of config / default)
|
|
31
|
+
taxonomy: bool | None = None,
|
|
32
|
+
remove_punctuation: bool | None = None,
|
|
33
|
+
remove_zabar: bool | None = None,
|
|
34
|
+
remove_zeer: bool | None = None,
|
|
35
|
+
remove_pesh: bool | None = None,
|
|
36
|
+
remove_diacritics: bool | None = None,
|
|
37
|
+
remove_digits: bool | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Evaluate Urdu text prediction quality against reference ground truth.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
pairs :
|
|
45
|
+
``[{"actual": "...", "pred": "..."}, ...]``
|
|
46
|
+
normalize :
|
|
47
|
+
Master switch. ``False`` → no preprocessing (raw compare).
|
|
48
|
+
config :
|
|
49
|
+
Fine-grained ``NormalizeConfig``. Ignored when ``normalize=False``.
|
|
50
|
+
per_sample :
|
|
51
|
+
Include per-row scores under ``samples``.
|
|
52
|
+
taxonomy / remove_punctuation / remove_zabar / remove_zeer / remove_pesh /
|
|
53
|
+
remove_diacritics / remove_digits :
|
|
54
|
+
Optional overrides on top of ``config``.
|
|
55
|
+
"""
|
|
56
|
+
if not pairs:
|
|
57
|
+
raise ValueError("pairs must be a non-empty list of {actual, pred} dicts.")
|
|
58
|
+
|
|
59
|
+
if not normalize:
|
|
60
|
+
cfg = NormalizeConfig.none()
|
|
61
|
+
else:
|
|
62
|
+
cfg = config or NormalizeConfig.default()
|
|
63
|
+
data = cfg.to_dict()
|
|
64
|
+
if taxonomy is not None:
|
|
65
|
+
data["taxonomy"] = taxonomy
|
|
66
|
+
if remove_punctuation is not None:
|
|
67
|
+
data["remove_punctuation"] = remove_punctuation
|
|
68
|
+
if remove_digits is not None:
|
|
69
|
+
data["remove_digits"] = remove_digits
|
|
70
|
+
if remove_zabar is not None:
|
|
71
|
+
data["remove_zabar"] = remove_zabar
|
|
72
|
+
if remove_zeer is not None:
|
|
73
|
+
data["remove_zeer"] = remove_zeer
|
|
74
|
+
if remove_pesh is not None:
|
|
75
|
+
data["remove_pesh"] = remove_pesh
|
|
76
|
+
if remove_diacritics is not None:
|
|
77
|
+
for key in (
|
|
78
|
+
"remove_zabar",
|
|
79
|
+
"remove_zeer",
|
|
80
|
+
"remove_pesh",
|
|
81
|
+
"remove_tanween",
|
|
82
|
+
"remove_shadda",
|
|
83
|
+
"remove_sukun",
|
|
84
|
+
"remove_other_marks",
|
|
85
|
+
):
|
|
86
|
+
data[key] = remove_diacritics
|
|
87
|
+
cfg = NormalizeConfig(**data)
|
|
88
|
+
|
|
89
|
+
actuals: list[str] = []
|
|
90
|
+
preds: list[str] = []
|
|
91
|
+
for i, row in enumerate(pairs):
|
|
92
|
+
if not isinstance(row, Mapping):
|
|
93
|
+
raise TypeError(f"Item {i} must be a dict, got {type(row).__name__}")
|
|
94
|
+
actuals.append(_pick(row, _ACTUAL_KEYS, "actual"))
|
|
95
|
+
preds.append(_pick(row, _PRED_KEYS, "pred"))
|
|
96
|
+
|
|
97
|
+
metrics = compute_metrics(actuals, preds, config=cfg)
|
|
98
|
+
result: dict[str, Any] = dict(metrics)
|
|
99
|
+
result["summary"] = format_metrics(metrics)
|
|
100
|
+
if per_sample:
|
|
101
|
+
result["samples"] = per_sample_errors(actuals, preds, config=cfg)
|
|
102
|
+
return result
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""WER / CER / chrF / BLEU and related Urdu text evaluation metrics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterable, Sequence
|
|
6
|
+
|
|
7
|
+
from jiwer import cer as jiwer_cer
|
|
8
|
+
from jiwer import wer as jiwer_wer
|
|
9
|
+
from rapidfuzz.distance import Levenshtein
|
|
10
|
+
from sacrebleu import CHRF, corpus_bleu
|
|
11
|
+
|
|
12
|
+
from .normalize import NormalizeConfig, normalize_urdu
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _as_list(texts: Iterable[str]) -> list[str]:
|
|
16
|
+
return ["" if t is None else str(t).strip() for t in texts]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _safe_pair(refs: list[str], hyps: list[str]) -> tuple[list[str], list[str]]:
|
|
20
|
+
return [r if r else " " for r in refs], [h if h else " " for h in hyps]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _edit_metrics(refs: list[str], hyps: list[str]) -> tuple[float, float]:
|
|
24
|
+
sr, sh = _safe_pair(refs, hyps)
|
|
25
|
+
return float(jiwer_wer(sr, sh)), float(jiwer_cer(sr, sh))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def compute_metrics(
|
|
29
|
+
references: Sequence[str],
|
|
30
|
+
hypotheses: Sequence[str],
|
|
31
|
+
*,
|
|
32
|
+
config: NormalizeConfig | None = None,
|
|
33
|
+
normalize: bool | None = None,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""
|
|
36
|
+
Corpus-level metrics for Urdu text evaluation.
|
|
37
|
+
|
|
38
|
+
``normalize=False`` disables preprocessing (same as ``NormalizeConfig.none()``).
|
|
39
|
+
Otherwise ``config`` (default ``NormalizeConfig()``) controls each step.
|
|
40
|
+
"""
|
|
41
|
+
refs = _as_list(references)
|
|
42
|
+
hyps = _as_list(hypotheses)
|
|
43
|
+
if len(refs) != len(hyps):
|
|
44
|
+
raise ValueError(f"Length mismatch: refs={len(refs)} hyps={len(hyps)}")
|
|
45
|
+
if not refs:
|
|
46
|
+
raise ValueError("Empty evaluation set.")
|
|
47
|
+
|
|
48
|
+
if normalize is False:
|
|
49
|
+
cfg = NormalizeConfig.none()
|
|
50
|
+
elif config is not None:
|
|
51
|
+
cfg = config
|
|
52
|
+
else:
|
|
53
|
+
cfg = NormalizeConfig.default()
|
|
54
|
+
|
|
55
|
+
raw_wer, raw_cer = _edit_metrics(refs, hyps)
|
|
56
|
+
raw_exact = sum(1 for r, h in zip(refs, hyps) if r == h) / len(refs)
|
|
57
|
+
|
|
58
|
+
refs_n = [normalize_urdu(r, cfg) for r in refs]
|
|
59
|
+
hyps_n = [normalize_urdu(h, cfg) for h in hyps]
|
|
60
|
+
wer, cer = _edit_metrics(refs_n, hyps_n)
|
|
61
|
+
exact_n = sum(1 for r, h in zip(refs_n, hyps_n) if r == h) / len(refs_n)
|
|
62
|
+
|
|
63
|
+
chrf = CHRF(word_order=0).corpus_score(hyps_n, [refs_n]).score / 100.0
|
|
64
|
+
bleu = corpus_bleu(hyps_n, [refs_n]).score / 100.0
|
|
65
|
+
|
|
66
|
+
sims = [
|
|
67
|
+
1.0 if (not r and not h) else Levenshtein.normalized_similarity(r, h)
|
|
68
|
+
for r, h in zip(refs_n, hyps_n)
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"n": float(len(refs)),
|
|
73
|
+
"normalize_config": cfg.to_dict(),
|
|
74
|
+
"wer": wer,
|
|
75
|
+
"cer": cer,
|
|
76
|
+
"word_accuracy": 1.0 - wer,
|
|
77
|
+
"char_accuracy": 1.0 - cer,
|
|
78
|
+
"normalized_exact_match": exact_n,
|
|
79
|
+
"chrf": chrf,
|
|
80
|
+
"bleu": bleu,
|
|
81
|
+
"mean_char_similarity": sum(sims) / len(sims),
|
|
82
|
+
"raw_wer": raw_wer,
|
|
83
|
+
"raw_cer": raw_cer,
|
|
84
|
+
"raw_exact_match": raw_exact,
|
|
85
|
+
"exact_match": raw_exact,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def per_sample_errors(
|
|
90
|
+
references: Sequence[str],
|
|
91
|
+
hypotheses: Sequence[str],
|
|
92
|
+
*,
|
|
93
|
+
config: NormalizeConfig | None = None,
|
|
94
|
+
normalize: bool | None = None,
|
|
95
|
+
) -> list[dict[str, float | int | str]]:
|
|
96
|
+
if normalize is False:
|
|
97
|
+
cfg = NormalizeConfig.none()
|
|
98
|
+
elif config is not None:
|
|
99
|
+
cfg = config
|
|
100
|
+
else:
|
|
101
|
+
cfg = NormalizeConfig.default()
|
|
102
|
+
|
|
103
|
+
refs = _as_list(references)
|
|
104
|
+
hyps = _as_list(hypotheses)
|
|
105
|
+
rows: list[dict[str, float | int | str]] = []
|
|
106
|
+
for r, h in zip(refs, hyps):
|
|
107
|
+
rn = normalize_urdu(r, cfg)
|
|
108
|
+
hn = normalize_urdu(h, cfg)
|
|
109
|
+
sr, sh = (rn or " "), (hn or " ")
|
|
110
|
+
rows.append(
|
|
111
|
+
{
|
|
112
|
+
"actual": r,
|
|
113
|
+
"pred": h,
|
|
114
|
+
"actual_normalized": rn,
|
|
115
|
+
"pred_normalized": hn,
|
|
116
|
+
"cer": float(jiwer_cer(sr, sh)),
|
|
117
|
+
"wer": float(jiwer_wer(sr, sh)),
|
|
118
|
+
"raw_cer": float(jiwer_cer(r or " ", h or " ")),
|
|
119
|
+
"raw_wer": float(jiwer_wer(r or " ", h or " ")),
|
|
120
|
+
"exact": int(r == h),
|
|
121
|
+
"normalized_exact": int(rn == hn),
|
|
122
|
+
"char_similarity": float(Levenshtein.normalized_similarity(rn, hn)),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
return rows
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def format_metrics(metrics: dict[str, Any]) -> str:
|
|
129
|
+
cfg = metrics.get("normalize_config") or {}
|
|
130
|
+
enabled = cfg.get("enabled", True)
|
|
131
|
+
return "\n".join(
|
|
132
|
+
[
|
|
133
|
+
f"n : {int(metrics['n'])}",
|
|
134
|
+
f"Normalization enabled : {enabled}",
|
|
135
|
+
"",
|
|
136
|
+
"— Primary scores (after configured normalization) —",
|
|
137
|
+
f"WER (↓ better) : {metrics['wer']:.4f}",
|
|
138
|
+
f"CER (↓ better) : {metrics['cer']:.4f}",
|
|
139
|
+
f"Word accuracy : {metrics['word_accuracy']:.4f}",
|
|
140
|
+
f"Char accuracy : {metrics['char_accuracy']:.4f}",
|
|
141
|
+
f"Norm. exact match : {metrics['normalized_exact_match']:.4f}",
|
|
142
|
+
f"chrF (↑ better) : {metrics['chrf']:.4f}",
|
|
143
|
+
f"BLEU (↑ better) : {metrics['bleu']:.4f}",
|
|
144
|
+
f"Mean char similarity : {metrics['mean_char_similarity']:.4f}",
|
|
145
|
+
"",
|
|
146
|
+
"— Raw surface strings —",
|
|
147
|
+
f"Raw WER : {metrics['raw_wer']:.4f}",
|
|
148
|
+
f"Raw CER : {metrics['raw_cer']:.4f}",
|
|
149
|
+
f"Raw exact match : {metrics['raw_exact_match']:.4f}",
|
|
150
|
+
]
|
|
151
|
+
)
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Urdu text normalization with fine-grained controls.
|
|
3
|
+
|
|
4
|
+
Supports turning individual steps on/off:
|
|
5
|
+
- character taxonomy (Arabic / presentation forms → Urdu)
|
|
6
|
+
- zabar / zeer / pesh and other diacritics (اعراب)
|
|
7
|
+
- punctuation removal
|
|
8
|
+
- whitespace / combine-character folding
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import string
|
|
15
|
+
import unicodedata
|
|
16
|
+
from dataclasses import asdict, dataclass, fields
|
|
17
|
+
from typing import Iterable
|
|
18
|
+
|
|
19
|
+
# Character taxonomy adapted from UrduHack (normalization/character.py)
|
|
20
|
+
# https://github.com/urduhack/urduhack
|
|
21
|
+
CORRECT_URDU_CHARACTERS: dict[str, list[str]] = {
|
|
22
|
+
"آ": ["ﺁ", "ﺂ"],
|
|
23
|
+
"أ": ["ﺃ"],
|
|
24
|
+
"ا": ["ﺍ", "ﺎ"],
|
|
25
|
+
"ب": ["ﺏ", "ﺐ", "ﺑ", "ﺒ"],
|
|
26
|
+
"پ": ["ﭖ", "ﭘ", "ﭙ"],
|
|
27
|
+
"ت": ["ﺕ", "ﺖ", "ﺗ", "ﺘ"],
|
|
28
|
+
"ٹ": ["ﭦ", "ﭧ", "ﭨ", "ﭩ"],
|
|
29
|
+
"ث": ["ﺛ", "ﺜ", "ﺚ"],
|
|
30
|
+
"ج": ["ﺝ", "ﺞ", "ﺟ", "ﺠ"],
|
|
31
|
+
"ح": ["ﺡ", "ﺣ", "ﺤ", "ﺢ"],
|
|
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
|
+
"۳": ["٣"],
|
|
70
|
+
"۴": ["٤"],
|
|
71
|
+
"۵": ["٥"],
|
|
72
|
+
"۶": ["٦"],
|
|
73
|
+
"۷": ["٧"],
|
|
74
|
+
"۸": ["٨"],
|
|
75
|
+
"۹": ["٩"],
|
|
76
|
+
"لا": ["ﻻ", "ﻼ"],
|
|
77
|
+
"": ["ـ"],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
COMBINE_URDU_CHARACTERS: dict[str, str] = {
|
|
81
|
+
"آ": "آ",
|
|
82
|
+
"أ": "أ",
|
|
83
|
+
"ۓ": "ۓ",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Named diacritic groups (اعراب)
|
|
87
|
+
ZABAR = "\u064E" # fatha َ
|
|
88
|
+
ZEER = "\u0650" # kasra ِ
|
|
89
|
+
PESH = "\u064F" # damma ُ
|
|
90
|
+
TANWEEN = "\u064B\u064C\u064D" # ً ٌ ٍ
|
|
91
|
+
SHADDA = "\u0651" # ّ
|
|
92
|
+
SUKUN = "\u0652" # ْ
|
|
93
|
+
OTHER_MARKS = (
|
|
94
|
+
"\u0653" # maddah
|
|
95
|
+
"\u0654" # hamza above
|
|
96
|
+
"\u0655" # hamza below
|
|
97
|
+
"\u0656" # subscript alef
|
|
98
|
+
"\u0657" # inverted damma
|
|
99
|
+
"\u0658" # noon ghunna
|
|
100
|
+
"\u0670" # superscript alef
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
URDU_PUNCTUATION = "؛،٫؟۔٪"
|
|
104
|
+
ASCII_PUNCTUATION = string.punctuation
|
|
105
|
+
ALL_PUNCTUATION = URDU_PUNCTUATION + ASCII_PUNCTUATION
|
|
106
|
+
|
|
107
|
+
_TRANSLATOR: dict[int, str] = {}
|
|
108
|
+
for _canon, _variants in CORRECT_URDU_CHARACTERS.items():
|
|
109
|
+
_TRANSLATOR.update(dict.fromkeys(map(ord, _variants), _canon))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class NormalizeConfig:
|
|
114
|
+
"""
|
|
115
|
+
Fine-grained Urdu normalization switches for evaluation.
|
|
116
|
+
|
|
117
|
+
Set ``enabled=False`` to disable all preprocessing (raw string compare).
|
|
118
|
+
Individual flags are ignored when ``enabled`` is False.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
enabled: bool = True
|
|
122
|
+
unicode_nfc: bool = True
|
|
123
|
+
whitespace: bool = True
|
|
124
|
+
taxonomy: bool = True
|
|
125
|
+
"""Map Arabic / presentation-form lookalikes to canonical Urdu letters."""
|
|
126
|
+
combine_characters: bool = True
|
|
127
|
+
"""Fold split sequences like ا+ٓ → آ."""
|
|
128
|
+
|
|
129
|
+
# Diacritics / اعراب
|
|
130
|
+
remove_zabar: bool = True
|
|
131
|
+
remove_zeer: bool = True
|
|
132
|
+
remove_pesh: bool = True
|
|
133
|
+
remove_tanween: bool = True
|
|
134
|
+
remove_shadda: bool = True
|
|
135
|
+
remove_sukun: bool = True
|
|
136
|
+
remove_other_marks: bool = True
|
|
137
|
+
|
|
138
|
+
# Extra cleanup
|
|
139
|
+
remove_punctuation: bool = False
|
|
140
|
+
remove_digits: bool = False
|
|
141
|
+
remove_tatweel: bool = True
|
|
142
|
+
|
|
143
|
+
# ---- presets ----------------------------------------------------------
|
|
144
|
+
@classmethod
|
|
145
|
+
def default(cls) -> "NormalizeConfig":
|
|
146
|
+
"""Taxonomy + diacritics + whitespace (punctuation kept)."""
|
|
147
|
+
return cls()
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def none(cls) -> "NormalizeConfig":
|
|
151
|
+
"""No normalization at all."""
|
|
152
|
+
return cls(enabled=False)
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def full(cls) -> "NormalizeConfig":
|
|
156
|
+
"""Everything on, including punctuation and digit stripping."""
|
|
157
|
+
return cls(remove_punctuation=True, remove_digits=True)
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def diacritics_only(cls) -> "NormalizeConfig":
|
|
161
|
+
"""Only strip اعراب (zabar/zeer/pesh/…)."""
|
|
162
|
+
return cls(
|
|
163
|
+
taxonomy=False,
|
|
164
|
+
combine_characters=False,
|
|
165
|
+
whitespace=True,
|
|
166
|
+
unicode_nfc=True,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def punctuation_only(cls) -> "NormalizeConfig":
|
|
171
|
+
"""Only remove punctuation (Urdu + ASCII)."""
|
|
172
|
+
return cls(
|
|
173
|
+
taxonomy=False,
|
|
174
|
+
combine_characters=False,
|
|
175
|
+
remove_zabar=False,
|
|
176
|
+
remove_zeer=False,
|
|
177
|
+
remove_pesh=False,
|
|
178
|
+
remove_tanween=False,
|
|
179
|
+
remove_shadda=False,
|
|
180
|
+
remove_sukun=False,
|
|
181
|
+
remove_other_marks=False,
|
|
182
|
+
remove_punctuation=True,
|
|
183
|
+
remove_tatweel=False,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def taxonomy_only(cls) -> "NormalizeConfig":
|
|
188
|
+
"""Only Arabic→Urdu character taxonomy (+ whitespace)."""
|
|
189
|
+
return cls(
|
|
190
|
+
remove_zabar=False,
|
|
191
|
+
remove_zeer=False,
|
|
192
|
+
remove_pesh=False,
|
|
193
|
+
remove_tanween=False,
|
|
194
|
+
remove_shadda=False,
|
|
195
|
+
remove_sukun=False,
|
|
196
|
+
remove_other_marks=False,
|
|
197
|
+
combine_characters=True,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def with_diacritics(self, remove: bool) -> "NormalizeConfig":
|
|
201
|
+
"""Toggle all diacritic flags together."""
|
|
202
|
+
return NormalizeConfig(
|
|
203
|
+
**{
|
|
204
|
+
**asdict(self),
|
|
205
|
+
"remove_zabar": remove,
|
|
206
|
+
"remove_zeer": remove,
|
|
207
|
+
"remove_pesh": remove,
|
|
208
|
+
"remove_tanween": remove,
|
|
209
|
+
"remove_shadda": remove,
|
|
210
|
+
"remove_sukun": remove,
|
|
211
|
+
"remove_other_marks": remove,
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def to_dict(self) -> dict:
|
|
216
|
+
return asdict(self)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _diacritic_chars(cfg: NormalizeConfig) -> str:
|
|
220
|
+
chars = ""
|
|
221
|
+
if cfg.remove_zabar:
|
|
222
|
+
chars += ZABAR
|
|
223
|
+
if cfg.remove_zeer:
|
|
224
|
+
chars += ZEER
|
|
225
|
+
if cfg.remove_pesh:
|
|
226
|
+
chars += PESH
|
|
227
|
+
if cfg.remove_tanween:
|
|
228
|
+
chars += TANWEEN
|
|
229
|
+
if cfg.remove_shadda:
|
|
230
|
+
chars += SHADDA
|
|
231
|
+
if cfg.remove_sukun:
|
|
232
|
+
chars += SUKUN
|
|
233
|
+
if cfg.remove_other_marks:
|
|
234
|
+
chars += OTHER_MARKS
|
|
235
|
+
return chars
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def strip_characters(text: str) -> str:
|
|
239
|
+
return text.translate(_TRANSLATOR)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def strip_combine_characters(text: str) -> str:
|
|
243
|
+
for key, value in COMBINE_URDU_CHARACTERS.items():
|
|
244
|
+
text = text.replace(key, value)
|
|
245
|
+
return text
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def strip_diacritics(text: str, chars: str | None = None) -> str:
|
|
249
|
+
"""Strip selected diacritics; default = all common اعراب."""
|
|
250
|
+
if chars is None:
|
|
251
|
+
chars = ZABAR + ZEER + PESH + TANWEEN + SHADDA + SUKUN + OTHER_MARKS
|
|
252
|
+
if not chars:
|
|
253
|
+
return text
|
|
254
|
+
return re.sub(f"[{re.escape(chars)}]", "", text)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def strip_punctuation(text: str) -> str:
|
|
258
|
+
table = str.maketrans("", "", ALL_PUNCTUATION)
|
|
259
|
+
return text.translate(table)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def strip_digits(text: str) -> str:
|
|
263
|
+
return re.sub(r"[0-9۰-۹٠-٩]", "", text)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def normalize_whitespace(text: str) -> str:
|
|
267
|
+
text = text.replace("\u00a0", " ").replace("\u200c", "")
|
|
268
|
+
text = text.replace("\u200e", "").replace("\u200f", "").replace("\ufeff", "")
|
|
269
|
+
return " ".join(text.split())
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# Aliases for helper usage
|
|
273
|
+
normalize_characters = strip_characters
|
|
274
|
+
normalize_combine_characters = strip_combine_characters
|
|
275
|
+
remove_diacritics = strip_diacritics
|
|
276
|
+
remove_punctuation = strip_punctuation
|
|
277
|
+
remove_digits = strip_digits
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def normalize_urdu(
|
|
281
|
+
text: str,
|
|
282
|
+
config: NormalizeConfig | None = None,
|
|
283
|
+
*,
|
|
284
|
+
# convenience kwargs override config fields when provided
|
|
285
|
+
enabled: bool | None = None,
|
|
286
|
+
taxonomy: bool | None = None,
|
|
287
|
+
remove_punctuation: bool | None = None,
|
|
288
|
+
remove_zabar: bool | None = None,
|
|
289
|
+
remove_zeer: bool | None = None,
|
|
290
|
+
remove_pesh: bool | None = None,
|
|
291
|
+
remove_diacritics_all: bool | None = None,
|
|
292
|
+
**overrides: bool,
|
|
293
|
+
) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Normalize one Urdu string according to ``NormalizeConfig``.
|
|
296
|
+
|
|
297
|
+
Pass either a ``config`` object or individual keyword flags.
|
|
298
|
+
``remove_diacritics_all=True/False`` toggles every اعراب flag at once.
|
|
299
|
+
"""
|
|
300
|
+
cfg = config or NormalizeConfig()
|
|
301
|
+
if overrides or any(
|
|
302
|
+
v is not None
|
|
303
|
+
for v in (
|
|
304
|
+
enabled,
|
|
305
|
+
taxonomy,
|
|
306
|
+
remove_punctuation,
|
|
307
|
+
remove_zabar,
|
|
308
|
+
remove_zeer,
|
|
309
|
+
remove_pesh,
|
|
310
|
+
remove_diacritics_all,
|
|
311
|
+
)
|
|
312
|
+
):
|
|
313
|
+
data = asdict(cfg)
|
|
314
|
+
if enabled is not None:
|
|
315
|
+
data["enabled"] = enabled
|
|
316
|
+
if taxonomy is not None:
|
|
317
|
+
data["taxonomy"] = taxonomy
|
|
318
|
+
if remove_punctuation is not None:
|
|
319
|
+
data["remove_punctuation"] = remove_punctuation
|
|
320
|
+
if remove_zabar is not None:
|
|
321
|
+
data["remove_zabar"] = remove_zabar
|
|
322
|
+
if remove_zeer is not None:
|
|
323
|
+
data["remove_zeer"] = remove_zeer
|
|
324
|
+
if remove_pesh is not None:
|
|
325
|
+
data["remove_pesh"] = remove_pesh
|
|
326
|
+
if remove_diacritics_all is not None:
|
|
327
|
+
for key in (
|
|
328
|
+
"remove_zabar",
|
|
329
|
+
"remove_zeer",
|
|
330
|
+
"remove_pesh",
|
|
331
|
+
"remove_tanween",
|
|
332
|
+
"remove_shadda",
|
|
333
|
+
"remove_sukun",
|
|
334
|
+
"remove_other_marks",
|
|
335
|
+
):
|
|
336
|
+
data[key] = remove_diacritics_all
|
|
337
|
+
valid = {f.name for f in fields(NormalizeConfig)}
|
|
338
|
+
for key, value in overrides.items():
|
|
339
|
+
if key in valid:
|
|
340
|
+
data[key] = value
|
|
341
|
+
cfg = NormalizeConfig(**data)
|
|
342
|
+
|
|
343
|
+
if text is None:
|
|
344
|
+
return ""
|
|
345
|
+
if not isinstance(text, str):
|
|
346
|
+
text = str(text)
|
|
347
|
+
if not cfg.enabled:
|
|
348
|
+
return text.strip()
|
|
349
|
+
|
|
350
|
+
if cfg.unicode_nfc:
|
|
351
|
+
text = unicodedata.normalize("NFC", text)
|
|
352
|
+
if cfg.whitespace:
|
|
353
|
+
text = normalize_whitespace(text)
|
|
354
|
+
if cfg.remove_tatweel:
|
|
355
|
+
text = text.replace("ـ", "")
|
|
356
|
+
|
|
357
|
+
marks = _diacritic_chars(cfg)
|
|
358
|
+
if marks:
|
|
359
|
+
text = strip_diacritics(text, marks)
|
|
360
|
+
|
|
361
|
+
if cfg.taxonomy:
|
|
362
|
+
text = strip_characters(text)
|
|
363
|
+
if cfg.combine_characters:
|
|
364
|
+
text = strip_combine_characters(text)
|
|
365
|
+
if cfg.remove_punctuation:
|
|
366
|
+
text = strip_punctuation(text)
|
|
367
|
+
if cfg.remove_digits:
|
|
368
|
+
text = strip_digits(text)
|
|
369
|
+
if cfg.whitespace:
|
|
370
|
+
text = normalize_whitespace(text)
|
|
371
|
+
return text
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def normalize_many(texts: Iterable[str], config: NormalizeConfig | None = None) -> list[str]:
|
|
375
|
+
cfg = config or NormalizeConfig()
|
|
376
|
+
return [normalize_urdu(t, cfg) for t in texts]
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: urdu-text-eval
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Urdu text evaluation & benchmarking library with configurable orthographic normalization (WER, CER, chrF, BLEU).
|
|
5
|
+
Author: urdu-text-eval
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/local/urdu-text-eval
|
|
8
|
+
Keywords: urdu,evaluation,benchmarking,wer,cer,chrf,bleu,nlp,ocr,asr,transliteration,text-evaluation
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: jiwer>=3.0.0
|
|
12
|
+
Requires-Dist: sacrebleu>=2.4.0
|
|
13
|
+
Requires-Dist: rapidfuzz>=3.0.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# urdu-text-eval
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/urdu-text-eval/)
|
|
20
|
+
[](https://opensource.org/licenses/MIT)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
|
|
23
|
+
**urdu-text-eval** is a Python benchmarking library for **Urdu text evaluation** (OCR, ASR, transliteration, machine translation, LLM text generation, and text processing).
|
|
24
|
+
|
|
25
|
+
It computes standard NLP metrics (**WER, CER, chrF, BLEU, Exact Match, Character Similarity**) with configurable **Urdu orthographic and taxonomy normalization** (handling Arabic lookalike codepoints, zabar/zeer/pesh diacritics, punctuation, numbers, and presentation forms).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Key Features
|
|
30
|
+
|
|
31
|
+
- **Standard Metrics**: Word Error Rate (WER), Character Error Rate (CER), Word/Char Accuracy, chrF, BLEU, Exact Match, and Levenshtein Character Similarity.
|
|
32
|
+
- **Urdu Taxonomy Normalization**: Standardizes Arabic variants (`ك` → `ک`, `ي` → `ی`, `ه` → `ہ`), presentation forms (`ﺍ`, `ﺎ`, `ﺐ`), and combined characters (`ا`+`ٓ` → `آ`).
|
|
33
|
+
- **Fine-Grained Diacritic Controls**: Selective removal of اعراب (**Zabar** `َ`, **Zeer** `ِ`, **Pesh** `ُ`, **Tanween** `ً ٌ ٍ`, **Shadda** `ّ`, **Sukun** `ْ`, Maddah, and Hamza marks).
|
|
34
|
+
- **Flexible Input Formats**: Evaluates simple list-of-dicts `[{"actual": "...", "pred": "..."}]` with support for common key aliases (`ref`, `target`, `gold`, `Urdu`, `hyp`, `prediction`, `output`).
|
|
35
|
+
- **Dual Reporting**: Always reports both **taxonomy-normalized** (primary) and **raw surface** metrics side-by-side.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Install via pip:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install urdu-text-eval
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
*(Dependencies: `jiwer`, `sacrebleu`, `rapidfuzz`)*
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from urdu_text_eval import evaluate
|
|
55
|
+
|
|
56
|
+
pairs = [
|
|
57
|
+
{"actual": "کیا یہ ہے؟", "pred": "كيا يہ ہے؟"},
|
|
58
|
+
{"actual": "آہ جو دل سے", "pred": "آه جو دل سے"},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
result = evaluate(pairs)
|
|
62
|
+
|
|
63
|
+
print(result["summary"])
|
|
64
|
+
print(f"WER: {result['wer']:.4f} | CER: {result['cer']:.4f} | chrF: {result['chrf']:.4f}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Output Structure
|
|
70
|
+
|
|
71
|
+
Calling `evaluate(pairs, per_sample=True)` returns a dictionary containing:
|
|
72
|
+
|
|
73
|
+
| Key | Type | Description |
|
|
74
|
+
|-----|------|-------------|
|
|
75
|
+
| `wer` | `float` | Primary Word Error Rate (0.0 = perfect match) |
|
|
76
|
+
| `cer` | `float` | Primary Character Error Rate |
|
|
77
|
+
| `word_accuracy` | `float` | `1.0 - wer` |
|
|
78
|
+
| `char_accuracy` | `float` | `1.0 - cer` |
|
|
79
|
+
| `normalized_exact_match` | `float` | Ratio of exact matches after normalization |
|
|
80
|
+
| `chrf` | `float` | Character n-gram F-score (sacrebleu) |
|
|
81
|
+
| `bleu` | `float` | Corpus BLEU score |
|
|
82
|
+
| `mean_char_similarity` | `float` | Mean normalized Levenshtein similarity |
|
|
83
|
+
| `raw_wer` / `raw_cer` | `float` | Metrics calculated on raw strings without normalization |
|
|
84
|
+
| `raw_exact_match` | `float` | Exact match ratio on raw strings |
|
|
85
|
+
| `summary` | `str` | Pre-formatted, printable evaluation report |
|
|
86
|
+
| `samples` | `list` | *(Optional, if `per_sample=True`)* List of dicts with per-row scores and normalized strings |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Normalization & Customization
|
|
91
|
+
|
|
92
|
+
Urdu text often varies in orthography (e.g. Arabic vs. Urdu keyboards, presence of diacritics/اعراب, punctuation). You can control normalization behavior precisely:
|
|
93
|
+
|
|
94
|
+
### 1. Master On/Off
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# Default: Normalization ON (Fair orthographic evaluation)
|
|
98
|
+
result = evaluate(pairs)
|
|
99
|
+
|
|
100
|
+
# Raw evaluation (No normalization applied)
|
|
101
|
+
result = evaluate(pairs, normalize=False)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Convenience Overrides
|
|
105
|
+
|
|
106
|
+
You can pass boolean flags directly to `evaluate()`:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
result = evaluate(
|
|
110
|
+
pairs,
|
|
111
|
+
taxonomy=True, # Convert Arabic codepoints (ك/ي/ه) to Urdu (ک/ی/ہ)
|
|
112
|
+
remove_diacritics=True, # Remove all اعراب (zabar, zeer, pesh, tanween, etc.)
|
|
113
|
+
remove_zabar=True, # Remove zabar (َ) only
|
|
114
|
+
remove_zeer=True, # Remove zeer (ِ) only
|
|
115
|
+
remove_pesh=True, # Remove pesh (ُ) only
|
|
116
|
+
remove_punctuation=True, # Remove Urdu (؛،؟۔٪) and ASCII punctuation
|
|
117
|
+
remove_digits=False, # Remove ASCII and Urdu digits
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Using `NormalizeConfig` or Presets
|
|
122
|
+
|
|
123
|
+
For reusability across benchmarks, configure a `NormalizeConfig`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from urdu_text_eval import evaluate, NormalizeConfig
|
|
127
|
+
|
|
128
|
+
# Presets
|
|
129
|
+
cfg_default = NormalizeConfig.default() # Taxonomy + diacritics + whitespace
|
|
130
|
+
cfg_raw = NormalizeConfig.none() # Raw string comparison
|
|
131
|
+
cfg_full = NormalizeConfig.full() # Everything on (including punctuation & digit stripping)
|
|
132
|
+
cfg_diac = NormalizeConfig.diacritics_only() # Only remove اعراب
|
|
133
|
+
cfg_punct = NormalizeConfig.punctuation_only() # Only remove punctuation
|
|
134
|
+
cfg_tax = NormalizeConfig.taxonomy_only() # Only taxonomy mapping
|
|
135
|
+
|
|
136
|
+
# Custom Configuration
|
|
137
|
+
cfg = NormalizeConfig(
|
|
138
|
+
enabled=True,
|
|
139
|
+
taxonomy=True, # Map Arabic/presentation characters
|
|
140
|
+
combine_characters=True, # Combine characters (ا + ٓ → آ)
|
|
141
|
+
remove_zabar=True, # Remove zabar
|
|
142
|
+
remove_zeer=True, # Remove zeer
|
|
143
|
+
remove_pesh=True, # Remove pesh
|
|
144
|
+
remove_tanween=True, # Remove tanween
|
|
145
|
+
remove_shadda=True, # Remove shadda
|
|
146
|
+
remove_sukun=True, # Remove sukun
|
|
147
|
+
remove_punctuation=False, # Keep punctuation
|
|
148
|
+
remove_digits=False, # Keep digits
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
result = evaluate(pairs, config=cfg)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Standalone Normalizer Function
|
|
157
|
+
|
|
158
|
+
You can also use the Urdu normalizer directly on individual text strings:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from urdu_text_eval import normalize_urdu, NormalizeConfig
|
|
162
|
+
|
|
163
|
+
# Default normalization
|
|
164
|
+
clean_text = normalize_urdu("كيا يہ ہے؟")
|
|
165
|
+
# Output: "کیا یہ ہے؟"
|
|
166
|
+
|
|
167
|
+
# Remove diacritics (zabar/zeer/pesh) only
|
|
168
|
+
clean_text = normalize_urdu("شیرِ پنجاب", remove_diacritics_all=True)
|
|
169
|
+
# Output: "شیر پنجاب"
|
|
170
|
+
|
|
171
|
+
# Punctuation removal
|
|
172
|
+
clean_text = normalize_urdu("سلام، دنیا!", config=NormalizeConfig.punctuation_only())
|
|
173
|
+
# Output: "سلام دنیا"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## API Summary
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from urdu_text_eval import (
|
|
182
|
+
evaluate, # Core benchmark function: evaluate([{"actual": "...", "pred": "..."}])
|
|
183
|
+
NormalizeConfig, # Configuration dataclass & presets for text normalization
|
|
184
|
+
normalize_urdu, # Single-string Urdu normalization function
|
|
185
|
+
compute_metrics, # Lower-level function: compute_metrics(references, hypotheses)
|
|
186
|
+
format_metrics, # Formats metrics dict into a human-readable text summary
|
|
187
|
+
per_sample_errors, # Returns list of per-item error breakdown dicts
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
Distributed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/tranlit_eval/py.typed
|
|
4
|
+
src/urdu_text_eval/__init__.py
|
|
5
|
+
src/urdu_text_eval/evaluate.py
|
|
6
|
+
src/urdu_text_eval/metrics.py
|
|
7
|
+
src/urdu_text_eval/normalize.py
|
|
8
|
+
src/urdu_text_eval/py.typed
|
|
9
|
+
src/urdu_text_eval.egg-info/PKG-INFO
|
|
10
|
+
src/urdu_text_eval.egg-info/SOURCES.txt
|
|
11
|
+
src/urdu_text_eval.egg-info/dependency_links.txt
|
|
12
|
+
src/urdu_text_eval.egg-info/requires.txt
|
|
13
|
+
src/urdu_text_eval.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|