CoolFace
Apppublic

kethansai/ct-mri-registration

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
index.html593 linesDownload Raw Back to root
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}591</script>592</body>593</html>