CoolFace
Apppublic

Rf33d/DR-CKD

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
index.html1112 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>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> &nbsp;·&nbsp; 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 &amp; 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