VISHAL18for4 commited on
Commit
38d999f
Β·
verified Β·
1 Parent(s): 1aaf0ef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -50
app.py CHANGED
@@ -300,11 +300,14 @@ VIEWER_SHELL = """
300
  <script type="module">
301
  import * as GaussianSplats3D from '@mkkellogg/gaussian-splats-3d';
302
 
303
- // Expose loadSplat globally so Gradio JS can call it
304
- window._swViewer = null;
305
- window._swB64 = null;
306
-
307
- window.swLoadB64 = async function(b64, sizeMb) {
 
 
 
308
  const idle = document.getElementById('sw-idle');
309
  const loading = document.getElementById('sw-loading');
310
  const controls = document.getElementById('sw-controls');
@@ -313,28 +316,26 @@ window.swLoadB64 = async function(b64, sizeMb) {
313
  const canvas = document.getElementById('sw-canvas');
314
  const msg = document.getElementById('sw-load-msg');
315
 
316
- // Store b64 for download
317
- window._swB64 = b64;
318
-
319
- // Hide idle, show loading
320
- idle.style.display = 'none';
321
- loading.style.display = 'flex';
322
  controls.style.display = 'none';
323
- if (sizeEl) sizeEl.textContent = sizeMb + ' MB Β· WebGL Β· your GPU';
324
-
325
- // Decode base64 β†’ Blob URL
326
- const bin = atob(b64);
327
- const buf = new Uint8Array(bin.length);
328
- for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
329
- const url = URL.createObjectURL(new Blob([buf], { type: 'application/octet-stream' }));
330
-
331
- // Dispose old viewer if any
332
- if (window._swViewer) {
333
- try { window._swViewer.dispose(); } catch(e) {}
334
- window._swViewer = null;
335
- }
336
 
337
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  const viewer = new GaussianSplats3D.Viewer({
339
  canvas,
340
  cameraUp: [0, -1, 0],
@@ -345,7 +346,7 @@ window.swLoadB64 = async function(b64, sizeMb) {
345
  window._swViewer = viewer;
346
 
347
  msg.textContent = 'RENDERING GAUSSIANS…';
348
- await viewer.addSplatScene(url, { progressiveLoad: true });
349
 
350
  loading.style.display = 'none';
351
  controls.style.display = 'flex';
@@ -360,15 +361,11 @@ window.swLoadB64 = async function(b64, sizeMb) {
360
  </script>
361
 
362
  <script>
363
- // Download using stored b64
364
  function swDownload() {
365
- if (!window._swB64) return;
366
- const bin = atob(window._swB64);
367
- const buf = new Uint8Array(bin.length);
368
- for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
369
- const blob = new Blob([buf], { type: 'application/octet-stream' });
370
  const a = document.createElement('a');
371
- a.href = URL.createObjectURL(blob);
372
  a.download = 'scene.ply';
373
  document.body.appendChild(a);
374
  a.click();
@@ -378,20 +375,29 @@ function swDownload() {
378
  """
379
 
380
 
381
- # ── Build the JS trigger to auto-load PLY into the viewer ──
382
- def make_load_script(ply_path: str) -> str:
383
- """Returns a tiny HTML snippet that calls swLoadB64() with the PLY data."""
384
- with open(ply_path, "rb") as f:
385
- b64 = base64.b64encode(f.read()).decode("utf-8")
386
- size_mb = round(os.path.getsize(ply_path) / 1024 / 1024, 2)
387
  return f"""
388
  <script>
389
- (function tryLoad() {{
390
- if (typeof window.swLoadB64 === 'function') {{
391
- window.swLoadB64(`{b64}`, '{size_mb}');
392
- }} else {{
393
- setTimeout(tryLoad, 200);
 
 
 
 
 
394
  }}
 
 
 
 
395
  }})();
396
  </script>
397
  """
@@ -454,7 +460,13 @@ with gr.Blocks(css=CSS, title="SplatWeb") as demo:
454
  # 3D viewer β€” always visible, waits for content
455
  gr.HTML(VIEWER_SHELL)
456
 
457
- # Hidden trigger β€” populated after processing, fires JS to load PLY
 
 
 
 
 
 
458
  trigger1 = gr.HTML(value="", visible=False)
459
 
460
  # ── Tab 2: Two angles + animation ───────────────────
@@ -516,15 +528,16 @@ with gr.Blocks(css=CSS, title="SplatWeb") as demo:
516
  # ── Handlers ──────────────────────────────────────────────
517
  def handle_single(img):
518
  if img is None:
519
- return "⚠ Please upload a photo first.", gr.update(value="", visible=False)
520
  try:
521
  ply, status = run_sharp(img)
522
  except Exception as e:
523
- return f"⚠ GPU rejected the request: {str(e)}\n(If this mentions quota/duration, you've hit your daily free ZeroGPU limit β€” wait for it to reset, or sign in with a HF account for a bigger quota.)", gr.update(value="", visible=False)
 
524
  if ply:
525
- script = make_load_script(ply)
526
- return status, gr.update(value=script, visible=True)
527
- return status, gr.update(value="", visible=False)
528
 
529
  def handle_dual(a, b, do_anim):
530
  if a is None or b is None:
@@ -563,7 +576,7 @@ with gr.Blocks(css=CSS, title="SplatWeb") as demo:
563
 
564
  return msg, html_out
565
 
566
- btn1.click(fn=handle_single, inputs=[img1], outputs=[st1, trigger1])
567
  btn2.click(fn=handle_dual, inputs=[imgA, imgB, anim_toggle], outputs=[st2, anim_file])
568
 
569
  gr.HTML("""
 
300
  <script type="module">
301
  import * as GaussianSplats3D from '@mkkellogg/gaussian-splats-3d';
302
 
303
+ // Expose loader globally so Gradio's tiny trigger script can call it.
304
+ // This version fetches the file as raw binary (no base64) β€” much faster
305
+ // on mobile, since base64 inflates size ~33% AND forces a slow manual
306
+ // byte-by-byte decode loop that can freeze the page for large models.
307
+ window._swViewer = null;
308
+ window._swBlobUrl = null;
309
+
310
+ window.swLoadUrl = async function(fileUrl, sizeMb) {
311
  const idle = document.getElementById('sw-idle');
312
  const loading = document.getElementById('sw-loading');
313
  const controls = document.getElementById('sw-controls');
 
316
  const canvas = document.getElementById('sw-canvas');
317
  const msg = document.getElementById('sw-load-msg');
318
 
319
+ idle.style.display = 'none';
320
+ loading.style.display = 'flex';
 
 
 
 
321
  controls.style.display = 'none';
322
+ if (sizeEl) sizeEl.textContent = (sizeMb ? sizeMb + ' MB Β· ' : '') + 'WebGL Β· your GPU';
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
  try {
325
+ msg.textContent = 'DOWNLOADING SCENE…';
326
+ const resp = await fetch(fileUrl);
327
+ if (!resp.ok) throw new Error('fetch failed: ' + resp.status);
328
+ const buf = await resp.arrayBuffer();
329
+ const blobUrl = URL.createObjectURL(new Blob([buf], { type: 'application/octet-stream' }));
330
+
331
+ if (window._swBlobUrl) { try { URL.revokeObjectURL(window._swBlobUrl); } catch(e) {} }
332
+ window._swBlobUrl = blobUrl;
333
+
334
+ if (window._swViewer) {
335
+ try { window._swViewer.dispose(); } catch(e) {}
336
+ window._swViewer = null;
337
+ }
338
+
339
  const viewer = new GaussianSplats3D.Viewer({
340
  canvas,
341
  cameraUp: [0, -1, 0],
 
346
  window._swViewer = viewer;
347
 
348
  msg.textContent = 'RENDERING GAUSSIANS…';
349
+ await viewer.addSplatScene(blobUrl, { progressiveLoad: true });
350
 
351
  loading.style.display = 'none';
352
  controls.style.display = 'flex';
 
361
  </script>
362
 
363
  <script>
364
+ // Download using the blob URL we already fetched β€” no re-decoding needed.
365
  function swDownload() {
366
+ if (!window._swBlobUrl) return;
 
 
 
 
367
  const a = document.createElement('a');
368
+ a.href = window._swBlobUrl;
369
  a.download = 'scene.ply';
370
  document.body.appendChild(a);
371
  a.click();
 
375
  """
376
 
377
 
378
+ # ── Tiny trigger: waits for the hidden File component's link to appear/
379
+ # update in the DOM, then hands its URL to the viewer. No file bytes ever
380
+ # pass through Python string formatting or the Gradio websocket payload β€”
381
+ # only a short bit of JS does, so this stays fast no matter how big the
382
+ # .ply is.
383
+ def make_load_trigger(size_mb) -> str:
384
  return f"""
385
  <script>
386
+ (function() {{
387
+ const host = document.getElementById('sw-ply-file');
388
+ if (!host) return;
389
+ function tryFire() {{
390
+ const a = host.querySelector('a[href]');
391
+ if (a && a.href && typeof window.swLoadUrl === 'function') {{
392
+ window.swLoadUrl(a.href, '{size_mb}');
393
+ return true;
394
+ }}
395
+ return false;
396
  }}
397
+ if (tryFire()) return;
398
+ const obs = new MutationObserver(function() {{ if (tryFire()) obs.disconnect(); }});
399
+ obs.observe(host, {{ childList: true, subtree: true, attributes: true }});
400
+ setTimeout(function() {{ obs.disconnect(); }}, 15000);
401
  }})();
402
  </script>
403
  """
 
460
  # 3D viewer β€” always visible, waits for content
461
  gr.HTML(VIEWER_SHELL)
462
 
463
+ # Hidden File component β€” Gradio serves this over its normal
464
+ # file route, so the browser can fetch() the raw bytes instead
465
+ # of us shipping a giant base64 string through the page.
466
+ ply_file = gr.File(visible=False, elem_id="sw-ply-file", label="ply")
467
+
468
+ # Hidden trigger β€” tiny script (no file data) that tells the
469
+ # viewer where to fetch the model from once ply_file updates
470
  trigger1 = gr.HTML(value="", visible=False)
471
 
472
  # ── Tab 2: Two angles + animation ───────────────────
 
528
  # ── Handlers ──────────────────────────────────────────────
529
  def handle_single(img):
530
  if img is None:
531
+ return "⚠ Please upload a photo first.", gr.update(value=None, visible=False), gr.update(value="", visible=False)
532
  try:
533
  ply, status = run_sharp(img)
534
  except Exception as e:
535
+ return (f"⚠ GPU rejected the request: {str(e)}\n(If this mentions quota/duration, you've hit your daily free ZeroGPU limit β€” wait for it to reset, or sign in with a HF account for a bigger quota.)",
536
+ gr.update(value=None, visible=False), gr.update(value="", visible=False))
537
  if ply:
538
+ size_mb = round(os.path.getsize(ply) / 1024 / 1024, 2)
539
+ return status, gr.update(value=ply, visible=False), gr.update(value=make_load_trigger(size_mb), visible=True)
540
+ return status, gr.update(value=None, visible=False), gr.update(value="", visible=False)
541
 
542
  def handle_dual(a, b, do_anim):
543
  if a is None or b is None:
 
576
 
577
  return msg, html_out
578
 
579
+ btn1.click(fn=handle_single, inputs=[img1], outputs=[st1, ply_file, trigger1])
580
  btn2.click(fn=handle_dual, inputs=[imgA, imgB, anim_toggle], outputs=[st2, anim_file])
581
 
582
  gr.HTML("""