kethansai/ct-mri-registration
0
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8"/>5<meta name="viewport" content="width=device-width,initial-scale=1.0"/>6<title>MedReg — CT/MRI Registration</title>7<link rel="preconnect" href="https://fonts.googleapis.com"/>8<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet"/>9<style>10*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}11 12:root{13 --bg:#04070d;14 --bg2:#0b1120;15 --bg3:#111828;16 --bg4:#181f30;17 --bg5:#1e2840;18 --accent:#00f5c4;19 --blue:#3b82f6;20 --purple:#8b5cf6;21 --text:#eef2ff;22 --text2:#94a3b8;23 --text3:#475569;24 --border:#1e2d47;25 --border2:#2a3f5f;26 --success:#22d3a5;27 --warn:#f59e0b;28 --danger:#f43f5e;29 --font:'Space Grotesk',sans-serif;30 --mono:'JetBrains Mono',monospace;31}32 33html{scroll-behavior:smooth}34body{background:var(--bg);color:var(--text);font-family:var(--font);font-size:15px;line-height:1.6;min-height:100vh;overflow-x:hidden;}35 36/* ── ANIMATED BG ── */37body::before{38 content:'';position:fixed;inset:0;39 background-image:40 linear-gradient(rgba(0,245,196,0.022) 1px,transparent 1px),41 linear-gradient(90deg,rgba(0,245,196,0.022) 1px,transparent 1px);42 background-size:64px 64px;43 pointer-events:none;z-index:0;44}45 46/* ── GLOW ORBS ── */47.orb{position:fixed;border-radius:50%;pointer-events:none;z-index:0;filter:blur(100px);}48.orb1{width:700px;height:700px;background:radial-gradient(circle,#1d4ed8,transparent 70%);top:-250px;right:-200px;opacity:0.15;}49.orb2{width:500px;height:500px;background:radial-gradient(circle,#00f5c4,transparent 70%);bottom:-150px;left:-150px;opacity:0.09;}50.orb3{width:400px;height:400px;background:radial-gradient(circle,#7c3aed,transparent 70%);top:30%;left:30%;opacity:0.07;}51 52/* ── NAV ── */53nav{54 position:sticky;top:0;z-index:200;55 display:flex;align-items:center;justify-content:space-between;56 padding:0 2.5rem;height:64px;57 background:rgba(4,7,13,0.8);58 backdrop-filter:blur(28px);59 border-bottom:1px solid var(--border);60}61.logo{display:flex;align-items:center;gap:10px;text-decoration:none;}62.logo-mark{63 width:34px;height:34px;border-radius:10px;64 background:var(--accent);65 display:flex;align-items:center;justify-content:center;66 font-weight:800;font-size:16px;color:#000;letter-spacing:-0.05em;67}68.logo-name{font-size:18px;font-weight:700;letter-spacing:-0.04em;color:var(--text);}69.nav-tags{display:flex;gap:6px;}70.ntag{71 font-family:var(--mono);font-size:11px;letter-spacing:0.06em;72 padding:3px 10px;border-radius:20px;73 border:1px solid var(--border2);color:var(--text3);74}75.ntag.on{76 border-color:rgba(0,245,196,0.35);77 color:var(--success);78 background:rgba(0,245,196,0.06);79}80.ntag.on::before{content:'● ';font-size:7px;vertical-align:1px;}81 82/* ── MAIN ── */83main{position:relative;z-index:1;max-width:980px;margin:0 auto;padding:5rem 2rem 9rem;}84 85/* ── HERO ── */86.hero{text-align:center;margin-bottom:4rem;}87 88.eyebrow{89 display:inline-flex;align-items:center;gap:8px;90 font-family:var(--mono);font-size:11px;color:var(--accent);91 letter-spacing:0.14em;text-transform:uppercase;92 border:1px solid rgba(0,245,196,0.2);93 padding:5px 16px;border-radius:20px;margin-bottom:2rem;94 background:rgba(0,245,196,0.05);95}96.eyebrow-dot{width:5px;height:5px;border-radius:50%;background:var(--accent);}97 98h1{99 font-size:clamp(2.8rem,7vw,5rem);100 font-weight:700;line-height:1.05;101 letter-spacing:-0.05em;102 margin-bottom:1.25rem;103 color:var(--text);104}105h1 .line2{106 background:linear-gradient(125deg,#00f5c4 0%,#38bdf8 45%,#818cf8 100%);107 -webkit-background-clip:text;-webkit-text-fill-color:transparent;108 background-clip:text;109}110 111.hero-desc{112 font-size:16px;color:var(--text2);113 max-width:480px;margin:0 auto 2.5rem;114 line-height:1.8;115}116 117/* feature chips */118.chips{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-bottom:3rem;}119.chip{120 display:flex;align-items:center;gap:7px;121 padding:6px 14px;border-radius:20px;122 border:1px solid var(--border2);background:var(--bg2);123 font-size:12px;font-weight:500;color:var(--text2);124 transition:border-color 0.2s,color 0.2s;125}126.chip:hover{border-color:var(--border2);color:var(--text);}127.chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}128 129/* stat strip */130.stat-strip{131 display:inline-flex;132 border:1px solid var(--border2);133 border-radius:14px;overflow:hidden;134 background:var(--bg2);135}136.stat{padding:12px 24px;border-right:1px solid var(--border2);text-align:center;}137.stat:last-child{border-right:none;}138.stat-val{font-family:var(--mono);font-size:20px;font-weight:500;color:var(--accent);display:block;}139.stat-lbl{font-size:10px;color:var(--text3);letter-spacing:0.1em;text-transform:uppercase;}140 141/* ── SECTION LABELS ── */142.sec{143 font-family:var(--mono);font-size:11px;font-weight:500;144 color:var(--text3);letter-spacing:0.1em;text-transform:uppercase;145 margin-bottom:1.1rem;146 display:flex;align-items:center;gap:12px;147}148.sec::after{content:'';flex:1;height:1px;background:var(--border);}149 150/* ── UPLOAD CARDS ── */151.upload-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem;}152@media(max-width:560px){.upload-grid{grid-template-columns:1fr;}}153 154.drop{155 position:relative;156 border:1px solid var(--border2);border-radius:20px;157 background:var(--bg2);158 padding:2.75rem 1.5rem 2.25rem;159 text-align:center;cursor:pointer;160 transition:border-color 0.25s,transform 0.25s;161 overflow:hidden;162}163.drop::before{164 content:'';position:absolute;inset:0;165 background:radial-gradient(ellipse at 50% 0%,rgba(0,245,196,0.09),transparent 65%);166 opacity:0;transition:opacity 0.3s;167}168.drop:hover{border-color:rgba(0,245,196,0.5);transform:translateY(-3px);}169.drop:hover::before,.drop.over::before,.drop.picked::before{opacity:1;}170.drop.over{border-color:rgba(0,245,196,0.6);}171.drop.picked{border-color:rgba(0,245,196,0.55);background:rgba(0,245,196,0.03);}172.drop input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer;z-index:2;}173 174.drop-ico{175 width:60px;height:60px;border-radius:16px;176 border:1px solid var(--border2);background:var(--bg3);177 display:flex;align-items:center;justify-content:center;178 font-size:26px;margin:0 auto 1.1rem;179 transition:border-color 0.25s,background 0.25s;180 position:relative;z-index:1;181}182.drop.picked .drop-ico{border-color:rgba(0,245,196,0.45);background:rgba(0,245,196,0.08);}183 184.drop-title{font-size:16px;font-weight:600;letter-spacing:-0.02em;position:relative;z-index:1;}185.drop-sub{font-size:12px;color:var(--text3);margin-top:3px;position:relative;z-index:1;}186.drop-badge{187 display:inline-block;margin-top:8px;188 font-family:var(--mono);font-size:10px;letter-spacing:0.04em;189 padding:3px 9px;border-radius:5px;190 background:var(--bg4);border:1px solid var(--border);color:var(--text3);191 position:relative;z-index:1;192}193.drop-file{194 display:none;margin-top:10px;195 font-size:12px;font-weight:500;color:var(--accent);196 font-family:var(--mono);word-break:break-all;197 position:relative;z-index:1;198}199.drop.picked .drop-file{display:block;}200.drop.picked .drop-sub,.drop.picked .drop-badge{display:none;}201 202/* ── RUN BUTTON ── */203.btn-run{204 width:100%;padding:1.1rem 2rem;205 border-radius:14px;border:none;206 background:var(--accent);color:#000;207 font-family:var(--font);font-size:15px;font-weight:700;208 letter-spacing:-0.01em;cursor:pointer;209 transition:opacity 0.2s,transform 0.12s,box-shadow 0.2s;210 position:relative;overflow:hidden;211}212.btn-run::before{213 content:'';position:absolute;inset:0;214 background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,0.2) 50%,transparent 100%);215 transform:translateX(-100%);216 transition:transform 0.6s;217}218.btn-run:not(:disabled):hover::before{transform:translateX(100%);}219.btn-run:not(:disabled):hover{opacity:0.88;box-shadow:0 0 30px rgba(0,245,196,0.2);}220.btn-run:not(:disabled):active{transform:scale(0.99);}221.btn-run:disabled{opacity:0.25;cursor:not-allowed;}222 223/* ── PIPELINE BAR ── */224.pipe{225 display:none;226 background:var(--bg2);border:1px solid var(--border);227 border-radius:12px;overflow:hidden;margin-bottom:1.5rem;228}229.pipe.show{display:flex;}230.pstep{231 flex:1;padding:0.75rem 0.5rem;232 text-align:center;font-size:11px;font-weight:500;233 color:var(--text3);border-right:1px solid var(--border);234 display:flex;flex-direction:column;align-items:center;gap:6px;235 transition:all 0.3s;font-family:var(--mono);letter-spacing:0.03em;236}237.pstep:last-child{border-right:none;}238.pdot{width:6px;height:6px;border-radius:50%;background:var(--border2);transition:all 0.3s;}239.pstep.active{color:var(--accent);background:rgba(0,245,196,0.05);}240.pstep.active .pdot{background:var(--accent);}241.pstep.done{color:var(--success);}242.pstep.done .pdot{background:var(--success);}243 244/* ── STATUS ── */245#status-area{margin-bottom:1.25rem;}246.sc{247 background:var(--bg2);border:1px solid var(--border2);248 border-radius:14px;padding:1.25rem 1.5rem;249 display:flex;align-items:center;gap:1rem;250}251.spin{252 width:20px;height:20px;flex-shrink:0;253 border:2px solid var(--border2);border-top-color:var(--accent);254 border-radius:50%;animation:spin 0.75s linear infinite;255}256@keyframes spin{to{transform:rotate(360deg)}}257.slbl{font-size:11px;color:var(--text3);font-family:var(--mono);letter-spacing:0.07em;text-transform:uppercase;margin-bottom:3px;}258.sval{font-size:14px;color:var(--text);}259.sval.ok{color:var(--success);}.sval.err{color:var(--danger);}260.ebox{261 margin-top:0.75rem;262 background:rgba(244,63,94,0.07);263 border:1px solid rgba(244,63,94,0.2);264 border-radius:10px;padding:0.75rem 1rem;265 font-size:12px;color:var(--danger);266 font-family:var(--mono);white-space:pre-wrap;267 word-break:break-all;max-height:160px;overflow-y:auto;268}269 270/* ── RESULTS ── */271#results{display:none;}272#results.show{display:block;}273 274/* metrics */275.mgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:0.75rem;margin-bottom:2.5rem;}276.mc{277 background:var(--bg2);border:1px solid var(--border);278 border-radius:14px;padding:1.2rem 1.1rem;279 position:relative;overflow:hidden;280 transition:border-color 0.2s,transform 0.2s;281}282.mc:hover{transform:translateY(-2px);}283.mc::after{284 content:'';position:absolute;top:0;left:0;right:0;height:2px;285 border-radius:2px 2px 0 0;286}287.mc.good::after{background:var(--success);}288.mc.mid::after{background:var(--warn);}289.mc.bad::after{background:var(--danger);}290.mc-glow{position:absolute;inset:0;opacity:0;transition:opacity 0.2s;}291.mc.good .mc-glow{background:radial-gradient(ellipse at 50% 0%,rgba(34,211,165,0.07),transparent 70%);}292.mc.mid .mc-glow{background:radial-gradient(ellipse at 50% 0%,rgba(245,158,11,0.07),transparent 70%);}293.mc.bad .mc-glow{background:radial-gradient(ellipse at 50% 0%,rgba(244,63,94,0.07),transparent 70%);}294.mc:hover .mc-glow{opacity:1;}295.mk{font-size:11px;color:var(--text3);font-family:var(--mono);letter-spacing:0.05em;margin-bottom:8px;position:relative;}296.mv{font-size:26px;font-weight:700;letter-spacing:-0.04em;font-family:var(--mono);position:relative;}297.mv.good{color:var(--success);}.mv.mid{color:var(--warn);}.mv.bad{color:var(--danger);}298 299/* previews */300.pgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:0.75rem;margin-bottom:2.5rem;}301@media(max-width:600px){.pgrid{grid-template-columns:repeat(2,1fr);}}302.pc{303 background:var(--bg2);border:1px solid var(--border);304 border-radius:14px;overflow:hidden;cursor:pointer;305 transition:border-color 0.2s,transform 0.2s;306}307.pc:hover{border-color:var(--border2);transform:translateY(-4px);}308.pc img{width:100%;aspect-ratio:1;object-fit:cover;display:block;background:#000;}309.pcap{padding:0.6rem 0.8rem;font-size:11px;color:var(--text3);font-family:var(--mono);letter-spacing:0.03em;}310 311/* download bar */312.dlbar{313 background:var(--bg2);314 border:1px solid var(--border2);315 border-radius:14px;padding:1.5rem;316 display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;317}318.dli strong{color:var(--text);font-weight:600;}319.dli{font-size:13px;color:var(--text2);}320.dli p{font-size:11px;color:var(--text3);font-family:var(--mono);margin-top:3px;}321.btn-dl{322 display:inline-flex;align-items:center;gap:8px;323 padding:0.75rem 1.5rem;border-radius:10px;324 border:1px solid var(--accent);325 background:rgba(0,245,196,0.07);326 color:var(--accent);font-family:var(--font);font-size:14px;font-weight:600;327 cursor:pointer;text-decoration:none;328 transition:background 0.15s,box-shadow 0.15s;329 white-space:nowrap;330}331.btn-dl:hover{background:rgba(0,245,196,0.14);box-shadow:0 0 20px rgba(0,245,196,0.12);}332 333/* ── LIGHTBOX ── */334.lb{display:none;position:fixed;inset:0;z-index:999;background:rgba(4,7,13,0.97);align-items:center;justify-content:center;flex-direction:column;}335.lb.open{display:flex;}336.lb img{max-width:88vw;max-height:78vh;border-radius:12px;border:1px solid var(--border2);}337.lbcap{margin-top:1rem;font-size:13px;color:var(--text2);font-family:var(--mono);}338.lbx{339 position:absolute;top:1.5rem;right:1.5rem;340 background:var(--bg3);border:1px solid var(--border2);341 color:var(--text2);width:38px;height:38px;342 border-radius:50%;cursor:pointer;font-size:16px;343 display:flex;align-items:center;justify-content:center;344 transition:all 0.15s;345}346.lbx:hover{background:var(--bg4);color:var(--text);}347</style>348</head>349<body>350 351<div class="orb orb1"></div>352<div class="orb orb2"></div>353<div class="orb orb3"></div>354 355<nav>356 <a class="logo" href="#">357 <div class="logo-mark">M</div>358 <span class="logo-name">MedReg</span>359 </a>360 <div class="nav-tags">361 <span class="ntag on">API Online</span>362 <span class="ntag">v1.0</span>363 <span class="ntag">DICOM · NIfTI</span>364 </div>365</nav>366 367<main>368 369 <!-- ── HERO ── -->370 <div class="hero">371 <div class="eyebrow"><div class="eyebrow-dot"></div>Medical Image Processing Pipeline</div>372 <h1>CT / MRI<br><span class="line2">Image Registration</span></h1>373 <p class="hero-desc">Upload DICOM series as ZIP files. Get rigidly-aligned NIfTI volumes, alignment metrics, and visual previews — in minutes.</p>374 375 <div class="chips">376 <div class="chip"><div class="chip-dot" style="background:#00f5c4"></div>3-D Rigid Registration</div>377 <div class="chip"><div class="chip-dot" style="background:#38bdf8"></div>FOV Auto-Crop</div>378 <div class="chip"><div class="chip-dot" style="background:#818cf8"></div>6 Alignment Metrics</div>379 <div class="chip"><div class="chip-dot" style="background:#22d3a5"></div>Bone Mask Overlay</div>380 <div class="chip"><div class="chip-dot" style="background:#f59e0b"></div>Edge Overlay</div>381 <div class="chip"><div class="chip-dot" style="background:#f43f5e"></div>Checkerboard</div>382 </div>383 384 <div class="stat-strip">385 <div class="stat"><span class="stat-val">NMI</span><span class="stat-lbl">Mutual Info</span></div>386 <div class="stat"><span class="stat-val">ITK</span><span class="stat-lbl">Transform</span></div>387 <div class="stat"><span class="stat-val">.nii</span><span class="stat-lbl">Output</span></div>388 <div class="stat"><span class="stat-val">6×</span><span class="stat-lbl">Previews</span></div>389 </div>390 </div>391 392 <!-- ── UPLOAD ── -->393 <div class="sec" style="margin-top:0.5rem">01 — Upload DICOM Series</div>394 <div class="upload-grid">395 <div class="drop" id="ct-drop">396 <input type="file" id="ct-file" accept=".zip"/>397 <div class="drop-ico">🩻</div>398 <div class="drop-title">CT Series</div>399 <div class="drop-sub">Drag & drop or click to browse</div>400 <div class="drop-badge">.zip of DICOM files</div>401 <div class="drop-file" id="ct-name"></div>402 </div>403 <div class="drop" id="mri-drop">404 <input type="file" id="mri-file" accept=".zip"/>405 <div class="drop-ico">🧲</div>406 <div class="drop-title">MRI Series</div>407 <div class="drop-sub">Drag & drop or click to browse</div>408 <div class="drop-badge">.zip of DICOM files</div>409 <div class="drop-file" id="mri-name"></div>410 </div>411 </div>412 413 <!-- ── RUN ── -->414 <div class="sec" style="margin-top:1.75rem">02 — Run Pipeline</div>415 <button class="btn-run" id="run-btn" disabled onclick="startReg()">Run Registration →</button>416 417 <!-- ── PIPE ── -->418 <div class="pipe" id="pipe">419 <div class="pstep" id="ps0"><div class="pdot"></div>Upload</div>420 <div class="pstep" id="ps1"><div class="pdot"></div>Load</div>421 <div class="pstep" id="ps2"><div class="pdot"></div>Register</div>422 <div class="pstep" id="ps3"><div class="pdot"></div>Crop</div>423 <div class="pstep" id="ps4"><div class="pdot"></div>Metrics</div>424 <div class="pstep" id="ps5"><div class="pdot"></div>Done</div>425 </div>426 427 <div id="status-area"></div>428 429 <!-- ── RESULTS ── -->430 <div id="results">431 <div class="sec">03 — Alignment Metrics</div>432 <div class="mgrid" id="mgrid"></div>433 434 <div class="sec">04 — Visual Previews</div>435 <div class="pgrid" id="pgrid"></div>436 437 <div class="sec">05 — Download Results</div>438 <div class="dlbar">439 <div class="dli">440 <strong>Registration complete.</strong>441 <p>NIfTI volumes · ITK transform · metrics.json · 6 preview PNGs</p>442 </div>443 <a class="btn-dl" id="dl-link" href="#">↓ Download ZIP</a>444 </div>445 </div>446 447</main>448 449<!-- ── LIGHTBOX ── -->450<div class="lb" id="lb" onclick="closeLb()">451 <button class="lbx" onclick="closeLb()">✕</button>452 <img id="lb-img" src="" alt=""/>453 <div class="lbcap" id="lb-cap"></div>454</div>455 456<script>457const API='';458const POLL=3000;459let jobId=null,timer=null;460 461['ct','mri'].forEach(m=>{462 const inp=document.getElementById(`${m}-file`);463 const zone=document.getElementById(`${m}-drop`);464 const nm=document.getElementById(`${m}-name`);465 inp.addEventListener('change',()=>{466 if(inp.files[0]){nm.textContent=inp.files[0].name;zone.classList.add('picked');check();}467 });468 zone.addEventListener('dragover',e=>{e.preventDefault();zone.classList.add('over');});469 zone.addEventListener('dragleave',()=>zone.classList.remove('over'));470 zone.addEventListener('drop',e=>{471 e.preventDefault();zone.classList.remove('over');472 const f=e.dataTransfer.files[0];473 if(f&&f.name.endsWith('.zip')){474 const dt=new DataTransfer();dt.items.add(f);inp.files=dt.files;475 nm.textContent=f.name;zone.classList.add('picked');check();476 }477 });478});479 480function check(){481 document.getElementById('run-btn').disabled=482 !(document.getElementById('ct-file').files[0]&&document.getElementById('mri-file').files[0]);483}484 485function setStep(n){486 for(let i=0;i<6;i++){487 const el=document.getElementById(`ps${i}`);488 el.className='pstep'+(i<n?' done':i===n?' active':'');489 }490}491 492async function startReg(){493 const ct=document.getElementById('ct-file').files[0];494 const mri=document.getElementById('mri-file').files[0];495 if(!ct||!mri)return;496 document.getElementById('run-btn').disabled=true;497 document.getElementById('run-btn').textContent='Processing…';498 document.getElementById('results').classList.remove('show');499 document.getElementById('pipe').classList.add('show');500 setStep(0);501 const fd=new FormData();502 fd.append('ct_zip',ct);fd.append('mri_zip',mri);503 showSt('loading','Uploading files to server…');504 try{505 const r=await fetch(`${API}/register`,{method:'POST',body:fd});506 if(!r.ok){const e=await r.json().catch(()=>({detail:r.statusText}));throw new Error(e.detail||'Upload failed');}507 const d=await r.json();jobId=d.job_id;setStep(1);508 showSt('loading',`Job queued — ID: ${jobId.slice(0,8)}…`);509 timer=setInterval(poll,POLL);510 }catch(e){511 showSt('error',e.message);512 document.getElementById('run-btn').disabled=false;513 document.getElementById('run-btn').textContent='Run Registration →';514 }515}516 517async function poll(){518 try{519 const r=await fetch(`${API}/status/${jobId}`);520 const d=await r.json();521 if(d.status==='queued'){setStep(1);showSt('loading','Queued — waiting for worker…');}522 else if(d.status==='running'){setStep(2);showSt('loading','Performing 3-D rigid registration… (1–5 min)');}523 else if(d.status==='done'){524 clearInterval(timer);setStep(5);525 showSt('done','Registration complete ✓');526 renderResults(d);527 }else if(d.status==='failed'){528 clearInterval(timer);529 showSt('error',d.error||'Pipeline failed');530 document.getElementById('run-btn').disabled=false;531 document.getElementById('run-btn').textContent='Run Registration →';532 }533 }catch(_){}534}535 536function showSt(type,msg){537 const a=document.getElementById('status-area');538 if(type==='loading'){539 a.innerHTML=`<div class="sc"><div class="spin"></div><div><div class="slbl">Status</div><div class="sval">${esc(msg)}</div></div></div>`;540 }else if(type==='done'){541 a.innerHTML=`<div class="sc"><span style="font-size:22px">✅</span><div><div class="slbl">Status</div><div class="sval ok">${esc(msg)}</div></div></div>`;542 }else{543 a.innerHTML=`<div class="sc" style="align-items:flex-start"><span style="font-size:22px;flex-shrink:0">❌</span><div style="width:100%"><div class="slbl">Error</div><div class="sval err">Pipeline failed</div><div class="ebox">${esc(msg)}</div></div></div>`;544 }545}546 547const PREVS=[548 {file:'ct_mid.png',label:'CT mid-slice'},549 {file:'mri_mid.png',label:'MRI mid-slice'},550 {file:'overlay_mid.png',label:'50/50 overlay'},551 {file:'checkerboard_mid.png',label:'Checkerboard'},552 {file:'edge_overlay_mid.png',label:'Edge overlay'},553 {file:'organ_overlap_mid.png',label:'Bone mask'},554];555const META={556 nmi_rigid_cropped:{label:'NMI',good:v=>v>1.3,mid:v=>v>1.1},557 edge_dice_rigid_cropped:{label:'Edge Dice',good:v=>v>0.4,mid:v=>v>0.2},558 fov_overlap_ratio:{label:'FOV Ratio',good:v=>v>0.6,mid:v=>v>0.3},559 ncc_rigid_cropped:{label:'NCC',good:v=>v>0.6,mid:v=>v>0.3},560 mad_rigid_cropped:{label:'MAD',good:v=>v<0.2,mid:v=>v<0.4},561 mismatch_rigid_cropped:{label:'Mismatch',good:v=>v<0.4,mid:v=>v<0.6},562};563 564function renderResults(data){565 document.getElementById('results').classList.add('show');566 const mg=document.getElementById('mgrid');mg.innerHTML='';567 const m=data.metrics||{};568 for(const[k,meta]of Object.entries(META)){569 const v=m[k];if(v===undefined)continue;570 const cls=meta.good(v)?'good':meta.mid(v)?'mid':'bad';571 mg.innerHTML+=`<div class="mc ${cls}"><div class="mc-glow"></div><div class="mk">${meta.label}</div><div class="mv ${cls}">${v.toFixed(4)}</div></div>`;572 }573 const pg=document.getElementById('pgrid');pg.innerHTML='';574 PREVS.forEach(({file,label})=>{575 const src=`${API}/preview/${jobId}/${file}`;576 pg.innerHTML+=`<div class="pc" onclick="openLb('${src}','${label}')"><img src="${src}" alt="${label}" loading="lazy"/><div class="pcap">${label}</div></div>`;577 });578 document.getElementById('dl-link').href=`${API}/download/${jobId}`;579 document.getElementById('run-btn').disabled=false;580 document.getElementById('run-btn').textContent='Run New Registration →';581}582 583function openLb(src,cap){584 document.getElementById('lb-img').src=src;585 document.getElementById('lb-cap').textContent=cap;586 document.getElementById('lb').classList.add('open');587}588function closeLb(){document.getElementById('lb').classList.remove('open');}589document.addEventListener('keydown',e=>{if(e.key==='Escape')closeLb();});590function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}591</script>592</body>593</html>