amb / index.html
vishnumeher's picture
Add 1 files
2a647a7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Segmentation Demo</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.dropzone {
border: 2px dashed #9CA3AF;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #3B82F6;
background-color: #EFF6FF;
}
.result-container {
transition: all 0.5s ease;
opacity: 0;
height: 0;
overflow: hidden;
}
.result-container.show {
opacity: 1;
height: auto;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.image-container {
position: relative;
width: 100%;
height: 300px;
}
.image-wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.image-wrapper img {
object-fit: contain;
width: 100%;
height: 100%;
}
.slider-container {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
padding: 5px 10px;
border-radius: 20px;
color: white;
display: flex;
align-items: center;
gap: 10px;
}
.slider {
width: 150px;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Image Segmentation Demo</h1>
<p class="text-gray-600">Upload an image to generate segmentation masks using our AI model</p>
</header>
<div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6 mb-8">
<div class="grid md:grid-cols-2 gap-8">
<!-- Upload Section -->
<div>
<div id="dropzone" class="dropzone p-8 text-center cursor-pointer">
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-4"></i>
<h3 class="text-lg font-medium text-gray-700 mb-2">Drag & Drop your image here</h3>
<p class="text-gray-500 text-sm mb-4">or</p>
<label for="file-upload" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition cursor-pointer">
Browse Files
</label>
<input id="file-upload" type="file" class="hidden" accept="image/*">
<p class="text-gray-400 text-xs mt-4">Supports: JPG, PNG, WEBP (Max 5MB)</p>
</div>
</div>
<div id="preview-container" class="mt-4 hidden">
<h3 class="text-sm font-medium text-gray-700 mb-2">Selected Image</h3>
<div class="image-container">
<div class="image-wrapper bg-gray-100 rounded-lg">
<img id="preview-image" src="" alt="Preview" class="hidden">
</div>
</div>
<div class="flex justify-between mt-4">
<button id="process-btn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
<i class="fas fa-magic mr-2"></i> Process Image
</button>
<button id="clear-btn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">
<i class="fas fa-trash-alt mr-2"></i> Clear
</button>
</div>
</div>
</div>
<!-- Results Section -->
<div>
<div id="loading-indicator" class="hidden">
<div class="flex flex-col items-center justify-center h-full">
<div class="loading-spinner border-4 border-blue-500 border-t-transparent rounded-full w-12 h-12 mb-4"></div>
<p class="text-gray-600">Processing your image...</p>
<p class="text-gray-400 text-sm">This may take a few moments</p>
</div>
</div>
<div id="result-container" class="result-container">
<h3 class="text-lg font-medium text-gray-700 mb-4">Segmentation Results</h3>
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-700">Segmentation Mask</h4>
<div class="flex items-center">
<button id="download-mask" class="text-blue-500 hover:text-blue-700 text-sm">
<i class="fas fa-download mr-1"></i> Download
</button>
</div>
</div>
<div class="image-container">
<div class="image-wrapper bg-gray-100 rounded-lg relative">
<img id="mask-image" src="" alt="Segmentation Mask" class="hidden">
<div class="slider-container hidden" id="mask-slider">
<i class="fas fa-eye"></i>
<input type="range" min="0" max="100" value="50" class="slider" id="mask-opacity">
<span id="mask-value">50%</span>
</div>
</div>
</div>
</div>
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-700">Overlay Comparison</h4>
<div class="flex items-center">
<button id="download-overlay" class="text-blue-500 hover:text-blue-700 text-sm">
<i class="fas fa-download mr-1"></i> Download
</button>
</div>
</div>
<div class="image-container">
<div class="image-wrapper bg-gray-100 rounded-lg relative">
<img id="overlay-image" src="" alt="Overlay" class="hidden">
<div class="slider-container">
<i class="fas fa-sliders-h"></i>
<input type="range" min="0" max="100" value="50" class="slider" id="overlay-opacity">
<span id="overlay-value">50%</span>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-700 mb-2">Segmentation Statistics</h4>
<div class="grid grid-cols-3 gap-4 text-center">
<div class="bg-white p-2 rounded shadow">
<p class="text-xs text-gray-500">Foreground Area</p>
<p id="foreground-area" class="font-bold">0 px²</p>
</div>
<div class="bg-white p-2 rounded shadow">
<p class="text-xs text-gray-500">Background Area</p>
<p id="background-area" class="font-bold">0 px²</p>
</div>
<div class="bg-white p-2 rounded shadow">
<p class="text-xs text-gray-500">Confidence</p>
<p id="confidence-score" class="font-bold">0%</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">About This Model</h2>
<div class="grid md:grid-cols-2 gap-6">
<div>
<h3 class="font-medium text-gray-700 mb-2">Model Details</h3>
<ul class="text-gray-600 space-y-2">
<li class="flex items-start">
<i class="fas fa-cog text-blue-500 mt-1 mr-2"></i>
<span>Architecture: Your Custom Segmentation Model</span>
</li>
<li class="flex items-start">
<i class="fas fa-weight-hanging text-blue-500 mt-1 mr-2"></i>
<span>Model File: .pth format</span>
</li>
<li class="flex items-start">
<i class="fas fa-tachometer-alt text-blue-500 mt-1 mr-2"></i>
<span>Inference Time: Varies by hardware</span>
</li>
<li class="flex items-start">
<i class="fas fa-chart-line text-blue-500 mt-1 mr-2"></i>
<span>Custom Trained Model</span>
</li>
</ul>
</div>
<div>
<h3 class="font-medium text-gray-700 mb-2">Performance Metrics</h3>
<div class="h-48">
<canvas id="metrics-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileUpload = document.getElementById('file-upload');
const previewContainer = document.getElementById('preview-container');
const previewImage = document.getElementById('preview-image');
const processBtn = document.getElementById('process-btn');
const clearBtn = document.getElementById('clear-btn');
const loadingIndicator = document.getElementById('loading-indicator');
const resultContainer = document.getElementById('result-container');
const maskImage = document.getElementById('mask-image');
const overlayImage = document.getElementById('overlay-image');
const maskSlider = document.getElementById('mask-slider');
const maskOpacity = document.getElementById('mask-opacity');
const maskValue = document.getElementById('mask-value');
const overlayOpacity = document.getElementById('overlay-opacity');
const overlayValue = document.getElementById('overlay-value');
const foregroundArea = document.getElementById('foreground-area');
const backgroundArea = document.getElementById('background-area');
const confidenceScore = document.getElementById('confidence-score');
const downloadMask = document.getElementById('download-mask');
const downloadOverlay = document.getElementById('download-overlay');
// Event Listeners
dropzone.addEventListener('click', () => fileUpload.click());
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropzone.classList.add('active');
}
function unhighlight() {
dropzone.classList.remove('active');
}
dropzone.addEventListener('drop', handleDrop, false);
fileUpload.addEventListener('change', handleFiles, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles({target: {files}});
}
function handleFiles(e) {
const file = e.target.files[0];
if (!file) return;
if (!file.type.match('image.*')) {
alert('Please select an image file (JPG, PNG, WEBP)');
return;
}
if (file.size > 5 * 1024 * 1024) {
alert('File size exceeds 5MB limit');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
previewImage.src = e.target.result;
previewImage.classList.remove('hidden');
previewContainer.classList.remove('hidden');
resultContainer.classList.remove('show');
};
reader.readAsDataURL(file);
}
processBtn.addEventListener('click', processImage);
clearBtn.addEventListener('click', clearAll);
function clearAll() {
fileUpload.value = '';
previewImage.src = '';
previewImage.classList.add('hidden');
previewContainer.classList.add('hidden');
maskImage.classList.add('hidden');
overlayImage.classList.add('hidden');
resultContainer.classList.remove('show');
maskSlider.classList.add('hidden');
}
maskOpacity.addEventListener('input', () => {
const value = maskOpacity.value;
maskValue.textContent = `${value}%`;
maskImage.style.opacity = value / 100;
});
overlayOpacity.addEventListener('input', () => {
const value = overlayOpacity.value;
overlayValue.textContent = `${value}%`;
overlayImage.style.opacity = value / 100;
});
async function processImage() {
if (!previewImage.src) {
alert('Please select an image first');
return;
}
// Show loading indicator
loadingIndicator.classList.remove('hidden');
resultContainer.classList.remove('show');
try {
// Convert the image to a blob for upload
const blob = await fetch(previewImage.src).then(r => r.blob());
// Create FormData to send the image
const formData = new FormData();
formData.append('image', blob, 'uploaded_image.jpg');
// 1. FIRST APPROACH: Using a Flask backend
// -----------------------------------------
// You would need to create a Flask server that loads your model
// and processes the image. The endpoint would be something like:
// const response = await fetch('http://localhost:5000/process', {
// method: 'POST',
// body: formData
// });
// 2. SECOND APPROACH: Using Pyodide to run Python in browser
// ---------------------------------------------------------
// This approach runs your model directly in the browser using WebAssembly
// You would need to:
// 1. Load Pyodide
// 2. Install required Python packages
// 3. Load your model
// 4. Process the image
// Here's a basic structure for the Pyodide approach:
/*
// Load Pyodide
let pyodide = await loadPyodide();
// Install required packages
await pyodide.loadPackage(["numpy", "Pillow", "torch"]);
// Load your custom segmentation code
// You would need to convert your model to a format that can be loaded in Pyodide
// and include it in your project files
await pyodide.runPythonAsync(`
import your_segmentation_module
from your_segmentation_module import YourSegmentationModel
# Load your model (you would need to provide the path to your model file)
model = YourSegmentationModel()
model.load_state_dict(torch.load('path/to/your/model.pth'))
model.eval()
# Process image function
def process_image(image_data):
# Your image processing and segmentation logic here
# Return the mask and statistics
return mask, foreground_area, background_area, confidence
`);
// Process the image
const response = await pyodide.runPythonAsync(`
image_data = get_image_data() # You would need to implement this
mask, fg_area, bg_area, conf = process_image(image_data)
# Convert results to format that can be returned to JS
# ...
`);
*/
// For this demo, we'll simulate a response
// In a real implementation, you would use one of the approaches above
const simulatedResponse = {
mask: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==", // Base64 encoded placeholder
foreground_area: 12345,
background_area: 45678,
confidence: 0.92
};
// Display the results
maskImage.src = `data:image/png;base64,${simulatedResponse.mask}`;
maskImage.classList.remove('hidden');
maskSlider.classList.remove('hidden');
overlayImage.src = previewImage.src;
overlayImage.classList.remove('hidden');
// Update statistics
foregroundArea.textContent = `${simulatedResponse.foreground_area} px²`;
backgroundArea.textContent = `${simulatedResponse.background_area} px²`;
confidenceScore.textContent = `${Math.round(simulatedResponse.confidence * 100)}%`;
// Setup download buttons
downloadMask.onclick = () => downloadImage(maskImage.src, 'segmentation-mask.png');
downloadOverlay.onclick = () => downloadImage(overlayImage.src, 'segmentation-overlay.png');
// Show results
resultContainer.classList.add('show');
} catch (error) {
console.error('Error:', error);
alert('Error processing image. Please try again.');
} finally {
loadingIndicator.classList.add('hidden');
}
}
function downloadImage(url, filename) {
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// Initialize chart
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('metrics-chart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['IoU', 'Precision', 'Recall', 'Dice'],
datasets: [{
label: 'Model Metrics',
data: [0.85, 0.88, 0.82, 0.87], // Replace with your model's actual metrics
backgroundColor: [
'rgba(59, 130, 246, 0.7)',
'rgba(16, 185, 129, 0.7)',
'rgba(245, 158, 11, 0.7)',
'rgba(139, 92, 246, 0.7)'
],
borderColor: [
'rgba(59, 130, 246, 1)',
'rgba(16, 185, 129, 1)',
'rgba(245, 158, 11, 1)',
'rgba(139, 92, 246, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 1.0
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
// MODEL INTEGRATION GUIDE
// -----------------------
// To integrate your custom segmentation model, you have two main options:
// 1. Flask Backend Approach (Recommended for production)
// ------------------------------------------------------
// - Create a Flask server that loads your model
// - The server should have an endpoint (e.g., /process) that:
// - Receives the image file
// - Processes it using your model
// - Returns the segmentation mask and statistics
// - In this HTML file, modify the processImage() function to call your Flask endpoint
// Example Flask server structure:
/*
from flask import Flask, request, jsonify
import your_segmentation_module
import base64
import io
from PIL import Image
app = Flask(__name__)
# Load your model (replace with your actual model path)
model = your_segmentation_module.YourSegmentationModel()
model.load_state_dict(torch.load('path/to/your/model.pth'))
model.eval()
@app.route('/process', methods=['POST'])
def process_image():
if 'image' not in request.files:
return jsonify({'error': 'No image provided'}), 400
image_file = request.files['image']
image = Image.open(image_file.stream)
# Process image with your model
mask, fg_area, bg_area, confidence = model.process(image)
# Convert mask to base64
buffered = io.BytesIO()
mask.save(buffered, format="PNG")
mask_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
return jsonify({
'mask': mask_base64,
'foreground_area': fg_area,
'background_area': bg_area,
'confidence': confidence
})
if __name__ == '__main__':
app.run(debug=True)
*/
// 2. Pyodide Approach (For browser-only implementation)
// ----------------------------------------------------
// - This runs Python directly in the browser using WebAssembly
// - Limitations: Larger initial load time, limited package support
// - Steps:
// a. Include Pyodide in your project
// b. Convert your model to a format that can be loaded in Pyodide
// c. Modify the processImage() function to use Pyodide
// Model Path Configuration:
// -------------------------
// Wherever you see 'path/to/your/model.pth' in the code comments,
// replace it with the actual path to your trained model file.
// This could be:
// - A relative path from your Flask server's root directory
// - An absolute path on your server
// - A URL if hosting the model file online
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=vishnumeher/amb" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>