HarshithaaaaReddddyyy commited on
Commit
05e0e08
·
verified ·
1 Parent(s): c0a3cf8

Delete preprocessor.py

Browse files
Files changed (1) hide show
  1. preprocessor.py +0 -268
preprocessor.py DELETED
@@ -1,268 +0,0 @@
1
- """
2
- Advanced Text Preprocessing for Multilingual Indic Text
3
- Handles Unicode normalization, noise removal, script detection preprocessing.
4
- """
5
- from __future__ import annotations
6
-
7
- import re
8
- import unicodedata
9
- from dataclasses import dataclass
10
- from typing import Dict, List, Optional, Tuple
11
-
12
- import numpy as np
13
- from loguru import logger
14
-
15
- from src.utils.languages import SCRIPT_UNICODE_BLOCKS, LANGUAGE_REGISTRY
16
-
17
-
18
- # ─────────────────────────────────────────────
19
- # Unicode Script Detection
20
- # ─────────────────────────────────────────────
21
-
22
- def get_char_script(char: str) -> str:
23
- """Get Unicode script name for a single character."""
24
- try:
25
- cat = unicodedata.category(char)
26
- name = unicodedata.name(char, "")
27
- # Extract script from Unicode name (e.g. "DEVANAGARI LETTER A" → "Devanagari")
28
- parts = name.split()
29
- if not parts:
30
- return "Unknown"
31
- first = parts[0].capitalize()
32
- # Multi-word scripts
33
- for script in SCRIPT_UNICODE_BLOCKS:
34
- if script.upper() in name.upper():
35
- return script
36
- return first
37
- except (ValueError, TypeError):
38
- return "Unknown"
39
-
40
-
41
- def get_dominant_script(text: str) -> Tuple[str, Dict[str, float]]:
42
- """
43
- Detect the dominant script in text.
44
- Returns (dominant_script, script_distribution).
45
- """
46
- script_counts: Dict[str, int] = {}
47
- total_alpha = 0
48
-
49
- for char in text:
50
- if not char.isalpha():
51
- continue
52
- total_alpha += 1
53
- cp = ord(char)
54
-
55
- matched = False
56
- for script, ranges in SCRIPT_UNICODE_BLOCKS.items():
57
- for lo, hi in ranges:
58
- if lo <= cp <= hi:
59
- script_counts[script] = script_counts.get(script, 0) + 1
60
- matched = True
61
- break
62
- if matched:
63
- break
64
- if not matched:
65
- script_counts["Other"] = script_counts.get("Other", 0) + 1
66
-
67
- if total_alpha == 0:
68
- return "Unknown", {}
69
-
70
- dist = {s: c / total_alpha for s, c in script_counts.items()}
71
- dominant = max(dist, key=dist.get) if dist else "Unknown"
72
- return dominant, dist
73
-
74
-
75
- # ─────────────────────────────────────────────
76
- # Preprocessor Config
77
- # ─────────────────────────────────────────────
78
-
79
- @dataclass
80
- class PreprocessorConfig:
81
- normalize_unicode: bool = True
82
- nfc_form: str = "NFC"
83
- remove_urls: bool = True
84
- remove_emails: bool = True
85
- remove_html_tags: bool = True
86
- normalize_whitespace: bool = True
87
- remove_zero_width: bool = True
88
- handle_mixed_numerals: bool = True
89
- preserve_script_punctuation: bool = True
90
- lowercase_latin: bool = False
91
- max_length: Optional[int] = 2000
92
- min_length: int = 10
93
-
94
-
95
- # ─────────────────────────────────────────────
96
- # Main Preprocessor
97
- # ─────────────────────────────────────────────
98
-
99
- class IndicTextPreprocessor:
100
- """
101
- Comprehensive text preprocessor for Indic multilingual corpora.
102
- Handles Unicode normalization, noise removal, and script-aware cleaning.
103
- """
104
-
105
- # Regex patterns
106
- _URL_RE = re.compile(
107
- r"https?://\S+|www\.\S+|ftp://\S+", re.UNICODE
108
- )
109
- _EMAIL_RE = re.compile(
110
- r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
111
- )
112
- _HTML_RE = re.compile(r"<[^>]+>")
113
- _WHITESPACE_RE = re.compile(r"\s+", re.UNICODE)
114
- _ZERO_WIDTH_RE = re.compile(
115
- r"[\u200b\u200c\u200d\u200e\u200f\u202a-\u202e\ufeff\u00ad]"
116
- )
117
- _MULTI_PUNCT_RE = re.compile(r"([!?।॥,;.]){2,}")
118
-
119
- # Indic numeral mappings to ASCII (for normalization)
120
- _NUMERAL_MAP = str.maketrans(
121
- "".join([
122
- "०१२३४५६७८९", # Devanagari
123
- "০১২৩৪৫৬৭৮৯", # Bengali
124
- "੦੧੨੩੪੫੬੭੮੯", # Gurmukhi
125
- "૦૧૨૩૪૫૬૭૮૯", # Gujarati
126
- "୦୧୨୩୪୫୬୭୮୯", # Odia
127
- "௦௧௨௩௪௫௬௭௮௯", # Tamil
128
- "౦౧౨౩౪౫౬౭౮౯", # Telugu
129
- "೦೧೨೩೪೫೬೭೮೯", # Kannada
130
- "൦൧൨൩൪൫൬൭൮൯", # Malayalam
131
- ]),
132
- "0123456789" * 9
133
- )
134
-
135
- def __init__(self, config: Optional[PreprocessorConfig] = None):
136
- self.config = config or PreprocessorConfig()
137
-
138
- def preprocess(self, text: str) -> str:
139
- """Full preprocessing pipeline."""
140
- if not text or not isinstance(text, str):
141
- return ""
142
-
143
- cfg = self.config
144
-
145
- # 1. Unicode normalization
146
- if cfg.normalize_unicode:
147
- text = unicodedata.normalize(cfg.nfc_form, text)
148
-
149
- # 2. Remove zero-width and invisible characters
150
- if cfg.remove_zero_width:
151
- text = self._ZERO_WIDTH_RE.sub("", text)
152
-
153
- # 3. Remove HTML tags
154
- if cfg.remove_html_tags:
155
- text = self._HTML_RE.sub(" ", text)
156
-
157
- # 4. Remove URLs
158
- if cfg.remove_urls:
159
- text = self._URL_RE.sub(" ", text)
160
-
161
- # 5. Remove emails
162
- if cfg.remove_emails:
163
- text = self._EMAIL_RE.sub(" ", text)
164
-
165
- # 6. Numeral normalization (optional)
166
- if cfg.handle_mixed_numerals:
167
- text = text.translate(self._NUMERAL_MAP)
168
-
169
- # 7. Collapse multiple punctuation
170
- text = self._MULTI_PUNCT_RE.sub(r"\1", text)
171
-
172
- # 8. Normalize whitespace
173
- if cfg.normalize_whitespace:
174
- text = self._WHITESPACE_RE.sub(" ", text).strip()
175
-
176
- # 9. Lowercase Latin (optional, e.g. for English)
177
- if cfg.lowercase_latin:
178
- text = self._selective_lowercase(text)
179
-
180
- # 10. Length truncation
181
- if cfg.max_length and len(text) > cfg.max_length:
182
- text = text[:cfg.max_length]
183
-
184
- return text
185
-
186
- def _selective_lowercase(self, text: str) -> str:
187
- """Lowercase only Latin characters, preserve others."""
188
- result = []
189
- for ch in text:
190
- cp = ord(ch)
191
- # Latin Basic + Extended
192
- if 0x0041 <= cp <= 0x005A or 0x00C0 <= cp <= 0x00D6:
193
- result.append(ch.lower())
194
- else:
195
- result.append(ch)
196
- return "".join(result)
197
-
198
- def batch_preprocess(
199
- self, texts: List[str], show_progress: bool = True
200
- ) -> List[str]:
201
- """Preprocess a batch of texts."""
202
- if show_progress:
203
- from tqdm.auto import tqdm
204
- return [self.preprocess(t) for t in tqdm(texts, desc="Preprocessing")]
205
- return [self.preprocess(t) for t in texts]
206
-
207
- def quality_score(self, text: str) -> float:
208
- """
209
- Returns a text quality score in [0, 1].
210
- Considers: alpha ratio, script consistency, length adequacy.
211
- """
212
- if not text:
213
- return 0.0
214
-
215
- alpha = sum(c.isalpha() for c in text)
216
- total = len(text)
217
- if total == 0:
218
- return 0.0
219
-
220
- alpha_ratio = alpha / total
221
-
222
- _, script_dist = get_dominant_script(text)
223
- script_concentration = max(script_dist.values()) if script_dist else 0.0
224
-
225
- length_score = min(1.0, len(text) / 100)
226
-
227
- return 0.3 * alpha_ratio + 0.4 * script_concentration + 0.3 * length_score
228
-
229
- def compute_text_features(self, text: str) -> Dict:
230
- """Rich feature set for a single text (used in analysis)."""
231
- dominant, dist = get_dominant_script(text)
232
- n_chars = len(text)
233
- n_alpha = sum(c.isalpha() for c in text)
234
- n_digits = sum(c.isdigit() for c in text)
235
- n_punct = sum(unicodedata.category(c).startswith("P") for c in text)
236
- n_spaces = sum(c.isspace() for c in text)
237
- unique_chars = len(set(text))
238
- type_token_ratio = unique_chars / max(n_chars, 1)
239
-
240
- # Character entropy
241
- from collections import Counter as _Counter
242
- char_counts = _Counter(text)
243
- total_f = sum(char_counts.values())
244
- char_entropy = -sum(
245
- (c / total_f) * np.log2(c / total_f)
246
- for c in char_counts.values() if c > 0
247
- ) if total_f > 0 else 0.0
248
-
249
- return {
250
- "n_chars": n_chars,
251
- "n_alpha": n_alpha,
252
- "n_digits": n_digits,
253
- "n_punct": n_punct,
254
- "n_spaces": n_spaces,
255
- "alpha_ratio": n_alpha / max(n_chars, 1),
256
- "digit_ratio": n_digits / max(n_chars, 1),
257
- "unique_char_ratio": type_token_ratio,
258
- "dominant_script": dominant,
259
- "script_distribution": dist,
260
- "script_count": len(dist),
261
- "is_mixed_script": len(dist) > 1 and max(dist.values()) < 0.95,
262
- "quality_score": self.quality_score(text),
263
- "n_words": len(text.split()),
264
- "avg_word_len": (
265
- np.mean([len(w) for w in text.split()]) if text.split() else 0
266
- ),
267
- "char_entropy": float(char_entropy),
268
- }