Spaces:
Running on Zero
Running on Zero
Languages increased from 21 to 36
Browse files
app.py
CHANGED
|
@@ -14,23 +14,38 @@ MAX_MODELS_LOADED = 5
|
|
| 14 |
MODEL_IDLE_TIMEOUT = 15 * 60 # 15 minutes
|
| 15 |
CLEANUP_INTERVAL = 15 * 60 # check every 15 minutes
|
| 16 |
|
| 17 |
-
#
|
| 18 |
MODELS = {
|
| 19 |
"Assamese": "prachuryyaIITG/CLASSER_Assamese_MuRIL",
|
| 20 |
"Bengali": "prachuryyaIITG/MultiCoNER2_Bengali_XLM",
|
|
|
|
|
|
|
| 21 |
"Bodo": "prachuryyaIITG/CLASSER_Bodo_MuRIL",
|
|
|
|
| 22 |
"Chinese": "prachuryyaIITG/MultiCoNER2_Chinese_XLM",
|
|
|
|
| 23 |
"English": "prachuryyaIITG/MultiCoNER2_English_XLM",
|
| 24 |
"Farsi": "prachuryyaIITG/MultiCoNER2_Farsi_XLM",
|
| 25 |
"French": "prachuryyaIITG/MultiCoNER2_French_XLM",
|
| 26 |
"German": "prachuryyaIITG/MultiCoNER2_German_XLM",
|
|
|
|
| 27 |
"Hindi": "prachuryyaIITG/MultiCoNER2_Hindi_XLM",
|
| 28 |
"Italian": "prachuryyaIITG/MultiCoNER2_Italian_XLM",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
"Marathi": "prachuryyaIITG/CLASSER_Marathi_MuRIL",
|
| 30 |
"Mizo": "prachuryyaIITG/FiNERVINER_Mizo_XLM",
|
| 31 |
"Nepali": "prachuryyaIITG/CLASSER_Nepali_MuRIL",
|
|
|
|
| 32 |
"Portuguese": "prachuryyaIITG/MultiCoNER2_Portuguese_XLM",
|
|
|
|
| 33 |
"Sanskrit": "prachuryyaIITG/CLASSER_Sanskrit_MuRIL",
|
|
|
|
|
|
|
| 34 |
"Spanish": "prachuryyaIITG/MultiCoNER2_Spanish_XLM",
|
| 35 |
"Swedish": "prachuryyaIITG/MultiCoNER2_Swedish_XLM",
|
| 36 |
"Tamil": "prachuryyaIITG/APTFiNER_Tamil_MuRIL",
|
|
@@ -39,28 +54,61 @@ MODELS = {
|
|
| 39 |
"Urdu": "prachuryyaIITG/Urdu_CLASSER_XLM",
|
| 40 |
}
|
| 41 |
|
| 42 |
-
#
|
| 43 |
TAG_TO_COARSE = {
|
| 44 |
-
#
|
|
|
|
| 45 |
"Scientist": "PERSON", "Artist": "PERSON", "Athlete": "PERSON",
|
| 46 |
"Politician": "PERSON", "Cleric": "PERSON", "SportsManager": "PERSON",
|
| 47 |
"OtherPER": "PERSON", "PER": "PERSON", "Person": "PERSON",
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
"Facility": "LOCATION", "OtherLOC": "LOCATION",
|
| 51 |
"HumanSettlement": "LOCATION", "Station": "LOCATION",
|
| 52 |
"LOC": "LOCATION", "Location": "LOCATION",
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
"MusicalGRP": "ORGANIZATION", "PublicCORP": "ORGANIZATION",
|
| 56 |
"PrivateCORP": "ORGANIZATION", "AerospaceManufacturer": "ORGANIZATION",
|
| 57 |
"SportsGRP": "ORGANIZATION", "CarManufacturer": "ORGANIZATION",
|
| 58 |
"ORG": "ORGANIZATION", "GRP": "ORGANIZATION", "Organization": "ORGANIZATION",
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
"Medication/Vaccine": "MEDICAL", "MedicalProcedure": "MEDICAL",
|
| 62 |
"AnatomicalStructure": "MEDICAL", "Symptom": "MEDICAL",
|
| 63 |
-
"Disease": "MEDICAL", "MED": "MEDICAL", "Medical": "MEDICAL"
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
|
| 66 |
# Cache and locking
|
|
@@ -142,12 +190,9 @@ def cpu_fallback_infer(text, language):
|
|
| 142 |
return ner(text, stride=64)
|
| 143 |
|
| 144 |
# --- PUNCTUATION & SPAN CLEANUP HELPER ---
|
| 145 |
-
PUNCT_PATTERN = r'^[\s\.,!?;:"\'\(\)\[\]\{\}।॥،؟’”…—]+|[\s\.,!?;:"\'\(\)\[\]\{\}।॥،؟’”…—]+$'
|
| 146 |
-
|
| 147 |
def clean_span_boundaries(text, start, end):
|
| 148 |
"""
|
| 149 |
Trims leading and trailing punctuation/whitespace from span character offsets.
|
| 150 |
-
Prevents punctuation attached to words (e.g. 'Real Madrid.') from being included in the entity.
|
| 151 |
"""
|
| 152 |
val = text[start:end]
|
| 153 |
|
|
@@ -175,7 +220,7 @@ def extract_regex_spans(text):
|
|
| 175 |
spans.append({'start': m.start(), 'end': m.end(), 'category': 'EMAIL', 'text': m.group()})
|
| 176 |
|
| 177 |
# Script-Aware Phone Numbers
|
| 178 |
-
digits = r'0-9\u0966-\u096F\u09E6-\u09EF\u0660-\u0669\u06F0-\u06F9\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\uFF10-\uFF19'
|
| 179 |
phone_pattern = rf'(?:\+?[' + digits + r']{1,3}[-.\s]?)?\(?[' + digits + r']{2,4}\)?[-.\s]?[' + digits + r']{3,4}[-.\s]?[' + digits + r']{3,4}\b'
|
| 180 |
for m in re.finditer(phone_pattern, text):
|
| 181 |
if len(re.sub(rf'[^{digits}]', '', m.group())) >= 7:
|
|
@@ -208,10 +253,19 @@ def process_pii_anonymization(text, language):
|
|
| 208 |
ner_spans = []
|
| 209 |
for res in raw_ner_results:
|
| 210 |
entity_type = res.get('entity_group', res.get('entity', ''))
|
| 211 |
-
|
|
|
|
| 212 |
|
|
|
|
|
|
|
| 213 |
if entity_clean in TAG_TO_COARSE:
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
start_pos = int(res['start'])
|
| 216 |
end_pos = int(res['end'])
|
| 217 |
|
|
@@ -222,7 +276,7 @@ def process_pii_anonymization(text, language):
|
|
| 222 |
ner_spans.append({
|
| 223 |
'start': start_pos,
|
| 224 |
'end': end_pos,
|
| 225 |
-
'category':
|
| 226 |
'text': clean_val
|
| 227 |
})
|
| 228 |
|
|
@@ -290,7 +344,7 @@ body, .gradio-container {
|
|
| 290 |
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
| 291 |
|
| 292 |
gr.Markdown("# Multilingual PII Anonymizer & Synthetic Pseudonymizer")
|
| 293 |
-
gr.Markdown("Anonymize PII (**PERSON**, **LOCATION**, **ORGANIZATION**, **MEDICAL**, Emails, Phones, IPs, Credit Cards) across **
|
| 294 |
|
| 295 |
with gr.Row():
|
| 296 |
lang_dropdown = gr.Dropdown(
|
|
@@ -341,6 +395,8 @@ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
|
| 341 |
["Albert Einstein wurde in Ulm geboren. Er litt an Diabetes.", "German"],
|
| 342 |
["مرزا غالب دہلی میں رہتے تھے۔", "Urdu"],
|
| 343 |
["Victor Hugo est né à Besançon. Appelez le +33-1-4268-5300.", "French"],
|
|
|
|
|
|
|
| 344 |
],
|
| 345 |
inputs=[input_text, lang_dropdown],
|
| 346 |
outputs=[sanitized_output, mapping_json],
|
|
|
|
| 14 |
MODEL_IDLE_TIMEOUT = 15 * 60 # 15 minutes
|
| 15 |
CLEANUP_INTERVAL = 15 * 60 # check every 15 minutes
|
| 16 |
|
| 17 |
+
# Complete list: 36 target languages
|
| 18 |
MODELS = {
|
| 19 |
"Assamese": "prachuryyaIITG/CLASSER_Assamese_MuRIL",
|
| 20 |
"Bengali": "prachuryyaIITG/MultiCoNER2_Bengali_XLM",
|
| 21 |
+
"Bhojpuri": "prachuryyaIITG/FiNE-MiBBiC_Bhojpuri_MuRIL",
|
| 22 |
+
"Bishnupriya": "prachuryyaIITG/FiNE-MiBBiC_Bishnupriya_MuRIL",
|
| 23 |
"Bodo": "prachuryyaIITG/CLASSER_Bodo_MuRIL",
|
| 24 |
+
"Chhattisgarhi": "prachuryyaIITG/FiNE-MiBBiC_Chhattisgarhi_MuRIL",
|
| 25 |
"Chinese": "prachuryyaIITG/MultiCoNER2_Chinese_XLM",
|
| 26 |
+
"Dogri": "prachuryyaIITG/SampurNER_Dogri_IndicBERTv2",
|
| 27 |
"English": "prachuryyaIITG/MultiCoNER2_English_XLM",
|
| 28 |
"Farsi": "prachuryyaIITG/MultiCoNER2_Farsi_XLM",
|
| 29 |
"French": "prachuryyaIITG/MultiCoNER2_French_XLM",
|
| 30 |
"German": "prachuryyaIITG/MultiCoNER2_German_XLM",
|
| 31 |
+
"Gujarati": "prachuryyaIITG/SampurNER_Gujarati_IndicBERTv2",
|
| 32 |
"Hindi": "prachuryyaIITG/MultiCoNER2_Hindi_XLM",
|
| 33 |
"Italian": "prachuryyaIITG/MultiCoNER2_Italian_XLM",
|
| 34 |
+
"Kannada": "prachuryyaIITG/SampurNER_Kannada_IndicBERTv2",
|
| 35 |
+
"Kashmiri": "prachuryyaIITG/SampurNER_Kashmiri_IndicBERTv2",
|
| 36 |
+
"Konkani": "prachuryyaIITG/SampurNER_Konkani_IndicBERTv2",
|
| 37 |
+
"Maithili": "prachuryyaIITG/SampurNER_Maithili_IndicBERTv2",
|
| 38 |
+
"Malayalam": "prachuryyaIITG/SampurNER_Malayalam_IndicBERTv2",
|
| 39 |
+
"Manipuri": "prachuryyaIITG/FiNERVINER_Manipuri_IndicBERTv2",
|
| 40 |
"Marathi": "prachuryyaIITG/CLASSER_Marathi_MuRIL",
|
| 41 |
"Mizo": "prachuryyaIITG/FiNERVINER_Mizo_XLM",
|
| 42 |
"Nepali": "prachuryyaIITG/CLASSER_Nepali_MuRIL",
|
| 43 |
+
"Odia": "prachuryyaIITG/SampurNER_Odia_IndicBERTv2",
|
| 44 |
"Portuguese": "prachuryyaIITG/MultiCoNER2_Portuguese_XLM",
|
| 45 |
+
"Punjabi": "prachuryyaIITG/SampurNER_Punjabi_IndicBERTv2",
|
| 46 |
"Sanskrit": "prachuryyaIITG/CLASSER_Sanskrit_MuRIL",
|
| 47 |
+
"Santali": "prachuryyaIITG/SampurNER_Santali_IndicBERTv2",
|
| 48 |
+
"Sindhi": "prachuryyaIITG/SampurNER_Sindhi_IndicBERTv2",
|
| 49 |
"Spanish": "prachuryyaIITG/MultiCoNER2_Spanish_XLM",
|
| 50 |
"Swedish": "prachuryyaIITG/MultiCoNER2_Swedish_XLM",
|
| 51 |
"Tamil": "prachuryyaIITG/APTFiNER_Tamil_MuRIL",
|
|
|
|
| 54 |
"Urdu": "prachuryyaIITG/Urdu_CLASSER_XLM",
|
| 55 |
}
|
| 56 |
|
| 57 |
+
# Unified mapping for MultiCoNER2, CLASSER, and FewNERD / SampurNER taxonomies
|
| 58 |
TAG_TO_COARSE = {
|
| 59 |
+
# --- PERSON ---
|
| 60 |
+
# MultiCoNER2 / CLASSER / FiNERVINER / APTFiNER
|
| 61 |
"Scientist": "PERSON", "Artist": "PERSON", "Athlete": "PERSON",
|
| 62 |
"Politician": "PERSON", "Cleric": "PERSON", "SportsManager": "PERSON",
|
| 63 |
"OtherPER": "PERSON", "PER": "PERSON", "Person": "PERSON",
|
| 64 |
+
# SampurNER (person-*)
|
| 65 |
+
"Actor": "PERSON", "Artist/Author": "PERSON", "Director": "PERSON",
|
| 66 |
+
"Scholar": "PERSON", "Soldier": "PERSON", "person-actor": "PERSON",
|
| 67 |
+
"person-artist/author": "PERSON", "person-athlete": "PERSON",
|
| 68 |
+
"person-director": "PERSON", "person-other": "PERSON",
|
| 69 |
+
"person-politician": "PERSON", "person-scholar": "PERSON",
|
| 70 |
+
"person-soldier": "PERSON",
|
| 71 |
+
|
| 72 |
+
# --- LOCATION & FACILITIES ---
|
| 73 |
+
# MultiCoNER2 / CLASSER
|
| 74 |
"Facility": "LOCATION", "OtherLOC": "LOCATION",
|
| 75 |
"HumanSettlement": "LOCATION", "Station": "LOCATION",
|
| 76 |
"LOC": "LOCATION", "Location": "LOCATION",
|
| 77 |
+
# FewNERD / SampurNER (location-* & building-*)
|
| 78 |
+
"GPE": "LOCATION", "Body of Water": "LOCATION", "Island": "LOCATION",
|
| 79 |
+
"Mountain": "LOCATION", "Park": "LOCATION", "Road/Transit": "LOCATION",
|
| 80 |
+
"Airport": "LOCATION", "Hospital": "LOCATION", "Hotel": "LOCATION",
|
| 81 |
+
"Library": "LOCATION", "Restaurant": "LOCATION", "Sports Facility": "LOCATION",
|
| 82 |
+
"Theater": "LOCATION", "location-GPE": "LOCATION", "location-bodiesofwater": "LOCATION",
|
| 83 |
+
"location-island": "LOCATION", "location-mountain": "LOCATION", "location-other": "LOCATION",
|
| 84 |
+
"location-park": "LOCATION", "location-road/railway/highway/transit": "LOCATION",
|
| 85 |
+
"building-airport": "LOCATION", "building-hospital": "LOCATION", "building-hotel": "LOCATION",
|
| 86 |
+
"building-library": "LOCATION", "building-other": "LOCATION", "building-restaurant": "LOCATION",
|
| 87 |
+
"building-sportsfacility": "LOCATION", "building-theater": "LOCATION",
|
| 88 |
+
|
| 89 |
+
# --- ORGANIZATION ---
|
| 90 |
+
# MultiCoNER2 / CLASSER
|
| 91 |
"MusicalGRP": "ORGANIZATION", "PublicCORP": "ORGANIZATION",
|
| 92 |
"PrivateCORP": "ORGANIZATION", "AerospaceManufacturer": "ORGANIZATION",
|
| 93 |
"SportsGRP": "ORGANIZATION", "CarManufacturer": "ORGANIZATION",
|
| 94 |
"ORG": "ORGANIZATION", "GRP": "ORGANIZATION", "Organization": "ORGANIZATION",
|
| 95 |
+
# FewNERD / SampurNER (organization-*)
|
| 96 |
+
"Company": "ORGANIZATION", "Education": "ORGANIZATION", "Government": "ORGANIZATION",
|
| 97 |
+
"Media": "ORGANIZATION", "Political Party": "ORGANIZATION", "Religion": "ORGANIZATION",
|
| 98 |
+
"Sports League": "ORGANIZATION", "Show Organization": "ORGANIZATION",
|
| 99 |
+
"organization-company": "ORGANIZATION", "organization-education": "ORGANIZATION",
|
| 100 |
+
"organization-government/governmentagency": "ORGANIZATION", "organization-media/newspaper": "ORGANIZATION",
|
| 101 |
+
"organization-other": "ORGANIZATION", "organization-politicalparty": "ORGANIZATION",
|
| 102 |
+
"organization-religion": "ORGANIZATION", "organization-showorganization": "ORGANIZATION",
|
| 103 |
+
"organization-sportsleague": "ORGANIZATION", "organization-sportsteam": "ORGANIZATION",
|
| 104 |
+
|
| 105 |
+
# --- MEDICAL ---
|
| 106 |
+
# MultiCoNER2 / CLASSER
|
| 107 |
"Medication/Vaccine": "MEDICAL", "MedicalProcedure": "MEDICAL",
|
| 108 |
"AnatomicalStructure": "MEDICAL", "Symptom": "MEDICAL",
|
| 109 |
+
"Disease": "MEDICAL", "MED": "MEDICAL", "Medical": "MEDICAL",
|
| 110 |
+
# FewNERD / SampurNER (misc-*)
|
| 111 |
+
"misc-disease": "MEDICAL", "misc-medical": "MEDICAL", "misc-biology": "MEDICAL"
|
| 112 |
}
|
| 113 |
|
| 114 |
# Cache and locking
|
|
|
|
| 190 |
return ner(text, stride=64)
|
| 191 |
|
| 192 |
# --- PUNCTUATION & SPAN CLEANUP HELPER ---
|
|
|
|
|
|
|
| 193 |
def clean_span_boundaries(text, start, end):
|
| 194 |
"""
|
| 195 |
Trims leading and trailing punctuation/whitespace from span character offsets.
|
|
|
|
| 196 |
"""
|
| 197 |
val = text[start:end]
|
| 198 |
|
|
|
|
| 220 |
spans.append({'start': m.start(), 'end': m.end(), 'category': 'EMAIL', 'text': m.group()})
|
| 221 |
|
| 222 |
# Script-Aware Phone Numbers
|
| 223 |
+
digits = r'0-9\u0966-\u096F\u09E6-\u09EF\u0660-\u0669\u06F0-\u06F9\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0A66-\u0A6F\u0AE6-\u0AEF\u0CDE-\u0CEF\u0D66-\u0D6F\uFF10-\uFF19'
|
| 224 |
phone_pattern = rf'(?:\+?[' + digits + r']{1,3}[-.\s]?)?\(?[' + digits + r']{2,4}\)?[-.\s]?[' + digits + r']{3,4}[-.\s]?[' + digits + r']{3,4}\b'
|
| 225 |
for m in re.finditer(phone_pattern, text):
|
| 226 |
if len(re.sub(rf'[^{digits}]', '', m.group())) >= 7:
|
|
|
|
| 253 |
ner_spans = []
|
| 254 |
for res in raw_ner_results:
|
| 255 |
entity_type = res.get('entity_group', res.get('entity', ''))
|
| 256 |
+
# Normalize entity tag string (handles B-, I-, sub-types)
|
| 257 |
+
entity_clean = entity_type.replace("B-", "").replace("I-", "")
|
| 258 |
|
| 259 |
+
# Check both full clean tag and prefix split
|
| 260 |
+
matched_cat = None
|
| 261 |
if entity_clean in TAG_TO_COARSE:
|
| 262 |
+
matched_cat = TAG_TO_COARSE[entity_clean]
|
| 263 |
+
elif entity_clean.split("_")[0] in TAG_TO_COARSE:
|
| 264 |
+
matched_cat = TAG_TO_COARSE[entity_clean.split("_")[0]]
|
| 265 |
+
elif entity_clean.split("-")[0] in TAG_TO_COARSE:
|
| 266 |
+
matched_cat = TAG_TO_COARSE[entity_clean.split("-")[0]]
|
| 267 |
+
|
| 268 |
+
if matched_cat:
|
| 269 |
start_pos = int(res['start'])
|
| 270 |
end_pos = int(res['end'])
|
| 271 |
|
|
|
|
| 276 |
ner_spans.append({
|
| 277 |
'start': start_pos,
|
| 278 |
'end': end_pos,
|
| 279 |
+
'category': matched_cat,
|
| 280 |
'text': clean_val
|
| 281 |
})
|
| 282 |
|
|
|
|
| 344 |
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
|
| 345 |
|
| 346 |
gr.Markdown("# Multilingual PII Anonymizer & Synthetic Pseudonymizer")
|
| 347 |
+
gr.Markdown("Anonymize sensitive PII (**PERSON**, **LOCATION**, **ORGANIZATION**, **MEDICAL**, Emails, Phones, IPs, Credit Cards) across **36 languages** into synthetic placeholders.")
|
| 348 |
|
| 349 |
with gr.Row():
|
| 350 |
lang_dropdown = gr.Dropdown(
|
|
|
|
| 395 |
["Albert Einstein wurde in Ulm geboren. Er litt an Diabetes.", "German"],
|
| 396 |
["مرزا غالب دہلی میں رہتے تھے۔", "Urdu"],
|
| 397 |
["Victor Hugo est né à Besançon. Appelez le +33-1-4268-5300.", "French"],
|
| 398 |
+
["ਰਵਿੰਦਰਨਾਥ ਟੈਗੋਰ ਕਲਕੱਤੇ ਵਿੱਚ ਰਹਿੰਦੇ ਸਨ।", "Punjabi"],
|
| 399 |
+
["ಶಿವರಾಮ ಕಾರಂತರು ಪುತ್ತೂರಿನಲ್ಲಿ ಜನಿಸಿದರು.", "Kannada"],
|
| 400 |
],
|
| 401 |
inputs=[input_text, lang_dropdown],
|
| 402 |
outputs=[sanitized_output, mapping_json],
|