LovnishVerma commited on
Commit
d629be6
Β·
verified Β·
1 Parent(s): 523c950

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -27
app.py CHANGED
@@ -2,10 +2,14 @@ import torch
2
  import gradio as gr
3
  from PIL import Image
4
  from transformers import AutoProcessor, AutoModelForCausalLM
5
- from gtts import gTTS
6
  import tempfile
 
 
 
7
 
8
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
9
 
10
  model = AutoModelForCausalLM.from_pretrained(
11
  'microsoft/Florence-2-base',
@@ -13,10 +17,46 @@ model = AutoModelForCausalLM.from_pretrained(
13
  torch_dtype=torch.float16 if device == "cuda" else torch.float32,
14
  ).to(device).eval()
15
 
16
- processor = AutoProcessor.from_pretrained('microsoft/Florence-2-base', trust_remote_code=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
 
19
- def generate_caption_stream(image):
20
  if image is None:
21
  yield "Please upload or capture an image.", None
22
  return
@@ -24,17 +64,27 @@ def generate_caption_stream(image):
24
  if not isinstance(image, Image.Image):
25
  image = Image.fromarray(image)
26
 
27
- inputs = processor(
28
- text="<MORE_DETAILED_CAPTION>",
29
- images=image,
30
- return_tensors="pt"
31
- ).to(device)
 
 
 
 
 
 
 
 
 
 
32
 
33
  with torch.inference_mode():
34
  output_ids = model.generate(
35
  input_ids=inputs["input_ids"],
36
  pixel_values=inputs["pixel_values"],
37
- max_new_tokens=256,
38
  do_sample=False,
39
  num_beams=1,
40
  )
@@ -42,63 +92,69 @@ def generate_caption_stream(image):
42
  generated_text = processor.batch_decode(output_ids, skip_special_tokens=False)[0]
43
  result = processor.post_process_generation(
44
  generated_text,
45
- task="<MORE_DETAILED_CAPTION>",
46
  image_size=(image.width, image.height),
47
  )
48
- caption = result["<MORE_DETAILED_CAPTION>"]
 
49
 
50
- # Stream word by word, no audio yet
 
 
 
51
  words = caption.split()
52
  partial = ""
53
  for word in words:
54
  partial += ("" if partial == "" else " ") + word
55
  yield partial, None
56
 
57
- # Final yield: generate and return audio
58
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
59
- gTTS(text=caption, lang="en", slow=False).save(tmp.name)
60
- audio_path = tmp.name
61
-
62
- print(f"\nFinal caption: {caption}")
63
  yield caption, audio_path
64
 
65
 
66
- with gr.Blocks(title="EchoLens RT") as demo:
67
- gr.Markdown("# πŸ‘οΈ EchoLens β€” Realtime Captioning + Speech")
68
- gr.Markdown("Upload or capture an image. Caption streams live then plays aloud.")
69
 
70
  with gr.Row():
71
  with gr.Column(scale=1):
72
  image_input = gr.Image(
73
- label="Image",
74
  type="numpy",
75
  sources=["upload", "webcam"],
 
 
 
 
 
 
76
  )
77
- btn = gr.Button("Describe Image β–Ά", variant="primary")
78
 
79
  with gr.Column(scale=1):
80
  caption_out = gr.Textbox(
81
  label="Caption",
82
- lines=5,
83
  interactive=False,
84
  show_copy_button=True,
85
  )
86
  audio_out = gr.Audio(
87
- label="Audio",
88
  type="filepath",
89
  autoplay=True,
90
  )
91
 
92
  btn.click(
93
  fn=generate_caption_stream,
94
- inputs=image_input,
95
  outputs=[caption_out, audio_out],
96
  show_progress=False,
97
  )
98
 
99
  image_input.change(
100
  fn=generate_caption_stream,
101
- inputs=image_input,
102
  outputs=[caption_out, audio_out],
103
  show_progress=False,
104
  )
 
2
  import gradio as gr
3
  from PIL import Image
4
  from transformers import AutoProcessor, AutoModelForCausalLM
5
+ import edge_tts
6
  import tempfile
7
+ import asyncio
8
+ import threading
9
+ import time
10
 
11
  device = "cuda" if torch.cuda.is_available() else "cpu"
12
+ print(f"Running on: {device}")
13
 
14
  model = AutoModelForCausalLM.from_pretrained(
15
  'microsoft/Florence-2-base',
 
17
  torch_dtype=torch.float16 if device == "cuda" else torch.float32,
18
  ).to(device).eval()
19
 
20
+ processor = AutoProcessor.from_pretrained(
21
+ 'microsoft/Florence-2-base',
22
+ trust_remote_code=True
23
+ )
24
+
25
+ # ── Warmup ──
26
+ def warmup():
27
+ dummy = Image.new("RGB", (224, 224), color=128)
28
+ inp = processor(text="<CAPTION>", images=dummy, return_tensors="pt").to(device)
29
+ with torch.inference_mode():
30
+ model.generate(
31
+ input_ids=inp["input_ids"],
32
+ pixel_values=inp["pixel_values"],
33
+ max_new_tokens=20,
34
+ num_beams=1,
35
+ )
36
+ print("Model warmed up!")
37
+
38
+ threading.Thread(target=warmup, daemon=True).start()
39
+
40
+ # ── Image hash cache ──
41
+ last_caption = {"text": "", "hash": None}
42
+
43
+ def image_hash(image: Image.Image) -> int:
44
+ thumb = image.resize((16, 16)).convert("L")
45
+ return hash(thumb.tobytes())
46
+
47
+ # ── edge-tts: async β†’ sync wrapper ──
48
+ async def _tts_async(text: str, path: str):
49
+ communicate = edge_tts.Communicate(text, voice="en-US-AriaNeural", rate="+10%")
50
+ await communicate.save(path)
51
+
52
+ def text_to_speech(text: str) -> str:
53
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
54
+ path = tmp.name
55
+ asyncio.run(_tts_async(text, path))
56
+ return path
57
 
58
 
59
+ def generate_caption_stream(image, task_choice):
60
  if image is None:
61
  yield "Please upload or capture an image.", None
62
  return
 
64
  if not isinstance(image, Image.Image):
65
  image = Image.fromarray(image)
66
 
67
+ h = image_hash(image)
68
+ if h == last_caption["hash"] and last_caption["text"]:
69
+ # Same frame β€” just re-speak
70
+ audio_path = text_to_speech(last_caption["text"])
71
+ yield last_caption["text"], audio_path
72
+ return
73
+
74
+ task_map = {
75
+ "Quick (faster)": "<CAPTION>",
76
+ "Detailed (slower)": "<MORE_DETAILED_CAPTION>",
77
+ }
78
+ task = task_map.get(task_choice, "<CAPTION>")
79
+
80
+ t0 = time.time()
81
+ inputs = processor(text=task, images=image, return_tensors="pt").to(device)
82
 
83
  with torch.inference_mode():
84
  output_ids = model.generate(
85
  input_ids=inputs["input_ids"],
86
  pixel_values=inputs["pixel_values"],
87
+ max_new_tokens=60 if task == "<CAPTION>" else 150,
88
  do_sample=False,
89
  num_beams=1,
90
  )
 
92
  generated_text = processor.batch_decode(output_ids, skip_special_tokens=False)[0]
93
  result = processor.post_process_generation(
94
  generated_text,
95
+ task=task,
96
  image_size=(image.width, image.height),
97
  )
98
+ caption = result[task]
99
+ print(f"Caption ({time.time()-t0:.2f}s): {caption}")
100
 
101
+ last_caption["text"] = caption
102
+ last_caption["hash"] = h
103
+
104
+ # Stream words while TTS generates in background
105
  words = caption.split()
106
  partial = ""
107
  for word in words:
108
  partial += ("" if partial == "" else " ") + word
109
  yield partial, None
110
 
111
+ # TTS after streaming
112
+ audio_path = text_to_speech(caption)
 
 
 
 
113
  yield caption, audio_path
114
 
115
 
116
+ with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
117
+ gr.Markdown("# πŸ‘οΈ EchoLens β€” Realtime Vision Assistant")
118
+ gr.Markdown("Designed for blind and visually impaired users. Capture β†’ Caption β†’ Speak.")
119
 
120
  with gr.Row():
121
  with gr.Column(scale=1):
122
  image_input = gr.Image(
123
+ label="Camera / Upload",
124
  type="numpy",
125
  sources=["upload", "webcam"],
126
+ mirror_webcam=False,
127
+ )
128
+ task_choice = gr.Radio(
129
+ choices=["Quick (faster)", "Detailed (slower)"],
130
+ value="Quick (faster)",
131
+ label="Caption mode",
132
  )
133
+ btn = gr.Button("Describe β–Ά", variant="primary", size="lg")
134
 
135
  with gr.Column(scale=1):
136
  caption_out = gr.Textbox(
137
  label="Caption",
138
+ lines=4,
139
  interactive=False,
140
  show_copy_button=True,
141
  )
142
  audio_out = gr.Audio(
143
+ label="Audio Description",
144
  type="filepath",
145
  autoplay=True,
146
  )
147
 
148
  btn.click(
149
  fn=generate_caption_stream,
150
+ inputs=[image_input, task_choice],
151
  outputs=[caption_out, audio_out],
152
  show_progress=False,
153
  )
154
 
155
  image_input.change(
156
  fn=generate_caption_stream,
157
+ inputs=[image_input, task_choice],
158
  outputs=[caption_out, audio_out],
159
  show_progress=False,
160
  )