xydistance / index.html
designfailure's picture
undefined - Initial Deployment
536c827 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Distance Estimation</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<style>
.camera-feed {
position: relative;
width: 100%;
height: 0;
padding-bottom: 75%; /* 4:3 aspect ratio */
background-color: #f0f0f0;
border-radius: 0.5rem;
overflow: hidden;
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.measurement-line {
stroke: #3b82f6;
stroke-width: 2;
stroke-dasharray: 5;
}
.dimension-text {
font-size: 12px;
fill: #3b82f6;
font-weight: bold;
background-color: rgba(255, 255, 255, 0.7);
padding: 2px 4px;
border-radius: 3px;
}
.object-label {
font-size: 10px;
fill: white;
background-color: rgba(59, 130, 246, 0.9);
padding: 2px 4px;
border-radius: 3px;
}
.depth-map {
position: relative;
width: 100%;
height: 0;
padding-bottom: 75%;
background-color: #1e293b;
border-radius: 0.5rem;
overflow: hidden;
}
.toggle-container {
position: relative;
display: inline-block;
width: 60px;
height: 30px;
}
.toggle-checkbox {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 22px;
width: 22px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
.toggle-checkbox:checked + .toggle-slider {
background-color: #3b82f6;
}
.toggle-checkbox:checked + .toggle-slider:before {
transform: translateX(30px);
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Smart Distance Estimation</h1>
<p class="text-gray-600">Measure real-world distances between objects using your smartphone's camera and LiDAR</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Camera Feed Section -->
<div class="bg-white rounded-lg shadow-md p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-700">Camera Feed</h2>
<div class="flex items-center space-x-4">
<div class="flex items-center">
<span class="mr-2 text-sm text-gray-600">LiDAR</span>
<label class="toggle-container">
<input type="checkbox" id="lidarToggle" class="toggle-checkbox" checked>
<span class="toggle-slider"></span>
</label>
</div>
<button id="captureBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md text-sm">
Capture Image
</button>
</div>
</div>
<div class="camera-feed">
<video id="video" autoplay playsinline class="w-full h-full object-cover" style="display: none;"></video>
<canvas id="canvas" width="640" height="480"></canvas>
</div>
<div class="mt-4 flex justify-center">
<button id="startCameraBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md text-sm mr-2">
Start Camera
</button>
<button id="stopCameraBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md text-sm">
Stop Camera
</button>
</div>
</div>
<!-- Depth Map Section -->
<div class="bg-white rounded-lg shadow-md p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-700">Depth Estimation</h2>
<div class="flex items-center space-x-4">
<div class="flex items-center">
<span class="mr-2 text-sm text-gray-600">Auto-detect</span>
<label class="toggle-container">
<input type="checkbox" id="autoDetectToggle" class="toggle-checkbox" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<div class="depth-map">
<canvas id="depthCanvas" width="640" height="480"></canvas>
</div>
<div class="mt-4">
<div class="flex justify-between mb-2">
<span class="text-sm text-gray-600">Near</span>
<span class="text-sm text-gray-600">Far</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-gradient-to-r from-blue-500 via-purple-500 to-red-500 h-2.5 rounded-full" style="width: 100%"></div>
</div>
</div>
</div>
</div>
<!-- Measurement Results -->
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Measurement Results</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-sm font-medium text-gray-500 mb-1">Horizontal Distance</h3>
<p class="text-2xl font-bold text-gray-800">
<span id="horizontalDistance">0.00</span> <span class="text-sm">meters</span>
</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-sm font-medium text-gray-500 mb-1">Vertical Distance</h3>
<p class="text-2xl font-bold text-gray-800">
<span id="verticalDistance">0.00</span> <span class="text-sm">meters</span>
</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-sm font-medium text-gray-500 mb-1">Depth Distance</h3>
<p class="text-2xl font-bold text-gray-800">
<span id="depthDistance">0.00</span> <span class="text-sm">meters</span>
</p>
</div>
</div>
<div class="mt-6">
<h3 class="text-lg font-medium text-gray-700 mb-2">Detected Objects</h3>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Object</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Width (m)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Height (m)</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distance (m)</th>
</tr>
</thead>
<tbody id="objectTableBody" class="bg-white divide-y divide-gray-200">
<!-- Objects will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Controls Section -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">Measurement Controls</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-lg font-medium text-gray-700 mb-3">Measurement Mode</h3>
<div class="space-y-3">
<div class="flex items-center">
<input id="singleMeasurement" name="measurementMode" type="radio" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="singleMeasurement" class="ml-2 block text-sm text-gray-700">Single Measurement</label>
</div>
<div class="flex items-center">
<input id="multiMeasurement" name="measurementMode" type="radio" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="multiMeasurement" class="ml-2 block text-sm text-gray-700">Multi Measurement</label>
</div>
<div class="flex items-center">
<input id="autoMeasurement" name="measurementMode" type="radio" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="autoMeasurement" class="ml-2 block text-sm text-gray-700">Auto Detect Objects</label>
</div>
</div>
<div class="mt-6">
<h3 class="text-lg font-medium text-gray-700 mb-3">Reference Points</h3>
<div class="flex items-center space-x-4">
<div class="flex items-center">
<input id="floorCeiling" name="referencePoints" type="radio" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="floorCeiling" class="ml-2 block text-sm text-gray-700">Floor/Ceiling</label>
</div>
<div class="flex items-center">
<input id="wallEdge" name="referencePoints" type="radio" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
<label for="wallEdge" class="ml-2 block text-sm text-gray-700">Wall Edge</label>
</div>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-medium text-gray-700 mb-3">Settings</h3>
<div class="space-y-4">
<div>
<label for="unitSelect" class="block text-sm font-medium text-gray-700">Measurement Unit</label>
<select id="unitSelect" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
<option value="meters">Meters</option>
<option value="feet">Feet</option>
<option value="inches">Inches</option>
</select>
</div>
<div>
<label for="confidenceThreshold" class="block text-sm font-medium text-gray-700">Confidence Threshold</label>
<input type="range" id="confidenceThreshold" min="0" max="100" value="70" class="mt-1 w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500">
<span>0%</span>
<span id="confidenceValue">70%</span>
<span>100%</span>
</div>
</div>
<div class="flex items-center">
<input id="showDepthMap" type="checkbox" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
<label for="showDepthMap" class="ml-2 block text-sm text-gray-700">Show Depth Map</label>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const depthCanvas = document.getElementById('depthCanvas');
const startCameraBtn = document.getElementById('startCameraBtn');
const stopCameraBtn = document.getElementById('stopCameraBtn');
const captureBtn = document.getElementById('captureBtn');
const horizontalDistanceEl = document.getElementById('horizontalDistance');
const verticalDistanceEl = document.getElementById('verticalDistance');
const depthDistanceEl = document.getElementById('depthDistance');
const objectTableBody = document.getElementById('objectTableBody');
const confidenceThreshold = document.getElementById('confidenceThreshold');
const confidenceValue = document.getElementById('confidenceValue');
const lidarToggle = document.getElementById('lidarToggle');
const autoDetectToggle = document.getElementById('autoDetectToggle');
const showDepthMap = document.getElementById('showDepthMap');
// Canvas context
const ctx = canvas.getContext('2d');
const depthCtx = depthCanvas.getContext('2d');
// State variables
let isCameraOn = false;
let measurements = [];
let selectedPoints = [];
let detectedObjects = [];
// Event Listeners
startCameraBtn.addEventListener('click', startCamera);
stopCameraBtn.addEventListener('click', stopCamera);
captureBtn.addEventListener('click', captureImage);
canvas.addEventListener('click', handleCanvasClick);
confidenceThreshold.addEventListener('input', updateConfidenceValue);
showDepthMap.addEventListener('change', toggleDepthMap);
// Initialize
function init() {
// Set up initial UI state
stopCameraBtn.disabled = true;
captureBtn.disabled = true;
// Mock data for demonstration
setTimeout(() => {
mockDetectObjects();
}, 1000);
}
// Camera functions
async function startCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1280 },
height: { ideal: 720 }
}
});
video.srcObject = stream;
video.style.display = 'block';
isCameraOn = true;
startCameraBtn.disabled = true;
stopCameraBtn.disabled = false;
captureBtn.disabled = false;
// Start processing frames
processVideo();
} catch (err) {
console.error("Error accessing camera:", err);
alert("Could not access the camera. Please ensure you've granted camera permissions.");
}
}
function stopCamera() {
const stream = video.srcObject;
if (stream) {
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null;
video.style.display = 'none';
}
isCameraOn = false;
startCameraBtn.disabled = false;
stopCameraBtn.disabled = true;
captureBtn.disabled = true;
}
function processVideo() {
if (!isCameraOn) return;
// Draw video frame to canvas
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// Simulate object detection (in a real app, this would call your backend)
if (autoDetectToggle.checked) {
simulateObjectDetection();
}
// Draw measurements
drawMeasurements();
// Continue processing
requestAnimationFrame(processVideo);
}
function captureImage() {
// In a real app, this would send the image to your backend for processing
alert("Image captured! In a real implementation, this would be sent to the backend for processing.");
// For demo purposes, simulate detection
simulateObjectDetection();
}
// Measurement functions
function handleCanvasClick(event) {
if (!isCameraOn) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
selectedPoints.push({ x, y });
if (selectedPoints.length === 2) {
// Calculate distance between points (in a real app, this would use your backend)
const distance = calculateDistance(selectedPoints[0], selectedPoints[1]);
measurements.push({
points: [...selectedPoints],
distance: distance,
type: 'manual'
});
selectedPoints = [];
updateMeasurementsUI();
}
drawMeasurements();
}
function calculateDistance(point1, point2) {
// Simple pixel distance (in a real app, this would convert to real-world units)
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
return Math.sqrt(dx * dx + dy * dy) / 100; // Simplified conversion
}
function drawMeasurements() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw video frame
if (isCameraOn) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
// Draw selected points
selectedPoints.forEach(point => {
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI);
ctx.fillStyle = '#3b82f6';
ctx.fill();
});
// Draw measurements
measurements.forEach(measurement => {
const [point1, point2] = measurement.points;
// Draw line
ctx.beginPath();
ctx.moveTo(point1.x, point1.y);
ctx.lineTo(point2.x, point2.y);
ctx.strokeStyle = '#3b82f6';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.setLineDash([]);
// Draw distance text
const midX = (point1.x + point2.x) / 2;
const midY = (point1.y + point2.y) / 2;
ctx.font = '12px Arial';
ctx.fillStyle = '#3b82f6';
ctx.textAlign = 'center';
ctx.fillText(`${measurement.distance.toFixed(2)} m`, midX, midY - 10);
});
// Draw detected objects
detectedObjects.forEach(obj => {
// Draw bounding box
ctx.strokeStyle = '#10b981';
ctx.lineWidth = 2;
ctx.strokeRect(obj.x, obj.y, obj.width, obj.height);
// Draw label
ctx.fillStyle = 'rgba(16, 185, 129, 0.8)';
ctx.fillRect(obj.x, obj.y - 20, ctx.measureText(`${obj.label} (${obj.confidence}%)`).width + 10, 20);
ctx.fillStyle = 'white';
ctx.font = '12px Arial';
ctx.textAlign = 'left';
ctx.fillText(`${obj.label} (${obj.confidence}%)`, obj.x + 5, obj.y - 5);
// Draw center point
ctx.beginPath();
ctx.arc(obj.x + obj.width/2, obj.y + obj.height/2, 3, 0, 2 * Math.PI);
ctx.fillStyle = '#10b981';
ctx.fill();
});
}
function updateMeasurementsUI() {
if (measurements.length > 0) {
const lastMeasurement = measurements[measurements.length - 1];
const dx = Math.abs(lastMeasurement.points[1].x - lastMeasurement.points[0].x);
const dy = Math.abs(lastMeasurement.points[1].y - lastMeasurement.points[0].y);
// Determine if measurement is more horizontal or vertical
if (dx > dy) {
horizontalDistanceEl.textContent = lastMeasurement.distance.toFixed(2);
} else {
verticalDistanceEl.textContent = lastMeasurement.distance.toFixed(2);
}
}
}
// Object detection simulation
function simulateObjectDetection() {
// Clear previous detections
detectedObjects = [];
// Generate mock objects
const mockObjects = [
{ label: 'Chair', confidence: 85, x: 150, y: 200, width: 80, height: 120 },
{ label: 'Table', confidence: 92, x: 300, y: 180, width: 150, height: 90 },
{ label: 'Window', confidence: 78, x: 100, y: 50, width: 200, height: 150 }
];
// Filter by confidence threshold
const threshold = parseInt(confidenceThreshold.value) / 100;
detectedObjects = mockObjects.filter(obj => obj.confidence / 100 >= threshold);
// Update object table
updateObjectTable();
// Simulate depth map
simulateDepthMap();
}
function mockDetectObjects() {
// For demo when camera is off
simulateObjectDetection();
drawMeasurements();
}
function updateObjectTable() {
// Clear table
objectTableBody.innerHTML = '';
// Add rows for each detected object
detectedObjects.forEach(obj => {
// Calculate approximate real-world dimensions (simplified)
const width = (obj.width / 100).toFixed(2);
const height = (obj.height / 100).toFixed(2);
const distance = (Math.sqrt(obj.x*obj.x + obj.y*obj.y) / 200).toFixed(2);
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${obj.label}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${width}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${height}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${distance}</td>
`;
objectTableBody.appendChild(row);
});
}
// Depth map simulation
function simulateDepthMap() {
// Create gradient for depth effect
const gradient = depthCtx.createLinearGradient(0, 0, depthCanvas.width, depthCanvas.height);
gradient.addColorStop(0, '#0000ff'); // Near (blue)
gradient.addColorStop(0.5, '#ff00ff'); // Medium (purple)
gradient.addColorStop(1, '#ff0000'); // Far (red)
depthCtx.fillStyle = gradient;
depthCtx.fillRect(0, 0, depthCanvas.width, depthCanvas.height);
// Add noise for realism
const imageData = depthCtx.getImageData(0, 0, depthCanvas.width, depthCanvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// Add some noise
const noise = Math.random() * 30 - 15;
data[i] = Math.min(255, Math.max(0, data[i] + noise));
data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise));
data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise));
}
depthCtx.putImageData(imageData, 0, 0);
// Add detected objects to depth map
detectedObjects.forEach(obj => {
depthCtx.strokeStyle = 'white';
depthCtx.lineWidth = 2;
depthCtx.strokeRect(obj.x, obj.y, obj.width, obj.height);
// Add depth text
const depthValue = (Math.sqrt(obj.x*obj.x + obj.y*obj.y) / 200).toFixed(2);
depthCtx.fillStyle = 'white';
depthCtx.font = '10px Arial';
depthCtx.textAlign = 'center';
depthCtx.fillText(`${depthValue}m`, obj.x + obj.width/2, obj.y + obj.height/2);
});
// Update depth distance display
if (detectedObjects.length > 0) {
const avgDepth = detectedObjects.reduce((sum, obj) => {
return sum + (Math.sqrt(obj.x*obj.x + obj.y*obj.y) / 200);
}, 0) / detectedObjects.length;
depthDistanceEl.textContent = avgDepth.toFixed(2);
}
}
// UI functions
function updateConfidenceValue() {
confidenceValue.textContent = `${confidenceThreshold.value}%`;
// If auto-detect is on, update detection
if (autoDetectToggle.checked && (isCameraOn || measurements.length > 0)) {
simulateObjectDetection();
drawMeasurements();
}
}
function toggleDepthMap() {
depthCanvas.style.display = showDepthMap.checked ? 'block' : 'none';
}
// Initialize the app
init();
</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=designfailure/xydistance" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>