Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions PanTS-Demo/public/contrast-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Contrast Enhancement Demo | BodyMaps</title>
<meta name="robots" content="noindex">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<style>
:root{--bg:#0e1116;--panel:#171b22;--line:#2a313c;--text:#e6e9ee;--mut:#97a0ad;--acc:#4da3ff}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,Segoe UI,sans-serif}
.wrap{max-width:1040px;margin:0 auto;padding:28px 16px 60px}
h1{font-size:26px;margin:0 0 4px}
.sub{color:var(--mut);margin:0 0 22px;max-width:70ch}
.row{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px;align-items:center}
.lab{color:var(--mut);font-size:12px;text-transform:uppercase;letter-spacing:.06em;margin-right:4px}
button.chip{background:var(--panel);border:1px solid var(--line);color:var(--text);padding:6px 12px;border-radius:999px;cursor:pointer;font:inherit;font-size:13px}
button.chip:hover{border-color:var(--acc)}
button.chip.on{background:var(--acc);border-color:var(--acc);color:#04101d;font-weight:600}
.stage{position:relative;background:#000;border:1px solid var(--line);border-radius:12px;overflow:hidden;touch-action:none;user-select:none}
canvas{display:block;width:100%;height:auto}
.tag{position:absolute;top:10px;padding:3px 9px;border-radius:6px;background:rgba(0,0,0,.6);font-size:12px;pointer-events:none}
.tag.l{left:10px}.tag.r{right:10px}
.divider{position:absolute;top:0;bottom:0;width:2px;background:var(--acc);pointer-events:none}
.knob{position:absolute;top:50%;width:34px;height:34px;margin:-17px 0 0 -16px;border-radius:50%;background:var(--acc);color:#04101d;display:grid;place-items:center;font-weight:700;pointer-events:none;box-shadow:0 2px 8px #0008}
.loupe{position:absolute;width:150px;height:150px;border:2px solid var(--acc);border-radius:50%;pointer-events:none;display:none;box-shadow:0 4px 14px #000a;background:#000}
.reveal{display:flex;gap:12px;align-items:center;margin:14px 0;flex-wrap:wrap}
input[type=range]{flex:1;min-width:200px;accent-color:var(--acc)}
.note{border:1px solid var(--line);background:var(--panel);border-radius:10px;padding:12px 14px;color:var(--mut);font-size:13px;margin-top:18px}
.note b{color:var(--text)}
.desc{color:var(--mut);font-size:13px;margin:6px 0 14px;min-height:20px}
</style>
</head>
<body>
<div class="wrap">
<h1>Contrast enhancement</h1>
<p class="sub">Real abdominal CT slices from the BodyMaps library. Drag the divider to compare the original with an enhanced version, hover for a magnified look, or play the progressive reveal.</p>

<div class="row"><span class="lab">Case</span><span id="cases"></span></div>
<div class="row"><span class="lab">Method</span><span id="methods"></span></div>
<div class="desc" id="desc"></div>

<div class="stage" id="stage">
<canvas id="cv"></canvas>
<div class="tag l">Original</div>
<div class="tag r" id="rtag">Enhanced</div>
<div class="divider" id="div"><div class="knob">&harr;</div></div>
<canvas class="loupe" id="loupe" width="150" height="150"></canvas>
</div>

<div class="reveal">
<button class="chip" id="play">&#9654; Play reveal</button>
<input type="range" id="rev" min="0" max="100" value="100" aria-label="Reveal progress">
<span id="revlab" style="color:var(--mut);font-size:13px;min-width:130px">Enhanced, full detail</span>
</div>

<div class="note"><b>What this shows.</b> These are classical image-processing methods (window stretch, local contrast, denoise, unsharp mask). They make existing detail easier to see; they do not add new information. The reveal is the enhanced image shown at increasing resolution, to illustrate the effect &mdash; it is not a learned super-resolution model. Slices are 8-bit library previews.</div>
</div>

<script>
const CASES=[{id:'1',w:512,h:333},{id:'17',w:484,h:344},{id:'30',w:510,h:422},{id:'35',w:512,h:390},{id:'121',w:461,h:341}];
const METHODS=[
{k:'stretch',n:'Window stretch',d:'Stretches the tissue range (2nd to 99.5th percentile) to fill the full brightness scale.'},
{k:'clahe',n:'Local contrast (CLAHE)',d:'Boosts contrast region by region, so soft-tissue differences that look flat become visible.'},
{k:'sharp',n:'Local contrast + sharpen',d:'CLAHE followed by an unsharp mask to crisp organ edges.'},
{k:'combined',n:'Denoise + contrast + sharpen',d:'Removes CT noise first, then local contrast and sharpening, so noise is not amplified.'}
];
const cv=document.getElementById('cv'),ctx=cv.getContext('2d');
const stage=document.getElementById('stage'),divEl=document.getElementById('div');
const loupe=document.getElementById('loupe'),lctx=loupe.getContext('2d');
let cs=CASES[0],me=METHODS[3],imgs={},split=0.5,revealT=1,reveal=false,anim=null;
const cache={};
function load(c,k){const key=c.id+k;if(cache[key])return cache[key];return cache[key]=new Promise(r=>{const i=new Image();i.onload=()=>r(i);i.src='contrast-demo/'+c.id+'_'+k+'.png';});}
async function setup(){
cv.width=cs.w;cv.height=cs.h;
imgs.o=await load(cs,'orig');imgs.e=await load(cs,me.k);
draw();
}
const off=document.createElement('canvas'),octx=off.getContext('2d');
function enhancedAtReveal(t){
// t: 0..1 -> block size 40 .. 1 (log scale)
const b=Math.max(1,Math.round(Math.pow(40,1-t)));
if(b<=1)return imgs.e;
off.width=Math.ceil(cs.w/b);off.height=Math.ceil(cs.h/b);
octx.imageSmoothingEnabled=true;octx.drawImage(imgs.e,0,0,off.width,off.height);
const o2=document.createElement('canvas');o2.width=cs.w;o2.height=cs.h;const c2=o2.getContext('2d');
c2.imageSmoothingEnabled=false;c2.drawImage(off,0,0,off.width,off.height,0,0,off.width*b,off.height*b);
return o2;
}
function draw(){
if(!imgs.o)return;
const w=cs.w,h=cs.h;
ctx.clearRect(0,0,w,h);
const right=reveal?enhancedAtReveal(revealT):imgs.e;
if(reveal){ctx.drawImage(right,0,0,w,h);divEl.style.display='none';document.querySelector('.tag.l').style.display='none';}
else{
divEl.style.display='';document.querySelector('.tag.l').style.display='';
ctx.drawImage(imgs.o,0,0,w,h);
const x=Math.round(split*w);
ctx.save();ctx.beginPath();ctx.rect(x,0,w-x,h);ctx.clip();ctx.drawImage(right,0,0,w,h);ctx.restore();
divEl.style.left=(split*100)+'%';
divEl.querySelector('.knob').style.top='50%';
}
}
function pos(e){const r=stage.getBoundingClientRect();return{x:(e.clientX-r.left)/r.width,y:(e.clientY-r.top)/r.height,px:e.clientX-r.left,py:e.clientY-r.top,r};}
let drag=false;
stage.addEventListener('pointerdown',e=>{if(reveal)return;drag=true;stage.setPointerCapture(e.pointerId);split=Math.min(1,Math.max(0,pos(e).x));draw();});
stage.addEventListener('pointerup',()=>drag=false);
stage.addEventListener('pointermove',e=>{
const p=pos(e);
if(drag){split=Math.min(1,Math.max(0,p.x));draw();}
// loupe: 4x of whichever side the cursor is on
if(reveal||!imgs.o){loupe.style.display='none';return;}
const sx=p.x*cs.w,sy=p.y*cs.h,z=4,s=150/z;
const src=(p.x<split)?imgs.o:imgs.e;
lctx.clearRect(0,0,150,150);lctx.imageSmoothingEnabled=false;
lctx.drawImage(src,sx-s/2,sy-s/2,s,s,0,0,150,150);
loupe.style.display='block';
loupe.style.left=Math.min(p.r.width-150,Math.max(0,p.px+18))+'px';
loupe.style.top=Math.min(p.r.height-150,Math.max(0,p.py-170<0?p.py+18:p.py-170))+'px';
});
stage.addEventListener('pointerleave',()=>{if(!drag)loupe.style.display='none';});

function chips(){
const c=document.getElementById('cases');c.innerHTML='';
CASES.forEach(k=>{const b=document.createElement('button');b.className='chip'+(k===cs?' on':'');b.textContent='Case '+k.id;b.onclick=()=>{cs=k;chips();setup();};c.appendChild(b);c.appendChild(document.createTextNode(' '));});
const m=document.getElementById('methods');m.innerHTML='';
METHODS.forEach(k=>{const b=document.createElement('button');b.className='chip'+(k===me?' on':'');b.textContent=k.n;b.onclick=()=>{me=k;chips();setup();};m.appendChild(b);m.appendChild(document.createTextNode(' '));});
document.getElementById('desc').textContent=me.d;
document.getElementById('rtag').textContent=me.n;
}
const rev=document.getElementById('rev'),revlab=document.getElementById('revlab'),play=document.getElementById('play');
function setRev(v,isPlaying){
revealT=v/100;reveal=v<100;rev.value=v;
revlab.textContent=v>=100?'Enhanced, full detail':'Resolution '+Math.round(100/Math.max(1,Math.pow(40,1-revealT)))+'%';
draw();
}
rev.oninput=()=>{stopAnim();setRev(+rev.value);};
function stopAnim(){if(anim){cancelAnimationFrame(anim);anim=null;}play.innerHTML='&#9654; Play reveal';}
play.onclick=()=>{
if(anim){stopAnim();return;}
play.innerHTML='&#10074;&#10074; Pause';
const t0=performance.now(),dur=4500;
const step=t=>{const v=Math.min(100,(t-t0)/dur*100);setRev(v);if(v<100)anim=requestAnimationFrame(step);else stopAnim();};
setRev(0);anim=requestAnimationFrame(step);
};
chips();setup();
</script>
</body>
</html>
Binary file added PanTS-Demo/public/contrast-demo/121_clahe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/121_combined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/121_orig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/121_sharp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/121_stretch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/17_clahe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/17_combined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/17_orig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/17_sharp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/17_stretch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/1_clahe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/1_combined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/1_orig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/1_sharp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/1_stretch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/30_clahe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/30_combined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/30_orig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/30_sharp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/30_stretch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/35_clahe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/35_combined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/35_orig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/35_sharp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added PanTS-Demo/public/contrast-demo/35_stretch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading