Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Depth Estimation, 3D Visualization</title> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: #f7f7f7; | |
color: #333; | |
line-height: 1.6; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 30px; | |
background: white; | |
padding: 30px; | |
border-radius: 12px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
} | |
.header h1 { | |
font-size: 2.5rem; | |
font-weight: 700; | |
color: #1a1a1a; | |
margin-bottom: 8px; | |
} | |
.header p { | |
font-size: 1.1rem; | |
color: #666; | |
} | |
.main-content { | |
background: white; | |
border-radius: 12px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
overflow: hidden; | |
} | |
/* Tabs */ | |
.tabs { | |
display: flex; | |
background: #f8f9fa; | |
border-bottom: 1px solid #e9ecef; | |
} | |
.tab { | |
flex: 1; | |
padding: 15px 20px; | |
background: none; | |
border: none; | |
cursor: pointer; | |
font-size: 1rem; | |
font-weight: 500; | |
color: #666; | |
transition: all 0.3s ease; | |
border-bottom: 3px solid transparent; | |
} | |
.tab.active { | |
background: white; | |
color: #1a1a1a; | |
border-bottom-color: #007bff; | |
} | |
.tab:hover { | |
background: #e9ecef; | |
} | |
.tab-content { | |
display: none; | |
padding: 30px; | |
} | |
.tab-content.active { | |
display: block; | |
} | |
/* Upload Section */ | |
.upload-section { | |
text-align: center; | |
padding: 40px 20px; | |
border: 2px dashed #dee2e6; | |
border-radius: 8px; | |
background: #f8f9fa; | |
margin-bottom: 30px; | |
transition: all 0.3s ease; | |
} | |
.upload-section.dragover { | |
border-color: #007bff; | |
background: #e7f3ff; | |
} | |
.file-input { | |
display: none; | |
} | |
.upload-btn { | |
background: #007bff; | |
color: white; | |
padding: 12px 24px; | |
border: none; | |
border-radius: 6px; | |
font-size: 1rem; | |
cursor: pointer; | |
transition: background 0.3s ease; | |
} | |
.upload-btn:hover { | |
background: #0056b3; | |
} | |
/* Parameters */ | |
.parameters { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 20px; | |
margin-bottom: 30px; | |
} | |
.parameter-group { | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 8px; | |
border: 1px solid #e9ecef; | |
} | |
.parameter-group label { | |
display: block; | |
margin-bottom: 8px; | |
font-weight: 600; | |
color: #495057; | |
font-size: 0.9rem; | |
} | |
.parameter-group input[type="range"] { | |
width: 100%; | |
margin-bottom: 8px; | |
} | |
.parameter-group .value-display { | |
font-size: 0.85rem; | |
color: #6c757d; | |
font-weight: 500; | |
} | |
/* Buttons */ | |
.process-btn { | |
background: #28a745; | |
color: white; | |
padding: 12px 30px; | |
border: none; | |
border-radius: 6px; | |
font-size: 1rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: background 0.3s ease; | |
margin: 20px 0; | |
} | |
.process-btn:hover { | |
background: #218838; | |
} | |
.process-btn:disabled { | |
background: #6c757d; | |
cursor: not-allowed; | |
} | |
/* Loading */ | |
.loading { | |
text-align: center; | |
padding: 30px; | |
display: none; | |
} | |
.spinner { | |
border: 3px solid #f3f3f3; | |
border-top: 3px solid #007bff; | |
border-radius: 50%; | |
width: 30px; | |
height: 30px; | |
animation: spin 1s linear infinite; | |
margin: 0 auto 15px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
/* Results */ | |
.results { | |
display: none; | |
} | |
.results h2 { | |
margin-bottom: 20px; | |
color: #1a1a1a; | |
font-size: 1.5rem; | |
} | |
.image-comparison { | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 20px; | |
margin-bottom: 30px; | |
} | |
.image-container { | |
text-align: center; | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 8px; | |
border: 1px solid #e9ecef; | |
} | |
.image-container img { | |
max-width: 100%; | |
height: auto; | |
border-radius: 6px; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
} | |
.image-container h3 { | |
margin-bottom: 15px; | |
color: #495057; | |
font-size: 1.1rem; | |
} | |
/* Downloads */ | |
.downloads { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); | |
gap: 15px; | |
margin-bottom: 30px; | |
} | |
.download-btn { | |
background: #6c757d; | |
color: white; | |
padding: 10px 16px; | |
border: none; | |
border-radius: 6px; | |
text-decoration: none; | |
text-align: center; | |
display: block; | |
font-size: 0.9rem; | |
transition: background 0.3s ease; | |
} | |
.download-btn:hover { | |
background: #545b62; | |
color: white; | |
text-decoration: none; | |
} | |
/* 3D Plot */ | |
.plot-container { | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 8px; | |
border: 1px solid #e9ecef; | |
margin-top: 20px; | |
} | |
.plot-container h3 { | |
margin-bottom: 15px; | |
color: #495057; | |
font-size: 1.1rem; | |
} | |
#plot3d { | |
width: 100%; | |
height: 500px; | |
background: white; | |
border-radius: 6px; | |
} | |
/* Error */ | |
.error { | |
background: #f8d7da; | |
color: #721c24; | |
padding: 15px; | |
border-radius: 6px; | |
margin: 20px 0; | |
display: none; | |
border: 1px solid #f5c6cb; | |
} | |
/* Responsive */ | |
@media (max-width: 768px) { | |
.header h1 { | |
font-size: 2rem; | |
} | |
.image-comparison { | |
grid-template-columns: 1fr; | |
} | |
.parameters { | |
grid-template-columns: 1fr; | |
} | |
.tabs { | |
flex-direction: column; | |
} | |
.tab { | |
border-bottom: 1px solid #e9ecef; | |
border-right: none; | |
} | |
.tab.active { | |
border-bottom-color: #007bff; | |
} | |
} | |
/* Gradio-like styling */ | |
.gradio-container { | |
background: white; | |
border-radius: 12px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
margin-bottom: 20px; | |
} | |
.gradio-header { | |
padding: 20px 30px; | |
border-bottom: 1px solid #e9ecef; | |
background: #f8f9fa; | |
} | |
.gradio-header h3 { | |
margin: 0; | |
color: #1a1a1a; | |
font-size: 1.2rem; | |
font-weight: 600; | |
} | |
.gradio-body { | |
padding: 30px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="header"> | |
<h1>Depth Estimation, 3D Visualization</h1> | |
<p>Upload an image to generate depth maps and 3D visualizations</p> | |
</div> | |
<div class="main-content"> | |
<div class="tabs"> | |
<button class="tab active" onclick="showTab('input')">Input</button> | |
<button class="tab" onclick="showTab('results')">Results</button> | |
<button class="tab" onclick="showTab('3d')">3D Visualization</button> | |
</div> | |
<!-- Input Tab --> | |
<div id="input" class="tab-content active"> | |
<div class="gradio-container"> | |
<div class="gradio-header"> | |
<h3>Image Upload</h3> | |
</div> | |
<div class="gradio-body"> | |
<div class="upload-section" id="uploadSection"> | |
<h4>Upload Image</h4> | |
<p>Drag and drop an image here or click to select</p> | |
<input type="file" id="imageInput" class="file-input" accept="image/*"> | |
<button class="upload-btn" onclick="document.getElementById('imageInput').click()"> | |
Choose Image | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="gradio-container"> | |
<div class="gradio-header"> | |
<h3>Parameters</h3> | |
</div> | |
<div class="gradio-body"> | |
<div class="parameters"> | |
<div class="parameter-group"> | |
<label for="numPoints">Number of 3D Points</label> | |
<input type="range" id="numPoints" min="1000" max="100000" value="10000" step="1000"> | |
<div class="value-display">Value: <span id="numPointsValue">10000</span></div> | |
</div> | |
<div class="parameter-group"> | |
<label for="focalX">Focal Length X (pixels)</label> | |
<input type="range" id="focalX" min="100" max="1000" value="470.4" step="10"> | |
<div class="value-display">Value: <span id="focalXValue">470.4</span></div> | |
</div> | |
<div class="parameter-group"> | |
<label for="focalY">Focal Length Y (pixels)</label> | |
<input type="range" id="focalY" min="100" max="1000" value="470.4" step="10"> | |
<div class="value-display">Value: <span id="focalYValue">470.4</span></div> | |
</div> | |
</div> | |
<div style="text-align: center;"> | |
<button class="process-btn" id="processBtn" onclick="processImage()" disabled> | |
Compute Depth | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="loading" id="loading"> | |
<div class="spinner"></div> | |
<p>Processing image... This may take a few moments.</p> | |
</div> | |
<div class="error" id="error"></div> | |
</div> | |
<!-- Results Tab --> | |
<div id="results" class="tab-content"> | |
<div class="results" id="resultsContent"> | |
<div class="gradio-container"> | |
<div class="gradio-header"> | |
<h3>Depth Map Results</h3> | |
</div> | |
<div class="gradio-body"> | |
<div class="image-comparison"> | |
<div class="image-container"> | |
<h3>Original Image</h3> | |
<img id="originalImage" alt="Original Image"> | |
</div> | |
<div class="image-container"> | |
<h3>Depth Map</h3> | |
<img id="depthImage" alt="Depth Map"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="gradio-container"> | |
<div class="gradio-header"> | |
<h3>Download Results</h3> | |
</div> | |
<div class="gradio-body"> | |
<div class="downloads"> | |
<a href="#" class="download-btn" id="grayDepthDownload">Download Grayscale Depth</a> | |
<a href="#" class="download-btn" id="rawDepthDownload">Download Raw Depth</a> | |
<a href="#" class="download-btn" id="pointcloudDownload">Download Point Cloud</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- 3D Visualization Tab --> | |
<div id="3d" class="tab-content"> | |
<div class="gradio-container"> | |
<div class="gradio-header"> | |
<h3>3D Point Cloud Visualization</h3> | |
</div> | |
<div class="gradio-body"> | |
<div class="plot-container"> | |
<div id="plot3d"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Tab functionality | |
function showTab(tabName) { | |
// Hide all tab contents | |
const tabContents = document.querySelectorAll('.tab-content'); | |
tabContents.forEach(content => content.classList.remove('active')); | |
// Remove active class from all tabs | |
const tabs = document.querySelectorAll('.tab'); | |
tabs.forEach(tab => tab.classList.remove('active')); | |
// Show selected tab content | |
document.getElementById(tabName).classList.add('active'); | |
// Add active class to clicked tab | |
event.target.classList.add('active'); | |
} | |
// Update value displays | |
document.getElementById('numPoints').addEventListener('input', function() { | |
document.getElementById('numPointsValue').textContent = this.value; | |
}); | |
document.getElementById('focalX').addEventListener('input', function() { | |
document.getElementById('focalXValue').textContent = this.value; | |
}); | |
document.getElementById('focalY').addEventListener('input', function() { | |
document.getElementById('focalYValue').textContent = this.value; | |
}); | |
// File upload handling | |
const uploadSection = document.getElementById('uploadSection'); | |
const imageInput = document.getElementById('imageInput'); | |
const processBtn = document.getElementById('processBtn'); | |
// Drag and drop functionality | |
uploadSection.addEventListener('dragover', function(e) { | |
e.preventDefault(); | |
uploadSection.classList.add('dragover'); | |
}); | |
uploadSection.addEventListener('dragleave', function(e) { | |
e.preventDefault(); | |
uploadSection.classList.remove('dragover'); | |
}); | |
uploadSection.addEventListener('drop', function(e) { | |
e.preventDefault(); | |
uploadSection.classList.remove('dragover'); | |
const files = e.dataTransfer.files; | |
if (files.length > 0) { | |
imageInput.files = files; | |
handleFileSelect(); | |
} | |
}); | |
imageInput.addEventListener('change', handleFileSelect); | |
function handleFileSelect() { | |
if (imageInput.files.length > 0) { | |
processBtn.disabled = false; | |
uploadSection.innerHTML = ` | |
<h4>Image Selected</h4> | |
<p>${imageInput.files[0].name}</p> | |
<button class="upload-btn" onclick="document.getElementById('imageInput').click()"> | |
Choose Different Image | |
</button> | |
`; | |
} | |
} | |
function processImage() { | |
if (!imageInput.files.length) return; | |
const formData = new FormData(); | |
formData.append('image', imageInput.files[0]); | |
formData.append('num_points', document.getElementById('numPoints').value); | |
formData.append('focal_x', document.getElementById('focalX').value); | |
formData.append('focal_y', document.getElementById('focalY').value); | |
// Show loading | |
document.getElementById('loading').style.display = 'block'; | |
document.getElementById('error').style.display = 'none'; | |
document.getElementById('resultsContent').style.display = 'none'; | |
processBtn.disabled = true; | |
fetch('/process', { | |
method: 'POST', | |
body: formData | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
document.getElementById('loading').style.display = 'none'; | |
processBtn.disabled = false; | |
if (data.success) { | |
// Display results | |
document.getElementById('originalImage').src = data.original_image; | |
document.getElementById('depthImage').src = data.colored_depth; | |
// Update download links | |
document.getElementById('grayDepthDownload').href = data.downloads.gray_depth; | |
document.getElementById('rawDepthDownload').href = data.downloads.raw_depth; | |
document.getElementById('pointcloudDownload').href = data.downloads.pointcloud; | |
// Display 3D plot | |
try { | |
const plotData = JSON.parse(data['3d_plot']); | |
Plotly.newPlot('plot3d', plotData.data, plotData.layout, { | |
responsive: true, | |
displayModeBar: true, | |
displaylogo: false | |
}); | |
} catch (error) { | |
console.error('Error displaying 3D plot:', error); | |
document.getElementById('plot3d').innerHTML = '<p style="text-align: center; color: #666;">3D visualization could not be loaded</p>'; | |
} | |
document.getElementById('resultsContent').style.display = 'block'; | |
// Show results tab | |
showTab('results'); | |
} else { | |
throw new Error(data.error || 'Unknown error occurred'); | |
} | |
}) | |
.catch(error => { | |
document.getElementById('loading').style.display = 'none'; | |
processBtn.disabled = false; | |
document.getElementById('error').textContent = 'Error: ' + error.message; | |
document.getElementById('error').style.display = 'block'; | |
}); | |
} | |
</script> | |
</body> | |
</html> |