Spaces:
Sleeping
Sleeping
Download superres_widget.html from ShesterG/visual-tree: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/spaces/ShesterG/visual-tree/resolve/main/superres_widget.html
- Command line
-
hf download hf://spaces/ShesterG/visual-tree/superres_widget.html
-
curl -L -o superres_widget.html https://huggingface.co/spaces/ShesterG/visual-tree/resolve/main/superres_widget.html
10.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <style> | |
| html,body{margin:0;height:100%;background:#0f1117;color:#e8eaf0;font-family:'Inter','Segoe UI',sans-serif;overflow:hidden} | |
| #wrap{display:flex;height:100vh;gap:18px;padding:14px;box-sizing:border-box} | |
| /* super-res video LEFT, tree RIGHT */ | |
| #left{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;justify-content:center} | |
| #right{flex:1 1 auto;position:relative} | |
| svg{width:100%;height:100%} | |
| .edge{stroke:#3a3f55;stroke-width:1} | |
| .node{transition:fill .15s ease} | |
| #imgbox{position:relative;border-radius:8px;overflow:hidden;box-shadow:0 0 0 1px #2a2d3e} | |
| #cv{display:block} | |
| #cap{font-size:12px;color:#7b8290;margin-top:8px;width:360px;text-align:center;line-height:1.35; | |
| height:4.05em;overflow:hidden;display:flex;align-items:center;justify-content:center} | |
| #ctrl{margin-top:10px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center} | |
| button{background:#1a2240;color:#e8eaf0;border:1px solid #5b9cf6;border-radius:6px;padding:5px 11px;font-size:13px;cursor:pointer} | |
| button:hover{background:#24305a} | |
| button:disabled{opacity:.5;cursor:default} | |
| #dl{border-color:#3ee08a} | |
| .legend{position:absolute;right:10px;top:8px;font-size:12px;color:#c8ccd8;line-height:1.7} | |
| .dot{display:inline-block;width:11px;height:11px;border-radius:50%;margin-right:6px;vertical-align:middle;border:1px solid #0f1117} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="wrap"> | |
| <div id="left"> | |
| <div id="imgbox"><canvas id="cv"></canvas><img id="img" style="display:none"></div> | |
| <div id="cap"></div> | |
| <div id="ctrl"> | |
| <button id="restart">⟲ Restart</button> | |
| <button id="play">⏸ Pause</button> | |
| <button id="back">◂ Back</button> | |
| <button id="step">Forward ▸</button> | |
| <button id="dl">⬇ Download</button> | |
| </div> | |
| </div> | |
| <div id="right"> | |
| <svg id="svg"></svg> | |
| <div class="legend"> | |
| <div><span class="dot" style="background:#ffffff"></span>not reached yet</div> | |
| <div><span class="dot" style="background:#f4a261"></span>in the frontier (queued)</div> | |
| <div><span class="dot" style="background:#5b9cf6"></span>now sharpening</div> | |
| <div><span class="dot" style="background:#b00018"></span>already looked at</div> | |
| <div style="margin-top:6px;border-top:1px solid #2a2d3e;padding-top:6px">image starts blurry (root) → each region <b style="color:#3ee08a">snaps sharp</b> at its leaf</div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const WHITE='#ffffff', QUEUED='#f4a261', PRESENT='#5b9cf6', PAST='#b00018'; | |
| let T, G, P, NODES, ORDER, children, N, FMAX, maxdepth; | |
| let f, timer, playing=true; | |
| let S=440, R0=26, NB=16, blurLv=[]; // blur pyramid (level 0 = sharp) | |
| const img=document.getElementById('img'), cv=document.getElementById('cv'), ctx=cv.getContext('2d'); | |
| const svg=document.getElementById('svg'), right=document.getElementById('right'), cap=document.getElementById('cap'); | |
| function sizeImage(){ | |
| S=Math.round(Math.min(window.innerHeight*0.58, window.innerWidth*0.34, 440)); | |
| cv.width=S; cv.height=S; cv.style.width=S+'px'; cv.style.height=S+'px'; | |
| R0=Math.max(8, Math.round(S*0.06)); // max blur radius scales with canvas | |
| } | |
| // NB full-image blur levels: draw the sharp image, then the blurred image over it | |
| // (the sharp base keeps borders from darkening where the blur samples off-canvas). | |
| function buildBlur(){ | |
| blurLv=[]; | |
| for(let i=0;i<NB;i++){ | |
| const r=R0*i/(NB-1); | |
| const c=document.createElement('canvas'); c.width=S; c.height=S; | |
| const cx=c.getContext('2d'); | |
| cx.drawImage(img,0,0,S,S); | |
| if(r>=0.3){ cx.filter='blur('+r.toFixed(2)+'px)'; cx.drawImage(img,0,0,S,S); cx.filter='none'; } | |
| blurLv.push(c); | |
| } | |
| } | |
| // priority = MEAN CLS-attention over the segment's patches (favors small salient parts). | |
| const PRI = n => (n.attm!=null ? n.attm : n.att/Math.max(n.n,1)); | |
| function buildOrder(){ | |
| children=NODES.map(()=>[]); | |
| NODES.forEach(n=>{ if(n.parent>=0) children[n.parent].push(n.id); }); | |
| const q=[0], steps=[]; | |
| while(q.length){ | |
| let bi=0; for(let i=1;i<q.length;i++) if(PRI(NODES[q[i]])>PRI(NODES[q[bi]])) bi=i; | |
| const id=q.splice(bi,1)[0]; children[id].forEach(c=>q.push(c)); steps.push(id); | |
| } | |
| return steps; | |
| } | |
| // leaf -> 0 (fully sharp); else coarser, linear in depth (root = fully blurred). | |
| function targetRadius(j){ return NODES[j].leaf ? 0 : R0*(maxdepth-NODES[j].depth)/Math.max(maxdepth,1); } | |
| // per-patch blur radius after looking at ORDER[0..looked-1] (deepest looked segment wins). | |
| function curRadius(f){ | |
| const c=new Float32Array(P).fill(R0), looked=Math.min(f,N); | |
| for(let i=0;i<looked;i++){ const j=ORDER[i], tr=targetRadius(j); NODES[j].patches.forEach(p=>{ c[p]=tr; }); } | |
| return c; | |
| } | |
| function renderImage(f){ | |
| const cell=S/G, cur=curRadius(f); | |
| ctx.clearRect(0,0,S,S); | |
| for(let p=0;p<P;p++){ | |
| const lvf=Math.max(0,Math.min(NB-1, cur[p]/R0*(NB-1))); | |
| const lo=Math.floor(lvf), hi=Math.min(lo+1,NB-1), fr=lvf-lo; | |
| const c=(p%G)*cell, r=Math.floor(p/G)*cell; | |
| ctx.globalAlpha=1; ctx.drawImage(blurLv[lo], c,r,cell,cell, c,r,cell,cell); | |
| if(fr>0.002){ ctx.globalAlpha=fr; ctx.drawImage(blurLv[hi], c,r,cell,cell, c,r,cell,cell); } | |
| } | |
| ctx.globalAlpha=1; | |
| // blue outline of the segment currently being sharpened (ties the video to the tree) | |
| const present=(f>=1&&f<=N)?ORDER[f-1]:null; | |
| if(present!=null){ const ps=NODES[present].patches, set=new Set(ps); | |
| ctx.strokeStyle='rgba(91,156,246,0.95)'; ctx.lineWidth=2; | |
| ps.forEach(p=>{ const rr0=Math.floor(p/G),cc0=p%G; | |
| [[rr0-1,cc0],[rr0+1,cc0],[rr0,cc0-1],[rr0,cc0+1]].forEach(([rr,cc])=>{ | |
| if(rr<0||rr>=G||cc<0||cc>=G||!set.has(rr*G+cc)){ | |
| ctx.beginPath(); | |
| if(rr<rr0){ctx.moveTo(cc0*cell,rr0*cell);ctx.lineTo((cc0+1)*cell,rr0*cell);} | |
| else if(rr>rr0){ctx.moveTo(cc0*cell,(rr0+1)*cell);ctx.lineTo((cc0+1)*cell,(rr0+1)*cell);} | |
| else if(cc<cc0){ctx.moveTo(cc0*cell,rr0*cell);ctx.lineTo(cc0*cell,(rr0+1)*cell);} | |
| else{ctx.moveTo((cc0+1)*cell,rr0*cell);ctx.lineTo((cc0+1)*cell,(rr0+1)*cell);} | |
| ctx.stroke(); | |
| }}); | |
| }); | |
| } | |
| // progress bar (matches the orange bar in the exported mp4) | |
| ctx.fillStyle='#f4a261'; ctx.fillRect(0,0,S*Math.min(f,N)/Math.max(N,1),5); | |
| } | |
| let circles=[]; | |
| function drawTree(){ | |
| const W=right.clientWidth, H=right.clientHeight, pad=28; | |
| svg.setAttribute('viewBox',`0 0 ${W} ${H}`); | |
| const xs=x=>pad+(x/Math.max(T.nleaves-1,1))*(W-2*pad); | |
| const ys=d=>pad+(d/Math.max(T.maxdepth,1))*(H-2*pad); | |
| let s=''; | |
| NODES.forEach(n=>{ if(n.parent>=0){const p=NODES[n.parent]; | |
| s+=`<line class="edge" x1="${xs(p.x)}" y1="${ys(p.depth)}" x2="${xs(n.x)}" y2="${ys(n.depth)}"/>`;}}); | |
| NODES.forEach(n=>{ const r=Math.min(20,2.5+1.9*Math.sqrt(n.att)); | |
| s+=`<circle class="node" data-id="${n.id}" cx="${xs(n.x)}" cy="${ys(n.depth)}" r="${r}" | |
| fill="${WHITE}" stroke="#0f1117" stroke-width="1"/>`;}); | |
| svg.innerHTML=s; circles=[...svg.querySelectorAll('.node')]; | |
| } | |
| function render(){ | |
| const st=NODES.map(()=>WHITE), looked=Math.min(f,N); | |
| for(let i=0;i<looked;i++) st[ORDER[i]]=PAST; | |
| const pushed=new Set([0]); | |
| for(let i=0;i<looked;i++) children[ORDER[i]].forEach(c=>pushed.add(c)); | |
| pushed.forEach(id=>{ if(st[id]===WHITE) st[id]=QUEUED; }); | |
| const present=(f>=1 && f<=N)? ORDER[f-1] : null; | |
| if(present!=null) st[present]=PRESENT; | |
| circles.forEach(el=>{ el.setAttribute('fill', st[+el.dataset.id]); }); | |
| renderImage(f); | |
| if(f===0) cap.textContent='Fully blurred = root segment. The highest-attention segment sharpens next.'; | |
| else if(f<=N){ const id=ORDER[f-1], nch=children[id].length; | |
| cap.textContent=`Step ${f}/${N}: sharpen node #${id} (mean ${PRI(NODES[id]).toFixed(2)}% attn/patch, ${NODES[id].n} patches)`+ | |
| (nch?` → push ${nch} child segment${nch>1?'s':''}.`:` → leaf: this region snaps to full detail.`); } | |
| else cap.textContent='Done — every region has snapped to full resolution.'; | |
| } | |
| function goTo(nf){ f=Math.max(0,Math.min(FMAX,nf)); render(); } | |
| function stop(){ playing=false; clearInterval(timer); document.getElementById('play').textContent='▶ Play'; } | |
| function start(){ playing=true; document.getElementById('play').textContent='⏸ Pause'; | |
| clearInterval(timer); timer=setInterval(()=>{ if(f>=FMAX){ stop(); return; } goTo(f+1); },700); } | |
| document.getElementById('back').onclick=()=>{ stop(); goTo(f-1); }; | |
| document.getElementById('step').onclick=()=>{ stop(); goTo(f+1); }; | |
| document.getElementById('play').onclick=()=>{ if(f>=FMAX) goTo(0); playing?stop():start(); }; | |
| document.getElementById('restart').onclick=()=>{ goTo(0); start(); }; | |
| // ---- client-side download: record the canvas play-through as a .webm ---- | |
| const dlBtn=document.getElementById('dl'); | |
| if(typeof MediaRecorder==='undefined' || !cv.captureStream){ dlBtn.style.display='none'; } | |
| dlBtn.onclick=()=>{ | |
| stop(); const stream=cv.captureStream(30); | |
| let mime='video/webm;codecs=vp9'; if(!MediaRecorder.isTypeSupported(mime)) mime='video/webm'; | |
| const rec=new MediaRecorder(stream,{mimeType:mime, videoBitsPerSecond:6e6}), chunks=[]; | |
| rec.ondataavailable=e=>{ if(e.data.size) chunks.push(e.data); }; | |
| rec.onstop=()=>{ const blob=new Blob(chunks,{type:mime}); const url=URL.createObjectURL(blob); | |
| const a=document.createElement('a'); a.href=url; a.download='reveal_superres.webm'; | |
| document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); | |
| dlBtn.textContent='⬇ Download'; dlBtn.disabled=false; start(); }; | |
| dlBtn.textContent='● recording…'; dlBtn.disabled=true; | |
| rec.start(); goTo(0); | |
| let k=0; const HOLD=4, tick=setInterval(()=>{ // hold each look a few frames, then advance | |
| if(k>=(N+2)*HOLD){ clearInterval(tick); rec.stop(); return; } | |
| goTo(Math.floor(k/HOLD)); k++; | |
| }, 1000/30); | |
| }; | |
| window.addEventListener('resize',()=>{ if(T){ sizeImage(); buildBlur(); drawTree(); render(); } }); | |
| let dataReady=false, imgReady=false; | |
| function go(){ if(!(dataReady&&imgReady)) return; | |
| sizeImage(); P=G*G; buildBlur(); ORDER=buildOrder(); N=ORDER.length; FMAX=N+1; drawTree(); goTo(0); start(); } | |
| img.onload=()=>{ imgReady=true; go(); }; | |
| T=window.TREE; G=T.g; NODES=T.nodes; maxdepth=T.maxdepth; dataReady=true; | |
| img.src=window.IMG_SRC; // injected data URI | |
| </script> | |
| </body> | |
| </html> | |