Spaces:
Sleeping
Sleeping
| const MAX_WIDTH = 256; | |
| const MAX_HEIGHT = 256; | |
| const gridSize = 14; | |
| const canvasSize = 630; | |
| const cellSize = canvasSize / gridSize; | |
| const dropZone = document.getElementById('drop-zone'); | |
| const imageInput = document.getElementById('image-input'); | |
| const browseBtn = document.getElementById('browse-btn'); | |
| const preview = document.getElementById('preview'); | |
| const localImagesDiv = document.getElementById('local-images'); | |
| const attn = document.getElementById('attn'); | |
| let selectedFile = null; | |
| const localSamples = [ | |
| { name: "example1", src: "example/testImg.jpg" }, | |
| { name: "example0", src: "example/testDogsImg.jpg" }, | |
| { name: "example2", src: "example/testImg2.jpg" }, | |
| { name: "example0", src: "example/testDogsImg2.jpg" }, | |
| ]; | |
| // Render local images | |
| localSamples.forEach((img, idx) => { | |
| const thumb = document.createElement("img"); | |
| thumb.src = img.src; | |
| thumb.alt = img.name; | |
| thumb.title = img.name; | |
| thumb.className = "img-example"; | |
| thumb.tabIndex = 0; | |
| thumb.dataset.idx = idx; | |
| thumb.addEventListener("click", async () => { | |
| // Visual selection | |
| document.querySelectorAll('.img-example').forEach(t => t.classList.remove('selected')); | |
| thumb.classList.add('selected'); | |
| // Load image as File object using fetch (simulate user upload) | |
| const file = await urlToFile(img.src, img.name + ".jpg", "image/jpeg"); | |
| selectedFile = file; | |
| handleFile(file, true); | |
| imageInput.value = ''; // clear manual input | |
| }); | |
| localImagesDiv.appendChild(thumb); | |
| }); | |
| async function urlToFile(url, filename, mimeType) { | |
| // Fetch the image as blob then as File | |
| const res = await fetch(url); | |
| const blob = await res.blob(); | |
| return new File([blob], filename, { type: mimeType }); | |
| } | |
| // Drag-and-drop handlers | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('dragover'); | |
| }); | |
| dropZone.addEventListener('dragleave', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('dragover'); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('dragover'); | |
| if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { | |
| const file = e.dataTransfer.files[0]; | |
| selectedFile = file; // Store for form submit | |
| handleFile(file, true); // Show preview in drop field | |
| imageInput.value = ''; | |
| } | |
| }); | |
| // Click to open file picker | |
| browseBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| imageInput.click(); | |
| }); | |
| dropZone.addEventListener('click', (e) => { | |
| if (e.target === dropZone || e.target === document.getElementById('drop-zone-text')) { | |
| imageInput.click(); | |
| } | |
| }); | |
| // File input change | |
| imageInput.addEventListener('change', function (e) { | |
| if (this.files && this.files.length > 0) { | |
| selectedFile = this.files[0]; | |
| handleFile(this.files[0], true); | |
| } | |
| }); | |
| function drawImageAndGrid(ctx, img) { | |
| ctx.clearRect(0, 0, canvasSize, canvasSize); | |
| ctx.drawImage(img, 0, 0, canvasSize, canvasSize); | |
| // Draw grid lines | |
| ctx.strokeStyle = 'gray'; | |
| ctx.lineWidth = 3; | |
| for (let i = 0; i <= gridSize; i++) { | |
| ctx.beginPath(); | |
| ctx.moveTo(i * cellSize, 0); | |
| ctx.lineTo(i * cellSize, canvasSize); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.moveTo(0, i * cellSize); | |
| ctx.lineTo(canvasSize, i * cellSize); | |
| ctx.stroke(); | |
| } | |
| } | |
| function drawImageAndGridForAttn(ctx, img, index, attnData, threshold7, threshold8, threshold9) { | |
| ctx.globalAlpha = 1.0; // Set transparency (0.0 - 1.0) | |
| ctx.strokeStyle = '#ea580c'; | |
| ctx.clearRect(0, 0, canvasSize, canvasSize); | |
| ctx.drawImage(img, 0, 0, canvasSize, canvasSize); | |
| ctx.fillStyle = '#ffc800' | |
| for (let row = 0; row <= gridSize; row++) { | |
| for (let col = 0; col <= gridSize; col++) { | |
| if (attnData[index][row*gridSize+col+1] > threshold9[index]) { | |
| ctx.globalAlpha = 0.8; | |
| ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); | |
| } | |
| else if (attnData[index][row*gridSize+col+1] > threshold8[index]) { | |
| ctx.globalAlpha = 0.5; | |
| ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); | |
| } | |
| else if (attnData[index][row*gridSize+col+1] > threshold7[index]) { | |
| ctx.globalAlpha = 0.2; | |
| ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); | |
| } | |
| } | |
| } | |
| } | |
| // Handle image preview and resizing | |
| function handleFile(file, showInDropZone = false) { | |
| if (!file) return; | |
| const img = new window.Image(); | |
| const url = URL.createObjectURL(file); | |
| img.onload = function () { | |
| let width = img.width; | |
| let height = img.height; | |
| if (width > MAX_WIDTH || height > MAX_HEIGHT) { | |
| const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height); | |
| width = Math.round(width * ratio); | |
| height = Math.round(height * ratio); | |
| } | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = width; | |
| canvas.height = height; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.drawImage(img, 0, 0, width, height); | |
| const previewImg = document.createElement('img'); | |
| previewImg.src = canvas.toDataURL(file.type); | |
| previewImg.alt = "Preview"; | |
| previewImg.style.maxWidth = MAX_WIDTH + "px"; | |
| previewImg.style.maxHeight = MAX_HEIGHT + "px"; | |
| previewImg.style.display = "block"; | |
| previewImg.style.margin = "12px auto 0 auto"; | |
| previewImg.style.borderRadius = "6px"; | |
| previewImg.style.boxShadow = "0 0 3px #ccc"; | |
| // Add close icon | |
| const closeBtn = document.createElement('span'); | |
| closeBtn.textContent = "×"; | |
| closeBtn.className = "close-preview-btn"; | |
| closeBtn.title = "Remove image"; | |
| closeBtn.onclick = function(e) { | |
| e.stopPropagation(); | |
| // Restore drop zone to original state | |
| dropZone.innerHTML = `<span id="drop-zone-text">Drop image here or <button type="button" id="browse-btn">Browse</button></span> | |
| <input type="file" id="image-input" accept="image/*" style="display: none;" required />`; | |
| imageInput.value = ''; | |
| selectedFile = null; | |
| // Re-attach listeners | |
| document.getElementById('browse-btn').addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| imageInput.click(); | |
| }); | |
| // Re-attach input event | |
| document.getElementById('image-input').addEventListener('change', function (e) { | |
| if (this.files && this.files.length > 0) { | |
| selectedFile = this.files[0]; | |
| handleFile(this.files[0], true); | |
| } | |
| }); | |
| attn.innerHTML = ''; | |
| attn.style.backgroundColor = "#b3b3b3" | |
| }; | |
| if (showInDropZone) { | |
| // Remove previous images | |
| dropZone.innerHTML = ''; | |
| // Create a wrapper for image and close button | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = "preview-wrapper"; | |
| wrapper.style.position = "relative"; | |
| wrapper.appendChild(previewImg); | |
| wrapper.appendChild(closeBtn); | |
| dropZone.appendChild(wrapper); | |
| } else { | |
| preview.innerHTML = ''; | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = "preview-wrapper"; | |
| wrapper.style.position = "relative"; | |
| wrapper.appendChild(previewImg); | |
| wrapper.appendChild(closeBtn); | |
| preview.appendChild(wrapper); | |
| } | |
| URL.revokeObjectURL(url); | |
| }; | |
| img.src = url; | |
| } | |
| // Submit handler | |
| document.getElementById('upload-form').addEventListener('submit', async function (e) { | |
| e.preventDefault(); | |
| const resultDiv = document.getElementById('result'); | |
| resultDiv.textContent = 'Classifying...'; | |
| if (!selectedFile) { | |
| resultDiv.textContent = 'Please select an image.'; | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('file', selectedFile); | |
| await sendToBackend(formData, resultDiv); | |
| }); | |
| async function sendToBackend(formData, resultDiv) { | |
| try { | |
| const response = await fetch('/classify_img', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) throw new Error('Server error'); | |
| const data = await response.json(); | |
| attn.innerHTML = ''; | |
| const attnCanvas = document.createElement('canvas'); | |
| attnCanvas.width = canvasSize; | |
| attnCanvas.height = canvasSize; | |
| const ctx = attnCanvas.getContext('2d'); | |
| const img = new Image(); | |
| console.log("selectedFile: ", selectedFile) | |
| const url = URL.createObjectURL(selectedFile); | |
| img.src = url | |
| img.onload = function () { | |
| drawImageAndGridForAttn(ctx, img, 0, data.attn, data.threshold7, data.threshold8, data.threshold9) | |
| attn.appendChild(attnCanvas); | |
| } | |
| attnCanvas.addEventListener('click', (event) => { | |
| // Get mouse position relative to canvas | |
| const rect = attnCanvas.getBoundingClientRect(); | |
| const x = event.clientX - rect.left; | |
| const y = event.clientY - rect.top; | |
| // Determine which grid cell was clicked | |
| const col = Math.floor(x / cellSize); | |
| const row = Math.floor(y / cellSize); | |
| const index = row*gridSize+col+1 | |
| drawImageAndGridForAttn(ctx, img, index, data.attn, data.threshold7, data.threshold8, data.threshold9) | |
| // Highlight the clicked cell | |
| ctx.globalAlpha = 1.0; | |
| ctx.strokeStyle = '#ea580c'; | |
| ctx.lineWidth = 5; | |
| ctx.strokeRect(col * cellSize, row * cellSize, cellSize, cellSize); | |
| }); | |
| resultDiv.textContent = `Prediction: ${data?.output?.replace(/^"|"$/g, '') || JSON.stringify(data?.output)}`; | |
| } catch (err) { | |
| resultDiv.textContent = 'Error: ' + err.message; | |
| } | |
| } |