prachuryyaIITG commited on
Commit
2205747
·
verified ·
1 Parent(s): 7aaabc8

Languages increased from 21 to 36

Browse files
Files changed (1) hide show
  1. app.py +74 -18
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
- # Trimmed list: 21 target languages
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
- # Fine-grained tag mappings into coarse categories
43
  TAG_TO_COARSE = {
44
- # Person
 
45
  "Scientist": "PERSON", "Artist": "PERSON", "Athlete": "PERSON",
46
  "Politician": "PERSON", "Cleric": "PERSON", "SportsManager": "PERSON",
47
  "OtherPER": "PERSON", "PER": "PERSON", "Person": "PERSON",
48
-
49
- # Location
 
 
 
 
 
 
 
 
50
  "Facility": "LOCATION", "OtherLOC": "LOCATION",
51
  "HumanSettlement": "LOCATION", "Station": "LOCATION",
52
  "LOC": "LOCATION", "Location": "LOCATION",
53
-
54
- # Organization / Group
 
 
 
 
 
 
 
 
 
 
 
 
55
  "MusicalGRP": "ORGANIZATION", "PublicCORP": "ORGANIZATION",
56
  "PrivateCORP": "ORGANIZATION", "AerospaceManufacturer": "ORGANIZATION",
57
  "SportsGRP": "ORGANIZATION", "CarManufacturer": "ORGANIZATION",
58
  "ORG": "ORGANIZATION", "GRP": "ORGANIZATION", "Organization": "ORGANIZATION",
59
-
60
- # Medical
 
 
 
 
 
 
 
 
 
 
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
- entity_clean = entity_type.replace("B-", "").replace("I-", "").split("_")[0]
 
212
 
 
 
213
  if entity_clean in TAG_TO_COARSE:
214
- coarse_cat = TAG_TO_COARSE[entity_clean]
 
 
 
 
 
 
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': coarse_cat,
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 **21 languages** into synthetic placeholders.")
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],