ApurvaKondekar commited on
Commit
4bfa608
·
verified ·
1 Parent(s): 43cf165

new app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -152
app.py CHANGED
@@ -1,3 +1,8 @@
 
 
 
 
 
1
  import gradio as gr
2
  import torch
3
  import torch.nn as nn
@@ -23,11 +28,18 @@ import subprocess
23
  # =========================================================
24
 
25
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
26
  SAMPLE_RATE = 16000
 
27
  TEXT_MAX_LEN = 64
 
28
  LABELS = ["angry", "happy", "neutral", "sad"]
29
 
30
- gpu_status = "🟢 GPU Enabled" if torch.cuda.is_available() else "🔴 CPU Mode"
 
 
 
 
31
 
32
  # =========================================================
33
  # LOAD PROCESSORS
@@ -46,7 +58,9 @@ tokenizer = AutoTokenizer.from_pretrained(
46
  # =========================================================
47
 
48
  class ResNetVideoEncoder(nn.Module):
 
49
  def __init__(self, out_dim=768):
 
50
  super().__init__()
51
 
52
  base = models.resnet18(pretrained=False)
@@ -119,6 +133,7 @@ class HBF(nn.Module):
119
  self.drop = nn.Dropout(0.1)
120
 
121
  self.act1 = nn.GELU()
 
122
  self.act2 = nn.Tanh()
123
 
124
  self.n = n_layers
@@ -176,10 +191,6 @@ class AVVideoModel(nn.Module):
176
 
177
  self.fc = nn.Linear(768, num_classes)
178
 
179
- self.fc_audio = nn.Linear(768, num_classes)
180
- self.fc_text = nn.Linear(768, num_classes)
181
- self.fc_video = nn.Linear(768, num_classes)
182
-
183
  def forward(
184
  self,
185
  audio,
@@ -213,17 +224,15 @@ class AVVideoModel(nn.Module):
213
 
214
  v_pool = self.v_enc(video)
215
 
216
- a_pool = torch.nan_to_num(a_pool)
217
- t_pool = torch.nan_to_num(t_pool)
218
- v_pool = torch.nan_to_num(v_pool)
219
-
220
- fused = self.hbf(a_pool, t_pool, v_pool)
221
-
222
- fused = torch.nan_to_num(fused)
223
 
224
- fused_logits = self.fc(fused)
225
 
226
- return fused_logits
227
 
228
  # =========================================================
229
  # LOAD MODEL
@@ -246,11 +255,11 @@ try:
246
 
247
  model.eval()
248
 
249
- print("Model loaded successfully")
250
 
251
  except Exception as e:
252
 
253
- print(f" Error loading model: {e}")
254
 
255
  # =========================================================
256
  # VIDEO PROCESSING
@@ -316,58 +325,42 @@ def extract_video_frames(
316
 
317
  def extract_audio_from_video(video_path):
318
 
319
- try:
320
-
321
- audio_path = tempfile.NamedTemporaryFile(
322
- delete=False,
323
- suffix=".wav"
324
- ).name
325
-
326
- command = [
327
- "ffmpeg",
328
- "-i", video_path,
329
- "-vn",
330
- "-acodec", "pcm_s16le",
331
- "-ar", str(SAMPLE_RATE),
332
- "-ac", "1",
333
- "-y",
334
- audio_path
335
- ]
336
-
337
- subprocess.run(
338
- command,
339
- stdout=subprocess.DEVNULL,
340
- stderr=subprocess.DEVNULL,
341
- check=True
342
- )
343
-
344
- return audio_path
345
-
346
- except Exception as e:
347
 
348
- raise ValueError(
349
- f"Audio extraction failed: {str(e)}"
350
- )
351
 
352
  # =========================================================
353
- # TRANSCRIPTION
354
  # =========================================================
355
 
356
  whisper_model = whisper.load_model("base")
357
 
358
  def transcribe_audio(audio_path):
359
 
360
- try:
361
-
362
- result = whisper_model.transcribe(audio_path)
363
-
364
- return result["text"].strip()
365
 
366
- except Exception as e:
367
-
368
- raise ValueError(
369
- f"Transcription failed: {str(e)}"
370
- )
371
 
372
  # =========================================================
373
  # PREPROCESSING
@@ -379,7 +372,6 @@ def preprocess_inputs(
379
  video_path
380
  ):
381
 
382
- # AUDIO
383
  wav, _ = librosa.load(
384
  audio_path,
385
  sr=SAMPLE_RATE
@@ -397,7 +389,6 @@ def preprocess_inputs(
397
  audio_values
398
  ).to(DEVICE)
399
 
400
- # TEXT
401
  text_clean = re.sub(
402
  r"[^a-zA-Z0-9\s]",
403
  "",
@@ -416,14 +407,8 @@ def preprocess_inputs(
416
 
417
  text_mask = text_inputs.attention_mask.to(DEVICE)
418
 
419
- # VIDEO
420
  frames = extract_video_frames(video_path)
421
 
422
- if frames is None:
423
- raise ValueError(
424
- "Could not extract video frames"
425
- )
426
-
427
  frames_tensor = (
428
  torch.tensor(frames)
429
  .permute(0, 3, 1, 2)
@@ -446,7 +431,7 @@ def preprocess_inputs(
446
  )
447
 
448
  # =========================================================
449
- # PREDICTION FUNCTION
450
  # =========================================================
451
 
452
  def predict_emotion(video_file):
@@ -454,24 +439,21 @@ def predict_emotion(video_file):
454
  if video_file is None:
455
 
456
  return (
457
- "Please upload a video",
458
  None,
459
  ""
460
  )
461
 
462
  try:
463
 
464
- # EXTRACT AUDIO
465
  audio_path = extract_audio_from_video(
466
  video_file
467
  )
468
 
469
- # TRANSCRIBE
470
  transcribed_text = transcribe_audio(
471
  audio_path
472
  )
473
 
474
- # PREPROCESS
475
  (
476
  audio,
477
  audio_mask,
@@ -484,7 +466,6 @@ def predict_emotion(video_file):
484
  video_file
485
  )
486
 
487
- # INFERENCE
488
  with torch.no_grad():
489
 
490
  logits = model(
@@ -505,25 +486,20 @@ def predict_emotion(video_file):
505
  for i in range(len(LABELS))
506
  }
507
 
508
- predicted_emotion = LABELS[probs.argmax()]
 
 
509
 
510
  confidence = float(probs.max())
511
 
512
- emoji_map = {
513
- "happy": "😄",
514
- "sad": "😢",
515
- "angry": "😠",
516
- "neutral": "😐"
517
- }
518
-
519
  result_text = f"""
520
- # {emoji_map[predicted_emotion]} {predicted_emotion.upper()}
 
 
 
 
 
521
 
522
- ## Confidence Score
523
- ### {confidence:.2%}
524
- """
525
-
526
- # CLEANUP
527
  if os.path.exists(audio_path):
528
  os.remove(audio_path)
529
 
@@ -536,51 +512,75 @@ def predict_emotion(video_file):
536
  except Exception as e:
537
 
538
  return (
539
- f"Error: {str(e)}",
540
  None,
541
  ""
542
  )
543
 
544
  # =========================================================
545
- # CUSTOM CSS
546
  # =========================================================
547
 
548
  custom_css = """
549
 
550
  body {
551
- background: linear-gradient(
552
- to right,
553
- #0f172a,
554
- #1e293b
555
- );
556
  }
557
 
558
  .gradio-container {
559
- max-width: 1200px !important;
560
  margin: auto;
 
561
  }
562
 
563
  .main-title {
564
  text-align: center;
565
  font-size: 48px;
566
  font-weight: 800;
567
- color: white;
568
- margin-top: 20px;
569
  }
570
 
571
  .subtitle {
572
  text-align: center;
573
- font-size: 18px;
574
- color: #cbd5e1;
575
- margin-bottom: 25px;
 
 
 
 
 
 
 
576
  }
577
 
578
  .footer {
579
  text-align: center;
580
- color: #94a3b8;
581
  margin-top: 30px;
582
  font-size: 14px;
583
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  """
585
 
586
  # =========================================================
@@ -588,76 +588,89 @@ body {
588
  # =========================================================
589
 
590
  with gr.Blocks(
591
- theme=gr.themes.Glass(),
592
- css=custom_css,
593
- title="Emotion Recognition"
594
  ) as demo:
595
 
596
  # HEADER
 
597
  gr.HTML("""
598
  <div class="main-title">
599
- 🎭 Multimodal Emotion Recognition
600
  </div>
601
 
602
  <div class="subtitle">
603
- AI-powered Emotion Detection using Audio, Text & Video Fusion
604
  </div>
605
  """)
606
 
607
- gr.Markdown(f"### {gpu_status}")
 
 
 
 
 
 
608
 
609
- # INFO SECTION
610
  with gr.Row():
611
 
612
  with gr.Column():
613
 
614
- gr.Markdown("""
615
- ### 🔍 Modalities
616
 
617
- - 🎤 Audio
618
- - 📝 Text
619
- - 🎥 Video
620
- """)
 
 
 
621
 
622
  with gr.Column():
623
 
624
- gr.Markdown("""
625
- ### 🤖 Models Used
626
 
627
- - Wav2Vec2
628
- - BERT
629
- - ResNet18
630
- - Whisper
631
- """)
 
 
 
632
 
633
  gr.Markdown("---")
634
 
635
  # MAIN SECTION
 
636
  with gr.Row(equal_height=True):
637
 
638
- # LEFT SIDE
 
639
  with gr.Column(scale=1):
640
 
641
- gr.Markdown("## 📤 Upload Video")
642
 
643
  video_input = gr.Video(
644
  label="Input Video",
645
- height=350
646
  )
647
 
648
  predict_btn = gr.Button(
649
- "🚀 Analyze Emotion",
650
  variant="primary",
651
  size="lg"
652
  )
653
 
654
- # RIGHT SIDE
 
655
  with gr.Column(scale=1):
656
 
657
- gr.Markdown("## 📊 Results")
658
 
659
  result_text = gr.Markdown(
660
- value="Upload a video to begin analysis"
661
  )
662
 
663
  result_output = gr.Label(
@@ -666,50 +679,46 @@ with gr.Blocks(
666
  )
667
 
668
  transcription_output = gr.Textbox(
669
- label="📝 Transcribed Text",
670
- lines=5,
671
  interactive=False
672
  )
673
 
674
  gr.Markdown("---")
675
 
676
- # ABOUT MODEL
 
677
  with gr.Accordion(
678
- "ℹ️ About This Model",
679
  open=False
680
  ):
681
 
682
  gr.Markdown("""
683
- This system combines:
684
-
685
- ### 🎤 Audio Analysis
686
- Wav2Vec2 captures emotional tone and speech patterns.
687
-
688
- ### 📝 Text Analysis
689
- BERT analyzes semantic meaning from transcripts.
690
-
691
- ### 🎥 Video Analysis
692
- ResNet18 extracts facial expression features.
693
-
694
- ### 🧠 Fusion Network
695
- HBF combines all modalities for final prediction.
696
-
697
- ---
698
- Supported Emotions:
699
- - Angry
700
- - Happy
701
- - Neutral
702
- - Sad
703
  """)
704
 
705
  # FOOTER
 
706
  gr.HTML("""
707
  <div class="footer">
708
- Built with ❤️ using PyTorch, Transformers, Whisper & Gradio
709
  </div>
710
  """)
711
 
712
  # BUTTON ACTION
 
713
  predict_btn.click(
714
  fn=predict_emotion,
715
  inputs=[video_input],
 
1
+ # =========================================================
2
+ # PROFESSIONAL MULTIMODAL EMOTION RECOGNITION UI
3
+ # Sky Blue + White Theme
4
+ # =========================================================
5
+
6
  import gradio as gr
7
  import torch
8
  import torch.nn as nn
 
28
  # =========================================================
29
 
30
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
31
+
32
  SAMPLE_RATE = 16000
33
+
34
  TEXT_MAX_LEN = 64
35
+
36
  LABELS = ["angry", "happy", "neutral", "sad"]
37
 
38
+ gpu_status = (
39
+ "GPU Enabled"
40
+ if torch.cuda.is_available()
41
+ else "Running on CPU"
42
+ )
43
 
44
  # =========================================================
45
  # LOAD PROCESSORS
 
58
  # =========================================================
59
 
60
  class ResNetVideoEncoder(nn.Module):
61
+
62
  def __init__(self, out_dim=768):
63
+
64
  super().__init__()
65
 
66
  base = models.resnet18(pretrained=False)
 
133
  self.drop = nn.Dropout(0.1)
134
 
135
  self.act1 = nn.GELU()
136
+
137
  self.act2 = nn.Tanh()
138
 
139
  self.n = n_layers
 
191
 
192
  self.fc = nn.Linear(768, num_classes)
193
 
 
 
 
 
194
  def forward(
195
  self,
196
  audio,
 
224
 
225
  v_pool = self.v_enc(video)
226
 
227
+ fused = self.hbf(
228
+ a_pool,
229
+ t_pool,
230
+ v_pool
231
+ )
 
 
232
 
233
+ logits = self.fc(fused)
234
 
235
+ return logits
236
 
237
  # =========================================================
238
  # LOAD MODEL
 
255
 
256
  model.eval()
257
 
258
+ print("Model loaded successfully")
259
 
260
  except Exception as e:
261
 
262
+ print(f"Model loading failed: {e}")
263
 
264
  # =========================================================
265
  # VIDEO PROCESSING
 
325
 
326
  def extract_audio_from_video(video_path):
327
 
328
+ audio_path = tempfile.NamedTemporaryFile(
329
+ delete=False,
330
+ suffix=".wav"
331
+ ).name
332
+
333
+ command = [
334
+ "ffmpeg",
335
+ "-i", video_path,
336
+ "-vn",
337
+ "-acodec", "pcm_s16le",
338
+ "-ar", str(SAMPLE_RATE),
339
+ "-ac", "1",
340
+ "-y",
341
+ audio_path
342
+ ]
343
+
344
+ subprocess.run(
345
+ command,
346
+ stdout=subprocess.DEVNULL,
347
+ stderr=subprocess.DEVNULL,
348
+ check=True
349
+ )
 
 
 
 
 
 
350
 
351
+ return audio_path
 
 
352
 
353
  # =========================================================
354
+ # WHISPER
355
  # =========================================================
356
 
357
  whisper_model = whisper.load_model("base")
358
 
359
  def transcribe_audio(audio_path):
360
 
361
+ result = whisper_model.transcribe(audio_path)
 
 
 
 
362
 
363
+ return result["text"].strip()
 
 
 
 
364
 
365
  # =========================================================
366
  # PREPROCESSING
 
372
  video_path
373
  ):
374
 
 
375
  wav, _ = librosa.load(
376
  audio_path,
377
  sr=SAMPLE_RATE
 
389
  audio_values
390
  ).to(DEVICE)
391
 
 
392
  text_clean = re.sub(
393
  r"[^a-zA-Z0-9\s]",
394
  "",
 
407
 
408
  text_mask = text_inputs.attention_mask.to(DEVICE)
409
 
 
410
  frames = extract_video_frames(video_path)
411
 
 
 
 
 
 
412
  frames_tensor = (
413
  torch.tensor(frames)
414
  .permute(0, 3, 1, 2)
 
431
  )
432
 
433
  # =========================================================
434
+ # PREDICTION
435
  # =========================================================
436
 
437
  def predict_emotion(video_file):
 
439
  if video_file is None:
440
 
441
  return (
442
+ "Please upload a video.",
443
  None,
444
  ""
445
  )
446
 
447
  try:
448
 
 
449
  audio_path = extract_audio_from_video(
450
  video_file
451
  )
452
 
 
453
  transcribed_text = transcribe_audio(
454
  audio_path
455
  )
456
 
 
457
  (
458
  audio,
459
  audio_mask,
 
466
  video_file
467
  )
468
 
 
469
  with torch.no_grad():
470
 
471
  logits = model(
 
486
  for i in range(len(LABELS))
487
  }
488
 
489
+ predicted_emotion = LABELS[
490
+ probs.argmax()
491
+ ]
492
 
493
  confidence = float(probs.max())
494
 
 
 
 
 
 
 
 
495
  result_text = f"""
496
+ ## Predicted Emotion
497
+
498
+ ### {predicted_emotion.upper()}
499
+
500
+ Confidence Score: {confidence:.2%}
501
+ """
502
 
 
 
 
 
 
503
  if os.path.exists(audio_path):
504
  os.remove(audio_path)
505
 
 
512
  except Exception as e:
513
 
514
  return (
515
+ f"Error: {str(e)}",
516
  None,
517
  ""
518
  )
519
 
520
  # =========================================================
521
+ # CLEAN PROFESSIONAL CSS
522
  # =========================================================
523
 
524
  custom_css = """
525
 
526
  body {
527
+ background: #eaf6ff;
 
 
 
 
528
  }
529
 
530
  .gradio-container {
531
+ max-width: 1250px !important;
532
  margin: auto;
533
+ padding-top: 20px;
534
  }
535
 
536
  .main-title {
537
  text-align: center;
538
  font-size: 48px;
539
  font-weight: 800;
540
+ color: #0f4c81;
541
+ margin-bottom: 10px;
542
  }
543
 
544
  .subtitle {
545
  text-align: center;
546
+ font-size: 20px;
547
+ color: #3b82b6;
548
+ margin-bottom: 30px;
549
+ }
550
+
551
+ .section-box {
552
+ background: white;
553
+ border-radius: 16px;
554
+ padding: 20px;
555
+ box-shadow: 0px 4px 12px rgba(0,0,0,0.08);
556
  }
557
 
558
  .footer {
559
  text-align: center;
560
+ color: #4b5563;
561
  margin-top: 30px;
562
  font-size: 14px;
563
  }
564
+
565
+ .gr-button {
566
+ background: #38bdf8 !important;
567
+ border: none !important;
568
+ color: white !important;
569
+ font-weight: 600 !important;
570
+ }
571
+
572
+ .gr-button:hover {
573
+ background: #0ea5e9 !important;
574
+ }
575
+
576
+ h1, h2, h3, h4 {
577
+ color: #0f4c81 !important;
578
+ }
579
+
580
+ label {
581
+ color: #0f4c81 !important;
582
+ font-weight: 600 !important;
583
+ }
584
  """
585
 
586
  # =========================================================
 
588
  # =========================================================
589
 
590
  with gr.Blocks(
591
+ title="Multimodal Emotion Recognition",
592
+ theme=gr.themes.Soft(),
593
+ css=custom_css
594
  ) as demo:
595
 
596
  # HEADER
597
+
598
  gr.HTML("""
599
  <div class="main-title">
600
+ Multimodal Emotion Recognition
601
  </div>
602
 
603
  <div class="subtitle">
604
+ AI-based Emotion Detection using Audio, Text and Video Fusion
605
  </div>
606
  """)
607
 
608
+ # STATUS
609
+
610
+ gr.Markdown(
611
+ f"### System Status: {gpu_status}"
612
+ )
613
+
614
+ # INFO PANELS
615
 
 
616
  with gr.Row():
617
 
618
  with gr.Column():
619
 
620
+ with gr.Group():
 
621
 
622
+ gr.Markdown("""
623
+ ### Modalities Used
624
+
625
+ - Audio Analysis
626
+ - Speech Transcription
627
+ - Facial Expression Analysis
628
+ """)
629
 
630
  with gr.Column():
631
 
632
+ with gr.Group():
 
633
 
634
+ gr.Markdown("""
635
+ ### Models Used
636
+
637
+ - Wav2Vec2
638
+ - BERT
639
+ - ResNet18
640
+ - Whisper
641
+ """)
642
 
643
  gr.Markdown("---")
644
 
645
  # MAIN SECTION
646
+
647
  with gr.Row(equal_height=True):
648
 
649
+ # LEFT
650
+
651
  with gr.Column(scale=1):
652
 
653
+ gr.Markdown("## Upload Video")
654
 
655
  video_input = gr.Video(
656
  label="Input Video",
657
+ height=400
658
  )
659
 
660
  predict_btn = gr.Button(
661
+ "Analyze Emotion",
662
  variant="primary",
663
  size="lg"
664
  )
665
 
666
+ # RIGHT
667
+
668
  with gr.Column(scale=1):
669
 
670
+ gr.Markdown("## Results")
671
 
672
  result_text = gr.Markdown(
673
+ value="Upload a video and click Analyze Emotion."
674
  )
675
 
676
  result_output = gr.Label(
 
679
  )
680
 
681
  transcription_output = gr.Textbox(
682
+ label="Transcribed Text",
683
+ lines=6,
684
  interactive=False
685
  )
686
 
687
  gr.Markdown("---")
688
 
689
+ # ABOUT SECTION
690
+
691
  with gr.Accordion(
692
+ "About the Model",
693
  open=False
694
  ):
695
 
696
  gr.Markdown("""
697
+ This multimodal system combines:
698
+
699
+ - Audio features using Wav2Vec2
700
+ - Text understanding using BERT
701
+ - Video feature extraction using ResNet18
702
+ - Hybrid Fusion Block for final prediction
703
+
704
+ Supported emotions:
705
+
706
+ - Angry
707
+ - Happy
708
+ - Neutral
709
+ - Sad
 
 
 
 
 
 
 
710
  """)
711
 
712
  # FOOTER
713
+
714
  gr.HTML("""
715
  <div class="footer">
716
+ Built using PyTorch, Transformers, Whisper and Gradio
717
  </div>
718
  """)
719
 
720
  # BUTTON ACTION
721
+
722
  predict_btn.click(
723
  fn=predict_emotion,
724
  inputs=[video_input],