// Interactive Segmentation DOM elements const inputCanvas = document.getElementById('inputCanvas'); const segmentedCanvas = document.getElementById('segmentedCanvas'); const imageUpload = document.getElementById('imageUpload'); const clearPointsButton = document.getElementById('clearPoints'); const voidsButton = document.getElementById('voidsButton'); const chipsButton = document.getElementById('chipsButton'); const retrainModelButton = document.getElementById('retrainModelButton'); const etaDisplay = document.getElementById('etaDisplay'); // Automatic Segmentation DOM elements const automaticImageUpload = document.getElementById('automaticImageUpload'); const automaticProcessedImage = document.getElementById('automaticProcessedImage'); const resultsTableBody = document.getElementById('resultsTableBody'); const clearTableButton = document.getElementById('clearTableButton'); const exportTableButton = document.getElementById('exportTableButton'); // Constants for consistent canvas and SAM model dimensions const CANVAS_SIZE = 512; inputCanvas.width = CANVAS_SIZE; inputCanvas.height = CANVAS_SIZE; segmentedCanvas.width = CANVAS_SIZE; segmentedCanvas.height = CANVAS_SIZE; // Interactive segmentation variables let points = { Voids: [], Chips: [] }; let labels = { Voids: [], Chips: [] }; let currentClass = 'Voids'; let imageUrl = ''; let originalImageWidth = 0; let originalImageHeight = 0; let trainingInProgress = false; // Disable right-click menu on canvas inputCanvas.addEventListener('contextmenu', (event) => event.preventDefault()); // Switch between classes voidsButton.addEventListener('click', () => { currentClass = 'Voids'; voidsButton.classList.add('active'); chipsButton.classList.remove('active'); clearAndRestorePoints(); }); chipsButton.addEventListener('click', () => { currentClass = 'Chips'; chipsButton.classList.add('active'); voidsButton.classList.remove('active'); clearAndRestorePoints(); }); // Handle image upload for interactive tool imageUpload.addEventListener('change', async (event) => { const file = event.target.files[0]; const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/upload', { method: 'POST', body: formData }); const data = await response.json(); if (data.error) { console.error('Error uploading image:', data.error); return; } imageUrl = data.image_url; console.log('Uploaded image URL:', imageUrl); const img = new Image(); img.src = imageUrl; img.onload = () => { console.log('Image loaded:', img.width, img.height); originalImageWidth = img.width; originalImageHeight = img.height; resizeAndDrawImage(inputCanvas, img); resizeAndDrawImage(segmentedCanvas, img); }; img.onerror = () => { console.error('Failed to load image from URL:', imageUrl); }; } catch (error) { console.error('Failed to upload image:', error); } }); // Handle input canvas clicks inputCanvas.addEventListener('mousedown', async (event) => { const rect = inputCanvas.getBoundingClientRect(); const x = (event.clientX - rect.left) * (originalImageWidth / CANVAS_SIZE); const y = (event.clientY - rect.top) * (originalImageHeight / CANVAS_SIZE); if (event.button === 2) { points[currentClass].push([x, y]); labels[currentClass].push(0); // Exclude point (red) } else if (event.button === 0) { points[currentClass].push([x, y]); labels[currentClass].push(1); // Include point (green) } drawPoints(); await updateSegmentation(); }); // Clear points for current class clearPointsButton.addEventListener('click', () => { points[currentClass] = []; labels[currentClass] = []; drawPoints(); resetSegmentation(); }); function resizeAndDrawImage(canvas, img) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas // Scale the image to fit within the canvas const scale = Math.min(canvas.width / img.width, canvas.height / img.height); const x = (canvas.width - img.width * scale) / 2; const y = (canvas.height - img.height * scale) / 2; ctx.drawImage(img, x, y, img.width * scale, img.height * scale); } // Draw points on canvases function drawPoints() { [inputCanvas, segmentedCanvas].forEach((canvas) => { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE); const img = new Image(); img.src = imageUrl; img.onload = () => { resizeAndDrawImage(canvas, img); points[currentClass].forEach(([x, y], i) => { const scaledX = x * (CANVAS_SIZE / originalImageWidth); const scaledY = y * (CANVAS_SIZE / originalImageHeight); ctx.beginPath(); ctx.arc(scaledX, scaledY, 5, 0, 2 * Math.PI); ctx.fillStyle = labels[currentClass][i] === 1 ? 'green' : 'red'; ctx.fill(); }); }; img.onerror = () => { console.error('Error loading image for canvas:', img.src); }; }); } async function updateSegmentation() { try { const response = await fetch('/segment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ points: points[currentClass], labels: labels[currentClass], class: currentClass.toLowerCase() }) }); const data = await response.json(); if (data.error) { console.error('Error during segmentation:', data.error); alert(`Segmentation error: ${data.error}`); return; } console.log('Segmentation result:', data); const img = new Image(); img.src = `${data.segmented_url}?t=${new Date().getTime()}`; // Add timestamp to prevent caching img.onload = () => { console.log('Segmented image loaded successfully:', img.src); resizeAndDrawImage(segmentedCanvas, img); // Render the segmented image }; img.onerror = () => { console.error('Failed to load segmented image:', img.src); alert('Failed to load the segmented image.'); }; } catch (error) { console.error('Error updating segmentation:', error); alert('Failed to process segmentation.'); } } // Reset segmented canvas function resetSegmentation() { const ctx = segmentedCanvas.getContext('2d'); ctx.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE); const img = new Image(); img.src = imageUrl; img.onload = () => resizeAndDrawImage(segmentedCanvas, img); } // Handle automatic segmentation automaticImageUpload.addEventListener('change', async (event) => { const file = event.target.files[0]; const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/automatic_segment', { method: 'POST', body: formData }); const data = await response.json(); if (data.error) return console.error('Error during automatic segmentation:', data.error); // Display the processed image const processedImage = document.getElementById('automaticProcessedImage'); processedImage.src = `${data.segmented_url}?t=${new Date().getTime()}`; processedImage.style.display = 'block'; // Optionally append the table data appendRowToTable(data.table_data); } catch (error) { console.error('Failed to process image automatically:', error); } }); function appendRowToTable(tableData) { // Remove duplicates based on the image name and chip number const existingRows = Array.from(resultsTableBody.querySelectorAll('tr')); const existingIdentifiers = existingRows.map(row => { const cells = row.querySelectorAll('td'); return `${cells[0]?.textContent}_${cells[1]?.textContent}`; // Combine Image Name and Chip # }); tableData.chips.forEach((chip, index) => { const uniqueId = `${tableData.image_name}_${chip.chip_number}`; if (existingIdentifiers.includes(uniqueId)) return; // Skip if already present const row = document.createElement('tr'); // Image Name (unchanged for each chip) const imageNameCell = document.createElement('td'); imageNameCell.textContent = tableData.image_name; row.appendChild(imageNameCell); // Chip # (1, 2, etc.) const chipNumberCell = document.createElement('td'); chipNumberCell.textContent = chip.chip_number; row.appendChild(chipNumberCell); // Chip Area const chipAreaCell = document.createElement('td'); chipAreaCell.textContent = chip.chip_area.toFixed(2); row.appendChild(chipAreaCell); // Void % (Total void area / Chip area * 100) const voidPercentageCell = document.createElement('td'); voidPercentageCell.textContent = chip.void_percentage.toFixed(2); row.appendChild(voidPercentageCell); // Max Void % (Largest void area / Chip area * 100) const maxVoidPercentageCell = document.createElement('td'); maxVoidPercentageCell.textContent = chip.max_void_percentage.toFixed(2); row.appendChild(maxVoidPercentageCell); resultsTableBody.appendChild(row); }); } // Handle automatic segmentation automaticImageUpload.addEventListener('change', async (event) => { const file = event.target.files[0]; const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/automatic_segment', { method: 'POST', body: formData }); const data = await response.json(); if (data.error) return console.error('Error during automatic segmentation:', data.error); automaticProcessedImage.src = `${data.segmented_url}?t=${new Date().getTime()}`; automaticProcessedImage.style.display = 'block'; appendRowToTable(data.table_data); // Append new data to the table } catch (error) { console.error('Failed to process image automatically:', error); } }); // Clear table clearTableButton.addEventListener('click', () => { resultsTableBody.innerHTML = ''; }); // Export table to CSV exportTableButton.addEventListener('click', () => { const rows = Array.from(resultsTableBody.querySelectorAll('tr')); const csvContent = [ ['Image Name', 'Chip #', 'Chip Area', 'Void %', 'Max Void %'], ...rows.map(row => Array.from(row.children).map(cell => cell.textContent) ), ] .map(row => row.join(',')) .join('\n'); const blob = new Blob([csvContent], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'segmentation_results.csv'; link.click(); URL.revokeObjectURL(url); }); saveBothButton.addEventListener('click', async () => { const imageName = imageUrl.split('/').pop(); // Extract the image name from the URL if (!imageName) { alert("No image to save."); return; } const confirmSave = confirm("Are you sure you want to save both voids and chips segmentations?"); if (!confirmSave) return; try { const response = await fetch('/save_both', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_name: imageName }) }); const result = await response.json(); if (response.ok) { alert(result.message); } else { alert("Failed to save segmentations."); } } catch (error) { console.error("Error saving segmentations:", error); alert("Failed to save segmentations."); } }); // Update the "historyButton" click listener to populate the list correctly document.getElementById('historyButton').addEventListener('click', async () => { try { const response = await fetch('/get_history'); // Fetch the saved history const result = await response.json(); if (response.ok) { const historyList = document.getElementById('historyList'); historyList.innerHTML = ''; // Clear the list if (result.images.length === 0) { historyList.innerHTML = '