Download brain_lab/static/mindeye.js from Chungulus/brain-lab: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/mindeye.js
- Command line
-
hf download hf://spaces/Chungulus/brain-lab/brain_lab/static/mindeye.js
-
curl -L -o mindeye.js https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/mindeye.js
16.8 kB
| import {api,card,escape,toast} from './common.js'; | |
| import {liveFMRI} from './mindeye-live.js?v=brain-hub-37'; | |
| // Reuse the authors' unmodified figure files across rounds and navigation. | |
| // Each canvas is a viewport onto a published tile, not a generated reconstruction. | |
| const images=new Map(); | |
| function loadImage(key,url){ | |
| if(!images.has(key))images.set(key,new Promise((resolve,reject)=>{ | |
| const image=new Image();image.decoding='async';image.onload=()=>resolve(image); | |
| image.onerror=()=>{images.delete(key);reject(Error('The published research images could not load. Try again.'))};image.src=url; | |
| })); | |
| return images.get(key); | |
| } | |
| export async function mindsEye(root){ | |
| root.innerHTML=card('What did this person see?',` | |
| <div class="mindeye-intro"><span class="mindeye-replay" id="mindeye-source-badge">PUBLISHED RESEARCH REPLAY</span><p id="mindeye-intro-text">A person looked at a photograph inside an MRI scanner. MindEye2 reconstructed an image from their recorded brain responses. Can you match it to the photograph?</p></div> | |
| <div class="mindeye-toolbar"><div class="segmented" role="tablist" aria-label="Mind’s Eye activity"><button id="mindeye-play-tab" role="tab" aria-selected="true">Guess the picture</button><button id="mindeye-explore-tab" role="tab" aria-selected="false">Explore examples</button><button id="mindeye-live-tab" role="tab" aria-selected="false">New fMRI decode</button></div><span class="mindeye-score" id="mindeye-score">Your matches: 0 / 0</span></div> | |
| <div class="mindeye-training" id="mindeye-training-controls"><span>Training with this person</span><div class="button-row"><button class="secondary selected" data-mindeye-training="one-hour">1 hour</button><button class="secondary" data-mindeye-training="forty-hours">40 hours</button></div></div> | |
| <p class="note" id="mindeye-training-note">The model was already pretrained on seven other participants. These buttons choose saved research results.</p> | |
| <p class="mindeye-loading" id="mindeye-loading" role="status">Loading the published examples…</p> | |
| <section id="mindeye-play" role="tabpanel" aria-label="Guess the picture"> | |
| <div class="mindeye-game"><div class="mindeye-reconstruction"><span class="eyebrow">THE AI’S RECONSTRUCTION</span><canvas id="mindeye-reconstruction" width="450" height="450" role="img" aria-label="MindEye2’s published reconstruction from a recorded fMRI example"></canvas><span class="mindeye-caption" id="mindeye-training-caption">1 hour of participant training</span></div><div><div class="mindeye-choice-heading"><h3>Which photograph was it?</h3><select id="mindeye-difficulty" aria-label="Number of photographs"><option value="4">4 photographs</option><option value="8">8 photographs</option></select></div><div class="mindeye-choices" id="mindeye-choices"></div></div></div> | |
| <div class="mindeye-feedback" id="mindeye-feedback" aria-live="polite" hidden></div> | |
| <div class="button-row mindeye-actions"><button class="primary" id="mindeye-next" disabled>Next picture →</button><button class="secondary" id="mindeye-show-original" hidden>Compare with the original</button><button class="secondary" id="mindeye-reset">Reset score</button></div> | |
| <p class="note">Your matches count your guesses in this game. They are not a measurement of MindEye2’s accuracy.</p> | |
| </section> | |
| <section id="mindeye-explore" role="tabpanel" aria-label="Explore examples" hidden> | |
| <div class="mindeye-browse"><label for="mindeye-example">Example <input id="mindeye-example" type="number" min="1" max="1000" value="3"> / 1,000</label><div class="button-row"><button class="secondary" id="mindeye-example-back" aria-label="Previous example">←</button><button class="secondary" id="mindeye-example-go">Show example</button><button class="secondary" id="mindeye-example-next" aria-label="Next example">→</button></div></div> | |
| <div class="mindeye-compare"><div><span class="eyebrow">WHAT THE PERSON SAW</span><canvas id="mindeye-original" width="450" height="450" role="img" aria-label="Original photograph viewed by the participant"></canvas></div><div><span class="eyebrow">WHAT MINDEYE2 RECONSTRUCTED</span><canvas id="mindeye-compared" width="450" height="450" role="img" aria-label="Published MindEye2 reconstruction of the same example"></canvas></div></div> | |
| <div class="mindeye-observe"><b>Look for the clues.</b> Which objects, colors, and positions survived? Which details changed?</div> | |
| <details class="simple-details" id="mindeye-wipe-details"><summary>Slide between the two images</summary><canvas id="mindeye-wipe" width="600" height="600" role="img" aria-label="Original and reconstruction comparison"></canvas><label class="mindeye-wipe-label">Original <input id="mindeye-wipe-position" type="range" min="0" max="100" value="50" aria-label="Original versus reconstruction boundary"> Reconstruction</label></details> | |
| </section> | |
| <section id="mindeye-live" role="tabpanel" aria-label="New fMRI decode" hidden></section> | |
| <details class="simple-details" id="mindeye-practice-details"><summary>Try the training-data experiment</summary><p class="note">The same giraffe photograph, with different amounts of training data from one participant. Move through the authors’ actual saved results.</p><div class="mindeye-compare mindeye-practice"><div><span class="eyebrow">THE ORIGINAL</span><canvas id="mindeye-giraffe-original" width="360" height="360" role="img" aria-label="Giraffe photograph from the authors’ training-data figure"></canvas></div><div><span class="eyebrow">THE RECONSTRUCTION</span><canvas id="mindeye-giraffe-recon" width="360" height="360" role="img" aria-label="Published reconstruction at the selected training duration"></canvas></div></div><label class="mindeye-practice-label">Training data: <b id="mindeye-practice-value">1 hour</b><input id="mindeye-practice" type="range" min="0" max="5" step="1" value="2" aria-label="Amount of participant training data"></label><div class="mindeye-practice-ticks"><span>10 min</span><span>30 min</span><span>1 h</span><span>2 h</span><span>10 h</span><span>40 h</span></div><p class="note">An individual example can still contain mistakes. The time labels describe training data, not how long you need to wait.</p></details> | |
| <details class="simple-details" id="mindeye-method"><summary>How does a brain pattern become a picture?</summary><div class="mindeye-pipeline"><div><span>01</span><b>Record the response</b><p>fMRI records patterns while a volunteer views photographs.</p></div><i>→</i><div><span>02</span><b>Find visual features</b><p>A learned mapping predicts an image representation in CLIP space.</p></div><i>→</i><div><span>03</span><b>Build an image</b><p>A diffusion model turns the predicted features into a reconstruction.</p></div></div><p class="note">This exhibit uses 1,000 published pairs for NSD participant 1. Each reconstruction is an enhanced output released by the MindEye2 authors. The AI ran in their study; the game and comparison controls run here. Raw brain recordings are not included in this replay.</p><div class="source-links"><a href="https://medarc-ai.github.io/mindeye2/" target="_blank" rel="noreferrer">MindEye2 project ↗</a><a href="https://arxiv.org/abs/2403.11207" target="_blank" rel="noreferrer">ICML 2024 paper ↗</a><a href="https://github.com/MedARC-AI/MindEyeV2" target="_blank" rel="noreferrer">Authors’ code & figures ↗</a><a href="/static/mindeye/provenance.json" target="_blank" rel="noreferrer">Sources & verification ↗</a></div></details> | |
| `,'MINDEYE2 · NSD PARTICIPANT 1'); | |
| const $=selector=>root.querySelector(selector); | |
| let alive=true,info,game,training='one-hour',tab='play',example=2,comparison=null,renderId=0,requestId=0; | |
| const paints=new WeakMap(); | |
| const mounted=()=>alive&&root.isConnected; | |
| const urls={}; | |
| const live=liveFMRI($('#mindeye-live'),async(canvas,index)=>{const data=await api('mindeye/examples/'+index);await draw(canvas,data.original)}); | |
| function loading(message=''){if(mounted()){$('#mindeye-loading').textContent=message;$('#mindeye-loading').hidden=!message}} | |
| async function draw(canvas,view,seen=renderId){ | |
| const painting=Symbol();paints.set(canvas,painting); | |
| const pending=canvas.getContext('2d');pending.fillStyle='#edf1e7';pending.fillRect(0,0,canvas.width,canvas.height); | |
| const image=await loadImage(view.asset,urls[view.asset]); | |
| if(!mounted()||!canvas.isConnected||paints.get(canvas)!==painting)return; | |
| const ctx=canvas.getContext('2d');ctx.imageSmoothingEnabled=true;ctx.imageSmoothingQuality='high'; | |
| ctx.clearRect(0,0,canvas.width,canvas.height);ctx.drawImage(image,...view.rect,0,0,canvas.width,canvas.height); | |
| } | |
| function score(){const s=game?.score||{matches:0,attempts:0};$('#mindeye-score').textContent=`Your matches: ${s.matches} / ${s.attempts}`} | |
| function modeButtons(){root.querySelectorAll('[data-mindeye-training]').forEach(b=>b.classList.toggle('selected',b.dataset.mindeyeTraining===training));$('#mindeye-training-caption').textContent=(training==='one-hour'?'1 hour':'40 hours')+' of participant training'} | |
| function setTab(which){ | |
| requestId++;renderId++; | |
| tab=which;$('#mindeye-play').hidden=which!=='play';$('#mindeye-explore').hidden=which!=='explore';$('#mindeye-live').hidden=which!=='live'; | |
| for(const selector of ['#mindeye-score','#mindeye-training-controls','#mindeye-training-note','#mindeye-practice-details','#mindeye-method'])$(selector).hidden=which==='live'; | |
| $('#mindeye-source-badge').textContent=which==='live'?'LOCAL fMRI DECODER':'PUBLISHED RESEARCH REPLAY'; | |
| $('#mindeye-intro-text').textContent=which==='live'?'Choose a recorded example or submit a compatible fMRI array. MindEye2 makes a new coarse image from the recorded brain responses.':'A person looked at a photograph inside an MRI scanner. MindEye2 reconstructed an image from their recorded brain responses. Can you match it to the photograph?'; | |
| $('#mindeye-live-tab').setAttribute('aria-selected',String(which==='live'));if(which==='live'){loading();live.show()} | |
| $('#mindeye-play-tab').setAttribute('aria-selected',String(which==='play'));$('#mindeye-explore-tab').setAttribute('aria-selected',String(which==='explore')); | |
| if(which==='explore')showExample(example); | |
| } | |
| async function renderRound(){ | |
| if(!mounted()||!game?.round)return; | |
| const round=game.round,seen=++renderId;training=round.training;modeButtons();score(); | |
| $('#mindeye-choices').innerHTML=round.choices.map((c,i)=>`<button class="mindeye-photo ${round.answered?(c.id===round.correct_choice?'correct':c.id===round.selected_choice?'missed':''):''}" data-mindeye-choice="${c.id}" ${round.answered?'disabled':''} aria-label="${round.answered?'Photograph':'Choose photograph'} ${i+1}${round.answered&&c.id===round.correct_choice?' — original seen image':''}${round.answered&&c.id===round.selected_choice?' — your choice':''}"><canvas width="240" height="240" aria-hidden="true"></canvas><span>Photograph ${i+1}${round.answered&&c.id===round.correct_choice?' · Original':''}</span></button>`).join(''); | |
| const feedback=$('#mindeye-feedback');feedback.hidden=!round.answered;feedback.classList.toggle('matched',Boolean(round.correct)); | |
| if(round.answered)feedback.textContent=round.correct?'You matched it. This is the photograph the participant saw.':'The outlined photograph is the one the participant saw. Compare the details to find the clues.'; | |
| $('#mindeye-show-original').hidden=!round.answered; | |
| root.querySelectorAll('[data-mindeye-choice]').forEach(button=>button.onclick=async()=>{ | |
| root.querySelectorAll('[data-mindeye-choice]').forEach(b=>b.disabled=true);const req=++requestId; | |
| try{const data=await api('mindeye/answer',{round_id:round.id,choice_id:button.dataset.mindeyeChoice});if(!mounted()||req!==requestId)return;game=data;await renderRound()} | |
| catch(e){if(mounted()){toast(e.message,true);await renderRound()}} | |
| }); | |
| loading('Loading published images…'); | |
| try{ | |
| await Promise.all([draw($('#mindeye-reconstruction'),round.reconstruction,seen),...round.choices.map((c,i)=>draw(root.querySelectorAll('[data-mindeye-choice] canvas')[i],c.image,seen))]); | |
| if(mounted()&&seen===renderId){loading();$('#mindeye-next').disabled=false} | |
| }catch(e){if(mounted()){loading(e.message);$('#mindeye-next').disabled=false}} | |
| } | |
| async function newRound(){ | |
| const req=++requestId;$('#mindeye-next').disabled=true;loading('Choosing another published example…'); | |
| try{const data=await api('mindeye/round',{training,choices:Number($('#mindeye-difficulty').value)});if(!mounted()||req!==requestId)return;game=data;await renderRound()} | |
| catch(e){if(mounted()){loading(e.message);$('#mindeye-next').disabled=false}} | |
| } | |
| async function showExample(index){ | |
| index=Math.max(0,Math.min(999,Math.trunc(index)));const req=++requestId;loading('Loading the comparison…'); | |
| try{ | |
| const data=await api('mindeye/examples/'+index);if(!mounted()||req!==requestId)return; | |
| comparison=data;example=index;$('#mindeye-example').value=index+1;const seen=++renderId; | |
| await Promise.all([draw($('#mindeye-original'),data.original,seen),draw($('#mindeye-compared'),data.reconstructions[training],seen)]); | |
| if(mounted()&&seen===renderId){loading();if($('#mindeye-wipe-details').open)await drawWipe()} | |
| }catch(e){if(mounted())loading(e.message)} | |
| } | |
| async function drawWipe(){ | |
| if(!comparison||!mounted())return; | |
| const seen=renderId,data=comparison; | |
| const [original,reconstruction]=await Promise.all([loadImage('one-hour',urls['one-hour']),loadImage(training,urls[training])]); | |
| if(!mounted()||seen!==renderId)return; | |
| const canvas=$('#mindeye-wipe'),ctx=canvas.getContext('2d'),edge=Number($('#mindeye-wipe-position').value)*canvas.width/100; | |
| ctx.drawImage(reconstruction,...data.reconstructions[training].rect,0,0,canvas.width,canvas.height); | |
| ctx.save();ctx.beginPath();ctx.rect(0,0,edge,canvas.height);ctx.clip();ctx.drawImage(original,...data.original.rect,0,0,canvas.width,canvas.height);ctx.restore(); | |
| ctx.strokeStyle='#fffefa';ctx.lineWidth=5;ctx.beginPath();ctx.moveTo(edge,0);ctx.lineTo(edge,canvas.height);ctx.stroke(); | |
| } | |
| async function drawPractice(){ | |
| if(!info||!mounted())return; | |
| const data=info.training_example,selected=data.settings[Number($('#mindeye-practice').value)]; | |
| $('#mindeye-practice-value').textContent=selected.label; | |
| try{await Promise.all([draw($('#mindeye-giraffe-original'),{asset:'training-figure',rect:data.original}),draw($('#mindeye-giraffe-recon'),{asset:'training-figure',rect:selected.rect})])}catch(e){toast(e.message,true)} | |
| } | |
| root.querySelectorAll('[data-mindeye-training]').forEach(button=>button.onclick=async()=>{ | |
| const next=button.dataset.mindeyeTraining;if(training===next)return;training=next;modeButtons(); | |
| if(!game?.round){if(tab==='explore')return showExample(example);return} | |
| const req=++requestId;loading('Loading this training comparison…'); | |
| try{const data=await api('mindeye/training',{round_id:game.round.id,training});if(!mounted()||req!==requestId)return;game=data;if(tab==='explore')await showExample(example);else await renderRound()} | |
| catch(e){if(mounted()){loading(e.message);training=game.round.training;modeButtons()}} | |
| }); | |
| $('#mindeye-play-tab').onclick=async()=>{setTab('play');if(game?.round)await renderRound()}; | |
| $('#mindeye-explore-tab').onclick=()=>setTab('explore');$('#mindeye-live-tab').onclick=()=>setTab('live'); | |
| $('#mindeye-next').onclick=newRound;$('#mindeye-difficulty').onchange=newRound; | |
| $('#mindeye-reset').onclick=async()=>{try{game=await api('mindeye/reset',{});if(mounted())await newRound()}catch(e){toast(e.message,true)}}; | |
| $('#mindeye-show-original').onclick=()=>{example=game.round.comparison.index;setTab('explore')}; | |
| $('#mindeye-example-go').onclick=()=>showExample(Number($('#mindeye-example').value)-1); | |
| $('#mindeye-example').onkeydown=e=>{if(e.key==='Enter')showExample(Number(e.target.value)-1)}; | |
| $('#mindeye-example-back').onclick=()=>showExample((example+999)%1000);$('#mindeye-example-next').onclick=()=>showExample((example+1)%1000); | |
| $('#mindeye-wipe-position').oninput=drawWipe;$('#mindeye-wipe-details').ontoggle=()=>{if($('#mindeye-wipe-details').open)drawWipe()}; | |
| $('#mindeye-practice-details').ontoggle=()=>{if($('#mindeye-practice-details').open)drawPractice()};$('#mindeye-practice').oninput=drawPractice; | |
| try{ | |
| const data=await Promise.all([api('mindeye'),api('mindeye/state')]);if(!mounted())return ()=>{}; | |
| info=data[0];game=data[1];for(const item of info.modes)urls[item.id]=item.url;urls['training-figure']=info.training_example.url; | |
| if(game.round){$('#mindeye-difficulty').value=game.round.choice_count;await renderRound()}else await newRound(); | |
| }catch(e){if(mounted())loading(e.message)} | |
| return ()=>{alive=false;renderId++;requestId++;live.dispose()}; | |
| } | |