G-Madhuri commited on
Commit
2f52de1
·
1 Parent(s): e207688

Deploy multilingual text recognition app

Browse files
bengali_samples/11_6_98_102_140_102_140_129_102_127.jpeg ADDED
bengali_samples/14_2_336_81_552_81_547_120_340_119.jpeg ADDED
bengali_samples/16_2_45_13_555_15_548_138_24_123.jpeg ADDED
bengali_samples/3_7_1540_1008_1893_1013_1840_1173_1540_1208.jpeg ADDED
bengali_samples/5_0_114_52_207_48_204_96_108_101.jpeg ADDED
finetuned_bengali_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d2f4adc40aebc41c549e98e5d127d25b8f144e6e627eedab01ecfc25a291e9e
3
+ size 95408177
oriya_samples/1_4_127_239_153_233_153_248_123_252.jpeg ADDED
oriya_samples/4_0_483_106_520_104_520_130_477_129.jpeg ADDED
oriya_samples/4_18_415_238_467_242_466_263_406_262.jpeg ADDED
oriya_samples/4_5_426_187_475_180_474_204_422_204.jpeg ADDED
oriya_samples/5_0_364_82_446_79_446_115_378_122.jpeg ADDED
parseq ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 1902db043c029a7e03a3818c616c06600af574be
parseq_oriya_final_direct.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c5ed9591196d55701f23720c46789ef33e947d4276077ed2cae5a8f9a0ea1e2
3
+ size 95406376
parseq_telugu_finetuned_final_5epochs.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae8d31642b5d2a4727ba13a6da81d3308c3b204f86982e9d873a328c7e51ceb9
3
+ size 95410199
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio==4.16.0
2
+ torch==2.0.1
3
+ torchvision==0.15.2
4
+ Pillow
5
+ numpy
space.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import torchvision.transforms as T
4
+ from PIL import Image
5
+ import os
6
+ import sys
7
+ import warnings
8
+ import logging
9
+ import numpy as np
10
+
11
+ # Setup logging
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # =========================
16
+ # Setup PARSeq path
17
+ # =========================
18
+ parseq_path = os.path.join(os.path.dirname(__file__), 'parseq')
19
+ if os.path.exists(parseq_path):
20
+ sys.path.insert(0, parseq_path)
21
+ else:
22
+ logger.error(f"PARSeq not found at {parseq_path}")
23
+ exit()
24
+
25
+ from strhub.data.utils import Tokenizer
26
+ import torch.hub
27
+
28
+ warnings.filterwarnings('ignore')
29
+
30
+ # =========================
31
+ # Configuration
32
+ # =========================
33
+ ORIYA_CHARSET = "ଅଆଇଈଉଊଋଌଏଐଓଔକଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଳଵଶଷସହାିିୀୁୂୃୄେୈୋୌ୍ଂଁଃ"
34
+
35
+ LANGUAGES = {
36
+ "Telugu": {
37
+ "model_path": "parseq_telugu_finetuned_final_5epochs.pth",
38
+ "samples_dir": "telugu_samples",
39
+ },
40
+ "Bengali": {
41
+ "model_path": "finetuned_bengali_model.pth",
42
+ "samples_dir": "bengali_samples",
43
+ },
44
+ "Oriya": {
45
+ "model_path": "parseq_oriya_final_direct.pth",
46
+ "samples_dir": "oriya_samples",
47
+ "charset": ORIYA_CHARSET,
48
+ }
49
+ }
50
+
51
+ # =========================
52
+ # Image Transform
53
+ # =========================
54
+ transform = T.Compose([
55
+ T.Resize((32, 128)),
56
+ T.ToTensor(),
57
+ T.Normalize(mean=[0.5], std=[0.5])
58
+ ])
59
+
60
+ # =========================
61
+ # Decode
62
+ # =========================
63
+ def decode_prediction(logits, tokenizer):
64
+ pred_ids = logits.argmax(-1)[0]
65
+ chars = []
66
+ for t in pred_ids:
67
+ t = t.item()
68
+ if t == tokenizer.eos_id:
69
+ break
70
+ if t not in [tokenizer.pad_id, tokenizer.bos_id] and t < len(tokenizer._itos):
71
+ chars.append(tokenizer._itos[t])
72
+ return "".join(chars)
73
+
74
+ # =========================
75
+ # Model Cache
76
+ # =========================
77
+ model_cache = {}
78
+
79
+ def load_model(model_path, lang_name):
80
+ cache_key = f"{lang_name}_{model_path}"
81
+ if cache_key in model_cache:
82
+ return model_cache[cache_key]
83
+
84
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
85
+
86
+ if not os.path.exists(model_path):
87
+ logger.error(f"Model not found: {model_path}")
88
+ return None, None, None
89
+
90
+ try:
91
+ # Load checkpoint with weights_only=False for compatibility with older models
92
+ checkpoint = torch.load(model_path, map_location='cpu', weights_only=False)
93
+
94
+ if 'charset' in checkpoint:
95
+ charset_str = checkpoint['charset']
96
+ elif lang_name == "Oriya":
97
+ charset_str = ORIYA_CHARSET
98
+ else:
99
+ # Try to infer charset from model
100
+ logger.warning(f"No charset found for {lang_name}, using default")
101
+ return None, None, None
102
+
103
+ model = torch.hub.load('baudm/parseq', 'parseq', pretrained=False, trust_repo=True)
104
+ model.tokenizer = Tokenizer(charset_str)
105
+
106
+ # Handle different checkpoint formats
107
+ if 'model_state_dict' in checkpoint:
108
+ state_dict = checkpoint['model_state_dict']
109
+ elif 'model' in checkpoint:
110
+ state_dict = checkpoint['model']
111
+ else:
112
+ state_dict = checkpoint
113
+
114
+ # Remove unexpected keys if any
115
+ new_state_dict = {}
116
+ for k, v in state_dict.items():
117
+ if 'module.' in k:
118
+ k = k.replace('module.', '')
119
+ new_state_dict[k] = v
120
+
121
+ model.load_state_dict(new_state_dict, strict=False)
122
+ model = model.to(device)
123
+ model.eval()
124
+
125
+ model_cache[cache_key] = (model, device, model.tokenizer)
126
+ logger.info(f"Loaded {lang_name} model successfully")
127
+ return model, device, model.tokenizer
128
+
129
+ except Exception as e:
130
+ logger.error(f"Error loading {lang_name}: {e}")
131
+ return None, None, None
132
+
133
+ # =========================
134
+ # Inference
135
+ # =========================
136
+ def inference_image(model, image, device, tokenizer):
137
+ if image.mode != 'RGB':
138
+ image = image.convert('RGB')
139
+
140
+ img_tensor = transform(image).unsqueeze(0).to(device)
141
+
142
+ with torch.no_grad():
143
+ logits = model(img_tensor)
144
+ predicted_text = decode_prediction(logits, tokenizer)
145
+
146
+ probs = torch.softmax(logits, dim=-1)
147
+ max_probs = probs.max(dim=-1)[0][0]
148
+ avg_conf = max_probs[:len(predicted_text)].mean().item() if len(predicted_text) > 0 else 0
149
+
150
+ return predicted_text, avg_conf
151
+
152
+ # =========================
153
+ # Get samples for specific language
154
+ # =========================
155
+ def get_samples_for_language(language):
156
+ """Get sample images for a specific language"""
157
+ config = LANGUAGES[language]
158
+ folder = config["samples_dir"]
159
+ samples = []
160
+
161
+ if os.path.exists(folder):
162
+ for f in sorted(os.listdir(folder)):
163
+ if f.lower().endswith(('.png', '.jpg', '.jpeg')):
164
+ samples.append(os.path.join(folder, f))
165
+
166
+ return samples[:6]
167
+
168
+ # =========================
169
+ # Create a tab for each language
170
+ # =========================
171
+ def create_language_tab(language):
172
+ """Create a tab interface for a specific language"""
173
+
174
+ # Get samples for this language
175
+ sample_images = get_samples_for_language(language)
176
+
177
+ with gr.Row():
178
+ # Left column - Image preview and controls
179
+ with gr.Column(scale=1):
180
+ image_input = gr.Image(
181
+ type="pil",
182
+ label=f"📷 {language} Image Preview",
183
+ height=350,
184
+ interactive=True
185
+ )
186
+
187
+ # Extract button right below the preview
188
+ extract_btn = gr.Button(
189
+ f"✨ Extract Text",
190
+ variant="primary"
191
+ )
192
+
193
+ # Sample images section
194
+ if sample_images:
195
+ gr.Markdown("---")
196
+ gr.Markdown(f"### 📸 Click any {language} sample image to preview")
197
+
198
+ # Create gallery that doesn't expand when clicked
199
+ sample_gallery = gr.Gallery(
200
+ value=sample_images,
201
+ label=f"{language} Sample Images",
202
+ columns=3,
203
+ rows=2,
204
+ object_fit="contain",
205
+ height="auto",
206
+ allow_preview=False,
207
+ interactive=False
208
+ )
209
+
210
+ # Function to update main preview when sample is selected
211
+ def update_preview_from_sample(evt: gr.SelectData):
212
+ selected_index = evt.index
213
+ selected_image_path = sample_images[selected_index]
214
+ return Image.open(selected_image_path)
215
+
216
+ sample_gallery.select(
217
+ update_preview_from_sample,
218
+ outputs=image_input
219
+ )
220
+
221
+ # Right column - Results
222
+ with gr.Column(scale=1):
223
+ output_text = gr.Textbox(
224
+ label="📝 Extracted Text",
225
+ lines=6,
226
+ placeholder="Extracted text will appear here...",
227
+ interactive=False
228
+ )
229
+ confidence = gr.Textbox(
230
+ label="🎯 Confidence Score",
231
+ placeholder="Confidence will appear here...",
232
+ interactive=False
233
+ )
234
+
235
+ # Handle prediction
236
+ def predict_wrapper(image):
237
+ if image is None:
238
+ return "⚠️ Please upload or select an image first", ""
239
+
240
+ model_path = LANGUAGES[language]["model_path"]
241
+ model, device, tokenizer = load_model(model_path, language)
242
+
243
+ if model is None:
244
+ return f"❌ Failed to load {language} model. Please check if the model file exists and is valid.", ""
245
+
246
+ text, conf = inference_image(model, image, device, tokenizer)
247
+
248
+ if text == "":
249
+ return "🔍 No text detected in the image", ""
250
+
251
+ return text, f"✅ Confidence: {conf:.2%}"
252
+
253
+ extract_btn.click(
254
+ fn=predict_wrapper,
255
+ inputs=[image_input],
256
+ outputs=[output_text, confidence]
257
+ )
258
+
259
+ return image_input
260
+
261
+ # =========================
262
+ # Main UI with Tabs
263
+ # =========================
264
+ with gr.Blocks(theme=gr.themes.Soft(), title="Multilingual Scene Text Recognition", css="""
265
+ .gradio-container {
266
+ max-width: 1400px !important;
267
+ margin: auto !important;
268
+ }
269
+
270
+ /* Make tab text clearly visible */
271
+ .tab-nav button {
272
+ font-size: 18px !important;
273
+ font-weight: bold !important;
274
+ padding: 12px 24px !important;
275
+ color: #000000 !important;
276
+ background-color: #f0f0f0 !important;
277
+ border: 2px solid #ccc !important;
278
+ margin-right: 8px !important;
279
+ border-radius: 8px 8px 0 0 !important;
280
+ }
281
+
282
+ .tab-nav button.selected {
283
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
284
+ color: white !important;
285
+ border: none !important;
286
+ }
287
+
288
+ .tab-nav button:hover {
289
+ background-color: #e0e0e0 !important;
290
+ transform: translateY(-2px);
291
+ }
292
+
293
+ /* Button styling */
294
+ button {
295
+ transition: all 0.3s ease !important;
296
+ font-weight: bold !important;
297
+ font-size: 16px !important;
298
+ margin-top: 10px !important;
299
+ margin-bottom: 10px !important;
300
+ }
301
+
302
+ button:hover {
303
+ transform: translateY(-2px) !important;
304
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2) !important;
305
+ }
306
+
307
+ /* Gallery styling - prevent expansion */
308
+ .gr-gallery {
309
+ border: 2px solid #e0e0e0;
310
+ border-radius: 10px;
311
+ padding: 10px;
312
+ background-color: #fafafa;
313
+ }
314
+
315
+ .gr-gallery .gallery-item {
316
+ cursor: pointer !important;
317
+ transition: transform 0.2s !important;
318
+ }
319
+
320
+ .gr-gallery .gallery-item:hover {
321
+ transform: scale(1.05) !important;
322
+ }
323
+
324
+ /* Box styling */
325
+ .gr-box {
326
+ border-radius: 10px;
327
+ border: 1px solid #e0e0e0;
328
+ }
329
+
330
+ /* Primary button styling */
331
+ .gr-button-primary {
332
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
333
+ color: white !important;
334
+ border: none !important;
335
+ }
336
+ """) as demo:
337
+
338
+ # Header
339
+ gr.Markdown("""
340
+ # 📖 Multilingual Scene Text Recognition System
341
+ ### Extract text from images in Telugu, Bengali, and Oriya languages
342
+
343
+ ---
344
+ """)
345
+
346
+ # Create tabs for each language
347
+ with gr.Tabs():
348
+ for lang in LANGUAGES.keys():
349
+ with gr.TabItem(f"🔤 {lang}"):
350
+ create_language_tab(lang)
351
+
352
+ # Footer
353
+ gr.Markdown("""
354
+ ---
355
+ ### 💡 How to use:
356
+ 1. **Select a language tab** (Telugu, Bengali, or Oriya)
357
+ 2. **Click any sample thumbnail** - it will load into the main preview above
358
+ 3. **Click "Extract Text"** button below the preview
359
+ 4. **View results** on the right side
360
+
361
+ ### 📌 Note:
362
+ - Sample thumbnails stay as thumbnails - they don't expand when clicked
363
+ - Only the main preview area changes when you click a sample
364
+ - You can also upload your own images
365
+ """)
366
+
367
+ # =========================
368
+ # Run
369
+ # =========================
370
+ if __name__ == "__main__":
371
+ # Check directories
372
+ for lang, config in LANGUAGES.items():
373
+ if not os.path.exists(config["model_path"]):
374
+ logger.warning(f"⚠️ Model not found: {config['model_path']} for {lang}")
375
+ if not os.path.exists(config["samples_dir"]):
376
+ os.makedirs(config["samples_dir"], exist_ok=True)
377
+ logger.warning(f"📁 Created samples directory: {config['samples_dir']}")
378
+
379
+ demo.launch(
380
+ server_name="0.0.0.0",
381
+ server_port=7860,
382
+ share=False
383
+ )
telugu_samples/1_238_3_13_65_80_65_80_89_13_89.jpeg ADDED
telugu_samples/2_47_0_452_379_635_379_635_470_452_470.jpeg ADDED
telugu_samples/3_19_0_320_11_354_11_354_29_320_29.jpeg ADDED
telugu_samples/4_281_2_67_33_153_33_153_141_67_141.jpeg ADDED
telugu_samples/5_99_4_619_424_767_424_767_499_619_499.jpeg ADDED