Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>NeuraScan</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <style> | |
| :root{ | |
| --bg:#0b1220; | |
| --card:#0f1b33; | |
| --card2:#0c172e; | |
| --text:#e8eefc; | |
| --muted:#a9b7d0; | |
| --border:rgba(255,255,255,.12); | |
| --primary:#4f7cff; | |
| --ring:rgba(79,124,255,.35); | |
| } | |
| :root[data-theme="light"]{ | |
| --bg:#f6f7fb; | |
| --card:#ffffff; | |
| --card2:#ffffff; | |
| --text:#0c1222; | |
| --muted:#44506a; | |
| --border:rgba(0,0,0,.12); | |
| --primary:#355dff; | |
| --ring:rgba(53,93,255,.25); | |
| } | |
| *{box-sizing:border-box} | |
| body{ | |
| margin:0; | |
| font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif; | |
| background:var(--bg); | |
| color:var(--text); | |
| } | |
| header{ | |
| display:flex; | |
| justify-content:space-between; | |
| align-items:center; | |
| padding:20px 28px; | |
| } | |
| .brand{display:flex;gap:12px;align-items:center} | |
| .brand img{width:40px;height:40px} | |
| .chip{ | |
| padding:8px 12px; | |
| border-radius:999px; | |
| background:var(--card); | |
| border:1px solid var(--border); | |
| cursor:pointer; | |
| } | |
| nav{ | |
| padding:0 28px 18px; | |
| display:flex; | |
| gap:10px; | |
| } | |
| .tab{ | |
| padding:8px 16px; | |
| border-radius:999px; | |
| border:1px solid var(--border); | |
| background:rgba(255,255,255,.06); | |
| cursor:pointer; | |
| } | |
| .tab.active{ | |
| background:rgba(79,124,255,.18); | |
| box-shadow:0 0 0 4px var(--ring); | |
| } | |
| .section{display:none} | |
| .section.show{display:block} | |
| main{ | |
| display:grid; | |
| grid-template-columns:1.2fr 1fr; | |
| gap:24px; | |
| padding:0 28px 28px; | |
| } | |
| @media(max-width:900px){main{grid-template-columns:1fr}} | |
| .card{ | |
| background:linear-gradient(180deg,var(--card),var(--card2)); | |
| border-radius:18px; | |
| border:1px solid var(--border); | |
| padding:22px; | |
| } | |
| h2{margin:0 0 12px;font-size:20px} | |
| .controls{ | |
| display:flex; | |
| gap:12px; | |
| flex-wrap:wrap; | |
| align-items:center; | |
| } | |
| .select-wrap{position:relative} | |
| .select-wrap::after{ | |
| content:"▾"; | |
| position:absolute; | |
| right:14px; | |
| top:50%; | |
| transform:translateY(-50%); | |
| pointer-events:none; | |
| color:var(--muted); | |
| } | |
| select{ | |
| appearance:none; | |
| padding:10px 40px 10px 14px; | |
| border-radius:12px; | |
| border:1px solid var(--border); | |
| background:rgba(255,255,255,.06); | |
| color:var(--text); | |
| font-weight:600; | |
| min-width:260px; | |
| } | |
| .file-btn{ | |
| padding:10px 14px; | |
| border-radius:12px; | |
| border:1px dashed var(--border); | |
| cursor:pointer; | |
| background:rgba(255,255,255,.04); | |
| } | |
| button.primary{ | |
| padding:10px 18px; | |
| border-radius:12px; | |
| border:none; | |
| background:linear-gradient(180deg,var(--primary),#2f62ff); | |
| color:white; | |
| font-weight:700; | |
| cursor:pointer; | |
| } | |
| button.secondary{ | |
| padding:10px 14px; | |
| border-radius:12px; | |
| border:1px solid var(--border); | |
| background:rgba(255,255,255,.04); | |
| cursor:pointer; | |
| } | |
| button:disabled{opacity:.6} | |
| .upload-box{ | |
| margin-top:14px; | |
| padding:18px; | |
| border:2px dashed var(--border); | |
| border-radius:14px; | |
| text-align:center; | |
| color:var(--muted); | |
| } | |
| .preview{margin-top:14px;background:black;border-radius:14px;overflow:hidden} | |
| .preview.hidden{display:none} | |
| .preview img{width:100%;max-height:360px;object-fit:contain} | |
| .result{margin-top:16px} | |
| .result-title{font-size:22px;font-weight:800} | |
| .result-msg{color:var(--muted);margin-top:6px} | |
| .examples .ex{ | |
| margin-top:12px; | |
| border:1px solid var(--border); | |
| border-radius:14px; | |
| overflow:hidden; | |
| } | |
| .examples .lbl{padding:10px;font-weight:800;border-bottom:1px solid var(--border)} | |
| .examples .desc{padding:0 10px 10px;color:var(--muted)} | |
| .examples img{width:100%;max-height:280px;object-fit:contain;background:black} | |
| .team{ | |
| display:grid; | |
| grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); | |
| gap:14px; | |
| } | |
| .member{ | |
| display:flex; | |
| gap:12px; | |
| align-items:center; | |
| padding:10px; | |
| border:1px solid var(--border); | |
| border-radius:14px; | |
| } | |
| .member img{width:48px;height:48px;border-radius:50%;cursor:pointer} | |
| footer{ | |
| padding:14px 28px; | |
| border-top:1px solid var(--border); | |
| font-size:13px; | |
| color:var(--muted); | |
| } | |
| /* modal */ | |
| .modal-backdrop{ | |
| position:fixed; | |
| inset:0; | |
| background:rgba(0,0,0,.6); | |
| display:none; | |
| align-items:center; | |
| justify-content:center; | |
| } | |
| .modal-backdrop.show{display:flex} | |
| .modal{ | |
| background:var(--card); | |
| padding:18px; | |
| border-radius:16px; | |
| border:1px solid var(--border); | |
| width:420px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <img src="{{ url_for('static', filename='assets/brain/brain.svg') }}"> | |
| <div> | |
| <strong>NeuraScan</strong><br> | |
| <small style="color:var(--muted)">AI-assisted MRI screening (demo)</small> | |
| </div> | |
| </div> | |
| <button id="themeToggle" class="chip">☀️</button> | |
| </header> | |
| <nav> | |
| <button class="tab active" onclick="showSection('scan',this)">Scan</button> | |
| <button class="tab" onclick="showSection('info',this)">Info</button> | |
| </nav> | |
| <section id="scan" class="section show"> | |
| <main> | |
| <div class="card"> | |
| <h2>New scan</h2> | |
| <div class="controls"> | |
| <span class="select-wrap"><select id="modelSelect"></select></span> | |
| <label class="file-btn"> | |
| Choose image | |
| <input type="file" id="fileInput" accept="image/*" hidden> | |
| </label> | |
| <button class="primary" id="runBtn" onclick="runScan()">Run scan</button> | |
| <button class="secondary" id="newBtn" style="display:none" onclick="newScan()">New scan</button> | |
| </div> | |
| <div class="upload-box" id="dropZone"> | |
| Drag & drop or paste (Ctrl+V) an MRI image | |
| </div> | |
| <div class="preview hidden" id="previewBox"> | |
| <img id="previewImg"> | |
| </div> | |
| <div class="result"> | |
| <div id="resultTitle" class="result-title">—</div> | |
| <div id="resultMsg" class="result-msg">Upload an image to begin.</div> | |
| <button id="likelyBtn" class="secondary" style="display:none;margin-top:10px" onclick="showLikely()">Show most likely result</button> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Examples</h2> | |
| <div class="examples"> | |
| <div class="ex"> | |
| <div class="lbl">Supported example</div> | |
| <div class="desc">Brain-only MRI slice (no skull visible).</div> | |
| <img src="{{ url_for('static', filename='assets/brain/supportedexample.jpg') }}"> | |
| </div> | |
| <div class="ex"> | |
| <div class="lbl">Unsupported example</div> | |
| <div class="desc">Skull visible — please upload a brain-only slice.</div> | |
| <img src="{{ url_for('static', filename='assets/brain/unsupportedexample.jpg') }}"> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </section> | |
| <section id="info" class="section"> | |
| <main> | |
| <div class="card"> | |
| <h2>About NeuraScan</h2> | |
| <p>Educational demo for Alzheimer’s stage classification. Low confidence → <b>Uncertain</b>.</p> | |
| <h2>Under the supervision of:</h2> | |
| <p> | |
| Prof. Muhammad Sayed Hammad<br> | |
| Eng. Heidi Ahmed | |
| </p> | |
| <h2>Team</h2> | |
| <div class="team"> | |
| <div class="member"><img src="{{ url_for('static', filename='assets/images/team/fatma.jpg') }}" onclick="window.open('https://www.linkedin.com/in/fatma-al-zahraa-emad-326b64234/')"><span>Fatma Al-Zahraa Emad</span></div> | |
| <div class="member"><img src="{{ url_for('static', filename='assets/images/team/gehad.jpg') }}" onclick="window.open('https://www.linkedin.com/in/gehad-mohamed-2a4946252/')"><span>Gehad Mohamed</span></div> | |
| <div class="member"><img src="{{ url_for('static', filename='assets/images/team/heba.jpg') }}" onclick="window.open('https://www.linkedin.com/in/hebatullah-elgazoly-308ab2243/')"><span>Hebatullah El Gazoly</span></div> | |
| <div class="member"><img src="{{ url_for('static', filename='assets/images/team/asem.jpg') }}" onclick="window.open('https://www.linkedin.com/in/mohamedasem318/')"><span>Mohamed Assem</span></div> | |
| <div class="member"><img src="{{ url_for('static', filename='assets/images/team/sameh.jpg') }}" onclick="window.open('https://www.linkedin.com/in/muhamedsameh/')"><span>Mohamed Sameh</span></div> | |
| </div> | |
| </div> | |
| </main> | |
| </section> | |
| <footer> | |
| Contact: <a href="mailto:mohamedasem318@gmail.com">Mohamed Assem</a> • | |
| <a href="mailto:mohamed.sameh8103@gmail.com">Mohamed Sameh</a><br> | |
| Educational demo — not medical advice | |
| </footer> | |
| <div class="modal-backdrop" id="modalBackdrop" onclick="closeModalIfBackdrop(event)"> | |
| <div class="modal"> | |
| <h3>Most likely result</h3> | |
| <p id="modalLabel"></p> | |
| <p id="modalProb"></p> | |
| <button class="secondary" onclick="closeModal()">Close</button> | |
| </div> | |
| </div> | |
| <script> | |
| function showSection(id,btn){ | |
| document.querySelectorAll('.section').forEach(s=>s.classList.remove('show')); | |
| document.getElementById(id).classList.add('show'); | |
| document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active')); | |
| btn.classList.add('active'); | |
| } | |
| function setTheme(t){ | |
| document.documentElement.dataset.theme=t; | |
| localStorage.setItem("theme",t); | |
| themeToggle.textContent=t==="dark"?"☀️":"🌙"; | |
| } | |
| themeToggle.onclick=()=>setTheme(document.documentElement.dataset.theme==="dark"?"light":"dark"); | |
| setTheme(localStorage.getItem("theme")||"dark"); | |
| let currentFile=null,lastMostLikely=null; | |
| const previewBox=document.getElementById("previewBox"); | |
| const previewImg=document.getElementById("previewImg"); | |
| function setFile(f){ | |
| currentFile=f; | |
| previewImg.src=URL.createObjectURL(f); | |
| previewBox.classList.remove("hidden"); | |
| resultTitle.textContent="—"; | |
| resultMsg.textContent="Ready to run scan."; | |
| likelyBtn.style.display="none"; | |
| newBtn.style.display="none"; | |
| lastMostLikely=null; | |
| } | |
| fileInput.onchange=e=>e.target.files[0]&&setFile(e.target.files[0]); | |
| dropZone.ondragover=e=>{e.preventDefault()}; | |
| dropZone.ondrop=e=>{ | |
| e.preventDefault(); | |
| e.dataTransfer.files[0]&&setFile(e.dataTransfer.files[0]); | |
| }; | |
| window.addEventListener("paste",e=>{ | |
| for(const i of e.clipboardData.items){ | |
| if(i.type.startsWith("image/")){setFile(i.getAsFile());break;} | |
| } | |
| }); | |
| async function loadModels(){ | |
| const r=await fetch("/api/models"); | |
| const d=await r.json(); | |
| modelSelect.innerHTML=""; | |
| d.models.forEach(m=>{ | |
| const o=document.createElement("option"); | |
| o.value=m.id;o.textContent=m.name; | |
| modelSelect.appendChild(o); | |
| }); | |
| modelSelect.value=d.default_model_id; | |
| } | |
| async function runScan(){ | |
| if(!currentFile){resultMsg.textContent="Upload an image first.";return;} | |
| runBtn.disabled=true; | |
| modelSelect.disabled=true; | |
| resultTitle.textContent="Running…"; | |
| resultMsg.textContent="Analyzing image…"; | |
| likelyBtn.style.display="none"; | |
| lastMostLikely=null; | |
| try{ | |
| const fd=new FormData(); | |
| fd.append("file",currentFile); | |
| fd.append("model_id",modelSelect.value); | |
| const r=await fetch("/api/classify",{method:"POST",body:fd}); | |
| const d=await r.json(); | |
| if(d.prediction.label==="Uncertain"){ | |
| resultTitle.textContent="Uncertain"; | |
| resultMsg.textContent="Consult a professional."; | |
| const probs=[...(d.probabilities||[])].sort((a,b)=>b.prob-a.prob); | |
| if(probs[0]){ | |
| lastMostLikely=probs[0]; | |
| likelyBtn.style.display="inline-block"; | |
| } | |
| }else{ | |
| resultTitle.textContent=d.prediction.label; | |
| resultMsg.textContent=`Confidence: ${(d.prediction.confidence*100).toFixed(1)}%`; | |
| likelyBtn.style.display="none"; | |
| lastMostLikely=null; | |
| } | |
| newBtn.style.display="inline-block"; | |
| }catch(e){ | |
| resultTitle.textContent="Sorry"; | |
| resultMsg.textContent="Something went wrong."; | |
| }finally{ | |
| runBtn.disabled=false; | |
| modelSelect.disabled=false; | |
| } | |
| } | |
| function newScan(){ | |
| currentFile=null; | |
| fileInput.value=""; | |
| previewBox.classList.add("hidden"); | |
| previewImg.src=""; | |
| resultTitle.textContent="—"; | |
| resultMsg.textContent="Upload an image to begin."; | |
| likelyBtn.style.display="none"; | |
| newBtn.style.display="none"; | |
| lastMostLikely=null; | |
| } | |
| function showLikely(){ | |
| if(!lastMostLikely)return; | |
| modalLabel.textContent=lastMostLikely.label; | |
| modalProb.textContent=`Probability: ${(lastMostLikely.prob*100).toFixed(1)}%`; | |
| modalBackdrop.classList.add("show"); | |
| } | |
| function closeModal(){modalBackdrop.classList.remove("show")} | |
| function closeModalIfBackdrop(e){if(e.target.id==="modalBackdrop")closeModal()} | |
| loadModels(); | |
| </script> | |
| </body> | |
| </html> | |