Rf33d/DR-CKD
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>DR–CKD Early Detection</title>7<link rel="preconnect" href="https://fonts.googleapis.com">8<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">9<style>10/* ── Reset & Base ── */11*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }12 13:root {14 --navy: #071e34;15 --navy2: #0e2d4a;16 --teal: #0d9488;17 --teal2: #14b8a6;18 --teal-dim: rgba(13,148,136,0.15);19 --white: #f0f6fc;20 --muted: #8ba3ba;21 --border: rgba(255,255,255,0.07);22 --card: rgba(255,255,255,0.04);23 --card2: rgba(255,255,255,0.07);24 --risk-vl: #22c55e;25 --risk-l: #84cc16;26 --risk-m: #f59e0b;27 --risk-h: #ef4444;28 --risk-vh: #9f1239;29 --font: 'Sora', sans-serif;30 --mono: 'DM Mono', monospace;31}32 33body {34 font-family: var(--font);35 background: var(--navy);36 color: var(--white);37 min-height: 100vh;38 overflow-x: hidden;39}40 41/* ── Background grid pattern ── */42body::before {43 content: '';44 position: fixed;45 inset: 0;46 background-image:47 linear-gradient(rgba(13,148,136,0.03) 1px, transparent 1px),48 linear-gradient(90deg, rgba(13,148,136,0.03) 1px, transparent 1px);49 background-size: 40px 40px;50 pointer-events: none;51 z-index: 0;52}53 54/* ── Header ── */55header {56 position: relative;57 z-index: 10;58 padding: 28px 48px 24px;59 border-bottom: 1px solid var(--border);60 display: flex;61 align-items: center;62 gap: 18px;63}64 65.logo-icon {66 width: 44px; height: 44px;67 border-radius: 10px;68 background: var(--teal);69 display: flex; align-items: center; justify-content: center;70 flex-shrink: 0;71}72 73.logo-icon svg { width: 24px; height: 24px; fill: white; }74 75header h1 {76 font-size: 1.2rem;77 font-weight: 600;78 letter-spacing: -0.01em;79}80header h1 span { color: var(--teal2); }81 82header p {83 font-size: 0.75rem;84 color: var(--muted);85 margin-top: 2px;86 font-weight: 300;87}88 89.badge {90 margin-left: auto;91 font-size: 0.68rem;92 font-family: var(--mono);93 padding: 4px 10px;94 border-radius: 20px;95 border: 1px solid var(--teal);96 color: var(--teal2);97 letter-spacing: 0.04em;98}99 100/* ── Main Layout ── */101main {102 position: relative;103 z-index: 1;104 max-width: 1100px;105 margin: 0 auto;106 padding: 48px 24px 120px;107}108 109/* ── Upload Section ── */110#upload-section { text-align: center; }111 112#upload-section h2 {113 font-size: 2rem;114 font-weight: 700;115 letter-spacing: -0.03em;116 margin-bottom: 10px;117 background: linear-gradient(135deg, #f0f6fc 40%, var(--teal2));118 -webkit-background-clip: text;119 -webkit-text-fill-color: transparent;120 background-clip: text;121}122 123#upload-section p {124 color: var(--muted);125 font-size: 0.9rem;126 margin-bottom: 36px;127 font-weight: 300;128}129 130/* ── Drop Zone ── */131#drop-zone {132 border: 2px dashed rgba(13,148,136,0.4);133 border-radius: 18px;134 padding: 56px 40px;135 cursor: pointer;136 transition: all 0.25s ease;137 background: var(--card);138 max-width: 520px;139 margin: 0 auto 28px;140 position: relative;141}142 143#drop-zone:hover, #drop-zone.dragover {144 border-color: var(--teal2);145 background: var(--teal-dim);146 transform: translateY(-2px);147}148 149#drop-zone .icon {150 font-size: 2.8rem;151 margin-bottom: 14px;152 display: block;153 filter: grayscale(0.3);154}155 156#drop-zone h3 {157 font-size: 1rem;158 font-weight: 500;159 margin-bottom: 6px;160}161 162#drop-zone p {163 font-size: 0.8rem;164 color: var(--muted);165 margin: 0;166}167 168#drop-zone p span {169 color: var(--teal2);170 font-weight: 500;171}172 173#file-input { display: none; }174 175/* Preview inside drop zone */176#preview-wrap {177 display: none;178 position: relative;179}180#preview-img {181 max-height: 220px;182 border-radius: 10px;183 object-fit: contain;184 width: 100%;185}186 187/* ── Analyze Button ── */188#analyze-btn {189 display: none;190 margin: 0 auto;191 padding: 14px 40px;192 background: var(--teal);193 color: white;194 border: none;195 border-radius: 10px;196 font-family: var(--font);197 font-size: 0.95rem;198 font-weight: 600;199 cursor: pointer;200 letter-spacing: 0.02em;201 transition: all 0.2s;202}203#analyze-btn:hover { background: var(--teal2); transform: translateY(-1px); }204#analyze-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }205 206/* ── Loading ── */207#loading {208 display: none;209 text-align: center;210 padding: 48px;211}212 213.spinner {214 width: 48px; height: 48px;215 border: 3px solid var(--border);216 border-top-color: var(--teal);217 border-radius: 50%;218 animation: spin 0.8s linear infinite;219 margin: 0 auto 16px;220}221 222@keyframes spin { to { transform: rotate(360deg); } }223 224#loading p { color: var(--muted); font-size: 0.85rem; }225 226/* ── Error ── */227#error-msg {228 display: none;229 background: rgba(239,68,68,0.1);230 border: 1px solid rgba(239,68,68,0.3);231 color: #fca5a5;232 border-radius: 10px;233 padding: 14px 20px;234 max-width: 520px;235 margin: 20px auto 0;236 font-size: 0.88rem;237 text-align: center;238}239 240/* ── Results Section ── */241#results {242 display: none;243 animation: fadeUp 0.5s ease forwards;244}245 246@keyframes fadeUp {247 from { opacity: 0; transform: translateY(20px); }248 to { opacity: 1; transform: translateY(0); }249}250 251.results-header {252 display: flex;253 align-items: center;254 justify-content: space-between;255 margin-bottom: 28px;256 flex-wrap: wrap;257 gap: 12px;258}259 260.results-header h2 {261 font-size: 1.3rem;262 font-weight: 600;263}264 265.btn-secondary {266 padding: 10px 22px;267 border-radius: 8px;268 font-family: var(--font);269 font-size: 0.82rem;270 font-weight: 500;271 cursor: pointer;272 transition: all 0.2s;273 letter-spacing: 0.02em;274}275 276#new-btn {277 background: transparent;278 border: 1px solid var(--border);279 color: var(--muted);280}281#new-btn:hover { border-color: var(--teal); color: var(--teal2); }282 283#pdf-btn {284 background: var(--teal);285 border: none;286 color: white;287}288#pdf-btn:hover { background: var(--teal2); }289 290/* ── Image Cards ── */291.images-row {292 display: grid;293 grid-template-columns: 1fr 1fr;294 gap: 16px;295 margin-bottom: 20px;296}297 298.img-card {299 background: var(--card);300 border: 1px solid var(--border);301 border-radius: 14px;302 overflow: hidden;303}304 305.img-card-header {306 padding: 10px 16px;307 font-size: 0.75rem;308 font-weight: 600;309 letter-spacing: 0.06em;310 text-transform: uppercase;311 color: var(--muted);312 border-bottom: 1px solid var(--border);313 display: flex;314 align-items: center;315 gap: 8px;316}317 318.img-card-header .dot {319 width: 7px; height: 7px;320 border-radius: 50%;321 background: var(--teal);322}323 324.img-card img {325 width: 100%;326 display: block;327 object-fit: cover;328 max-height: 260px;329}330 331/* ── Info Grid ── */332.info-grid {333 display: grid;334 grid-template-columns: 1fr 1fr;335 gap: 16px;336 margin-bottom: 20px;337}338 339.info-card {340 background: var(--card);341 border: 1px solid var(--border);342 border-radius: 14px;343 padding: 20px 22px;344}345 346.info-card h3 {347 font-size: 0.7rem;348 font-weight: 600;349 letter-spacing: 0.08em;350 text-transform: uppercase;351 color: var(--muted);352 margin-bottom: 10px;353}354 355.grade-display {356 display: flex;357 align-items: flex-end;358 gap: 10px;359 margin-bottom: 14px;360}361 362.grade-label {363 font-size: 1.5rem;364 font-weight: 700;365 letter-spacing: -0.02em;366}367 368.confidence-badge {369 font-family: var(--mono);370 font-size: 0.78rem;371 color: var(--teal2);372 padding: 3px 8px;373 background: var(--teal-dim);374 border-radius: 5px;375 margin-bottom: 4px;376}377 378/* ── Probability Bars ── */379.prob-bars { margin-top: 12px; }380 381.prob-row {382 display: flex;383 align-items: center;384 gap: 10px;385 margin-bottom: 7px;386}387 388.prob-label {389 font-size: 0.72rem;390 color: var(--muted);391 width: 80px;392 flex-shrink: 0;393}394 395.prob-bar-wrap {396 flex: 1;397 height: 6px;398 background: var(--border);399 border-radius: 3px;400 overflow: hidden;401}402 403.prob-bar-fill {404 height: 100%;405 border-radius: 3px;406 background: var(--teal);407 transition: width 0.8s ease;408}409 410.prob-bar-fill.active { background: var(--teal2); }411 412.prob-val {413 font-family: var(--mono);414 font-size: 0.68rem;415 color: var(--muted);416 width: 38px;417 text-align: right;418}419 420/* ── CKD Risk Card ── */421.risk-badge {422 display: inline-block;423 font-size: 0.85rem;424 font-weight: 600;425 padding: 6px 14px;426 border-radius: 8px;427 margin-bottom: 12px;428}429 430.ckd-detail {431 display: flex;432 flex-direction: column;433 gap: 9px;434}435 436.ckd-row {437 display: flex;438 gap: 10px;439 font-size: 0.82rem;440}441 442.ckd-key {443 color: var(--muted);444 font-weight: 500;445 min-width: 90px;446 flex-shrink: 0;447 font-size: 0.75rem;448}449 450.ckd-val {451 color: var(--white);452 font-size: 0.82rem;453}454 455/* CKD risk meter */456.risk-meter {457 height: 6px;458 border-radius: 3px;459 background: linear-gradient(to right, #22c55e, #f59e0b, #ef4444, #7f1d1d);460 position: relative;461 margin: 12px 0 4px;462}463 464.risk-needle {465 position: absolute;466 top: -3px;467 width: 12px; height: 12px;468 border-radius: 50%;469 background: white;470 border: 2px solid var(--navy);471 transform: translateX(-50%);472 box-shadow: 0 0 6px rgba(255,255,255,0.4);473 transition: left 1s ease;474}475 476/* ── Reset Button ── */477.action-row {478 display: flex;479 gap: 12px;480 justify-content: flex-end;481 margin-top: 4px;482}483 484/* ── Chatbot ── */485#chat-btn {486 position: fixed;487 bottom: 28px;488 right: 28px;489 width: 52px; height: 52px;490 border-radius: 50%;491 background: var(--teal);492 border: none;493 cursor: pointer;494 display: flex;495 align-items: center;496 justify-content: center;497 box-shadow: 0 4px 20px rgba(13,148,136,0.35);498 z-index: 100;499 transition: all 0.2s;500}501#chat-btn:hover { background: var(--teal2); transform: scale(1.08); }502#chat-btn svg { width: 22px; height: 22px; fill: white; }503 504#chat-panel {505 position: fixed;506 bottom: 94px;507 right: 28px;508 width: 320px;509 max-height: 440px;510 background: var(--navy2);511 border: 1px solid var(--border);512 border-radius: 16px;513 overflow: hidden;514 display: none;515 flex-direction: column;516 z-index: 100;517 box-shadow: 0 8px 40px rgba(0,0,0,0.4);518 animation: slideUp 0.2s ease;519}520 521@keyframes slideUp {522 from { opacity: 0; transform: translateY(14px); }523 to { opacity: 1; transform: translateY(0); }524}525 526#chat-panel.open { display: flex; }527 528.chat-header {529 padding: 14px 16px;530 background: rgba(13,148,136,0.12);531 border-bottom: 1px solid var(--border);532 display: flex;533 align-items: center;534 gap: 10px;535}536 537.chat-header .avatar {538 width: 28px; height: 28px;539 border-radius: 50%;540 background: var(--teal);541 display: flex; align-items: center; justify-content: center;542 font-size: 0.8rem;543}544 545.chat-header h4 {546 font-size: 0.85rem;547 font-weight: 600;548 flex: 1;549}550 551.chat-header span {552 font-size: 0.65rem;553 color: var(--teal2);554 font-family: var(--mono);555}556 557#chat-messages {558 flex: 1;559 overflow-y: auto;560 padding: 14px;561 display: flex;562 flex-direction: column;563 gap: 10px;564 scrollbar-width: thin;565 scrollbar-color: var(--border) transparent;566}567 568.msg {569 max-width: 85%;570 padding: 9px 13px;571 border-radius: 12px;572 font-size: 0.8rem;573 line-height: 1.5;574}575 576.msg.bot {577 background: var(--card2);578 color: var(--white);579 align-self: flex-start;580 border-bottom-left-radius: 3px;581}582 583.msg.user {584 background: var(--teal);585 color: white;586 align-self: flex-end;587 border-bottom-right-radius: 3px;588}589 590.chat-input-row {591 padding: 10px 12px;592 border-top: 1px solid var(--border);593 display: flex;594 gap: 8px;595}596 597#chat-input {598 flex: 1;599 background: var(--card);600 border: 1px solid var(--border);601 border-radius: 8px;602 padding: 8px 12px;603 color: var(--white);604 font-family: var(--font);605 font-size: 0.8rem;606 outline: none;607}608 609#chat-input:focus { border-color: var(--teal); }610 611#chat-send {612 width: 34px; height: 34px;613 border-radius: 8px;614 background: var(--teal);615 border: none;616 cursor: pointer;617 display: flex;618 align-items: center;619 justify-content: center;620 flex-shrink: 0;621 transition: background 0.2s;622}623#chat-send:hover { background: var(--teal2); }624#chat-send svg { width: 15px; height: 15px; fill: white; }625 626/* Quick suggestions */627.suggestions {628 display: flex;629 flex-wrap: wrap;630 gap: 5px;631 padding: 6px 12px 0;632}633 634.suggestion-chip {635 font-size: 0.68rem;636 padding: 4px 9px;637 border-radius: 12px;638 background: var(--card);639 border: 1px solid var(--border);640 color: var(--muted);641 cursor: pointer;642 transition: all 0.15s;643 font-family: var(--font);644}645.suggestion-chip:hover { border-color: var(--teal); color: var(--teal2); }646 647/* ── Responsive ── */648@media (max-width: 640px) {649 header { padding: 18px 20px; }650 main { padding: 28px 16px 100px; }651 .images-row, .info-grid { grid-template-columns: 1fr; }652 #upload-section h2 { font-size: 1.4rem; }653}654</style>655</head>656<body>657 658<!-- ── Header ── -->659<header>660 <div class="logo-icon">661 <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/></svg>662 </div>663 <div>664 <h1>DR–<span>CKD</span> Early Detection</h1>665 <p>Diabetic Retinopathy Based Early Detection of Chronic Kidney Disease</p>666 </div>667 <div class="badge">ConvNeXt-Large · Phase 2</div>668</header>669 670<!-- ── Main ── -->671<main>672 673 <!-- Upload -->674 <section id="upload-section">675 <h2>Analyze Fundus Image</h2>676 <p>Upload a retinal fundus photograph to predict DR grade and estimate CKD risk</p>677 678 <div id="drop-zone">679 <div id="placeholder-content">680 <span class="icon">🔬</span>681 <h3>Drop fundus image here</h3>682 <p>or <span>click to browse</span> · PNG, JPG, JPEG</p>683 </div>684 <div id="preview-wrap">685 <img id="preview-img" alt="Preview">686 </div>687 </div>688 <input type="file" id="file-input" accept="image/png,image/jpeg,image/jpg">689 690 <div id="error-msg"></div>691 692 <button id="analyze-btn">Analyze Image →</button>693 </section>694 695 <!-- Loading -->696 <div id="loading">697 <div class="spinner"></div>698 <p>Running preprocessing pipeline & model inference...</p>699 </div>700 701 <!-- Results -->702 <section id="results">703 <div class="results-header">704 <h2>Analysis Results</h2>705 <div style="display:flex;gap:10px;">706 <button class="btn-secondary" id="new-btn">← New Image</button>707 <button class="btn-secondary" id="pdf-btn">⬇ Download PDF</button>708 </div>709 </div>710 711 <!-- Images -->712 <div class="images-row">713 <div class="img-card">714 <div class="img-card-header"><div class="dot"></div>Preprocessed Original</div>715 <img id="original-img" alt="Preprocessed fundus">716 </div>717 <div class="img-card">718 <div class="img-card-header"><div class="dot" style="background:#f59e0b"></div>GradCAM Overlay</div>719 <img id="gradcam-img" alt="GradCAM heatmap">720 </div>721 </div>722 723 <!-- Info cards -->724 <div class="info-grid">725 726 <!-- DR Prediction card -->727 <div class="info-card">728 <h3>DR Prediction</h3>729 <div class="grade-display">730 <div class="grade-label" id="grade-label">—</div>731 <div class="confidence-badge" id="conf-badge">—</div>732 </div>733 <div class="prob-bars" id="prob-bars"></div>734 </div>735 736 <!-- CKD Risk card -->737 <div class="info-card">738 <h3>CKD Risk Assessment</h3>739 <div class="risk-badge" id="risk-badge">—</div>740 <div class="risk-meter">741 <div class="risk-needle" id="risk-needle" style="left:5%"></div>742 </div>743 <div class="ckd-detail" id="ckd-detail"></div>744 </div>745 </div>746 747 </section><!-- /results -->748</main>749 750<!-- ── Chatbot ── -->751<button id="chat-btn" title="Ask a question">752 <svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/></svg>753</button>754 755<div id="chat-panel">756 <div class="chat-header">757 <div class="avatar">🤖</div>758 <h4>DR–CKD Assistant</h4>759 <span>online</span>760 </div>761 <div class="suggestions" id="suggestions">762 <button class="suggestion-chip" onclick="sendSuggestion(this)">What is DR?</button>763 <button class="suggestion-chip" onclick="sendSuggestion(this)">What is CKD?</button>764 <button class="suggestion-chip" onclick="sendSuggestion(this)">How does GradCAM work?</button>765 <button class="suggestion-chip" onclick="sendSuggestion(this)">What is eGFR?</button>766 </div>767 <div id="chat-messages">768 <div class="msg bot">Hello! I can answer questions about Diabetic Retinopathy, Chronic Kidney Disease, and how this tool works. Ask me anything! 👋</div>769 </div>770 <div class="chat-input-row">771 <input id="chat-input" type="text" placeholder="Type a question..." autocomplete="off">772 <button id="chat-send">773 <svg viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2z"/></svg>774 </button>775 </div>776</div>777 778<!-- ── JavaScript ── -->779<script>780// ─────────────────────────────781// State782// ─────────────────────────────783let currentResult = null;784let selectedFile = null;785 786// ─────────────────────────────787// Upload / Drop Zone788// ─────────────────────────────789const dropZone = document.getElementById('drop-zone');790const fileInput = document.getElementById('file-input');791const analyzeBtn = document.getElementById('analyze-btn');792const placeholder = document.getElementById('placeholder-content');793const previewWrap = document.getElementById('preview-wrap');794const previewImg = document.getElementById('preview-img');795 796dropZone.addEventListener('click', () => fileInput.click());797 798dropZone.addEventListener('dragover', e => {799 e.preventDefault();800 dropZone.classList.add('dragover');801});802 803dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));804 805dropZone.addEventListener('drop', e => {806 e.preventDefault();807 dropZone.classList.remove('dragover');808 const file = e.dataTransfer.files[0];809 if (file) loadPreview(file);810});811 812fileInput.addEventListener('change', () => {813 if (fileInput.files[0]) loadPreview(fileInput.files[0]);814});815 816function loadPreview(file) {817 selectedFile = file;818 const reader = new FileReader();819 reader.onload = ev => {820 previewImg.src = ev.target.result;821 placeholder.style.display = 'none';822 previewWrap.style.display = 'block';823 analyzeBtn.style.display = 'block';824 hideError();825 };826 reader.readAsDataURL(file);827}828 829// ─────────────────────────────830// Analyze831// ─────────────────────────────832analyzeBtn.addEventListener('click', runAnalysis);833 834async function runAnalysis() {835 if (!selectedFile) return;836 hideError();837 showLoading();838 839 const formData = new FormData();840 formData.append('image', selectedFile);841 842 try {843 const resp = await fetch('/predict', { method: 'POST', body: formData });844 const data = await resp.json();845 846 if (!resp.ok || data.error) {847 showError(data.error || 'Something went wrong.');848 hideLoading();849 return;850 }851 852 currentResult = data;853 hideLoading();854 showResults(data);855 856 } catch (err) {857 showError('Network error. Make sure the backend server is running.');858 hideLoading();859 }860}861 862// ─────────────────────────────863// Show Results864// ─────────────────────────────865function showResults(d) {866 document.getElementById('upload-section').style.display = 'none';867 document.getElementById('results').style.display = 'block';868 869 document.getElementById('original-img').src = 'data:image/png;base64,' + d.original_img;870 document.getElementById('gradcam-img').src = 'data:image/png;base64,' + d.gradcam_img;871 872 document.getElementById('grade-label').textContent = d.dr_label;873 document.getElementById('conf-badge').textContent = 'Confidence: ' + d.confidence + '%';874 875 // Prob bars876 const barsEl = document.getElementById('prob-bars');877 barsEl.innerHTML = '';878 d.class_names.forEach((name, i) => {879 const isActive = i === d.dr_grade;880 barsEl.innerHTML += `881 <div class="prob-row">882 <div class="prob-label">${name}</div>883 <div class="prob-bar-wrap">884 <div class="prob-bar-fill ${isActive ? 'active' : ''}"885 style="width:0%" data-target="${d.all_probs[i]}%"></div>886 </div>887 <div class="prob-val">${d.all_probs[i]}%</div>888 </div>`;889 });890 // Animate bars891 requestAnimationFrame(() => {892 document.querySelectorAll('.prob-bar-fill').forEach(b => {893 b.style.width = b.dataset.target;894 });895 });896 897 // CKD Risk898 const ckd = d.ckd;899 const badge = document.getElementById('risk-badge');900 badge.textContent = ckd.risk_category;901 badge.style.background = hexToRgba(d.risk_color, 0.15);902 badge.style.color = d.risk_color;903 badge.style.border = `1px solid ${hexToRgba(d.risk_color, 0.3)}`;904 905 // Needle position (0–4 grade → 0–100%)906 const needlePos = (d.dr_grade / 4) * 92 + 4;907 setTimeout(() => {908 document.getElementById('risk-needle').style.left = needlePos + '%';909 }, 100);910 911 document.getElementById('ckd-detail').innerHTML = `912 <div class="ckd-row"><span class="ckd-key">CKD Risk</span><span class="ckd-val">${ckd.risk_percentage}%</span></div>913 <div class="ckd-row"><span class="ckd-key">eGFR Range</span><span class="ckd-val">${ckd.egfr_range} mL/min/1.73m²</span></div>914 <div class="ckd-row"><span class="ckd-key">CKD Stage</span><span class="ckd-val">${ckd.ckd_stage}</span></div>915 <div class="ckd-row"><span class="ckd-key">Action</span><span class="ckd-val">${ckd.clinical_action}</span></div>916 `;917}918 919// ─────────────────────────────920// New Image921// ─────────────────────────────922document.getElementById('new-btn').addEventListener('click', () => {923 currentResult = null;924 selectedFile = null;925 fileInput.value = '';926 placeholder.style.display = 'block';927 previewWrap.style.display = 'none';928 analyzeBtn.style.display = 'none';929 document.getElementById('results').style.display = 'none';930 document.getElementById('upload-section').style.display = 'block';931});932 933// ─────────────────────────────934// PDF Download935// ─────────────────────────────936document.getElementById('pdf-btn').addEventListener('click', async () => {937 if (!currentResult) return;938 const btn = document.getElementById('pdf-btn');939 btn.textContent = 'Generating...';940 btn.disabled = true;941 942 try {943 const resp = await fetch('/generate_pdf', {944 method: 'POST',945 headers: { 'Content-Type': 'application/json' },946 body: JSON.stringify(currentResult)947 });948 const blob = await resp.blob();949 const url = URL.createObjectURL(blob);950 const a = document.createElement('a');951 a.href = url;952 a.download = 'CKD_Risk_Report.pdf';953 a.click();954 URL.revokeObjectURL(url);955 } catch (e) {956 alert('PDF generation failed.');957 } finally {958 btn.textContent = '⬇ Download PDF';959 btn.disabled = false;960 }961});962 963// ─────────────────────────────964// UI helpers965// ─────────────────────────────966function showLoading() {967 document.getElementById('upload-section').style.display = 'none';968 document.getElementById('loading').style.display = 'block';969}970function hideLoading() {971 document.getElementById('loading').style.display = 'none';972}973function showError(msg) {974 const el = document.getElementById('error-msg');975 el.textContent = '⚠ ' + msg;976 el.style.display = 'block';977 document.getElementById('upload-section').style.display = 'block';978}979function hideError() {980 document.getElementById('error-msg').style.display = 'none';981}982function hexToRgba(hex, alpha) {983 const r = parseInt(hex.slice(1,3),16);984 const g = parseInt(hex.slice(3,5),16);985 const b = parseInt(hex.slice(5,7),16);986 return `rgba(${r},${g},${b},${alpha})`;987}988 989// ─────────────────────────────990// Chatbot (no API — pure JS Q&A)991// ─────────────────────────────992const QA = [993 {994 keys: ['hello','hi','hey','good morning','good afternoon'],995 ans: 'Hello! 👋 How can I help you today? You can ask me about Diabetic Retinopathy, CKD, GradCAM, or how this tool works.'996 },997 {998 keys: ['what is dr','diabetic retinopathy','what is diabetic'],999 ans: 'Diabetic Retinopathy (DR) is a diabetes complication that damages blood vessels in the retina. It has 5 stages: No DR → Mild → Moderate → Severe → Proliferative. Early detection prevents blindness.'1000 },1001 {1002 keys: ['what is ckd','chronic kidney disease','kidney disease'],1003 ans: 'Chronic Kidney Disease (CKD) is gradual loss of kidney function. It is classified into stages G1–G5 based on eGFR values. Diabetic patients with retinopathy have a significantly higher risk of CKD due to shared microvascular damage.'1004 },1005 {1006 keys: ['what is egfr','egfr','glomerular filtration'],1007 ans: 'eGFR (estimated Glomerular Filtration Rate) measures how well your kidneys filter blood. >90 = Normal, 60–89 = Mild decrease, 45–59 = Moderate, 15–29 = Severe, <15 = Kidney failure. It is the key marker for CKD staging.'1008 },1009 {1010 keys: ['gradcam','grad-cam','heatmap','attention map'],1011 ans: 'GradCAM (Gradient-weighted Class Activation Mapping) highlights the retinal regions most important for the model\'s prediction. Red/warm areas = high attention. It helps clinicians understand what the model focused on — like microaneurysms or neovascularization.'1012 },1013 {1014 keys: ['convnext','model','architecture','deep learning','neural network'],1015 ans: 'We use ConvNeXt-Large pretrained on ImageNet-22K. It has a custom head: LayerNorm → Dropout(0.3) → Linear(1536→512) → GELU → Dropout(0.2) → Linear(512→5). Trained in two phases: frozen warmup (Phase 1) then progressive unfreezing (Phase 2).'1016 },1017 {1018 keys: ['preprocessing','ben graham','clahe','how image processed'],1019 ans: 'The preprocessing pipeline: (1) crop_black_border — removes dark circular border, (2) Ben Graham method — Gaussian blur subtraction (sigmaX=10) to normalize illumination, (3) CLAHE — local contrast enhancement on the LAB L-channel, (4) Resize to 224×224 + ImageNet normalization.'1020 },1021 {1022 keys: ['accuracy','performance','results','how accurate','metrics'],1023 ans: 'The model was trained on the 2015 Kaggle DR dataset with 5000 images per class (balanced). Performance was evaluated using accuracy, QWK (Quadratic Weighted Kappa), confusion matrix, and per-class F1 score. Check your Phase 2 training history for exact numbers.'1024 },1025 {1026 keys: ['phase 1','phase 2','training','fine tuning','fine-tuning'],1027 ans: 'Phase 1 — Frozen backbone warmup: only the classification head is trained for a few epochs. Phase 2 — Progressive unfreezing: backbone stages are gradually unfrozen with discriminative learning rates, EMA, MixUp, and CutMix for regularization.'1028 },1029 {1030 keys: ['pdf','report','download'],1031 ans: 'You can download a PDF report after analysis using the "Download PDF" button. It includes the DR grade, confidence, all class probabilities, CKD risk details, and the GradCAM images side by side.'1032 },1033 {1034 keys: ['fundus','fundus image','retina','eye','retinal'],1035 ans: 'A fundus image is a photograph of the back of the eye (retina). It is captured with a fundus camera. These images show blood vessels, the optic disc, and macula — allowing detection of DR signs like microaneurysms, hemorrhages, and neovascularization.'1036 },1037 {1038 keys: ['stages','grades','classification','levels','no dr','mild','moderate','severe','proliferative'],1039 ans: 'DR has 5 grades: Grade 0 — No DR (normal), Grade 1 — Mild (microaneurysms only), Grade 2 — Moderate (more extensive), Grade 3 — Severe (many hemorrhages, venous beading), Grade 4 — Proliferative (new vessel growth, high vision loss risk).'1040 },1041 {1042 keys: ['risk','very low','low risk','high risk','very high'],1043 ans: 'CKD risk by DR grade: No DR = Very Low (2.5%), Mild = Low (10%), Moderate = Moderate (35%), Severe = High (60%), Proliferative = Very High (85%). These are based on clinical literature on DR-CKD comorbidity.'1044 },1045 {1046 keys: ['dataset','kaggle','training data'],1047 ans: 'We used the Kaggle 2015 Diabetic Retinopathy dataset (colored, resized). The data was preprocessed, augmented to 5000 images per class, then split 75% train / 15% validation / 10% test with stratification to maintain class balance.'1048 },1049 {1050 keys: ['error','wrong image','not fundus','invalid'],1051 ans: 'The system validates that the uploaded image appears to be a retinal fundus photograph. If you get an error, make sure you upload an actual fundus image — not a regular photo, screenshot, or document.'1052 },1053 {1054 keys: ['thank','thanks','thank you'],1055 ans: 'You\'re welcome! Good luck with your thesis defence! 🎓'1056 },1057 {1058 keys: ['bye','goodbye','see you'],1059 ans: 'Goodbye! Best of luck! 👋'1060 }1061];1062 1063function getBotReply(text) {1064 const lower = text.toLowerCase().trim();1065 for (const item of QA) {1066 if (item.keys.some(k => lower.includes(k))) {1067 return item.ans;1068 }1069 }1070 return "I'm not sure about that. Try asking: \"What is DR?\", \"How does GradCAM work?\", \"What is eGFR?\", or \"What is CKD?\"";1071}1072 1073function appendMsg(text, who) {1074 const msgs = document.getElementById('chat-messages');1075 const div = document.createElement('div');1076 div.className = 'msg ' + who;1077 div.textContent = text;1078 msgs.appendChild(div);1079 msgs.scrollTop = msgs.scrollHeight;1080}1081 1082function sendMessage() {1083 const input = document.getElementById('chat-input');1084 const text = input.value.trim();1085 if (!text) return;1086 input.value = '';1087 appendMsg(text, 'user');1088 // Hide suggestions after first message1089 document.getElementById('suggestions').style.display = 'none';1090 setTimeout(() => appendMsg(getBotReply(text), 'bot'), 400);1091}1092 1093function sendSuggestion(btn) {1094 const text = btn.textContent;1095 appendMsg(text, 'user');1096 document.getElementById('suggestions').style.display = 'none';1097 setTimeout(() => appendMsg(getBotReply(text), 'bot'), 400);1098}1099 1100document.getElementById('chat-send').addEventListener('click', sendMessage);1101document.getElementById('chat-input').addEventListener('keydown', e => {1102 if (e.key === 'Enter') sendMessage();1103});1104 1105// Toggle chatbot panel1106document.getElementById('chat-btn').addEventListener('click', () => {1107 document.getElementById('chat-panel').classList.toggle('open');1108});1109</script>1110</body>1111</html>1112 