// 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 = '
  • No images found in history.
  • '; return; } result.images.forEach(image => { const listItem = document.createElement('li'); listItem.className = 'list-group-item'; const imageName = document.createElement('span'); imageName.textContent = image; const deleteButton = document.createElement('button'); deleteButton.className = 'btn btn-danger btn-sm'; deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', async () => { if (confirm(`Are you sure you want to delete ${image}?`)) { await deleteHistoryItem(image, listItem); } }); listItem.appendChild(imageName); listItem.appendChild(deleteButton); historyList.appendChild(listItem); }); new bootstrap.Modal(document.getElementById('historyModal')).show(); } else { alert("Failed to fetch history."); } } catch (error) { console.error("Error fetching history:", error); alert("Failed to fetch history."); } }); // Function to delete history item async function deleteHistoryItem(imageName, listItem) { try { const response = await fetch('/delete_history_item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_name: imageName }) }); const result = await response.json(); if (response.ok) { alert(result.message); listItem.remove(); // Remove the item from the list } else { alert("Failed to delete image."); } } catch (error) { console.error("Error deleting image:", error); alert("Failed to delete image."); } } historyButton.addEventListener('click', async () => { try { const response = await fetch('/get_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 = '
  • No images found in history.
  • '; return; } result.images.forEach(image => { const listItem = document.createElement('li'); listItem.className = 'list-group-item d-flex justify-content-between align-items-center'; listItem.textContent = image; const deleteButton = document.createElement('button'); deleteButton.className = 'btn btn-danger btn-sm'; deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', async () => { if (confirm(`Are you sure you want to delete ${image}?`)) { await deleteHistoryItem(image, listItem); } }); listItem.appendChild(deleteButton); historyList.appendChild(listItem); }); new bootstrap.Modal(document.getElementById('historyModal')).show(); } else { alert("Failed to fetch history."); } } catch (error) { console.error("Error fetching history:", error); alert("Failed to fetch history."); } }); // Function to delete history item async function deleteHistoryItem(imageName, listItem) { try { const response = await fetch('/delete_history_item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_name: imageName }) }); const result = await response.json(); if (response.ok) { alert(result.message); listItem.remove(); // Remove the item from the list } else { alert("Failed to delete image."); } } catch (error) { console.error("Error deleting image:", error); alert("Failed to delete image."); } } // Handle Retrain Model button click retrainModelButton.addEventListener('click', async () => { if (!trainingInProgress) { const confirmRetrain = confirm("Are you sure you want to retrain the model?"); if (!confirmRetrain) return; try { const response = await fetch('/retrain_model', { method: 'POST' }); const result = await response.json(); if (response.ok) { // Update button to "Cancel Training" trainingInProgress = true; retrainModelButton.textContent = "Cancel Training"; retrainModelButton.classList.replace("btn-primary", "btn-danger"); startTrainingMonitor(); // Start monitoring the training status } else { alert(result.error || "Failed to start retraining."); } } catch (error) { console.error("Error starting training:", error); alert("An error occurred while starting the training process."); } } else { // Handle cancel training const confirmCancel = confirm("Are you sure you want to cancel the training?"); if (!confirmCancel) return; try { const response = await fetch('/cancel_training', { method: 'POST' }); const result = await response.json(); if (response.ok) { // Reset button to "Retrain Model" trainingInProgress = false; retrainModelButton.textContent = "Retrain Model"; retrainModelButton.classList.replace("btn-danger", "btn-primary"); alert(result.message || "Training canceled successfully."); } else { alert(result.error || "Failed to cancel training."); } } catch (error) { console.error("Error canceling training:", error); alert("An error occurred while canceling the training process."); } } }); function startTrainingMonitor() { const monitorInterval = setInterval(async () => { try { const response = await fetch('/training_status'); const result = await response.json(); const retrainButton = document.getElementById('retrainModelButton'); const cancelButton = document.getElementById('cancelTrainingButton'); const etaDisplay = document.getElementById('etaDisplay'); if (result.status === 'running') { // Show training progress retrainButton.style.display = 'none'; cancelButton.style.display = 'inline-block'; etaDisplay.textContent = `Estimated Time Left: ${result.eta || "Calculating..."}`; } else if (result.status === 'idle' || result.status === 'cancelled') { // Revert button to "Retrain Model" (blue) cancelButton.style.display = 'none'; retrainButton.style.display = 'inline-block'; retrainButton.textContent = 'Retrain Model'; retrainButton.classList.replace('btn-danger', 'btn-primary'); etaDisplay.textContent = ''; // Stop monitoring if training is idle if (result.status === 'idle') { clearInterval(monitorInterval); } } } catch (error) { console.error("Error fetching training status:", error); } }, 5000); // Poll every 5 seconds } function resetTrainingUI() { trainingInProgress = false; retrainModelButton.textContent = "Retrain Model"; retrainModelButton.classList.replace("btn-danger", "btn-primary"); etaDisplay.textContent = ""; } clearHistoryButton.addEventListener('click', async () => { const confirmClear = confirm("Are you sure you want to clear the history? This will delete all images and masks."); if (!confirmClear) return; try { const response = await fetch('/clear_history', { method: 'POST' }); const result = await response.json(); if (response.ok) { alert(result.message); // Optionally update UI to reflect the cleared history const historyList = document.getElementById('historyList'); if (historyList) historyList.innerHTML = '
  • No images found in history.
  • '; } else { alert("Failed to clear history."); } } catch (error) { console.error("Error clearing history:", error); alert("Failed to clear history."); } }); // Toggle training progress display function showTrainingProgress(message = "Initializing...", timeLeft = "Calculating...") { document.getElementById("trainingProgress").style.display = "block"; document.getElementById("progressMessage").textContent = message; document.getElementById("estimatedTimeLeft").textContent = `Estimated Time Left: ${timeLeft}`; } function hideTrainingProgress() { document.getElementById("trainingProgress").style.display = "none"; } // Toggle Cancel Training Button function showCancelTrainingButton() { document.getElementById("cancelTrainingButton").style.display = "inline-block"; document.getElementById("retrainModelButton").style.display = "none"; } function hideCancelTrainingButton() { document.getElementById("cancelTrainingButton").style.display = "none"; document.getElementById("retrainModelButton").style.display = "inline-block"; } // Add event listener to Cancel Training button document.getElementById("cancelTrainingButton").addEventListener("click", async () => { const confirmCancel = confirm("Are you sure you want to cancel training?"); if (!confirmCancel) return; try { const response = await fetch("/cancel_training", { method: "POST" }); const result = await response.json(); if (result.message) { alert(result.message); hideTrainingProgress(); hideCancelTrainingButton(); } } catch (error) { console.error("Error canceling training:", error); alert("Failed to cancel training."); } }); // Handle training status updates socket.on('training_status', (data) => { const trainingButton = document.getElementById('retrainModelButton'); const cancelButton = document.getElementById('cancelTrainingButton'); if (data.status === 'completed') { // Update UI: change "Cancel Training" to "Retrain Model" trainingButton.style.display = 'inline-block'; cancelButton.style.display = 'none'; // Show a popup or notification for training completion alert(data.message || "Training completed successfully!"); } else if (data.status === 'failed') { // Update UI: change "Cancel Training" to "Retrain Model" trainingButton.style.display = 'inline-block'; cancelButton.style.display = 'none'; // Show a popup or notification for training failure alert(data.message || "Training failed. Please try again."); } }); socket.on('button_update', (data) => { const retrainButton = document.getElementById('retrainModelButton'); const cancelButton = document.getElementById('cancelTrainingButton'); if (data.action === 'retrain') { // Update to "Retrain Model" button retrainButton.style.display = 'inline-block'; retrainButton.textContent = 'Retrain Model'; retrainButton.classList.replace('btn-danger', 'btn-primary'); cancelButton.style.display = 'none'; } }); function updateButtonToRetrainModel() { const button = document.getElementById('retrainModelButton'); button.innerText = "Retrain Model"; button.classList.replace("btn-danger", "btn-primary"); button.disabled = false; } socket.on('training_status', (data) => { const retrainButton = document.getElementById('retrainModelButton'); const cancelButton = document.getElementById('cancelTrainingButton'); if (data.status === 'completed') { retrainButton.style.display = 'inline-block'; // Show retrain button retrainButton.textContent = "Retrain Model"; retrainButton.classList.replace("btn-danger", "btn-primary"); cancelButton.style.display = 'none'; // Hide cancel button // Notify user alert(data.message); } else if (data.status === 'cancelled') { retrainButton.style.display = 'inline-block'; retrainButton.textContent = "Retrain Model"; retrainButton.classList.replace("btn-danger", "btn-primary"); cancelButton.style.display = 'none'; // Notify user alert(data.message); } }); // Ensure the modal backdrop is properly removed when the modal is closed document.getElementById('historyModal').addEventListener('hidden.bs.modal', function () { document.body.classList.remove('modal-open'); const backdrop = document.querySelector('.modal-backdrop'); if (backdrop) { backdrop.remove(); } });