LovnishVerma commited on
Commit
453c4a3
Β·
verified Β·
1 Parent(s): a45c38f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -136
app.py CHANGED
@@ -56,8 +56,7 @@ def text_to_speech(text: str) -> str:
56
 
57
 
58
  def describe_frame(frame_b64: str, task_choice: str):
59
- """Called every 3s by JS via hidden button. Receives raw base64 jpeg."""
60
- if not frame_b64 or frame_b64 == "none" or "," not in frame_b64:
61
  return gr.update(), gr.update()
62
  try:
63
  img_bytes = base64.b64decode(frame_b64.split(",")[1])
@@ -102,10 +101,9 @@ def describe_frame(frame_b64: str, task_choice: str):
102
  return caption, audio_path
103
 
104
 
105
- def describe_once(image, task_choice):
106
- """Manual describe with word streaming."""
107
  if image is None:
108
- yield "Please open the camera first.", None
109
  return
110
  if not isinstance(image, Image.Image):
111
  image = Image.fromarray(image)
@@ -134,118 +132,22 @@ def describe_once(image, task_choice):
134
  last_caption["text"] = caption
135
  last_caption["hash"] = image_hash(image)
136
 
137
- for word in caption.split():
138
- yield (caption[:caption.index(word) + len(word)]), None
 
 
 
139
 
140
  audio_path = text_to_speech(caption)
141
  yield caption, audio_path
142
 
143
 
144
- def describe_upload(image, task_choice):
145
- """Upload image describe with streaming."""
146
- if image is None:
147
- yield "Please upload an image.", None
148
- return
149
- yield from describe_once(image, task_choice)
150
-
151
-
152
- WEBCAM_JS = """
153
- <script>
154
- (function() {
155
- let realtimeTimer = null;
156
- let isRunning = false;
157
-
158
- function getVideo() {
159
- // Gradio renders webcam inside shadow DOM or iframe β€” try all videos
160
- const videos = document.querySelectorAll('video');
161
- for (const v of videos) {
162
- if (v.videoWidth > 0 && v.readyState >= 2) return v;
163
- }
164
- return null;
165
- }
166
-
167
- function setNativeValue(el, value) {
168
- const setter = Object.getOwnPropertyDescriptor(
169
- window.HTMLTextAreaElement.prototype, 'value'
170
- ).set;
171
- setter.call(el, value);
172
- el.dispatchEvent(new Event('input', { bubbles: true }));
173
- }
174
-
175
- function captureAndSend() {
176
- const video = getVideo();
177
- if (!video) {
178
- console.warn('[EchoLens] No active video found');
179
- return;
180
- }
181
- const canvas = document.createElement('canvas');
182
- canvas.width = video.videoWidth;
183
- canvas.height = video.videoHeight;
184
- canvas.getContext('2d').drawImage(video, 0, 0);
185
- const b64 = canvas.toDataURL('image/jpeg', 0.75);
186
-
187
- const box = document.querySelector('#frame-box textarea');
188
- if (!box) { console.warn('[EchoLens] No frame-box textarea'); return; }
189
- setNativeValue(box, b64);
190
-
191
- setTimeout(() => {
192
- const btn = document.querySelector('#frame-btn button');
193
- if (btn) {
194
- btn.click();
195
- console.log('[EchoLens] Frame sent');
196
- } else {
197
- console.warn('[EchoLens] No frame-btn button');
198
- }
199
- }, 150);
200
- }
201
-
202
- window.echoToggleRealtime = function() {
203
- const toggleBtn = document.querySelector('#rt-btn button');
204
- if (!isRunning) {
205
- isRunning = true;
206
- if (toggleBtn) {
207
- toggleBtn.textContent = '⏹ Stop Realtime';
208
- toggleBtn.style.background = '#ef4444';
209
- toggleBtn.style.color = 'white';
210
- }
211
- captureAndSend();
212
- realtimeTimer = setInterval(captureAndSend, 3000);
213
- console.log('[EchoLens] Realtime ON');
214
- } else {
215
- isRunning = false;
216
- clearInterval(realtimeTimer);
217
- realtimeTimer = null;
218
- if (toggleBtn) {
219
- toggleBtn.textContent = 'β–Ά Start Realtime';
220
- toggleBtn.style.background = '';
221
- toggleBtn.style.color = '';
222
- }
223
- console.log('[EchoLens] Realtime OFF');
224
- }
225
- };
226
-
227
- window.echoDescribeOnce = function() {
228
- captureAndSend();
229
- };
230
- })();
231
- </script>
232
- """
233
-
234
-
235
  with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
236
 
237
- gr.HTML(WEBCAM_JS)
238
-
239
- gr.Markdown("""
240
- # πŸ‘οΈ EchoLens β€” Realtime Vision Assistant
241
- **For blind and visually impaired users.**
242
- - Open your camera below
243
- - Press **Describe Once** for a single description
244
- - Press **Start Realtime** to auto-describe every 3 seconds
245
- """)
246
 
247
  with gr.Row():
248
- # ── LEFT: camera ──────────────────────────────────────
249
  with gr.Column(scale=1):
250
  webcam_input = gr.Image(
251
  label="Live Camera",
@@ -262,27 +164,9 @@ with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
262
  value="Quick (faster)",
263
  label="Caption detail",
264
  )
265
- with gr.Row():
266
- # These buttons call JS directly via elem_id
267
- gr.HTML("""
268
- <div style="display:flex; gap:8px; margin-top:4px;">
269
- <button
270
- onclick="window.echoDescribeOnce()"
271
- style="flex:1; padding:10px; background:#6366f1; color:white;
272
- border:none; border-radius:8px; font-size:15px; cursor:pointer;">
273
- πŸ“Έ Describe Once
274
- </button>
275
- <button
276
- id="rt-btn-inner"
277
- onclick="window.echoToggleRealtime()"
278
- style="flex:1; padding:10px; background:#10b981; color:white;
279
- border:none; border-radius:8px; font-size:15px; cursor:pointer;">
280
- β–Ά Start Realtime
281
- </button>
282
- </div>
283
- """)
284
-
285
- # ── RIGHT: output ──────────────────────────────────────
286
  with gr.Column(scale=1):
287
  caption_out = gr.Textbox(
288
  label="Caption",
@@ -296,15 +180,27 @@ with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
296
  type="filepath",
297
  autoplay=True,
298
  )
299
- gr.Markdown("*Realtime mode auto-describes every 3 seconds.*")
 
 
 
 
 
300
 
301
- # ── Hidden plumbing: JS β†’ Python ──────────────────────────
302
  with gr.Row(visible=False):
303
- frame_box = gr.Textbox(elem_id="frame-box", label="frame_box")
304
- with gr.Column(elem_id="frame-btn"):
305
- frame_btn = gr.Button("send", elem_id="frame-submit")
 
 
 
 
 
 
 
 
306
 
307
- # Upload describe
308
  upload_input.change(
309
  fn=describe_upload,
310
  inputs=[upload_input, task_choice],
@@ -312,7 +208,6 @@ with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
312
  show_progress=False,
313
  )
314
 
315
- # Hidden frame button β†’ describe_frame (realtime + describe-once via JS)
316
  frame_btn.click(
317
  fn=describe_frame,
318
  inputs=[frame_box, task_choice],
@@ -321,5 +216,81 @@ with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
321
  queue=True,
322
  )
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  if __name__ == "__main__":
325
  demo.launch(debug=True)
 
56
 
57
 
58
  def describe_frame(frame_b64: str, task_choice: str):
59
+ if not frame_b64 or "," not in frame_b64:
 
60
  return gr.update(), gr.update()
61
  try:
62
  img_bytes = base64.b64decode(frame_b64.split(",")[1])
 
101
  return caption, audio_path
102
 
103
 
104
+ def describe_upload(image, task_choice):
 
105
  if image is None:
106
+ yield "Please upload an image.", None
107
  return
108
  if not isinstance(image, Image.Image):
109
  image = Image.fromarray(image)
 
132
  last_caption["text"] = caption
133
  last_caption["hash"] = image_hash(image)
134
 
135
+ words = caption.split()
136
+ partial = ""
137
+ for word in words:
138
+ partial += ("" if partial == "" else " ") + word
139
+ yield partial, None
140
 
141
  audio_path = text_to_speech(caption)
142
  yield caption, audio_path
143
 
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  with gr.Blocks(title="EchoLens RT", theme=gr.themes.Soft()) as demo:
146
 
147
+ gr.Markdown("# πŸ‘οΈ EchoLens β€” Realtime Vision Assistant")
148
+ gr.Markdown("**For blind and visually impaired users.** Open camera β†’ click **Start Realtime** for auto-description every 3 seconds.")
 
 
 
 
 
 
 
149
 
150
  with gr.Row():
 
151
  with gr.Column(scale=1):
152
  webcam_input = gr.Image(
153
  label="Live Camera",
 
164
  value="Quick (faster)",
165
  label="Caption detail",
166
  )
167
+ describe_btn = gr.Button("πŸ“Έ Describe Once", variant="primary")
168
+ realtime_btn = gr.Button("β–Ά Start Realtime", variant="secondary", elem_id="realtime-btn")
169
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  with gr.Column(scale=1):
171
  caption_out = gr.Textbox(
172
  label="Caption",
 
180
  type="filepath",
181
  autoplay=True,
182
  )
183
+ status_out = gr.Textbox(
184
+ label="Status",
185
+ value="Ready.",
186
+ interactive=False,
187
+ lines=1,
188
+ )
189
 
190
+ # Hidden plumbing for JS β†’ Python frame passing
191
  with gr.Row(visible=False):
192
+ frame_box = gr.Textbox(elem_id="frame-box", label="fb")
193
+ frame_btn = gr.Button("go", elem_id="frame-btn")
194
+
195
+ # ── Gradio events ──────────────────────────────────────────
196
+
197
+ describe_btn.click(
198
+ fn=describe_upload,
199
+ inputs=[webcam_input, task_choice],
200
+ outputs=[caption_out, audio_out],
201
+ show_progress=False,
202
+ )
203
 
 
204
  upload_input.change(
205
  fn=describe_upload,
206
  inputs=[upload_input, task_choice],
 
208
  show_progress=False,
209
  )
210
 
 
211
  frame_btn.click(
212
  fn=describe_frame,
213
  inputs=[frame_box, task_choice],
 
216
  queue=True,
217
  )
218
 
219
+ # Realtime toggle β€” Python just flips label/color,
220
+ # JS (below) does the actual capture loop
221
+ realtime_btn.click(
222
+ fn=None,
223
+ js="""
224
+ () => {
225
+ const btn = document.querySelector('#realtime-btn button');
226
+ if (!btn) return;
227
+
228
+ if (btn.dataset.running === 'true') {
229
+ // --- STOP ---
230
+ btn.dataset.running = 'false';
231
+ clearInterval(window._echoTimer);
232
+ window._echoTimer = null;
233
+ btn.textContent = 'β–Ά Start Realtime';
234
+ btn.style.background = '';
235
+ btn.style.color = '';
236
+ const s = document.querySelector('#status-box textarea');
237
+ if (s) { Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(s,'Realtime stopped.'); s.dispatchEvent(new Event('input',{bubbles:true})); }
238
+ } else {
239
+ // --- START ---
240
+ btn.dataset.running = 'true';
241
+ btn.textContent = '⏹ Stop Realtime';
242
+ btn.style.background = '#ef4444';
243
+ btn.style.color = 'white';
244
+ const s = document.querySelector('#status-box textarea');
245
+ if (s) { Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(s,'Realtime running...'); s.dispatchEvent(new Event('input',{bubbles:true})); }
246
+
247
+ function capture() {
248
+ const video = [...document.querySelectorAll('video')].find(v => v.videoWidth > 0 && v.readyState >= 2);
249
+ if (!video) { console.warn('[EchoLens] no video'); return; }
250
+ const c = document.createElement('canvas');
251
+ c.width = video.videoWidth; c.height = video.videoHeight;
252
+ c.getContext('2d').drawImage(video, 0, 0);
253
+ const b64 = c.toDataURL('image/jpeg', 0.75);
254
+
255
+ const box = document.querySelector('#frame-box textarea');
256
+ if (!box) { console.warn('[EchoLens] no frame-box'); return; }
257
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(box, b64);
258
+ box.dispatchEvent(new Event('input',{bubbles:true}));
259
+
260
+ setTimeout(() => {
261
+ const fb = document.querySelector('#frame-btn button');
262
+ if (fb) fb.click();
263
+ else console.warn('[EchoLens] no frame-btn');
264
+ }, 200);
265
+ }
266
+
267
+ capture(); // immediate
268
+ window._echoTimer = setInterval(capture, 3500);
269
+ }
270
+ }
271
+ """,
272
+ )
273
+
274
+ # Status box update from JS
275
+ status_out.change(fn=None, inputs=[], outputs=[])
276
+
277
+ # Inject status box elem_id via HTML trick
278
+ gr.HTML("""
279
+ <script>
280
+ // Patch status textarea elem_id so JS can find it
281
+ document.addEventListener('DOMContentLoaded', () => {
282
+ setTimeout(() => {
283
+ const labels = document.querySelectorAll('.label-wrap span');
284
+ labels.forEach(l => {
285
+ if (l.textContent === 'Status') {
286
+ const ta = l.closest('.form')?.querySelector('textarea');
287
+ if (ta) ta.closest('.block')?.setAttribute('id','status-box');
288
+ }
289
+ });
290
+ }, 2000);
291
+ });
292
+ </script>
293
+ """)
294
+
295
  if __name__ == "__main__":
296
  demo.launch(debug=True)