CoolFace
Apppublic

gbreadman13/sam2-api

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
0likes
web_demo.html597 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="ru">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>SAM2 Box Prompt Demo</title>7    <style>8        * {9            margin: 0;10            padding: 0;11            box-sizing: border-box;12        }13        14        body {15            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;16            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);17            min-height: 100vh;18            padding: 20px;19        }20        21        .container {22            max-width: 1400px;23            margin: 0 auto;24        }25        26        h1 {27            color: white;28            text-align: center;29            margin-bottom: 10px;30            font-size: 2.5em;31            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);32        }33        34        .subtitle {35            color: rgba(255,255,255,0.9);36            text-align: center;37            margin-bottom: 30px;38            font-size: 1.1em;39        }40        41        .panel {42            background: white;43            border-radius: 16px;44            padding: 30px;45            box-shadow: 0 20px 60px rgba(0,0,0,0.3);46            margin-bottom: 20px;47        }48        49        .upload-section {50            text-align: center;51            padding: 40px;52            border: 3px dashed #667eea;53            border-radius: 12px;54            background: #f8f9ff;55            cursor: pointer;56            transition: all 0.3s;57        }58        59        .upload-section:hover {60            background: #f0f1ff;61            border-color: #764ba2;62        }63        64        .upload-section.dragover {65            background: #e8e9ff;66            border-color: #764ba2;67            transform: scale(1.02);68        }69        70        input[type="file"] {71            display: none;72        }73        74        .upload-btn {75            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);76            color: white;77            border: none;78            padding: 15px 40px;79            font-size: 1.1em;80            border-radius: 8px;81            cursor: pointer;82            transition: transform 0.2s;83            font-weight: 600;84        }85        86        .upload-btn:hover {87            transform: translateY(-2px);88            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);89        }90        91        .workspace {92            display: none;93            margin-top: 30px;94        }95        96        .workspace.active {97            display: grid;98            grid-template-columns: 1fr 1fr;99            gap: 30px;100        }101        102        .canvas-container {103            position: relative;104            background: #f8f9ff;105            border-radius: 12px;106            padding: 20px;107            box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);108        }109        110        .canvas-title {111            font-size: 1.2em;112            font-weight: 600;113            margin-bottom: 15px;114            color: #333;115        }116        117        canvas {118            display: block;119            max-width: 100%;120            border-radius: 8px;121            box-shadow: 0 4px 12px rgba(0,0,0,0.15);122            cursor: crosshair;123        }124        125        .instructions {126            background: #fff3cd;127            border: 2px solid #ffc107;128            border-radius: 8px;129            padding: 15px;130            margin-bottom: 20px;131            font-size: 0.95em;132            line-height: 1.6;133        }134        135        .instructions strong {136            color: #856404;137        }138        139        .controls {140            display: flex;141            gap: 15px;142            margin-top: 20px;143            flex-wrap: wrap;144        }145        146        button {147            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);148            color: white;149            border: none;150            padding: 12px 30px;151            font-size: 1em;152            border-radius: 8px;153            cursor: pointer;154            transition: all 0.2s;155            font-weight: 600;156        }157        158        button:hover {159            transform: translateY(-2px);160            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);161        }162        163        button:disabled {164            background: #ccc;165            cursor: not-allowed;166            transform: none;167        }168        169        button.clear {170            background: #dc3545;171        }172        173        button.clear:hover {174            box-shadow: 0 5px 15px rgba(220, 53, 69, 0.4);175        }176        177        .loading {178            display: none;179            text-align: center;180            padding: 30px;181        }182        183        .loading.active {184            display: block;185        }186        187        .spinner {188            border: 4px solid #f3f3f3;189            border-top: 4px solid #667eea;190            border-radius: 50%;191            width: 50px;192            height: 50px;193            animation: spin 1s linear infinite;194            margin: 0 auto 15px;195        }196        197        @keyframes spin {198            0% { transform: rotate(0deg); }199            100% { transform: rotate(360deg); }200        }201        202        .result-info {203            background: #d1ecf1;204            border: 2px solid #bee5eb;205            border-radius: 8px;206            padding: 15px;207            margin-top: 15px;208            font-size: 0.9em;209        }210        211        .result-info h4 {212            margin-bottom: 10px;213            color: #0c5460;214        }215        216        .result-info p {217            margin: 5px 0;218            color: #0c5460;219        }220        221        .error {222            background: #f8d7da;223            border: 2px solid #f5c6cb;224            color: #721c24;225            padding: 15px;226            border-radius: 8px;227            margin-top: 15px;228            display: none;229        }230        231        .error.active {232            display: block;233        }234        235        @media (max-width: 1200px) {236            .workspace.active {237                grid-template-columns: 1fr;238            }239        }240        241        .badge {242            display: inline-block;243            background: #28a745;244            color: white;245            padding: 4px 12px;246            border-radius: 12px;247            font-size: 0.85em;248            font-weight: 600;249            margin-left: 10px;250        }251    </style>252</head>253<body>254    <div class="container">255        <h1>🎯 SAM2 Box Prompt Demo</h1>256        <p class="subtitle">Выдели объект прямоугольником → Получи точную сегментацию</p>257        258        <div class="panel">259            <div class="upload-section" id="uploadSection">260                <h2 style="margin-bottom: 15px;">📸 Загрузи изображение</h2>261                <p style="color: #666; margin-bottom: 20px;">Кликни или перетащи фото сюда</p>262                <button class="upload-btn" onclick="document.getElementById('fileInput').click()">263                    Выбрать файл264                </button>265                <input type="file" id="fileInput" accept="image/*">266            </div>267            268            <div class="workspace" id="workspace">269                <div class="instructions">270                    <strong>📝 Как пользоваться:</strong><br>271                    1. Зажми левую кнопку мыши и нарисуй прямоугольник вокруг объекта<br>272                    2. Нажми "Сегментировать" чтобы получить результат<br>273                    3. Справа увидишь вырезанный объект с прозрачностью274                </div>275                276                <div>277                    <div class="canvas-container">278                        <div class="canvas-title">279                            Исходное изображение 280                            <span class="badge" id="imageSizeBadge"></span>281                        </div>282                        <canvas id="sourceCanvas"></canvas>283                    </div>284                    285                    <div class="controls">286                        <button id="segmentBtn" onclick="segmentImage()">287                            🚀 Сегментировать288                        </button>289                        <button class="clear" onclick="clearBox()">290                            🗑️ Очистить выделение291                        </button>292                        <button class="clear" onclick="resetAll()">293                            🔄 Новое изображение294                        </button>295                    </div>296                </div>297                298                <div>299                    <div class="canvas-container">300                        <div class="canvas-title">Результат (вырезанный объект)</div>301                        <canvas id="resultCanvas"></canvas>302                    </div>303                    304                    <div class="loading" id="loading">305                        <div class="spinner"></div>306                        <p>Обрабатываю изображение...</p>307                    </div>308                    309                    <div class="result-info" id="resultInfo" style="display: none;">310                        <h4>📊 Информация о сегменте:</h4>311                        <p><strong>Площадь:</strong> <span id="resultArea"></span> пикселей</p>312                        <p><strong>BBox:</strong> <span id="resultBBox"></span></p>313                        <p><strong>Confidence:</strong> <span id="resultConfidence"></span></p>314                    </div>315                    316                    <div class="error" id="error"></div>317                </div>318            </div>319        </div>320    </div>321 322    <script>323        const API_URL = 'http://localhost:8000';324        325        let sourceCanvas = document.getElementById('sourceCanvas');326        let resultCanvas = document.getElementById('resultCanvas');327        let sourceCtx = sourceCanvas.getContext('2d');328        let resultCtx = resultCanvas.getContext('2d');329        330        let currentImage = null;331        let isDrawing = false;332        let startX, startY, endX, endY;333        let boxCoords = null;334        335        // Drag & Drop336        const uploadSection = document.getElementById('uploadSection');337        338        uploadSection.addEventListener('dragover', (e) => {339            e.preventDefault();340            uploadSection.classList.add('dragover');341        });342        343        uploadSection.addEventListener('dragleave', () => {344            uploadSection.classList.remove('dragover');345        });346        347        uploadSection.addEventListener('drop', (e) => {348            e.preventDefault();349            uploadSection.classList.remove('dragover');350            const file = e.dataTransfer.files[0];351            if (file && file.type.startsWith('image/')) {352                loadImage(file);353            }354        });355        356        // File input357        document.getElementById('fileInput').addEventListener('change', (e) => {358            const file = e.target.files[0];359            if (file) {360                loadImage(file);361            }362        });363        364        function loadImage(file) {365            const reader = new FileReader();366            reader.onload = (e) => {367                const img = new Image();368                img.onload = () => {369                    currentImage = img;370                    setupCanvas(img);371                    document.getElementById('workspace').classList.add('active');372                    document.getElementById('imageSizeBadge').textContent = `${img.width}×${img.height}`;373                };374                img.src = e.target.result;375            };376            reader.readAsDataURL(file);377        }378        379        function setupCanvas(img) {380            const maxWidth = 600;381            const scale = Math.min(1, maxWidth / img.width);382            383            sourceCanvas.width = img.width * scale;384            sourceCanvas.height = img.height * scale;385            resultCanvas.width = sourceCanvas.width;386            resultCanvas.height = sourceCanvas.height;387            388            sourceCtx.drawImage(img, 0, 0, sourceCanvas.width, sourceCanvas.height);389            resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);390            391            boxCoords = null;392            document.getElementById('resultInfo').style.display = 'none';393            document.getElementById('error').classList.remove('active');394        }395        396        // Рисование бокса397        sourceCanvas.addEventListener('mousedown', (e) => {398            const rect = sourceCanvas.getBoundingClientRect();399            startX = e.clientX - rect.left;400            startY = e.clientY - rect.top;401            isDrawing = true;402        });403        404        sourceCanvas.addEventListener('mousemove', (e) => {405            if (!isDrawing) return;406            407            const rect = sourceCanvas.getBoundingClientRect();408            endX = e.clientX - rect.left;409            endY = e.clientY - rect.top;410            411            redrawCanvas();412            drawBox(startX, startY, endX, endY);413        });414        415        sourceCanvas.addEventListener('mouseup', (e) => {416            if (!isDrawing) return;417            418            const rect = sourceCanvas.getBoundingClientRect();419            endX = e.clientX - rect.left;420            endY = e.clientY - rect.top;421            isDrawing = false;422            423            // Сохраняем координаты в масштабе оригинального изображения424            const scaleX = currentImage.width / sourceCanvas.width;425            const scaleY = currentImage.height / sourceCanvas.height;426            427            boxCoords = {428                x1: Math.min(startX, endX) * scaleX,429                y1: Math.min(startY, endY) * scaleY,430                x2: Math.max(startX, endX) * scaleX,431                y2: Math.max(startY, endY) * scaleY432            };433            434            console.log('Box coordinates:', boxCoords);435        });436        437        function redrawCanvas() {438            sourceCtx.clearRect(0, 0, sourceCanvas.width, sourceCanvas.height);439            sourceCtx.drawImage(currentImage, 0, 0, sourceCanvas.width, sourceCanvas.height);440        }441        442        function drawBox(x1, y1, x2, y2) {443            const width = x2 - x1;444            const height = y2 - y1;445            446            sourceCtx.strokeStyle = '#00ff00';447            sourceCtx.lineWidth = 3;448            sourceCtx.setLineDash([10, 5]);449            sourceCtx.strokeRect(x1, y1, width, height);450            451            // Полупрозрачная заливка452            sourceCtx.fillStyle = 'rgba(0, 255, 0, 0.1)';453            sourceCtx.fillRect(x1, y1, width, height);454            455            sourceCtx.setLineDash([]);456        }457        458        function clearBox() {459            boxCoords = null;460            redrawCanvas();461            resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);462            document.getElementById('resultInfo').style.display = 'none';463        }464        465        function resetAll() {466            currentImage = null;467            boxCoords = null;468            document.getElementById('workspace').classList.remove('active');469            document.getElementById('fileInput').value = '';470            document.getElementById('resultInfo').style.display = 'none';471            document.getElementById('error').classList.remove('active');472        }473        474        async function segmentImage() {475            if (!boxCoords) {476                showError('Сначала нарисуй прямоугольник на изображении!');477                return;478            }479            480            const { x1, y1, x2, y2 } = boxCoords;481            482            if (x2 - x1 < 10 || y2 - y1 < 10) {483                showError('Выделенная область слишком маленькая. Нарисуй больший прямоугольник.');484                return;485            }486            487            document.getElementById('loading').classList.add('active');488            document.getElementById('error').classList.remove('active');489            document.getElementById('segmentBtn').disabled = true;490            491            try {492                // Конвертируем изображение в blob493                const canvas = document.createElement('canvas');494                canvas.width = currentImage.width;495                canvas.height = currentImage.height;496                const ctx = canvas.getContext('2d');497                ctx.drawImage(currentImage, 0, 0);498                499                const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.95));500                501                // Формируем запрос502                const formData = new FormData();503                formData.append('file', blob, 'image.jpg');504                505                const url = `${API_URL}/segment?box_x1=${x1}&box_y1=${y1}&box_x2=${x2}&box_y2=${y2}&extract_objects=true&include_masks=false`;506                507                console.log('Request URL:', url);508                509                const response = await fetch(url, {510                    method: 'POST',511                    body: formData512                });513                514                if (!response.ok) {515                    throw new Error(`HTTP ${response.status}: ${await response.text()}`);516                }517                518                const result = await response.json();519                console.log('Result:', result);520                521                if (result.success && result.segments.length > 0) {522                    displayResult(result.segments[0]);523                } else {524                    showError('Не удалось найти объект в выделенной области');525                }526                527            } catch (error) {528                console.error('Error:', error);529                showError(`Ошибка: ${error.message}`);530            } finally {531                document.getElementById('loading').classList.remove('active');532                document.getElementById('segmentBtn').disabled = false;533            }534        }535        536        function displayResult(segment) {537            if (!segment.extracted_image) {538                showError('Не получен вырезанный объект');539                return;540            }541            542            const img = new Image();543            img.onload = () => {544                resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);545                546                // Рисуем в центре canvas547                const scale = Math.min(548                    resultCanvas.width / img.width,549                    resultCanvas.height / img.height,550                    1551                );552                553                const w = img.width * scale;554                const h = img.height * scale;555                const x = (resultCanvas.width - w) / 2;556                const y = (resultCanvas.height - h) / 2;557                558                // Клетчатый фон для прозрачности559                drawCheckerboard(resultCtx, x, y, w, h);560                resultCtx.drawImage(img, x, y, w, h);561                562                // Показываем инфу563                document.getElementById('resultArea').textContent = segment.area.toLocaleString();564                document.getElementById('resultBBox').textContent = 565                    `${segment.bbox.width}×${segment.bbox.height} px`;566                document.getElementById('resultConfidence').textContent = 567                    `${(segment.confidence * 100).toFixed(1)}%`;568                document.getElementById('resultInfo').style.display = 'block';569            };570            img.src = segment.extracted_image;571        }572        573        function drawCheckerboard(ctx, x, y, w, h) {574            const size = 10;575            ctx.fillStyle = '#f0f0f0';576            ctx.fillRect(x, y, w, h);577            578            ctx.fillStyle = '#ddd';579            for (let i = 0; i < w; i += size) {580                for (let j = 0; j < h; j += size) {581                    if ((i / size + j / size) % 2 === 0) {582                        ctx.fillRect(x + i, y + j, size, size);583                    }584                }585            }586        }587        588        function showError(message) {589            const errorEl = document.getElementById('error');590            errorEl.textContent = '❌ ' + message;591            errorEl.classList.add('active');592        }593    </script>594</body>595</html>596 597