Download brain_lab/static/feature-view.js from Chungulus/brain-lab: direct link, hf CLI and curl.
- Browser
- Download file 6.84 kB
-
https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/feature-view.js
- Command line
-
hf download hf://spaces/Chungulus/brain-lab/brain_lab/static/feature-view.js
-
curl -L -o feature-view.js https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/feature-view.js
6.84 kB
| // Fixed image filters provide an honest illustrative CNN view; API internals | |
| // are unavailable. The output labels/probabilities are actual visual responses. | |
| export const featureHTML = () => `<div class="feature-toolbar"><span class="note" style="margin:0">Drawing → filters → features → guesses</span><button class="secondary" id="expand-features">Expand view ↗</button></div><div id="feature-home"><div class="feature-scroll" id="feature-scroll"><canvas id="feature-network" width="1140" height="530" aria-label="Illustrative image-filter layers connected to actual API guesses"></canvas></div></div><p class="note" id="feature-status">Tiles are fixed filters of your drawing, not Qwen or Decider’s internal activations. Output bars show actual visual candidate probabilities.</p><dialog id="feature-dialog" class="feature-dialog"><div class="toolbar"><h2>Inside the drawing</h2><button class="secondary" id="close-features">Close</button></div><div id="feature-full"></div><p class="note">Illustrative fixed filters · actual API guesses at the output</p></dialog>`; | |
| const kernels = [ | |
| [-1,0,1,-2,0,2,-1,0,1], [-1,-2,-1,0,0,0,1,2,1], | |
| [0,-1,0,-1,4,-1,0,-1,0], [1,1,1,1,1,1,1,1,1].map(x=>x/9), | |
| [2,-1,-1,-1,2,-1,-1,-1,2], [-1,-1,2,-1,2,-1,2,-1,-1] | |
| ]; | |
| const names=['Vertical edges','Horizontal edges','Fine detail','Blur / ink density','Diagonal ↘','Diagonal ↗']; | |
| function conv(a,n,k){const out=new Float32Array(n*n);for(let y=0;y<n;y++)for(let x=0;x<n;x++){let v=0;for(let j=-1;j<=1;j++)for(let i=-1;i<=1;i++)v+=a[Math.max(0,Math.min(n-1,y+j))*n+Math.max(0,Math.min(n-1,x+i))]*k[(j+1)*3+i+1];out[y*n+x]=v}return out} | |
| function pool(a,n){const m=n/2,out=new Float32Array(m*m);for(let y=0;y<m;y++)for(let x=0;x<m;x++)out[y*m+x]=Math.max(a[y*2*n+x*2],a[y*2*n+x*2+1],a[(y*2+1)*n+x*2],a[(y*2+1)*n+x*2+1]);return out} | |
| function tile(a,n,signed=true){const c=document.createElement('canvas');c.width=c.height=n;const ctx=c.getContext('2d'),im=ctx.createImageData(n,n);let max=.001;for(const v of a)max=Math.max(max,Math.abs(v));a.forEach((v,i)=>{const t=Math.max(-1,Math.min(1,v/max)),color=signed?(t<0?[213,86,60]:[47,139,181]):[47,139,181],q=signed?Math.abs(t):Math.max(0,t);for(let j=0;j<3;j++)im.data[i*4+j]=Math.round(248*(1-q)+color[j]*q);im.data[i*4+3]=255});ctx.putImageData(im,0,0);return c} | |
| export function featureView(source){ | |
| const canvas=document.querySelector('#feature-network'),ctx=canvas.getContext('2d'); | |
| const tiny=document.createElement('canvas');tiny.width=tiny.height=48; | |
| let layers=[],rows=[],busy=false,alive=true,raf,queued=false,hover=null; | |
| const xs=[55,235,385,540,710,960],ys=Array.from({length:6},(_,i)=>100+i*65); | |
| function update(){const c=tiny.getContext('2d');c.drawImage(source,0,0,48,48);const pixels=c.getImageData(0,0,48,48).data,a=new Float32Array(48*48);for(let i=0;i<a.length;i++)a[i]=1-(pixels[i*4]+pixels[i*4+1]+pixels[i*4+2])/765;const filtered=kernels.map(k=>conv(a,48,k)),relu=filtered.map(a=>a.map(v=>Math.max(0,v))),pooled=relu.map(a=>pool(a,48)),deep=pooled.map((a,i)=>conv(a,24,kernels[(i+2)%6]));layers=[[tile(a,48,false)],filtered.map(a=>tile(a,48)),relu.map(a=>tile(a,48,false)),pooled.map(a=>tile(a,24,false)),deep.map(a=>tile(a,24))];queued=false;draw()} | |
| function draw(t=performance.now()){ | |
| if(!alive)return;ctx.fillStyle='#fbfcf9';ctx.fillRect(0,0,1140,530); | |
| const headers=[['input','48 × 48 × 1'],['conv_1','48 × 48 × 6'],['relu_1','48 × 48 × 6'],['max_pool','24 × 24 × 6'],['conv_2','24 × 24 × 6'],['output','open vocabulary']]; | |
| ctx.textAlign='center';headers.forEach(([h,s],i)=>{ctx.fillStyle='#4c6258';ctx.font='13px Manrope, sans-serif';ctx.fillText(h,xs[i]+27,27);ctx.fillStyle='#97a298';ctx.font='10px sans-serif';ctx.fillText(s,xs[i]+27,45)}); | |
| // Dense, softly curved connections echo the supplied feature-map reference. | |
| ctx.lineWidth=.8; | |
| for(let col=0;col<5;col++)for(let a=0;a<(col===0?1:6);a++)for(let b=0;b<6;b++){ | |
| const y1=col===0?263:ys[a]+27,y2=ys[b]+27,x1=xs[col]+54,x2=xs[col+1]; | |
| ctx.strokeStyle=busy&&Math.floor(t/450)%5===col?'#b7cdc966':'#b8c5ba28';ctx.beginPath();ctx.moveTo(x1,y1);ctx.bezierCurveTo(x1+50,y1,x2-50,y2,x2,y2);ctx.stroke(); | |
| } | |
| layers.forEach((tiles,col)=>tiles.forEach((im,row)=>{const y=col===0?236:ys[row];ctx.drawImage(im,xs[col],y,54,54);ctx.strokeStyle='#dbe4db';ctx.strokeRect(xs[col],y,54,54);if(hover?.col===col&&hover.row===row){ctx.strokeStyle='#327764';ctx.lineWidth=2;ctx.strokeRect(xs[col]-2,y-2,58,58);ctx.lineWidth=.8}})); | |
| ctx.textAlign='left';for(let i=0;i<6;i++){const row=rows[i],y=ys[i];ctx.fillStyle=row?'#51685a':'#a5afa4';ctx.font='12px sans-serif';let label=row?.label||(busy?'Searching…':'Awaiting a guess');while(ctx.measureText(label).width>148)label=label.slice(0,-2)+'…';ctx.fillText(label,xs[5],y+19);ctx.fillStyle='#e7ece4';ctx.fillRect(xs[5],y+31,150,5);if(row){ctx.fillStyle=i===0?'#488f77':'#9fbbb0';ctx.fillRect(xs[5],y+31,150*row.probability,5);ctx.fillStyle='#8a998d';ctx.font='9px sans-serif';ctx.fillText(Math.round(row.probability*100)+'%',xs[5],y+50)}} | |
| const g=ctx.createLinearGradient(235,0,764,0);g.addColorStop(0,'#d5563c');g.addColorStop(.5,'#f8f8f4');g.addColorStop(1,'#2f8bb5');ctx.fillStyle=g;ctx.fillRect(235,505,529,5);ctx.fillStyle='#94a196';ctx.font='9px sans-serif';ctx.fillText('negative',235,523);ctx.fillText('0',493,523);ctx.fillText('positive · normalized per tile',635,523); | |
| if(hover){ctx.fillStyle='#476c5e';ctx.font='11px sans-serif';ctx.fillText(hover.col===0?'Drawing pixels':names[hover.row],55,485)} | |
| } | |
| function animate(t){if(busy){draw(t);raf=requestAnimationFrame(animate)}} | |
| const dialog=document.querySelector('#feature-dialog'),scroll=document.querySelector('#feature-scroll'); | |
| document.querySelector('#expand-features').onclick=()=>{document.querySelector('#feature-full').append(scroll);dialog.showModal();draw()}; | |
| const close=()=>dialog.close();document.querySelector('#close-features').onclick=close; | |
| dialog.onclose=()=>document.querySelector('#feature-home')?.append(scroll); | |
| canvas.onpointermove=e=>{const r=canvas.getBoundingClientRect(),x=(e.clientX-r.left)*1140/r.width,y=(e.clientY-r.top)*530/r.height;hover=null;for(let col=0;col<5;col++)for(let row=0;row<(col===0?1:6);row++){const yy=col===0?236:ys[row];if(x>=xs[col]&&x<=xs[col]+54&&y>=yy&&y<=yy+54)hover={col,row}}if(!busy)draw()};canvas.onpointerleave=()=>{hover=null;if(!busy)draw()}; | |
| update(); | |
| return {changed(){rows=[];if(!queued){queued=true;requestAnimationFrame(()=>{if(alive)update()})}},result(candidates){rows=candidates.slice(0,6);draw()},processing(value){busy=value;cancelAnimationFrame(raf);if(busy&&!matchMedia('(prefers-reduced-motion: reduce)').matches)raf=requestAnimationFrame(animate);else draw()},destroy(){alive=false;cancelAnimationFrame(raf);if(dialog.open)dialog.close()}}; | |
| } | |