Download experiments.js from Jam1e3/wing: direct link, hf CLI and curl.
- Browser
- Download file 25.6 kB
-
https://huggingface.co/spaces/Jam1e3/wing/resolve/main/experiments.js
- Command line
-
hf download hf://spaces/Jam1e3/wing/experiments.js
-
curl -L -o experiments.js https://huggingface.co/spaces/Jam1e3/wing/resolve/main/experiments.js
25.6 kB
| // Transcribed from https://mikuz12.github.io/wing/main.pdf: Tables 1–3, Figures 3–5, and Table 12. | |
| // Rows preserve the paper's reported numbers; no estimated error bars. | |
| (() => { | |
| const datasets = { | |
| semantics: {title:'Interaction semantics survive debiasing', source:'Figure 3(c)', page:7, note:'LARYBench accuracy with frozen latent features.', views:[{label:'Action classification', max:100, unit:'%', decimals:2, rows:[['WING-LAM',55.47],['CD-LAM',53.44],['DreamDojo',53.43],['LAPA',20.89],['VILLA-X',15.76],['UniVLA',14.61]]}]}, | |
| similarity: {title:'Low frequencies carry the strongest correspondence', source:'Figure 3(d)', page:7, note:'Similarity: matched pairs ↑ · mismatched pairs ↓.', views:[{label:'Matched human–robot pairs',max:1,unit:'',decimals:2,rows:[['Low frequency',.58],['Full trajectory',.44],['High frequency',.20]], highlight:'Low frequency'},{label:'Mismatched human–robot pairs',max:1,unit:'',decimals:2,rows:[['Low frequency',.07],['Full trajectory',.18],['High frequency',.15]],highlight:'Low frequency'}]}, | |
| simulation:{title:'Simulation performance',source:'Tables 1–2',page:7,note:'Success rate (%). All methods from the paper’s main simulation tables are included. Fast-WAM on RoboCasa–GR1 is our reproduction.',views:[]}, | |
| real:{title:'Real-world performance',source:'Figure 4 · Table 12',page:8,note:'Standard-setting values follow Figure 4; generalization values use the more precise averages in Table 12. Progress is the task score on a 0–10 scale.',views:[]}, | |
| components:{title:'Interaction-centric encoding + spectral guidance',source:'Table 3',page:8,note:'Controlled setting without egocentric WAM pretraining. “Neither” uses DreamDojo latents and time-domain guidance; “DCT only” uses DreamDojo latents with spectral guidance.',views:[]}, | |
| bandwidth:{title:'How much of the spectrum should we keep?',source:'Figure 5(a)',page:9,note:'Success rate (%). K = 8 retains the full spectrum in this ablation; K = 4 gives the best result in each suite.',views:[]}, | |
| pretraining:{title:'Same video budget, different learning signals',source:'Figure 5(b)',page:9,note:'200 hours of egocentric pretraining for every strategy. “No debiasing” replaces WING-LAM with DreamDojo LAM. Direct latent-action pretraining uses latent actions as prediction targets.',views:[]} | |
| }; | |
| function addViews(key, labels, names, values, options={}) { | |
| labels.forEach((label,i) => datasets[key].views.push({label,max:100,unit:'%',decimals:1,...options,rows:names.map((name,j)=>[name,values[j][i]])})); | |
| } | |
| addViews('simulation',['LIBERO · Average','LIBERO · Spatial','LIBERO · Object','LIBERO · Goal','LIBERO · Long'],['π₀','π₀.₅','LAPA','UniVLA','LaWAM','VLA-JEPA','Motus','LingBot-VA','Fast-WAM','WING'],[ | |
| [94.40,98,96.8,94.4,88.4],[96.9,98.8,98.2,98,92.4],[65.7,73.8,74.6,58.8,55.4],[95.2,96.5,96.8,95.6,92],[98.6,99.4,99.6,98.4,97],[97.2,96.2,99.6,97.2,95.8],[97.7,96.8,99.8,96.6,97.6],[98.5,98.5,99.6,97.2,98.5],[97.6,98.2,100,97,95.2],[99.2,99,100,98.8,99] | |
| ],{decimals:2}); | |
| addViews('simulation',['RoboTwin 2.0 · Average','RoboTwin 2.0 · Clean','RoboTwin 2.0 · Randomized'],['π₀','π₀.₅','X-VLA','LingBot-VLA','LaWAM','Motus','Fast-WAM','GigaWorld-Policy','LingBot-VA','WING'],[ | |
| [62.16,65.92,58.40],[79.75,82.74,76.76],[72.85,72.9,72.8],[87.62,88.56,86.68],[91.22,92.64,89.8],[87.84,88.66,87.02],[91.83,91.88,91.78],[85.70,86.36,85.04],[92.20,92.9,91.5],[93.8,94.56,93.04] | |
| ],{decimals:2}); | |
| addViews('simulation',['RoboCasa–GR1 · Average'],['GR00T-N1.6','StarVLA-OFT','DiT4DiT','LDA-1B','Being-H0.7','Fast-WAM','WING'],[[47.6],[48.8],[50.8],[55.4],[49.2],[51.9],[57.7]]); | |
| const tasks=['Battery Insertion','Battery Assembly','Pack Objects','Stack Cups']; | |
| const methods=['Fast-WAM','GR00T-N1.7','π₀.₅','WING']; | |
| addViews('real',tasks.map(t=>'Standard · '+t+' · Success'),methods,[[60,30,80,70],[40,35,60,70],[70,50,70,75],[75,55,90,80]]); | |
| addViews('real',tasks.map(t=>'Standard · '+t+' · Progress'),methods,[[6,5,8.5,8],[7,5.5,7.2,9],[7.3,6,8.1,8.5],[7.5,6,9,9.2]],{max:10,unit:' / 10'}); | |
| addViews('real',tasks.map(t=>'Generalization · '+t+' · Success'),methods,[[26.7,20,55,21.3],[20,13.3,17.5,15],[55,33.3,80,32.5],[50,31.7,78.8,37.5]]); | |
| addViews('real',tasks.map(t=>'Generalization · '+t+' · Progress'),methods,[[5.13,3.83,6.74,6.63],[4.33,3.25,4.11,6.08],[8.08,5.28,8.83,7.44],[8.17,5.08,8.63,8.03]],{max:10,unit:' / 10',decimals:2}); | |
| const suites=['LIBERO · Spatial','LIBERO · Object','LIBERO · Goal','LIBERO · Long','Real world · Standard','Real world · Generalization']; | |
| addViews('components',suites,['Neither','WING-LAM only','DCT only','Both'],[[99.2,97.2,96.2,92.6,65,36],[98,99.2,95.6,94.2,67.5,40.3],[99.2,98.2,98,96.2,71.3,43],[99.6,100,99.2,96.2,72.5,45]],{highlight:'Both'}); | |
| addViews('bandwidth',suites.slice(0,4),['K = 2','K = 8 (full)','K = 4'],[[97.2,98.6,95.2,94],[98.2,99.4,95.6,95.2],[99.6,100,99.2,96.2]],{highlight:'K = 4'}); | |
| addViews('pretraining',suites,['Hand-pose','Video-only','Latent action · no debiasing','Latent action','WING · no debiasing','WING'],[[98.2,99.4,97.4,96.2,72.5,40],[96.6,98.8,96,91.4,60,38],[94.2,96.4,92,84.4,58.8,30],[94,98.8,92.4,85.2,60,35.5],[98.6,98.4,97.6,96.4,68.8,41],[99,100,98.8,99,75,49.5]]); | |
| // Panel-specific ranges retain every reported value and label the actual axes. | |
| Object.assign(datasets.semantics.views[0], {min:10,max:60,ticks:[10,20,30,40,50,60]}); | |
| Object.assign(datasets.similarity.views[0], {min:0,max:.6,ticks:[0,.2,.4,.6]}); | |
| Object.assign(datasets.similarity.views[1], {min:0,max:.2,ticks:[0,.05,.1,.15,.2]}); | |
| [[96,97,98,99,100],[98,98.5,99,99.5,100],[94,96,98,100],[93,94,95,96,97]].forEach((ticks,i)=>Object.assign(datasets.bandwidth.views[i], {min:ticks[0],max:ticks[ticks.length-1],ticks})); | |
| const esc = value => String(value).replaceAll('&','&').replaceAll('<','<').replaceAll('"','"'); | |
| // Reuse the verified simulation values; each benchmark is a native HTML table. | |
| const tableGroups = [ | |
| {title:'LIBERO', indices:[1,2,3,4,0], headers:['Spatial','Object','Goal','Long','Avg.'], page:8}, | |
| {title:'RoboTwin 2.0', indices:[6,7,5], headers:['Clean','Rand.','Avg.'], page:8}, | |
| {title:'RoboCasa–GR1', indices:[8], headers:['Avg.'], page:7} | |
| ]; | |
| document.querySelector('#simulation-tables').innerHTML = `<div class="simulation-table-heading"><h4>Simulation performance</h4><span>Success rate (%)</span></div><div class="simulation-table-grid">${tableGroups.map(group => { | |
| const views=group.indices.map(i=>datasets.simulation.views[i]); | |
| const ranks=views.map(view=>[...new Set(view.rows.map(row=>row[1]))].sort((a,b)=>b-a)); | |
| return `<div class="simulation-table-panel"><table><caption>${group.title}<a href="https://mikuz12.github.io/wing/main.pdf#page=${group.page}" target="_blank" rel="noopener" aria-label="${group.title} results in the paper">↗</a></caption><thead><tr><th scope="col">Method</th>${group.headers.map(h=>`<th scope="col">${h}</th>`).join('')}</tr></thead><tbody>${views[0].rows.map(([name],r)=>`<tr class="${name==='WING'?'simulation-ours':''}"><th scope="row">${esc(name)}${group.indices[0]===8&&name==='Fast-WAM'?'†':''}</th>${views.map((view,c)=>{const val=view.rows[r][1],formatted=val.toFixed(view.decimals);return `<td>${val===ranks[c][0]?`<strong>${formatted}</strong>`:val===ranks[c][1]?`<span class="simulation-second">${formatted}</span>`:formatted}</td>`;}).join('')}</tr>`).join('')}</tbody></table></div>`; | |
| }).join('')}</div><p class="simulation-table-note">Best in <strong>bold</strong>; second best underlined. † Fast-WAM on RoboCasa–GR1 is our reproduction.</p>`; | |
| const componentViews=datasets.components.views; | |
| const componentMax=componentViews.map(v=>Math.max(...v.rows.map(r=>r[1]))); | |
| const configurations=[[false,false],[true,false],[false,true],[true,true]]; | |
| const mark=enabled=>`<span role="img" aria-label="${enabled?'Enabled':'Disabled'}" class="component-${enabled?'on':'off'}">${enabled?'✓':'✗'}</span>`; | |
| document.querySelector('#component-table-panel').innerHTML=`<div class="result-heading"><h4>Interaction-centric encoding + spectral guidance</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=8" target="_blank" rel="noopener">Table 3 ↗</a></div><div class="component-table-scroll" role="region" aria-label="Component ablation results" tabindex="0"><table><caption class="component-table-caption">Success rate (%) · Without egocentric WAM pretraining</caption><thead><tr><th colspan="2" scope="colgroup">Components</th><th colspan="4" scope="colgroup">LIBERO</th><th colspan="2" scope="colgroup">Real-world</th></tr><tr><th scope="col">WING-LAM</th><th scope="col">Spectral guidance</th><th scope="col">Spatial</th><th scope="col">Object</th><th scope="col">Goal</th><th scope="col">Long</th><th scope="col">Standard</th><th scope="col">Gen.</th></tr></thead><tbody>${configurations.map(([lam,dct],r)=>`<tr class="${r===3?'component-complete':''}"><th scope="row" aria-label="${esc(componentViews[0].rows[r][0])}">${mark(lam)}</th><td>${mark(dct)}</td>${componentViews.map((view,c)=>{const value=view.rows[r][1];return `<td>${value===componentMax[c]?`<strong>${value.toFixed(1)}</strong>`:value.toFixed(1)}</td>`;}).join('')}</tr>`).join('')}</tbody></table></div><p class="component-takeaway">Together, the two components improve real-world success by <strong>+7.5 pp</strong> in standard settings and <strong>+9.0 pp</strong> under generalization, compared with using neither.</p>`; | |
| const pretraining=document.querySelector('#pretraining-comparison'); | |
| const preMin=[90,95,90,80,55,25]; | |
| const preMax=[100,100,100,100,80,55]; | |
| const preNames=['Hand-pose','Video-only','Latent action','Latent action','WING','WING']; | |
| pretraining.innerHTML=`<div class="result-heading"><h4>Same video budget, different learning signals</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=9" target="_blank" rel="noopener">Figure 5(b) ↗</a></div><p class="pretraining-subtitle">200 hours of egocentric pretraining per strategy · Success rate (%)</p><div class="pretraining-scroll" role="region" aria-label="Scrollable strategy comparison" tabindex="0">${[[0,1,2],[3,4,5]].map((indices,band)=>`<table class="pretraining-table"><caption class="pretraining-table-caption">${band?'Long-horizon and real-world transfer':'LIBERO interaction suites'}</caption><thead><tr><th scope="col">Pretraining strategy</th>${indices.map(i=>`<th scope="col"><span>${suites[i].split(' · ')[1]}</span><small>${i<4?'LIBERO':'Real-world'} · ${preMin[i]}–${preMax[i]}%</small></th>`).join('')}</tr></thead><tbody>${preNames.map((name,r)=>`<tr class="${r===5?'pretraining-ours':r===4?'pretraining-variant':''} ${r===4?'pretraining-group-start':''}"><th scope="row">${name}${r===2||r===4?'<small>without debiasing</small>':''}</th>${indices.map(i=>{const v=datasets.pretraining.views[i].rows[r][1];return `<td><button type="button" class="pretraining-bar" aria-label="${esc(datasets.pretraining.views[i].rows[r][0])}, ${suites[i]}, ${v.toFixed(1)} percent" data-detail="${esc(datasets.pretraining.views[i].rows[r][0])} · ${suites[i]}: ${v.toFixed(1)}%"><span class="pretraining-track"><span class="pretraining-fill" style="width:${(v-preMin[i])/(preMax[i]-preMin[i])*100}%"></span></span><span class="pretraining-value">${v.toFixed(1)}</span></button></td>`;}).join('')}</tr>`).join('')}</tbody><tfoot><tr><td></td>${indices.map(i=>`<td><div class="pretraining-axis"><span>${preMin[i]}%</span><span>${(preMin[i]+preMax[i])/2}%</span><span>${preMax[i]}%</span></div></td>`).join('')}</tr></tfoot></table>`).join('')}</div><p class="pretraining-readout" aria-live="polite">Axes are truncated to show differences; each panel’s range is labeled. Hover, focus, or tap a bar for details.</p><p class="result-note">No debiasing: DreamDojo replaces WING-LAM. Pretraining targets: latent actions vs. spectral guidance (WING).</p>`; | |
| const preBars=[...pretraining.querySelectorAll('.pretraining-bar')]; | |
| preBars.forEach(button=>{ | |
| const activate=()=>{preBars.forEach(b=>b.classList.toggle('is-selected',b===button));pretraining.querySelector('.pretraining-readout').textContent=button.dataset.detail;}; | |
| button.addEventListener('pointerenter',activate);button.addEventListener('focus',activate);button.addEventListener('click',activate); | |
| button.addEventListener('pointerleave',()=>button.classList.remove('is-selected'));button.addEventListener('blur',()=>button.classList.remove('is-selected')); | |
| }); | |
| const realPanel=document.querySelector('#real-world-panel'); | |
| const realColors=['#c5cac0','#a7b09f','#7d8b77','#347eab']; | |
| realPanel.innerHTML=`<div class="result-heading"><h4>Real-world performance</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=8" target="_blank" rel="noopener">Figure 4 · Table 12 ↗</a></div><div class="real-legend">${methods.map((name,i)=>`<span><i style="background:${realColors[i]}"></i>${name}</span>`).join('')}</div><div class="real-scroll" tabindex="0" role="region" aria-label="Scrollable real-world results"><div class="real-conditions">${['Standard settings','Generalization settings (avg.)'].map((setting,c)=>`<section class="real-condition" aria-label="${setting}"><h5>${setting}</h5>${[0,1].map(metric=>`<div class="real-metric"><p>${metric?'Progress score · 0–10':'Success rate · %'}</p><div class="real-task-grid"><div class="real-gridlines" aria-hidden="true">${[0,.5,1].map(f=>`<span style="bottom:${f*100}%"><b>${f*(metric?10:100)}</b></span>`).join('')}</div>${tasks.map((task,t)=>{const view=datasets.real.views[c*8+metric*4+t];return `<div class="real-task" role="group" aria-label="${task}">${view.rows.map(([name,value],m)=>`<button type="button" class="real-bar ${m===3?'real-wing':''}" style="--real-height:${value/view.max*100}%;--real-color:${realColors[m]}" aria-label="${setting}, ${task}, ${name}, ${metric?'progress':'success'} ${value}${metric?' out of 10':' percent'}" data-detail="${setting} · ${task} · ${name}: ${value}${metric?' / 10':'%'}"><span class="real-fill"><span class="real-value">${Number(value.toFixed(view.decimals))}</span></span></button>`).join('')}</div>`;}).join('')}</div></div>`).join('')}<div class="real-task-names">${tasks.map(t=>`<span>${t}</span>`).join('')}</div></section>`).join('')}</div></div><p class="real-mobile-hint">Swipe horizontally to see both settings →</p><p class="real-detail" aria-live="polite">Four tasks · Four methods · Hover, focus, or tap a bar for details.</p><p class="result-note">Success measures full task completion; progress credits partial completion on a 0–10 scale. Generalization uses the precise averages from Table 12.</p>`; | |
| const realBars=[...realPanel.querySelectorAll('.real-bar')]; | |
| realBars.forEach(button=>{ | |
| const activate=()=>{realBars.forEach(b=>b.classList.toggle('is-selected',b===button));realPanel.querySelector('.real-detail').textContent=button.dataset.detail;}; | |
| button.addEventListener('pointerenter',activate);button.addEventListener('focus',activate);button.addEventListener('click',activate); | |
| const clear=()=>{button.classList.remove('is-selected');};button.addEventListener('pointerleave',clear);button.addEventListener('blur',clear); | |
| }); | |
| document.querySelectorAll('[data-chart]').forEach((card) => { | |
| const key=card.dataset.chart, data=datasets[key], id='chart-view-'+key+(card.dataset.viewIndex!==undefined?'-'+card.dataset.viewIndex:''); | |
| card.innerHTML=`<div class="result-heading"><h4>${data.title}</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=${data.page}" target="_blank" rel="noopener">${data.source} ↗</a></div><div class="result-toolbar"><label for="${id}">View <select id="${id}">${data.views.map((v,i)=>`<option value="${i}">${esc(v.label)}</option>`).join('')}</select></label><div class="result-legend"><span><i></i>Comparison</span><span><i class="result-blue"></i>WING / focus</span></div></div><p class="result-view-label"></p><div class="result-scroll" tabindex="0" role="region" aria-label="Scrollable chart"><div class="result-plot"></div></div><p class="result-swipe">Swipe horizontally to compare all methods →</p><p class="result-readout" aria-live="polite"></p><p class="result-note">${data.note}</p>`; | |
| const select=card.querySelector('select'), plot=card.querySelector('.result-plot'), readout=card.querySelector('.result-readout'); | |
| if (card.dataset.viewIndex !== undefined) { | |
| select.value=card.dataset.viewIndex; | |
| card.querySelector('.result-heading h4').textContent=data.views[Number(select.value)].label.replace('LIBERO · ', ''); | |
| } | |
| function render() { | |
| const view=data.views[Number(select.value)], fmt=v=>v.toFixed(view.decimals)+view.unit; | |
| const min=view.min ?? 0, range=view.max-min; | |
| const ticks=view.ticks ?? [0,.2,.4,.6,.8,1].map(f=>min+f*range); | |
| const focus=view.highlight || (key==='semantics'?'WING-LAM':'WING'); | |
| card.querySelector('.result-view-label').textContent=view.label; | |
| plot.style.setProperty('--chart-columns',view.rows.length); | |
| plot.style.minWidth=card.dataset.viewIndex!==undefined?'0':`${Math.max(360,view.rows.length*(card.closest('.result-pair')?56:91)+44)}px`; | |
| plot.innerHTML=`<div class="result-grid" aria-hidden="true">${[...ticks].reverse().map(t=>`<span style="bottom:${(t-min)/range*100}%"><b>${+t.toFixed(2)}${view.unit==='%'?'%':''}</b></span>`).join('')}</div><div class="result-bars">${view.rows.map(([name,value])=>`<button class="result-bar ${name===focus?'is-focus':''}" type="button" style="--bar-height:${(value-min)/range*100}%" aria-label="${esc(name)}: ${fmt(value)}; ${esc(view.label)}"><span class="result-column"><span class="result-fill"><span class="result-number">${value.toFixed(view.decimals)}</span></span></span><span class="result-name">${esc(name)}</span></button>`).join('')}</div>`; | |
| const defaultText=key==='semantics'?'Accuracy (%). Hover, focus, or tap a bar for details.':key==='bandwidth'?'SR (%). Hover, focus, or tap a bar for its exact value.':`${view.unit==='%'?(key==='semantics'?'Classification accuracy (%)':'SR (%)'):'Semantic similarity'} · ${min}–${view.max} scale${min>0?' (truncated)':''}. Hover, focus, or tap a bar for its exact value.`; | |
| readout.textContent=defaultText; | |
| const bars=[...plot.querySelectorAll('.result-bar')]; | |
| function highlight(button,i){bars.forEach(b=>b.classList.toggle('is-selected',b===button));readout.textContent=`${view.rows[i][0]} — ${fmt(view.rows[i][1])} · ${view.label}`;} | |
| bars.forEach((button,i)=>{button.addEventListener('pointerenter',()=>highlight(button,i));button.addEventListener('focus',()=>highlight(button,i));button.addEventListener('click',()=>highlight(button,i));button.addEventListener('pointerleave',()=>{if(document.activeElement!==button){button.classList.remove('is-selected');readout.textContent=defaultText;}});button.addEventListener('blur',()=>{button.classList.remove('is-selected');readout.textContent=defaultText;});}); | |
| } | |
| select.addEventListener('change',render);render(); | |
| }); | |
| })(); | |
| // Figure 3(b): exact means and standard deviations from graph/camera_sensitivity_with_std.py. | |
| const cameraSensitivityData = {"Ours": {"mean": [0.0025, 0.0053, 0.0117, 0.0288, 0.0655], "std": [0.0004, 0.0009, 0.002, 0.0043, 0.0072]}, "CD-lam": {"mean": [0.0314, 0.0617, 0.1186, 0.218, 0.3782], "std": [0.0054, 0.01, 0.0185, 0.0287, 0.0358]}, "LAPA": {"mean": [0.0982, 0.1628, 0.3108, 0.516, 0.6933], "std": [0.0168, 0.0306, 0.0517, 0.0643, 0.0781]}, "UniVLA": {"mean": [0.0236, 0.0792, 0.1534, 0.3043, 0.5808], "std": [0.0108, 0.0197, 0.028, 0.0542, 0.0763]}, "DreamDojo": {"mean": [0.0335, 0.0649, 0.1233, 0.2218, 0.3797], "std": [0.0061, 0.011, 0.0197, 0.0304, 0.0369]}, "villa-X": {"mean": [0.085, 0.1587, 0.2869, 0.4887, 0.7512], "std": [0.0152, 0.022, 0.0308, 0.0421, 0.0616]}}; | |
| (() => { | |
| const motion=document.querySelector('#motion-decodability'); | |
| const points=[['WING teacher',117,54,126,41,true],['WING student',146,97,153,88,true],['DreamDojo',299,172,246,158,false],['CD-LAM',317,194,270,225,false],['UniVLA',132,200,99,184,false],['LAPA',112,216,70,237,false],['VILLA-X',163,242,166,264,false]]; | |
| motion.innerHTML=`<div class="result-heading"><h4>Separate interaction from camera motion</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=7" target="_blank" rel="noopener">Figure 3(a) ↗</a></div><p class="diagnostic-subtitle">Motion decodability · MLP probes</p><svg class="diagnostic-svg" viewBox="0 0 400 330" role="group" aria-label="Motion decodability: WING representations retain interaction motion while reducing camera motion"><path d="M62 30V282H374" fill="none" stroke="#899486"/><path d="M159 30V282M62 200H374" fill="none" stroke="#bac3b5" stroke-dasharray="3 4"/><text x="159" y="299" text-anchor="middle">0</text><text x="53" y="204">0</text><text x="213" y="323" text-anchor="middle">Camera-motion R² →</text><text transform="translate(19 161) rotate(-90)" text-anchor="middle">Interaction-motion R² →</text>${points.map(([name,x,y,tx,ty,ours])=>`<g class="diagnostic-point" tabindex="0" role="img" aria-label="${name}" data-method="${name}"><circle cx="${x}" cy="${y}" r="${ours?7:6}" fill="${ours?'#347eab':'#a3aea0'}" stroke="#486052"/><text x="${tx}" y="${ty}" class="${ours?'diagnostic-ours':''}">${name}</text></g>`).join('')}</svg><p class="result-readout" aria-live="polite">WING retains interaction information with less camera-motion information.</p><p class="result-note">Preserve interaction motion; suppress camera motion.</p>`; | |
| motion.querySelectorAll('.diagnostic-point').forEach(point=>{ | |
| const show=()=>{motion.querySelectorAll('.diagnostic-point').forEach(p=>p.classList.toggle('is-selected',p===point));motion.querySelector('.result-readout').textContent=point.dataset.method+' · Camera-motion readout on the horizontal axis; interaction-motion readout on the vertical axis.';}; | |
| point.addEventListener('pointerenter',show);point.addEventListener('focus',show);point.addEventListener('click',show); | |
| point.addEventListener('pointerleave',()=>point.classList.remove('is-selected'));point.addEventListener('blur',()=>point.classList.remove('is-selected')); | |
| }); | |
| const card=document.querySelector('#camera-sensitivity'); | |
| const labels={'Ours':'WING','CD-lam':'CD-LAM','villa-X':'VILLA-X'}; | |
| const entries=Object.entries(cameraSensitivityData); | |
| const colors=['#347eab','#505b50','#758371','#939f89','#a9afa4','#bcc2b5']; | |
| const x=i=>60+i*72, y=v=>263-v/0.85*225; | |
| card.innerHTML=`<div class="result-heading"><h4>Stay stable as the camera moves</h4><a href="https://mikuz12.github.io/wing/main.pdf#page=7" target="_blank" rel="noopener">Figure 3(b) ↗</a></div><p class="diagnostic-subtitle">Camera sensitivity · Lower is better</p><div class="camera-legend">${entries.map(([name],i)=>`<button type="button" data-series="${i}" style="--series-color:${colors[i]}" aria-pressed="false"><i></i>${labels[name]||name}</button>`).join('')}</div><svg class="diagnostic-svg" viewBox="0 0 400 330" role="group" aria-label="Camera sensitivity across five perturbation levels">${[0,.2,.4,.6,.8].map(v=>`<path d="M60 ${y(v)}H348" stroke="#d6dacd"/><text x="50" y="${y(v)+4}" text-anchor="end">${v}</text>`).join('')}${[0,1,2,3,4].map(i=>`<text x="${x(i)}" y="284" text-anchor="middle">${i+1}</text>`).join('')}<text x="205" y="310" text-anchor="middle">Perturbation level →</text><text transform="translate(17 154) rotate(-90)" text-anchor="middle">Normalized sensitivity</text>${entries.map(([name,d],i)=>{const upper=d.mean.map((v,j)=>`${j?'L':'M'}${x(j)} ${y(v+d.std[j])}`).join(' '),lower=[...d.mean].reverse().map((v,j)=>`L${x(4-j)} ${y(v-d.std[4-j])}`).join(' ');return `<g class="camera-series" data-series="${i}"><path d="${upper} ${lower}Z" fill="${colors[i]}" opacity=".12"/><path d="${d.mean.map((v,j)=>`${j?'L':'M'}${x(j)} ${y(v)}`).join(' ')}" fill="none" stroke="${colors[i]}" stroke-width="${i?1.8:2.8}" ${i%2?'stroke-dasharray="5 3"':''}/>${d.mean.map((v,j)=>`<circle class="camera-point" tabindex="0" role="img" aria-label="${labels[name]||name}, level ${j+1}, ${v}, standard deviation ${d.std[j]}" data-readout="${labels[name]||name} · Level ${j+1}: ${v.toFixed(4)} ± ${d.std[j].toFixed(4)} (SD)" cx="${x(j)}" cy="${y(v)}" r="4.5" fill="${colors[i]}" stroke="#efefe7" stroke-width="1"/>`).join('')}</g>`;}).join('')}</svg><p class="result-readout" aria-live="polite">Select a legend entry or point to inspect a method.</p><p class="result-note">Lines: mean · Shaded bands: ±1 SD.</p>`; | |
| function selectSeries(index){card.querySelectorAll('.camera-series').forEach(g=>g.style.opacity=index===null||g.dataset.series===index?'1':'.2');card.querySelectorAll('.camera-legend button').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.series===index)));} | |
| card.querySelectorAll('.camera-legend button').forEach(button=>button.addEventListener('click',()=>{const next=button.getAttribute('aria-pressed')==='true'?null:button.dataset.series;selectSeries(next);card.querySelector('.result-readout').textContent=next===null?'All methods · Mean ±1 standard deviation':button.textContent+' · Select a point for its exact mean and standard deviation.';})); | |
| card.querySelectorAll('.camera-point').forEach(point=>{const show=()=>{selectSeries(point.parentElement.dataset.series);card.querySelector('.result-readout').textContent=point.dataset.readout;};point.addEventListener('pointerenter',show);point.addEventListener('focus',show);point.addEventListener('click',show);}); | |
| })(); | |