vision-web-app / static /index.html
David Ko
Initial deployment of vision-web-app
bd99505
raw
history blame
11.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Model Vision App</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #3f51b5; text-align: center; }
.container { display: flex; flex-direction: column; gap: 20px; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.model-selection { display: flex; gap: 10px; margin: 20px 0; }
.model-btn { padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; background-color: #e0e0e0; }
.model-btn.active { background-color: #3f51b5; color: white; }
.model-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.upload-container { display: flex; flex-direction: column; gap: 10px; }
#file-input { margin-bottom: 10px; }
#process-btn { padding: 10px 15px; background-color: #3f51b5; color: white; border: none; border-radius: 4px; cursor: pointer; }
#process-btn:disabled { background-color: #9e9e9e; cursor: not-allowed; }
.preview-container { display: flex; justify-content: center; margin: 20px 0; }
.preview-image { max-width: 100%; max-height: 300px; border-radius: 4px; }
.result-container { margin-top: 20px; }
.result-image { max-width: 100%; max-height: 400px; border-radius: 4px; }
.detection-list { list-style-type: none; padding: 0; }
.detection-item { padding: 8px; border-bottom: 1px solid #eee; }
.detection-item:last-child { border-bottom: none; }
.confidence { display: inline-block; padding: 2px 6px; background-color: #3f51b5; color: white; border-radius: 10px; font-size: 0.8em; margin-left: 8px; }
.performance { margin-top: 20px; font-size: 0.9em; color: #666; }
.error { color: #f44336; font-weight: bold; }
.loading { text-align: center; margin: 20px 0; }
</style>
</head>
<body>
<h1>Multi-Model Vision App</h1>
<div class="container">
<div class="card">
<h2>Select Model</h2>
<div class="model-selection">
<button id="yolo-btn" class="model-btn">YOLOv8 (Detection)</button>
<button id="detr-btn" class="model-btn">DETR (Detection)</button>
<button id="vit-btn" class="model-btn">ViT (Classification)</button>
</div>
</div>
<div class="card upload-container">
<h2>Upload Image</h2>
<input type="file" id="file-input" accept="image/*">
<div class="preview-container" id="preview-container"></div>
<button id="process-btn" disabled>Process Image</button>
</div>
<div class="card result-container" id="result-container" style="display: none;">
<h2 id="result-title">Results</h2>
<div id="result-content"></div>
</div>
</div>
<script>
// Model selection
const yoloBtn = document.getElementById('yolo-btn');
const detrBtn = document.getElementById('detr-btn');
const vitBtn = document.getElementById('vit-btn');
const fileInput = document.getElementById('file-input');
const processBtn = document.getElementById('process-btn');
const previewContainer = document.getElementById('preview-container');
const resultContainer = document.getElementById('result-container');
const resultTitle = document.getElementById('result-title');
const resultContent = document.getElementById('result-content');
let selectedModel = null;
let selectedFile = null;
let modelsStatus = { yolo: false, detr: false, vit: false };
// Check API status
async function checkApiStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
modelsStatus = data.models;
// Update UI based on model availability
yoloBtn.disabled = !modelsStatus.yolo;
detrBtn.disabled = !modelsStatus.detr;
vitBtn.disabled = !modelsStatus.vit;
if (!modelsStatus.yolo) yoloBtn.title = "YOLOv8 model not available";
if (!modelsStatus.detr) detrBtn.title = "DETR model not available";
if (!modelsStatus.vit) vitBtn.title = "ViT model not available";
} catch (error) {
console.error('Error checking API status:', error);
alert('Error connecting to the API. Please make sure the server is running.');
}
}
// Format time for display
function formatTime(ms) {
if (ms < 1000) return `${ms.toFixed(2)} ms`;
return `${(ms / 1000).toFixed(2)} s`;
}
// Select model
function selectModel(model) {
selectedModel = model;
yoloBtn.classList.remove('active');
detrBtn.classList.remove('active');
vitBtn.classList.remove('active');
switch(model) {
case 'yolo':
yoloBtn.classList.add('active');
break;
case 'detr':
detrBtn.classList.add('active');
break;
case 'vit':
vitBtn.classList.add('active');
break;
}
updateProcessButton();
}
// Update process button state
function updateProcessButton() {
processBtn.disabled = !selectedModel || !selectedFile;
}
// Handle file selection
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
selectedFile = file;
const reader = new FileReader();
reader.onload = (e) => {
previewContainer.innerHTML = `<img src="${e.target.result}" class="preview-image" alt="Preview">`;
};
reader.readAsDataURL(file);
updateProcessButton();
} else {
selectedFile = null;
previewContainer.innerHTML = '';
updateProcessButton();
}
});
// Model selection event listeners
yoloBtn.addEventListener('click', () => selectModel('yolo'));
detrBtn.addEventListener('click', () => selectModel('detr'));
vitBtn.addEventListener('click', () => selectModel('vit'));
// Process image
processBtn.addEventListener('click', async () => {
if (!selectedModel || !selectedFile) return;
resultContainer.style.display = 'block';
resultTitle.textContent = `Processing with ${selectedModel.toUpperCase()}...`;
resultContent.innerHTML = '<div class="loading">Processing image...</div>';
const formData = new FormData();
formData.append('image', selectedFile);
let endpoint = '';
switch(selectedModel) {
case 'yolo':
endpoint = '/api/detect/yolo';
break;
case 'detr':
endpoint = '/api/detect/detr';
break;
case 'vit':
endpoint = '/api/classify/vit';
break;
}
try {
const response = await fetch(endpoint, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
displayResults(selectedModel, data);
} catch (error) {
console.error('Error processing image:', error);
resultTitle.textContent = 'Error';
resultContent.innerHTML = `<div class="error">Error processing image: ${error.message}</div>`;
}
});
// Display results
function displayResults(model, data) {
resultTitle.textContent = `${model.toUpperCase()} Results`;
let html = '';
if (model === 'yolo' || model === 'detr') {
// Detection results
html += `
<div style="display: flex; flex-direction: column; gap: 20px;">
<div>
<h3>Processed Image</h3>
<img src="data:image/jpeg;base64,${data.image_with_boxes}" class="result-image" alt="Detection Result">
</div>
<div>
<h3>Detected Objects</h3>
`;
if (data.detections && data.detections.length > 0) {
html += '<ul class="detection-list">';
data.detections.forEach(item => {
html += `
<li class="detection-item">
${item.label}
<span class="confidence">${(item.confidence * 100).toFixed(0)}%</span>
</li>
`;
});
html += '</ul>';
} else {
html += '<p>No objects detected</p>';
}
html += `
</div>
<div class="performance">
<p>Inference Time: ${formatTime(data.inference_time)}</p>
<p>Total Processing Time: ${formatTime(data.total_time)}</p>
</div>
</div>
`;
} else if (model === 'vit') {
// Classification results
html += `
<div style="display: flex; flex-direction: column; gap: 20px;">
<div>
<h3>Classification Results</h3>
`;
if (data.classifications && data.classifications.length > 0) {
html += '<ul class="detection-list">';
data.classifications.forEach(item => {
html += `
<li class="detection-item">
${item.label}
<span class="confidence">${(item.confidence * 100).toFixed(1)}%</span>
</li>
`;
});
html += '</ul>';
} else {
html += '<p>No classifications found</p>';
}
html += `
</div>
<div class="performance">
<p>Inference Time: ${formatTime(data.inference_time)}</p>
<p>Total Processing Time: ${formatTime(data.total_time)}</p>
</div>
</div>
`;
}
resultContent.innerHTML = html;
}
// Initialize
checkApiStatus();
</script>
</body>
</html>