|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const radioButtons = document.querySelectorAll('input[name="inputMethod"]'); |
|
|
radioButtons.forEach(radio => { |
|
|
radio.addEventListener('change', toggleInputMethod); |
|
|
}); |
|
|
|
|
|
|
|
|
initializeSearchAndLabels(); |
|
|
}); |
|
|
|
|
|
function toggleInputMethod() { |
|
|
const localInputGroup = document.getElementById('localInputGroup'); |
|
|
const urlInputGroup = document.getElementById('urlInputGroup'); |
|
|
const selectedMethod = document.querySelector('input[name="inputMethod"]:checked').value; |
|
|
|
|
|
if (selectedMethod === 'local') { |
|
|
localInputGroup.style.display = 'block'; |
|
|
urlInputGroup.style.display = 'none'; |
|
|
|
|
|
document.getElementById('urlInput').value = ''; |
|
|
} else { |
|
|
localInputGroup.style.display = 'none'; |
|
|
urlInputGroup.style.display = 'block'; |
|
|
|
|
|
document.getElementById('fileInput').value = ''; |
|
|
} |
|
|
|
|
|
|
|
|
clearPreviewAndResults(); |
|
|
} |
|
|
|
|
|
function clearPreviewAndResults() { |
|
|
const imagePreview = document.getElementById('imagePreview'); |
|
|
const resultsDiv = document.getElementById('results'); |
|
|
|
|
|
imagePreview.src = ''; |
|
|
resultsDiv.innerHTML = ''; |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('fileInput').addEventListener('change', previewImage); |
|
|
document.getElementById('urlInput').addEventListener('input', debounce(previewImage, 500)); |
|
|
|
|
|
function debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
|
|
|
function previewImage() { |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const urlInput = document.getElementById('urlInput'); |
|
|
const imagePreview = document.getElementById('imagePreview'); |
|
|
const defaultPreviewMessage = document.getElementById('defaultPreviewMessage'); |
|
|
|
|
|
if (fileInput.files && fileInput.files[0]) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = function(e) { |
|
|
imagePreview.src = e.target.result; |
|
|
imagePreview.style.display = 'block'; |
|
|
defaultPreviewMessage.style.display = 'none'; |
|
|
}; |
|
|
reader.readAsDataURL(fileInput.files[0]); |
|
|
urlInput.value = ''; |
|
|
} else if (urlInput.value) { |
|
|
imagePreview.src = urlInput.value; |
|
|
imagePreview.style.display = 'block'; |
|
|
defaultPreviewMessage.style.display = 'none'; |
|
|
fileInput.value = ''; |
|
|
} else { |
|
|
|
|
|
imagePreview.style.display = 'none'; |
|
|
defaultPreviewMessage.style.display = 'block'; |
|
|
} |
|
|
} |
|
|
|
|
|
async function getBase64FromUrl(url) { |
|
|
const response = await fetch(url); |
|
|
const blob = await response.blob(); |
|
|
return new Promise((resolve, reject) => { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = () => { |
|
|
const base64String = reader.result.split(',')[1]; |
|
|
resolve(base64String); |
|
|
}; |
|
|
reader.onerror = reject; |
|
|
reader.readAsDataURL(blob); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function analyzeImage() { |
|
|
|
|
|
document.getElementById('results').innerHTML = ''; |
|
|
|
|
|
const spinner = document.getElementById('spinner'); |
|
|
const analyzeBtn = document.getElementById('analyzeBtn'); |
|
|
const resultsDiv = document.getElementById('results'); |
|
|
|
|
|
spinner.style.display = 'block'; |
|
|
analyzeBtn.disabled = true; |
|
|
analyzeBtn.textContent = 'Analyzing...'; |
|
|
|
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const urlInput = document.getElementById('urlInput'); |
|
|
let base64Image; |
|
|
|
|
|
try { |
|
|
|
|
|
const defaultLabels = Array.from(document.querySelectorAll('.default-labels input[type="checkbox"]:checked')) |
|
|
.map(checkbox => checkbox.value); |
|
|
|
|
|
const selectedLabels = Array.from(document.querySelectorAll('#selectedLabels .selected-label')) |
|
|
.map(label => label.dataset.label); |
|
|
|
|
|
|
|
|
const allSelectedLabels = [...defaultLabels, ...selectedLabels]; |
|
|
|
|
|
if (allSelectedLabels.length === 0) { |
|
|
throw new Error('Please select at least one category'); |
|
|
} |
|
|
|
|
|
if (fileInput.files && fileInput.files[0]) { |
|
|
|
|
|
const reader = new FileReader(); |
|
|
base64Image = await new Promise((resolve) => { |
|
|
reader.onload = (e) => { |
|
|
resolve(e.target.result.split(',')[1]); |
|
|
}; |
|
|
reader.readAsDataURL(fileInput.files[0]); |
|
|
}); |
|
|
} else if (urlInput.value) { |
|
|
|
|
|
base64Image = await getBase64FromUrl(urlInput.value); |
|
|
} else { |
|
|
throw new Error('Please select a file or enter a URL.'); |
|
|
} |
|
|
|
|
|
const payload = { |
|
|
images: [base64Image], |
|
|
labels: allSelectedLabels, |
|
|
multilabel: false |
|
|
}; |
|
|
|
|
|
const response = await fetch('http://localhost:8000/predict', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
displayResults(data.predictions); |
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
resultsDiv.innerHTML = `<p class="error-message">Error: ${error.message}</p>`; |
|
|
} finally { |
|
|
spinner.style.display = 'none'; |
|
|
analyzeBtn.disabled = false; |
|
|
analyzeBtn.textContent = 'Analyze'; |
|
|
} |
|
|
} |
|
|
|
|
|
function displayResults(predictions) { |
|
|
const resultsDiv = document.getElementById('results'); |
|
|
resultsDiv.innerHTML = ''; |
|
|
|
|
|
if (!predictions || predictions.length === 0) { |
|
|
resultsDiv.innerHTML = '<p>No predictions available.</p>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
predictions.forEach(prediction => { |
|
|
resultsDiv.innerHTML += '<h2>Results:</h2>'; |
|
|
for (const [label, probability] of Object.entries(prediction)) { |
|
|
const percentage = (probability * 100).toFixed(1); |
|
|
resultsDiv.innerHTML += ` |
|
|
<div class="prediction-result"> |
|
|
<strong>${label}:</strong> |
|
|
<div class="progress-bar"> |
|
|
<div class="progress" style="width: ${percentage}%"></div> |
|
|
</div> |
|
|
<span>${percentage}%</span> |
|
|
</div>`; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const medicalLabels = { |
|
|
'Others': ['Effusion', 'Edema', 'Scar Tissue', 'Calcification'], |
|
|
'Pulmonology': ['Pneumonia', 'Asthma', 'COPD', 'Tuberculosis', 'Pulmonary Fibrosis', 'Pleural Effusion', 'Pulmonary Edema', 'Lung Nodule', 'Atelectasis', 'Pneumothorax'], |
|
|
'Oncology': ['Tumor', 'Breast Cancer', 'Lung Cancer', 'Prostate Cancer', 'Leukemia', 'Lymphoma', 'Melanoma', 'Colorectal Cancer', 'Glioma', 'Metastasis'], |
|
|
'Orthopedics': ['Fracture', 'Arthritis', 'Osteoporosis', 'Scoliosis', 'Tendonitis', 'Joint Effusion', 'Disc Herniation', 'Osteomyelitis', 'Bursitis', 'Bone Lesion'], |
|
|
'Cardiology': ['Myocardial Infarction', 'Arrhythmia', 'Heart Failure', 'Cardiomegaly', 'Aortic Aneurysm', 'Valvular Heart Disease', 'Coronary Artery Disease', 'Pericardial Effusion', 'Pulmonary Embolism'], |
|
|
'Dermatology': ['Urtikaria', 'Akne', 'Eczema', 'Psoriasis', 'Melanoma', 'Basal Cell Carcinoma', 'Squamous Cell Carcinoma', 'Skin Ulcer', 'Rash'], |
|
|
'Gastroenterology': ['Cirrhosis', 'Hepatitis', 'Ulcer', 'Gastric Cancer', 'Polyp', 'Pancreatitis', 'Cholecystitis', 'Colitis', 'Crohn’s Disease'], |
|
|
'Neurology': ['Stroke', 'Multiple Sclerosis', 'Parkinson’s Disease', 'Alzheimer’s Disease', 'Epilepsy', 'Brain Tumor', 'Hydrocephalus', 'Meningitis', 'Intracranial Hemorrhage'], |
|
|
'Endocrinology': ['Diabetes', 'Thyroid Nodule', 'Goiter', 'Adrenal Tumor', 'Pituitary Adenoma', 'Hyperthyroidism', 'Hypothyroidism', 'Parathyroid Hyperplasia'], |
|
|
'Hematology': ['Anemia', 'Thrombocytopenia', 'Leukemia', 'Lymphoma', 'Hemophilia', 'Polycythemia', 'Sickle Cell Disease'], |
|
|
'Urology': ['Kidney Stone', 'Bladder Cancer', 'Prostate Cancer', 'Urinary Tract Infection', 'Renal Cyst', 'Hydronephrosis'], |
|
|
'Ophthalmology': ['Glaucoma', 'Cataract', 'Retinal Detachment', 'Macular Degeneration', 'Diabetic Retinopathy', 'Conjunctivitis'], |
|
|
'Gynecology': ['Ovarian Cyst', 'Fibroids', 'Endometriosis', 'Breast Cancer', 'Polycystic Ovary Syndrome', 'Cervical Cancer'], |
|
|
'Rheumatology': ['Rheumatoid Arthritis', 'Lupus', 'Scleroderma', 'Ankylosing Spondylitis', 'Gout', 'Sjogren’s Syndrome'] |
|
|
|
|
|
}; |
|
|
|
|
|
function initializeSearchAndLabels() { |
|
|
|
|
|
const existingSearchContainers = document.querySelectorAll('.search-container'); |
|
|
existingSearchContainers.forEach(container => container.remove()); |
|
|
|
|
|
|
|
|
const searchContainer = document.createElement('div'); |
|
|
searchContainer.className = 'search-container'; |
|
|
searchContainer.innerHTML = ` |
|
|
<div class="search-wrapper"> |
|
|
<input type="text" id="labelSearch" class="search-input" placeholder="Search labels..."> |
|
|
<button id="showAllLabels" class="show-all-btn">Show All</button> |
|
|
</div> |
|
|
<div id="labelDropdown" class="label-dropdown" style="display: none;"></div> |
|
|
`; |
|
|
|
|
|
|
|
|
const defaultLabels = document.querySelector('.default-labels'); |
|
|
if (defaultLabels) { |
|
|
defaultLabels.parentNode.insertBefore(searchContainer, defaultLabels.nextElementSibling); |
|
|
} |
|
|
|
|
|
|
|
|
const searchInput = document.getElementById('labelSearch'); |
|
|
const labelDropdown = document.getElementById('labelDropdown'); |
|
|
const showAllButton = document.getElementById('showAllLabels'); |
|
|
|
|
|
|
|
|
searchInput.addEventListener('focus', () => { |
|
|
const searchTerm = searchInput.value.trim().toLowerCase(); |
|
|
showFilteredResults(searchTerm); |
|
|
}); |
|
|
|
|
|
|
|
|
searchInput.addEventListener('input', (e) => { |
|
|
const searchTerm = e.target.value.trim().toLowerCase(); |
|
|
if (searchTerm) { |
|
|
showFilteredResults(searchTerm); |
|
|
} else { |
|
|
labelDropdown.style.display = 'none'; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
showAllButton.addEventListener('click', function(e) { |
|
|
e.stopPropagation(); |
|
|
labelDropdown.style.display = 'block'; |
|
|
showFilteredResults(''); |
|
|
searchInput.value = ''; |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
if (!searchInput.contains(e.target) && |
|
|
!labelDropdown.contains(e.target) && |
|
|
!showAllButton.contains(e.target)) { |
|
|
labelDropdown.style.display = 'none'; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function showFilteredResults(searchTerm) { |
|
|
const labelDropdown = document.getElementById('labelDropdown'); |
|
|
labelDropdown.innerHTML = ''; |
|
|
let hasResults = false; |
|
|
|
|
|
|
|
|
const sortedCategories = Object.entries(medicalLabels).sort((a, b) => a[0].localeCompare(b[0])); |
|
|
|
|
|
|
|
|
sortedCategories.forEach(([category, labels]) => { |
|
|
|
|
|
|
|
|
const matchingLabels = searchTerm === '' ? |
|
|
labels.sort() : |
|
|
labels.filter(label => label.toLowerCase().includes(searchTerm.toLowerCase())); |
|
|
|
|
|
if (matchingLabels.length > 0) { |
|
|
hasResults = true; |
|
|
|
|
|
|
|
|
const categoryHeader = document.createElement('div'); |
|
|
categoryHeader.className = 'dropdown-category-header'; |
|
|
categoryHeader.textContent = category; |
|
|
labelDropdown.appendChild(categoryHeader); |
|
|
|
|
|
|
|
|
matchingLabels.forEach(label => { |
|
|
const option = document.createElement('div'); |
|
|
option.className = 'dropdown-option'; |
|
|
|
|
|
const checkbox = document.createElement('input'); |
|
|
checkbox.type = 'checkbox'; |
|
|
checkbox.id = `${category}-${label}`; |
|
|
checkbox.checked = isLabelSelected(category, label); |
|
|
checkbox.addEventListener('change', () => updateSelectedLabels(category, label)); |
|
|
|
|
|
const labelText = document.createElement('span'); |
|
|
labelText.className = 'label-text'; |
|
|
labelText.textContent = label; |
|
|
|
|
|
const categoryBadge = document.createElement('span'); |
|
|
categoryBadge.className = 'category-badge'; |
|
|
categoryBadge.textContent = category; |
|
|
|
|
|
option.appendChild(checkbox); |
|
|
option.appendChild(labelText); |
|
|
option.appendChild(categoryBadge); |
|
|
labelDropdown.appendChild(option); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
labelDropdown.style.display = hasResults || searchTerm === '' ? 'block' : 'none'; |
|
|
} |
|
|
|
|
|
function isLabelSelected(category, label) { |
|
|
const selectedLabelsContainer = document.getElementById('selectedLabels'); |
|
|
return !!selectedLabelsContainer.querySelector( |
|
|
`[data-category="${category}"][data-label="${label}"]` |
|
|
); |
|
|
} |
|
|
|
|
|
function updateSelectedLabels(category, label) { |
|
|
const selectedLabelsContainer = document.getElementById('selectedLabels'); |
|
|
const checkbox = document.getElementById(`${category}-${label}`); |
|
|
|
|
|
if (checkbox.checked) { |
|
|
const labelElement = document.createElement('span'); |
|
|
labelElement.className = 'selected-label'; |
|
|
labelElement.textContent = `${label}`; |
|
|
labelElement.dataset.category = category; |
|
|
labelElement.dataset.label = label; |
|
|
|
|
|
const categoryBadge = document.createElement('span'); |
|
|
categoryBadge.className = 'category-badge'; |
|
|
categoryBadge.textContent = category; |
|
|
|
|
|
const removeButton = document.createElement('span'); |
|
|
removeButton.className = 'remove-label'; |
|
|
removeButton.innerHTML = '×'; |
|
|
removeButton.onclick = () => { |
|
|
labelElement.remove(); |
|
|
checkbox.checked = false; |
|
|
}; |
|
|
|
|
|
labelElement.appendChild(categoryBadge); |
|
|
labelElement.appendChild(removeButton); |
|
|
selectedLabelsContainer.appendChild(labelElement); |
|
|
} else { |
|
|
const existingLabel = selectedLabelsContainer.querySelector( |
|
|
`[data-category="${category}"][data-label="${label}"]` |
|
|
); |
|
|
if (existingLabel) { |
|
|
existingLabel.remove(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeSearchAndLabels); |
|
|
|
|
|
|