Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Brain Tumor Classification β AI-Powered MRI Analysis</title> | |
| <meta name="description" content="AI-powered brain tumor classification using MoCo Self-Supervised Learning with Swin-Transformer backbone. Upload MRI scans for instant analysis with GradCAM explainability."> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-primary: #0B1120; | |
| --bg-secondary: #111827; | |
| --bg-card: rgba(17, 24, 39, 0.7); | |
| --bg-glass: rgba(255, 255, 255, 0.04); | |
| --bg-glass-hover: rgba(255, 255, 255, 0.07); | |
| --border-glass: rgba(255, 255, 255, 0.08); | |
| --border-accent: rgba(99, 179, 237, 0.3); | |
| --text-primary: #F7FAFC; | |
| --text-secondary: #A0AEC0; | |
| --text-muted: #718096; | |
| --accent-blue: #63B3ED; | |
| --accent-cyan: #4FD1C5; | |
| --accent-indigo: #7F9CF5; | |
| --gradient-primary: linear-gradient(135deg, #63B3ED 0%, #4FD1C5 100%); | |
| --gradient-header: linear-gradient(135deg, #0D1B2A 0%, #1B2838 50%, #0F2027 100%); | |
| --gradient-button: linear-gradient(135deg, #2B6CB0 0%, #2C7A7B 100%); | |
| --gradient-button-hover: linear-gradient(135deg, #3182CE 0%, #38B2AC 100%); | |
| --success: #48BB78; | |
| --danger: #FC8181; | |
| --danger-bg: rgba(252, 129, 129, 0.1); | |
| --warning: #F6AD55; | |
| --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.5); | |
| --shadow-card: 0 4px 24px rgba(0, 0, 0, 0.3); | |
| --shadow-glow: 0 0 30px rgba(99, 179, 237, 0.15); | |
| --radius-lg: 16px; | |
| --radius-md: 10px; | |
| --radius-sm: 6px; | |
| --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Animated BG */ | |
| .bg-pattern { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 0; | |
| overflow: hidden; | |
| pointer-events: none; | |
| } | |
| .bg-pattern::before, | |
| .bg-pattern::after { | |
| content: ''; | |
| position: absolute; | |
| border-radius: 50%; | |
| filter: blur(100px); | |
| opacity: 0.12; | |
| animation: float 20s ease-in-out infinite; | |
| } | |
| .bg-pattern::before { | |
| width: 600px; height: 600px; | |
| background: var(--accent-blue); | |
| top: -200px; right: -100px; | |
| } | |
| .bg-pattern::after { | |
| width: 500px; height: 500px; | |
| background: var(--accent-cyan); | |
| bottom: -150px; left: -100px; | |
| animation-delay: -10s; | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translate(0, 0) scale(1); } | |
| 33% { transform: translate(30px, -30px) scale(1.05); } | |
| 66% { transform: translate(-20px, 20px) scale(0.95); } | |
| } | |
| .app-container { | |
| position: relative; | |
| z-index: 1; | |
| max-width: 1340px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| min-height: 100vh; | |
| } | |
| /* βββ HEADER βββ */ | |
| .header { | |
| background: var(--gradient-header); | |
| border: 1px solid var(--border-glass); | |
| border-radius: var(--radius-lg); | |
| padding: 36px 32px 28px; | |
| text-align: center; | |
| margin-bottom: 24px; | |
| position: relative; | |
| overflow: hidden; | |
| backdrop-filter: blur(20px); | |
| box-shadow: var(--shadow-card); | |
| } | |
| .header::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; | |
| height: 3px; | |
| background: var(--gradient-primary); | |
| } | |
| .header-icon { | |
| width: 64px; height: 64px; | |
| background: var(--bg-glass); | |
| border: 1px solid var(--border-accent); | |
| border-radius: 50%; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 30px; | |
| margin-bottom: 14px; | |
| box-shadow: var(--shadow-glow); | |
| } | |
| .header h1 { | |
| font-size: 28px; | |
| font-weight: 800; | |
| background: var(--gradient-primary); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 6px; | |
| letter-spacing: -0.5px; | |
| } | |
| .header p { | |
| color: var(--text-secondary); | |
| font-size: 14px; | |
| font-weight: 400; | |
| } | |
| .header-badges { | |
| display: flex; | |
| gap: 10px; | |
| justify-content: center; | |
| margin-top: 14px; | |
| flex-wrap: wrap; | |
| } | |
| .badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 4px 12px; | |
| background: var(--bg-glass); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 20px; | |
| font-size: 11px; | |
| color: var(--text-secondary); | |
| font-weight: 500; | |
| } | |
| .badge span { font-size: 12px; } | |
| /* βββ LAYOUT βββ */ | |
| .content-grid { | |
| display: grid; | |
| grid-template-columns: 420px 1fr; | |
| gap: 24px; | |
| align-items: start; | |
| } | |
| /* βββ CARDS βββ */ | |
| .card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-glass); | |
| border-radius: var(--radius-lg); | |
| padding: 24px; | |
| backdrop-filter: blur(16px); | |
| box-shadow: var(--shadow-card); | |
| transition: var(--transition); | |
| } | |
| .card:hover { | |
| border-color: var(--border-accent); | |
| } | |
| .card-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 18px; | |
| } | |
| .card-title .icon { | |
| width: 32px; height: 32px; | |
| background: var(--bg-glass); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 15px; | |
| } | |
| /* βββ LEFT PANEL βββ */ | |
| .left-panel { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| position: sticky; | |
| top: 20px; | |
| } | |
| /* Name Input */ | |
| .input-group { | |
| margin-bottom: 16px; | |
| } | |
| .input-label { | |
| display: block; | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| margin-bottom: 6px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .input-field { | |
| width: 100%; | |
| padding: 10px 14px; | |
| background: var(--bg-glass); | |
| border: 1px solid var(--border-glass); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-primary); | |
| font-family: inherit; | |
| font-size: 13px; | |
| transition: var(--transition); | |
| } | |
| .input-field:focus { | |
| outline: none; | |
| border-color: var(--accent-blue); | |
| box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.15); | |
| } | |
| .input-field::placeholder { | |
| color: var(--text-muted); | |
| } | |
| /* Upload Dropzone */ | |
| .dropzone { | |
| border: 2px dashed var(--border-glass); | |
| border-radius: var(--radius-md); | |
| padding: 32px 20px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| background: var(--bg-glass); | |
| position: relative; | |
| } | |
| .dropzone:hover, | |
| .dropzone.dragover { | |
| border-color: var(--accent-blue); | |
| background: var(--bg-glass-hover); | |
| box-shadow: var(--shadow-glow); | |
| } | |
| .dropzone.has-file { | |
| border-color: var(--success); | |
| border-style: solid; | |
| } | |
| .dropzone-icon { | |
| font-size: 36px; | |
| margin-bottom: 10px; | |
| display: block; | |
| } | |
| .dropzone-text { | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| margin-bottom: 4px; | |
| } | |
| .dropzone-sub { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .file-selected { | |
| display: none; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 10px 14px; | |
| background: rgba(72, 187, 120, 0.1); | |
| border: 1px solid rgba(72, 187, 120, 0.25); | |
| border-radius: var(--radius-sm); | |
| margin-top: 12px; | |
| } | |
| .file-selected.show { display: flex; } | |
| .file-selected .check-icon { | |
| width: 22px; height: 22px; | |
| background: var(--success); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-size: 12px; | |
| font-weight: 700; | |
| flex-shrink: 0; | |
| } | |
| .file-info-text { | |
| text-align: left; | |
| } | |
| .file-info-name { | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--success); | |
| } | |
| .file-info-size { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .format-info { | |
| margin-top: 14px; | |
| padding: 10px 12px; | |
| background: var(--bg-glass); | |
| border-left: 3px solid var(--accent-blue); | |
| border-radius: 0 var(--radius-sm) var(--radius-sm) 0; | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| line-height: 1.7; | |
| } | |
| /* Buttons */ | |
| .btn { | |
| width: 100%; | |
| padding: 12px 20px; | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| font-family: inherit; | |
| font-size: 13px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| transition: var(--transition); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn:disabled { | |
| opacity: 0.4; | |
| cursor: not-allowed; | |
| transform: none ; | |
| } | |
| .btn-primary { | |
| background: var(--gradient-button); | |
| color: white; | |
| box-shadow: 0 4px 15px rgba(43, 108, 176, 0.3); | |
| } | |
| .btn-primary:hover:not(:disabled) { | |
| background: var(--gradient-button-hover); | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px rgba(43, 108, 176, 0.4); | |
| } | |
| .btn-secondary { | |
| background: var(--bg-glass); | |
| color: var(--accent-cyan); | |
| border: 1px solid var(--border-accent); | |
| } | |
| .btn-secondary:hover:not(:disabled) { | |
| background: var(--bg-glass-hover); | |
| transform: translateY(-1px); | |
| } | |
| .btn-success { | |
| background: linear-gradient(135deg, #276749 0%, #2C7A7B 100%); | |
| color: white; | |
| box-shadow: 0 4px 15px rgba(39, 103, 73, 0.3); | |
| } | |
| .btn-success:hover:not(:disabled) { | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px rgba(39, 103, 73, 0.4); | |
| } | |
| .btn-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| /* βββ RIGHT PANEL βββ */ | |
| .right-panel { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| /* Result Cards */ | |
| .result-card { | |
| display: none; | |
| animation: slideUp 0.4s ease-out; | |
| } | |
| .result-card.show { | |
| display: block; | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(15px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Loading */ | |
| .loading-overlay { | |
| display: none; | |
| text-align: center; | |
| padding: 50px 20px; | |
| } | |
| .loading-overlay.show { display: block; } | |
| .spinner-ring { | |
| width: 48px; height: 48px; | |
| border: 3px solid var(--border-glass); | |
| border-top-color: var(--accent-blue); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| margin: 0 auto 16px; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .loading-text { | |
| font-size: 14px; | |
| color: var(--text-secondary); | |
| } | |
| .loading-sub { | |
| font-size: 12px; | |
| color: var(--text-muted); | |
| margin-top: 4px; | |
| } | |
| /* Error */ | |
| .error-card { | |
| background: var(--danger-bg); | |
| border: 1px solid rgba(252, 129, 129, 0.2); | |
| } | |
| .error-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: var(--danger); | |
| margin-bottom: 10px; | |
| } | |
| .error-body { | |
| font-size: 13px; | |
| color: var(--text-secondary); | |
| line-height: 1.6; | |
| } | |
| .error-body strong { | |
| color: var(--danger); | |
| } | |
| /* Prediction Result */ | |
| .prediction-class { | |
| font-size: 26px; | |
| font-weight: 800; | |
| background: var(--gradient-primary); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin: 8px 0 16px; | |
| } | |
| .confidence-row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 8px; | |
| } | |
| .confidence-label { | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .confidence-value { | |
| font-size: 14px; | |
| font-weight: 700; | |
| color: var(--accent-cyan); | |
| } | |
| .confidence-track { | |
| width: 100%; | |
| height: 8px; | |
| background: var(--bg-glass); | |
| border-radius: 4px; | |
| overflow: hidden; | |
| } | |
| .confidence-fill { | |
| height: 100%; | |
| background: var(--gradient-primary); | |
| border-radius: 4px; | |
| transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| /* Probabilities Table */ | |
| .prob-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| .prob-table th { | |
| text-align: left; | |
| padding: 10px 12px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| border-bottom: 1px solid var(--border-glass); | |
| } | |
| .prob-table td { | |
| padding: 10px 12px; | |
| font-size: 13px; | |
| border-bottom: 1px solid var(--border-glass); | |
| } | |
| .prob-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .prob-table .cls-name { | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .prob-table .cls-name.is-prediction { | |
| color: var(--accent-cyan); | |
| } | |
| .prob-bar-wrap { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .prob-bar-track { | |
| flex: 1; | |
| height: 6px; | |
| background: var(--bg-glass); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .prob-bar-fill { | |
| height: 100%; | |
| background: var(--gradient-primary); | |
| border-radius: 3px; | |
| transition: width 0.6s ease; | |
| } | |
| .prob-bar-fill.is-pred { | |
| background: linear-gradient(135deg, #4FD1C5 0%, #63B3ED 100%); | |
| } | |
| .prob-pct { | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| min-width: 48px; | |
| text-align: right; | |
| } | |
| /* GradCAM */ | |
| .gradcam-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| } | |
| .gradcam-item { | |
| border: 1px solid var(--border-glass); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| background: #000; | |
| } | |
| .gradcam-item img { | |
| width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| .gradcam-item-label { | |
| padding: 8px 12px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| text-align: center; | |
| background: var(--bg-glass); | |
| border-top: 1px solid var(--border-glass); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| /* Report Section */ | |
| .report-section { | |
| display: flex; | |
| gap: 10px; | |
| align-items: stretch; | |
| } | |
| .report-input { | |
| flex: 1; | |
| padding: 10px 14px; | |
| background: var(--bg-glass); | |
| border: 1px solid var(--border-glass); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-primary); | |
| font-family: inherit; | |
| font-size: 13px; | |
| transition: var(--transition); | |
| } | |
| .report-input:focus { | |
| outline: none; | |
| border-color: var(--accent-blue); | |
| box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.15); | |
| } | |
| .report-input::placeholder { | |
| color: var(--text-muted); | |
| } | |
| .report-section .btn { | |
| width: auto; | |
| white-space: nowrap; | |
| padding: 10px 20px; | |
| } | |
| .report-note { | |
| margin-top: 8px; | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| font-style: italic; | |
| } | |
| /* βββ Initial Empty State βββ */ | |
| .empty-state { | |
| text-align: center; | |
| padding: 60px 20px; | |
| } | |
| .empty-state .empty-icon { | |
| font-size: 52px; | |
| margin-bottom: 16px; | |
| opacity: 0.4; | |
| } | |
| .empty-state h3 { | |
| font-size: 16px; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| margin-bottom: 8px; | |
| } | |
| .empty-state p { | |
| font-size: 13px; | |
| color: var(--text-muted); | |
| max-width: 300px; | |
| margin: 0 auto; | |
| line-height: 1.6; | |
| } | |
| /* βββ Responsive βββ */ | |
| @media (max-width: 900px) { | |
| .content-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .left-panel { | |
| position: static; | |
| } | |
| .header h1 { font-size: 22px; } | |
| .gradcam-grid { grid-template-columns: 1fr; } | |
| } | |
| @media (max-width: 500px) { | |
| .app-container { padding: 12px; } | |
| .header { padding: 24px 16px 20px; } | |
| .card { padding: 16px; } | |
| .report-section { flex-direction: column; } | |
| .report-section .btn { width: 100%; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-pattern"></div> | |
| <div class="app-container"> | |
| <!-- HEADER --> | |
| <header class="header" id="app-header"> | |
| <div class="header-icon">π§ </div> | |
| <h1>Brain Tumor Classification</h1> | |
| <p>AI-Powered MRI Analysis with GradCAM Explainability</p> | |
| <div class="header-badges"> | |
| <div class="badge"><span>π¬</span> MoCo SSL</div> | |
| <div class="badge"><span>ποΈ</span> Swin-Transformer</div> | |
| <div class="badge"><span>π―</span> 4-Class Classification</div> | |
| <div class="badge"><span>ποΈ</span> GradCAM</div> | |
| </div> | |
| </header> | |
| <!-- MAIN CONTENT --> | |
| <div class="content-grid"> | |
| <!-- LEFT PANEL --> | |
| <aside class="left-panel"> | |
| <!-- Upload Card --> | |
| <div class="card" id="upload-card"> | |
| <div class="card-title"> | |
| <div class="icon">π€</div> | |
| <span>Upload MRI Scan</span> | |
| </div> | |
| <div class="dropzone" id="dropzone"> | |
| <span class="dropzone-icon">π©»</span> | |
| <div class="dropzone-text">Click to upload or drag & drop</div> | |
| <div class="dropzone-sub">Brain MRI images only</div> | |
| </div> | |
| <input type="file" id="fileInput" accept="image/*" style="display:none"> | |
| <div class="file-selected" id="fileSelected"> | |
| <div class="check-icon">β</div> | |
| <div class="file-info-text"> | |
| <div class="file-info-name" id="fileName">β</div> | |
| <div class="file-info-size" id="fileSize">β</div> | |
| </div> | |
| </div> | |
| <div class="format-info"> | |
| β Formats: PNG, JPG, JPEG, BMP, TIFF<br> | |
| β Max size: 16 MB<br> | |
| β Recommended: 224Γ224 px or higher | |
| </div> | |
| </div> | |
| <!-- Actions Card --> | |
| <div class="card" id="actions-card"> | |
| <div class="card-title"> | |
| <div class="icon">βοΈ</div> | |
| <span>Analysis Controls</span> | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn btn-primary" id="analyzeBtn" onclick="analyzeImage()"> | |
| π Analyze & Classify | |
| </button> | |
| <button class="btn btn-secondary" id="gradcamBtn" onclick="generateGradCAM()" style="display:none"> | |
| ποΈ GradCAM Visualization | |
| </button> | |
| <button class="btn btn-success" id="reportBtn" onclick="showReportPanel()" style="display:none"> | |
| π Download Report | |
| </button> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- RIGHT PANEL --> | |
| <main class="right-panel" id="right-panel"> | |
| <!-- Empty State --> | |
| <div class="card" id="emptyState"> | |
| <div class="empty-state"> | |
| <div class="empty-icon">π©»</div> | |
| <h3>No Analysis Yet</h3> | |
| <p>Upload a Brain MRI scan and click "Analyze & Classify" to get started.</p> | |
| </div> | |
| </div> | |
| <!-- Loading --> | |
| <div class="card loading-overlay" id="loading"> | |
| <div class="spinner-ring"></div> | |
| <div class="loading-text">Analyzing your MRI scan...</div> | |
| <div class="loading-sub">Running classification & generating GradCAM</div> | |
| </div> | |
| <!-- Error --> | |
| <div class="card error-card result-card" id="errorBox"> | |
| <div class="error-header">β οΈ Analysis Error</div> | |
| <div class="error-body" id="errorBody"></div> | |
| </div> | |
| <!-- Prediction Result --> | |
| <div class="card result-card" id="predictionBox"> | |
| <div class="card-title"> | |
| <div class="icon">π―</div> | |
| <span>Classification Result</span> | |
| </div> | |
| <div class="prediction-class" id="predictionClass">β</div> | |
| <div class="confidence-row"> | |
| <span class="confidence-label">Confidence</span> | |
| <span class="confidence-value" id="confidenceValue">β</span> | |
| </div> | |
| <div class="confidence-track"> | |
| <div class="confidence-fill" id="confidenceFill" style="width:0%"></div> | |
| </div> | |
| </div> | |
| <!-- Probabilities --> | |
| <div class="card result-card" id="probBox"> | |
| <div class="card-title"> | |
| <div class="icon">π</div> | |
| <span>Class Probabilities</span> | |
| </div> | |
| <table class="prob-table"> | |
| <thead> | |
| <tr> | |
| <th>Class</th> | |
| <th>Probability</th> | |
| </tr> | |
| </thead> | |
| <tbody id="probBody"></tbody> | |
| </table> | |
| </div> | |
| <!-- GradCAM --> | |
| <div class="card result-card" id="gradcamBox"> | |
| <div class="card-title"> | |
| <div class="icon">ποΈ</div> | |
| <span>GradCAM β Model Attention Map</span> | |
| </div> | |
| <div class="gradcam-grid"> | |
| <div class="gradcam-item"> | |
| <img id="originalImage" src="" alt="Original MRI"> | |
| <div class="gradcam-item-label">Original MRI</div> | |
| </div> | |
| <div class="gradcam-item"> | |
| <img id="gradcamImage" src="" alt="GradCAM Overlay"> | |
| <div class="gradcam-item-label">GradCAM Overlay</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Report --> | |
| <div class="card result-card" id="reportBox"> | |
| <div class="card-title"> | |
| <div class="icon">π</div> | |
| <span>Generate PDF Report</span> | |
| </div> | |
| <div class="report-section"> | |
| <input type="text" class="report-input" id="reportName" | |
| placeholder="Enter your name for the report" maxlength="100"> | |
| <button class="btn btn-success" id="downloadBtn" onclick="downloadReport()"> | |
| π₯ Download | |
| </button> | |
| </div> | |
| <div class="report-note"> | |
| β Name is required to generate the report. | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| <script> | |
| // βββ State βββ | |
| let currentFile = null; | |
| let sessionId = null; | |
| let predictionData = null; | |
| // βββ DOM βββ | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileSelected = document.getElementById('fileSelected'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const loading = document.getElementById('loading'); | |
| const errorBox = document.getElementById('errorBox'); | |
| const predictionBox = document.getElementById('predictionBox'); | |
| const probBox = document.getElementById('probBox'); | |
| const gradcamBox = document.getElementById('gradcamBox'); | |
| const reportBox = document.getElementById('reportBox'); | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const gradcamBtn = document.getElementById('gradcamBtn'); | |
| const reportBtn = document.getElementById('reportBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| // βββ File Upload βββ | |
| dropzone.addEventListener('click', () => fileInput.click()); | |
| dropzone.addEventListener('dragover', e => { | |
| e.preventDefault(); | |
| dropzone.classList.add('dragover'); | |
| }); | |
| dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover')); | |
| dropzone.addEventListener('drop', e => { | |
| e.preventDefault(); | |
| dropzone.classList.remove('dragover'); | |
| if (e.dataTransfer.files.length) selectFile(e.dataTransfer.files[0]); | |
| }); | |
| fileInput.addEventListener('change', e => { | |
| if (e.target.files.length) selectFile(e.target.files[0]); | |
| }); | |
| function selectFile(file) { | |
| if (!file) return; | |
| currentFile = file; | |
| dropzone.classList.add('has-file'); | |
| fileSelected.classList.add('show'); | |
| document.getElementById('fileName').textContent = file.name; | |
| document.getElementById('fileSize').textContent = | |
| (file.size / (1024 * 1024)).toFixed(2) + ' MB'; | |
| } | |
| // βββ Analyze βββ | |
| function analyzeImage() { | |
| if (!currentFile) { | |
| alert('Please select an MRI image first.'); | |
| return; | |
| } | |
| hideAllResults(); | |
| emptyState.style.display = 'none'; | |
| loading.classList.add('show'); | |
| const formData = new FormData(); | |
| formData.append('file', currentFile); | |
| fetch('/upload', { method: 'POST', body: formData }) | |
| .then(r => r.json().then(d => ({ ok: r.ok, data: d }))) | |
| .then(({ ok, data }) => { | |
| loading.classList.remove('show'); | |
| if (!ok || data.error) { | |
| showError(data.error || 'Unknown error', data.warning); | |
| return; | |
| } | |
| // Store session | |
| sessionId = data.session_id; | |
| predictionData = data; | |
| // Show results | |
| showPrediction(data); | |
| showProbabilities(data); | |
| gradcamBtn.style.display = 'flex'; | |
| reportBtn.style.display = 'flex'; | |
| }) | |
| .catch(err => { | |
| loading.classList.remove('show'); | |
| showError('Network error: ' + err.message); | |
| }); | |
| } | |
| function showError(message, warning) { | |
| let html = ''; | |
| if (warning) { | |
| html = `<strong>β οΈ ${warning}</strong><br><br>${message}`; | |
| } else { | |
| html = message; | |
| } | |
| document.getElementById('errorBody').innerHTML = html; | |
| errorBox.classList.add('show'); | |
| } | |
| function showPrediction(data) { | |
| const label = data.prediction === 'No Tumor' | |
| ? data.prediction | |
| : data.prediction + ' Tumor'; | |
| document.getElementById('predictionClass').textContent = label; | |
| document.getElementById('confidenceValue').textContent = data.confidence; | |
| // Animate fill bar | |
| requestAnimationFrame(() => { | |
| document.getElementById('confidenceFill').style.width = | |
| Math.min(data.confidence_num, 100) + '%'; | |
| }); | |
| predictionBox.classList.add('show'); | |
| } | |
| function showProbabilities(data) { | |
| const tbody = document.getElementById('probBody'); | |
| tbody.innerHTML = ''; | |
| const entries = Object.entries(data.all_probs) | |
| .sort((a, b) => b[1] - a[1]); | |
| entries.forEach(([cls, prob]) => { | |
| const pct = (prob * 100).toFixed(1); | |
| const isPred = cls === data.prediction; | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="cls-name ${isPred ? 'is-prediction' : ''}">${cls}</td> | |
| <td> | |
| <div class="prob-bar-wrap"> | |
| <div class="prob-bar-track"> | |
| <div class="prob-bar-fill ${isPred ? 'is-pred' : ''}" | |
| style="width:${pct}%"></div> | |
| </div> | |
| <span class="prob-pct">${pct}%</span> | |
| </div> | |
| </td>`; | |
| tbody.appendChild(row); | |
| }); | |
| probBox.classList.add('show'); | |
| } | |
| // βββ GradCAM βββ | |
| function generateGradCAM() { | |
| if (!sessionId) return; | |
| gradcamBtn.disabled = true; | |
| gradcamBtn.innerHTML = 'β³ Generating...'; | |
| fetch('/get-gradcam', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ session_id: sessionId }) | |
| }) | |
| .then(r => r.json()) | |
| .then(data => { | |
| gradcamBtn.disabled = false; | |
| gradcamBtn.innerHTML = 'ποΈ GradCAM Visualization'; | |
| if (data.error) { | |
| alert('GradCAM error: ' + data.error); | |
| return; | |
| } | |
| document.getElementById('originalImage').src = data.original; | |
| document.getElementById('gradcamImage').src = data.gradcam; | |
| gradcamBox.classList.add('show'); | |
| }) | |
| .catch(err => { | |
| gradcamBtn.disabled = false; | |
| gradcamBtn.innerHTML = 'ποΈ GradCAM Visualization'; | |
| alert('Failed to generate GradCAM: ' + err.message); | |
| }); | |
| } | |
| // βββ Report βββ | |
| function showReportPanel() { | |
| reportBox.classList.add('show'); | |
| document.getElementById('reportName').focus(); | |
| } | |
| function downloadReport() { | |
| const name = document.getElementById('reportName').value.trim(); | |
| if (!name) { | |
| alert('Please enter your name to generate the report.'); | |
| document.getElementById('reportName').focus(); | |
| return; | |
| } | |
| if (!sessionId) { | |
| alert('No analysis data. Please analyze an image first.'); | |
| return; | |
| } | |
| downloadBtn.disabled = true; | |
| downloadBtn.innerHTML = 'β³ Generating...'; | |
| fetch('/generate-report', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ name: name, session_id: sessionId }) | |
| }) | |
| .then(response => { | |
| if (!response.ok) { | |
| return response.json().then(d => { throw new Error(d.error || 'Report generation failed'); }); | |
| } | |
| return response.blob(); | |
| }) | |
| .then(blob => { | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `Brain_Tumor_Report_${name.replace(/\s+/g, '_')}.pdf`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| window.URL.revokeObjectURL(url); | |
| document.body.removeChild(a); | |
| downloadBtn.disabled = false; | |
| downloadBtn.innerHTML = 'π₯ Download'; | |
| }) | |
| .catch(err => { | |
| alert('Error: ' + err.message); | |
| downloadBtn.disabled = false; | |
| downloadBtn.innerHTML = 'π₯ Download'; | |
| }); | |
| } | |
| // βββ Helpers βββ | |
| function hideAllResults() { | |
| [errorBox, predictionBox, probBox, gradcamBox, reportBox].forEach(el => | |
| el.classList.remove('show') | |
| ); | |
| gradcamBtn.style.display = 'none'; | |
| reportBtn.style.display = 'none'; | |
| } | |
| </script> | |
| </body> | |
| </html> | |