b07e7a1182
- 上传图像 + 实时处理 + 6张结果可视化 - 实验室仪器风格深色主题 - 参数统计面板(T/pct/网格/斑点数) - 图片点击放大 + 单张/全部下载
177 lines
7.3 KiB
HTML
177 lines
7.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>cDNA Microarray Processing - Lab Console</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<div class="noise-overlay"></div>
|
|
|
|
<header class="header">
|
|
<div class="header-left">
|
|
<span class="logo-bracket">[</span>
|
|
<h1 class="logo-text">cDNA<span class="logo-accent">//</span>PROCESS</h1>
|
|
<span class="logo-bracket">]</span>
|
|
</div>
|
|
<div class="header-right">
|
|
<span class="status-dot"></span>
|
|
<span class="status-label">SYSTEM READY</span>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<section class="upload-section" id="uploadSection">
|
|
<div class="upload-zone-wrapper">
|
|
<div class="upload-zone" id="dropZone">
|
|
<svg class="upload-svg" width="52" height="52" viewBox="0 0 52 52">
|
|
<circle cx="26" cy="20" r="8" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
<path d="M13 42c-5 0-8-1.5-8-6s3.5-9 8-9c0-6 6-10 13-10s13 4 13 10c4.5 0 8 4.5 8 9s-3.5 6-8 6H13z" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
|
<circle cx="22" cy="16" r="2.2" fill="currentColor"/>
|
|
<circle cx="26" cy="11" r="2.8" fill="currentColor"/>
|
|
</svg>
|
|
<p class="upload-text">Drop image here<span class="upload-cursor">_</span></p>
|
|
<p class="upload-sub">click to browse · TIFF / PNG / JPEG</p>
|
|
<input type="file" id="fileInput" accept="image/*" hidden>
|
|
</div>
|
|
<div class="upload-preview" id="uploadPreview" style="display:none">
|
|
<img id="previewImg" alt="Preview">
|
|
<button class="btn-process" id="btnProcess">
|
|
<svg width="16" height="16" viewBox="0 0 16 16"><polygon points="3,1 16,8 3,15" fill="currentColor"/></svg>
|
|
START ANALYSIS
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="status-bar" id="statusBar" style="display:none">
|
|
<div class="status-track">
|
|
<div class="status-fill" id="statusFill"></div>
|
|
</div>
|
|
<span class="status-text" id="statusText">PROCESSING...</span>
|
|
</div>
|
|
|
|
<div class="error-msg" id="errorMsg" style="display:none"></div>
|
|
|
|
<section class="stats-panel" id="statsPanel" style="display:none">
|
|
<div class="stat-card"><span class="stat-label">THRESHOLD</span><span class="stat-value" id="statT">--</span></div>
|
|
<div class="stat-card"><span class="stat-label">ADAPTIVE %</span><span class="stat-value" id="statPct">--</span></div>
|
|
<div class="stat-card"><span class="stat-label">GRID</span><span class="stat-value" id="statGrid">--</span></div>
|
|
<div class="stat-card spot"><span class="stat-label">SPOTS FOUND</span><span class="stat-value" id="statSpots">--</span></div>
|
|
<div class="stat-card"><span class="stat-label">DIMENSIONS</span><span class="stat-value" id="statSize">--</span></div>
|
|
</section>
|
|
|
|
<section class="gallery" id="gallery" style="display:none">
|
|
<div class="gallery-header">
|
|
<h2 class="gallery-title">RESULTS</h2>
|
|
<button class="btn-dl-all" id="btnDownloadAll"><span>↓</span> DOWNLOAD ALL</button>
|
|
</div>
|
|
<div class="gallery-grid" id="galleryGrid"></div>
|
|
</section>
|
|
</main>
|
|
|
|
<div class="lightbox" id="lightbox">
|
|
<span class="lightbox-close" id="lightboxClose">×</span>
|
|
<img id="lightboxImg" src="" alt="Full size">
|
|
<a id="lightboxDownload" class="lightbox-dl" download>↓</a>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
var dz=document.getElementById('dropZone');
|
|
var fi=document.getElementById('fileInput');
|
|
var up=document.getElementById('uploadPreview');
|
|
var pi=document.getElementById('previewImg');
|
|
var bp=document.getElementById('btnProcess');
|
|
var sb=document.getElementById('statusBar');
|
|
var sf=document.getElementById('statusFill');
|
|
var st=document.getElementById('statusText');
|
|
var er=document.getElementById('errorMsg');
|
|
var sp=document.getElementById('statsPanel');
|
|
var ga=document.getElementById('gallery');
|
|
var gg=document.getElementById('galleryGrid');
|
|
var lb=document.getElementById('lightbox');
|
|
var li=document.getElementById('lightboxImg');
|
|
var lc=document.getElementById('lightboxClose');
|
|
var ld=document.getElementById('lightboxDownload');
|
|
var file=null;
|
|
|
|
dz.addEventListener('dragover',function(e){e.preventDefault();dz.classList.add('dragover')});
|
|
dz.addEventListener('dragleave',function(){dz.classList.remove('dragover')});
|
|
dz.addEventListener('drop',function(e){e.preventDefault();dz.classList.remove('dragover');if(e.dataTransfer.files.length)h(e.dataTransfer.files[0])});
|
|
dz.addEventListener('click',function(){fi.click()});
|
|
fi.addEventListener('change',function(e){if(e.target.files.length)h(e.target.files[0])});
|
|
|
|
function h(f){
|
|
file=f;
|
|
var r=new FileReader();
|
|
r.onload=function(e){
|
|
pi.src=e.target.result;
|
|
up.style.display='flex';
|
|
er.style.display='none';
|
|
sp.style.display='none';
|
|
ga.style.display='none';
|
|
};
|
|
r.readAsDataURL(f);
|
|
}
|
|
|
|
bp.addEventListener('click',function(){
|
|
if(!file) return;
|
|
var fd=new FormData(); fd.append('file',file);
|
|
sb.style.display='block'; sf.style.width='0%'; st.textContent='UPLOADING...'; er.style.display='none';
|
|
var w=0; var iv=setInterval(function(){w=Math.min(w+2,85);sf.style.width=w+'%'},50);
|
|
fetch('/process',{method:'POST',body:fd}).then(function(r){
|
|
clearInterval(iv); sf.style.width='100%'; st.textContent='COMPLETE';
|
|
if(!r.ok) throw new Error('Server error');
|
|
return r.json();
|
|
}).then(function(d){
|
|
if(d.error) throw new Error(d.error);
|
|
render(d);
|
|
setTimeout(function(){sb.style.display='none'},1500);
|
|
}).catch(function(e){
|
|
clearInterval(iv); sb.style.display='none';
|
|
er.textContent='ERROR: '+e.message; er.style.display='block';
|
|
});
|
|
});
|
|
|
|
function render(d){
|
|
var s=d.stats;
|
|
document.getElementById('statT').textContent=s.T_otsu;
|
|
document.getElementById('statPct').textContent=s.pct+'%';
|
|
document.getElementById('statGrid').textContent=s.lines_x+' x '+s.lines_y;
|
|
document.getElementById('statSpots').textContent=s.spots;
|
|
document.getElementById('statSize').textContent=s.width+' x '+s.height;
|
|
sp.style.display='flex';
|
|
var names=['grid_overlay','col_projection','row_projection','histogram','segmentation_raw','post_processed'];
|
|
var labels=['GRID OVERLAY','COLUMN PROJECTION','ROW PROJECTION','HISTOGRAM + OTSU','SEGMENTATION','POST-PROCESSED'];
|
|
gg.innerHTML='';
|
|
names.forEach(function(n,i){
|
|
var c=document.createElement('div'); c.className='gallery-card';
|
|
c.innerHTML='<div class="gallery-card-inner"><img src="'+d.images[n]+'" onclick="zoom(\''+n+'.png\',\''+d.images[n]+'\')"></div><div class="gallery-card-label">'+labels[i]+'</div>';
|
|
gg.appendChild(c);
|
|
});
|
|
ga.style.display='block';
|
|
document.getElementById('btnDownloadAll').onclick=function(){
|
|
names.forEach(function(n,i){
|
|
setTimeout(function(){
|
|
var a=document.createElement('a'); a.href=d.images[n]; a.download=n+'.png';
|
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
|
},i*200);
|
|
});
|
|
};
|
|
}
|
|
|
|
window.zoom=function(name,src){
|
|
lb.style.display='flex'; li.src=src; ld.href=src; ld.download=name;
|
|
};
|
|
|
|
lc.addEventListener('click',function(){lb.style.display='none'});
|
|
lb.addEventListener('click',function(e){if(e.target===lb) lb.style.display='none'});
|
|
document.addEventListener('keydown',function(e){if(e.key==='Escape') lb.style.display='none'});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|