Spaces:
Sleeping
Sleeping
| // Upload and Prediction JavaScript | |
| // Image preview functionality | |
| function setupImagePreview(inputId, previewId, placeholderId) { | |
| const input = document.getElementById(inputId); | |
| const preview = document.getElementById(previewId); | |
| const placeholder = input.parentElement.querySelector('.upload-placeholder'); | |
| if (input) { | |
| input.addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| preview.src = e.target.result; | |
| preview.style.display = 'block'; | |
| if (placeholder) placeholder.style.display = 'none'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| } | |
| } | |
| // Setup previews based on mode | |
| if (mode === 'multimodal') { | |
| setupImagePreview('fundusImage', 'fundusPreview'); | |
| setupImagePreview('octImage', 'octPreview'); | |
| } else if (mode === 'fundus') { | |
| setupImagePreview('fundusImage', 'fundusPreview'); | |
| } else if (mode === 'oct') { | |
| setupImagePreview('octImage', 'octPreview'); | |
| } | |
| // Form submission | |
| const form = document.getElementById('uploadForm'); | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const btnText = document.getElementById('btnText'); | |
| const btnLoader = document.getElementById('btnLoader'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const resultsSection = document.getElementById('resultsSection'); | |
| form.addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| // Show loading state | |
| btnText.style.display = 'none'; | |
| btnLoader.style.display = 'inline-block'; | |
| analyzeBtn.disabled = true; | |
| errorMessage.style.display = 'none'; | |
| resultsSection.style.display = 'none'; | |
| try { | |
| const formData = new FormData(form); | |
| const endpoint = `/predict/${mode}`; | |
| const response = await fetch(endpoint, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Prediction failed'); | |
| } | |
| const result = await response.json(); | |
| // Display results | |
| displayResults(result); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| errorMessage.textContent = 'An error occurred during analysis. Please try again.'; | |
| errorMessage.style.display = 'block'; | |
| } finally { | |
| // Reset button state | |
| btnText.style.display = 'inline'; | |
| btnLoader.style.display = 'none'; | |
| analyzeBtn.disabled = false; | |
| } | |
| }); | |
| function displayResults(result) { | |
| let html = ''; | |
| if (mode === 'fundus') { | |
| html = generateFundusResults(result); | |
| } else if (mode === 'oct') { | |
| html = generateOCTResults(result); | |
| } else if (mode === 'multimodal') { | |
| html = generateMultimodalResults(result); | |
| } | |
| resultsSection.innerHTML = html; | |
| resultsSection.style.display = 'block'; | |
| // Scroll to results | |
| resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } | |
| function generateFundusResults(result) { | |
| const isGlaucoma = result.prediction === 'GON+'; | |
| const headerClass = isGlaucoma ? 'warning' : 'success'; | |
| return ` | |
| <div class="result-header ${headerClass}"> | |
| <div class="result-prediction">${result.prediction}</div> | |
| <div class="result-confidence">Confidence: ${(result.confidence * 100).toFixed(1)}%</div> | |
| </div> | |
| <div class="result-grid"> | |
| <div class="result-card"> | |
| <h4>Original Image</h4> | |
| <img src="data:image/jpeg;base64,${result.original_image}" class="result-image" alt="Original"> | |
| </div> | |
| <div class="result-card"> | |
| <h4>Grad-CAM Visualization</h4> | |
| ${result.gradcam_image ? | |
| `<img src="data:image/png;base64,${result.gradcam_image}" class="result-image" alt="Grad-CAM">` : | |
| '<p>Visualization not available</p>' | |
| } | |
| </div> | |
| </div> | |
| <div class="clinical-features"> | |
| <div class="feature-item"> | |
| <div class="feature-label">vCDR (Cup-to-Disc Ratio)</div> | |
| <div class="feature-value">${result.vcdr.toFixed(2)}</div> | |
| </div> | |
| <div class="feature-item"> | |
| <div class="feature-label">Prediction Confidence</div> | |
| <div class="feature-value">${(result.confidence * 100).toFixed(1)}%</div> | |
| </div> | |
| </div> | |
| <div class="interpretation"> | |
| <strong>Clinical Interpretation:</strong><br> | |
| ${result.interpretation} | |
| </div> | |
| `; | |
| } | |
| function generateOCTResults(result) { | |
| const isNormal = result.prediction === 'NO'; | |
| const headerClass = isNormal ? 'success' : 'warning'; | |
| let top3HTML = result.top3.map((item, index) => | |
| `<div class="feature-item"> | |
| <div class="feature-label">${index + 1}. ${item.class}</div> | |
| <div class="feature-value">${(item.probability * 100).toFixed(1)}%</div> | |
| </div>` | |
| ).join(''); | |
| return ` | |
| <div class="result-header ${headerClass}"> | |
| <div class="result-prediction">${result.prediction}</div> | |
| <div class="result-confidence">Confidence: ${(result.confidence * 100).toFixed(1)}%</div> | |
| </div> | |
| <div class="result-grid"> | |
| <div class="result-card"> | |
| <h4>Original Image</h4> | |
| <img src="data:image/jpeg;base64,${result.original_image}" class="result-image" alt="Original"> | |
| </div> | |
| <div class="result-card"> | |
| <h4>Grad-CAM Visualization</h4> | |
| ${result.gradcam_image ? | |
| `<img src="data:image/png;base64,${result.gradcam_image}" class="result-image" alt="Grad-CAM">` : | |
| '<p>Visualization not available</p>' | |
| } | |
| </div> | |
| </div> | |
| <div class="result-card"> | |
| <h4>Top 3 Predictions</h4> | |
| <div class="clinical-features"> | |
| ${top3HTML} | |
| </div> | |
| </div> | |
| <div class="interpretation"> | |
| <strong>Clinical Interpretation:</strong><br> | |
| ${result.interpretation} | |
| </div> | |
| `; | |
| } | |
| function generateMultimodalResults(result) { | |
| const isGlaucoma = result.prediction === 'GLAUCOMA'; | |
| const headerClass = isGlaucoma ? 'warning' : 'success'; | |
| return ` | |
| <div class="result-header ${headerClass}"> | |
| <div class="result-prediction">${result.prediction}</div> | |
| <div class="result-confidence">Confidence: ${(result.confidence * 100).toFixed(1)}%</div> | |
| </div> | |
| <div class="result-grid"> | |
| <div class="result-card"> | |
| <h4>Fundus Image</h4> | |
| <img src="data:image/jpeg;base64,${result.fundus_image}" class="result-image" alt="Fundus"> | |
| <h5 style="margin-top: 1rem;">Fundus Grad-CAM</h5> | |
| ${result.fundus_gradcam ? | |
| `<img src="data:image/png;base64,${result.fundus_gradcam}" class="result-image" alt="Fundus Grad-CAM">` : | |
| '<p>Visualization not available</p>' | |
| } | |
| </div> | |
| <div class="result-card"> | |
| <h4>OCT Image</h4> | |
| <img src="data:image/jpeg;base64,${result.oct_image}" class="result-image" alt="OCT"> | |
| <h5 style="margin-top: 1rem;">OCT Grad-CAM</h5> | |
| ${result.oct_gradcam ? | |
| `<img src="data:image/png;base64,${result.oct_gradcam}" class="result-image" alt="OCT Grad-CAM">` : | |
| '<p>Visualization not available</p>' | |
| } | |
| </div> | |
| </div> | |
| <div class="clinical-features"> | |
| <div class="feature-item"> | |
| <div class="feature-label">vCDR (Cup-to-Disc Ratio)</div> | |
| <div class="feature-value">${result.vcdr.toFixed(2)}</div> | |
| </div> | |
| <div class="feature-item"> | |
| <div class="feature-label">RNFL Thickness</div> | |
| <div class="feature-value">${result.rnfl.toFixed(1)} μm</div> | |
| </div> | |
| <div class="feature-item"> | |
| <div class="feature-label">Prediction Confidence</div> | |
| <div class="feature-value">${(result.confidence * 100).toFixed(1)}%</div> | |
| </div> | |
| <div class="feature-item"> | |
| <div class="feature-label">Analysis Type</div> | |
| <div class="feature-value">Multimodal</div> | |
| </div> | |
| </div> | |
| <div class="interpretation"> | |
| <strong>Clinical Interpretation:</strong><br> | |
| ${result.interpretation} | |
| </div> | |
| `; | |
| } | |