|
|
|
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'); |
|
|
|
|
|
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'); |
|
|
|
|
|
const CANVAS_SIZE = 512; |
|
inputCanvas.width = CANVAS_SIZE; |
|
inputCanvas.height = CANVAS_SIZE; |
|
segmentedCanvas.width = CANVAS_SIZE; |
|
segmentedCanvas.height = CANVAS_SIZE; |
|
|
|
|
|
let points = { Voids: [], Chips: [] }; |
|
let labels = { Voids: [], Chips: [] }; |
|
let currentClass = 'Voids'; |
|
let imageUrl = ''; |
|
let originalImageWidth = 0; |
|
let originalImageHeight = 0; |
|
let trainingInProgress = false; |
|
|
|
|
|
inputCanvas.addEventListener('contextmenu', (event) => event.preventDefault()); |
|
|
|
|
|
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(); |
|
}); |
|
|
|
|
|
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); |
|
} |
|
}); |
|
|
|
|
|
|
|
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); |
|
} else if (event.button === 0) { |
|
points[currentClass].push([x, y]); |
|
labels[currentClass].push(1); |
|
} |
|
|
|
drawPoints(); |
|
await updateSegmentation(); |
|
}); |
|
|
|
|
|
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); |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
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()}`; |
|
img.onload = () => { |
|
console.log('Segmented image loaded successfully:', img.src); |
|
resizeAndDrawImage(segmentedCanvas, img); |
|
}; |
|
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.'); |
|
} |
|
} |
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
const processedImage = document.getElementById('automaticProcessedImage'); |
|
processedImage.src = `${data.segmented_url}?t=${new Date().getTime()}`; |
|
processedImage.style.display = 'block'; |
|
|
|
|
|
appendRowToTable(data.table_data); |
|
} catch (error) { |
|
console.error('Failed to process image automatically:', error); |
|
} |
|
}); |
|
|
|
function appendRowToTable(tableData) { |
|
|
|
const existingRows = Array.from(resultsTableBody.querySelectorAll('tr')); |
|
const existingIdentifiers = existingRows.map(row => { |
|
const cells = row.querySelectorAll('td'); |
|
return `${cells[0]?.textContent}_${cells[1]?.textContent}`; |
|
}); |
|
|
|
tableData.chips.forEach((chip, index) => { |
|
const uniqueId = `${tableData.image_name}_${chip.chip_number}`; |
|
if (existingIdentifiers.includes(uniqueId)) return; |
|
|
|
const row = document.createElement('tr'); |
|
|
|
|
|
const imageNameCell = document.createElement('td'); |
|
imageNameCell.textContent = tableData.image_name; |
|
row.appendChild(imageNameCell); |
|
|
|
|
|
const chipNumberCell = document.createElement('td'); |
|
chipNumberCell.textContent = chip.chip_number; |
|
row.appendChild(chipNumberCell); |
|
|
|
|
|
const chipAreaCell = document.createElement('td'); |
|
chipAreaCell.textContent = chip.chip_area.toFixed(2); |
|
row.appendChild(chipAreaCell); |
|
|
|
|
|
const voidPercentageCell = document.createElement('td'); |
|
voidPercentageCell.textContent = chip.void_percentage.toFixed(2); |
|
row.appendChild(voidPercentageCell); |
|
|
|
|
|
const maxVoidPercentageCell = document.createElement('td'); |
|
maxVoidPercentageCell.textContent = chip.max_void_percentage.toFixed(2); |
|
row.appendChild(maxVoidPercentageCell); |
|
|
|
resultsTableBody.appendChild(row); |
|
}); |
|
} |
|
|
|
|
|
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); |
|
} catch (error) { |
|
console.error('Failed to process image automatically:', error); |
|
} |
|
}); |
|
|
|
|
|
clearTableButton.addEventListener('click', () => { |
|
resultsTableBody.innerHTML = ''; |
|
}); |
|
|
|
|
|
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(); |
|
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."); |
|
} |
|
}); |
|
|
|
document.getElementById('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 = ''; |
|
|
|
if (result.images.length === 0) { |
|
historyList.innerHTML = '<li class="list-group-item">No images found in history.</li>'; |
|
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."); |
|
} |
|
}); |
|
|
|
|
|
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(); |
|
} 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 = ''; |
|
|
|
if (result.images.length === 0) { |
|
historyList.innerHTML = '<li class="list-group-item">No images found in history.</li>'; |
|
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."); |
|
} |
|
}); |
|
|
|
|
|
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(); |
|
} else { |
|
alert("Failed to delete image."); |
|
} |
|
} catch (error) { |
|
console.error("Error deleting image:", error); |
|
alert("Failed to delete image."); |
|
} |
|
} |
|
|
|
|
|
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) { |
|
|
|
trainingInProgress = true; |
|
retrainModelButton.textContent = "Cancel Training"; |
|
retrainModelButton.classList.replace("btn-primary", "btn-danger"); |
|
startTrainingMonitor(); |
|
} 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 { |
|
|
|
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) { |
|
|
|
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') { |
|
|
|
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') { |
|
|
|
cancelButton.style.display = 'none'; |
|
retrainButton.style.display = 'inline-block'; |
|
retrainButton.textContent = 'Retrain Model'; |
|
retrainButton.classList.replace('btn-danger', 'btn-primary'); |
|
etaDisplay.textContent = ''; |
|
|
|
|
|
if (result.status === 'idle') { |
|
clearInterval(monitorInterval); |
|
} |
|
} |
|
} catch (error) { |
|
console.error("Error fetching training status:", error); |
|
} |
|
}, 5000); |
|
} |
|
|
|
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); |
|
|
|
const historyList = document.getElementById('historyList'); |
|
if (historyList) historyList.innerHTML = '<li class="list-group-item">No images found in history.</li>'; |
|
} else { |
|
alert("Failed to clear history."); |
|
} |
|
} catch (error) { |
|
console.error("Error clearing history:", error); |
|
alert("Failed to clear history."); |
|
} |
|
}); |
|
|
|
|
|
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"; |
|
} |
|
|
|
|
|
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"; |
|
} |
|
|
|
|
|
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."); |
|
} |
|
}); |
|
|
|
socket.on('training_status', (data) => { |
|
const trainingButton = document.getElementById('retrainModelButton'); |
|
const cancelButton = document.getElementById('cancelTrainingButton'); |
|
|
|
if (data.status === 'completed') { |
|
|
|
trainingButton.style.display = 'inline-block'; |
|
cancelButton.style.display = 'none'; |
|
|
|
|
|
alert(data.message || "Training completed successfully!"); |
|
} else if (data.status === 'failed') { |
|
|
|
trainingButton.style.display = 'inline-block'; |
|
cancelButton.style.display = 'none'; |
|
|
|
|
|
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') { |
|
|
|
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'; |
|
retrainButton.textContent = "Retrain Model"; |
|
retrainButton.classList.replace("btn-danger", "btn-primary"); |
|
cancelButton.style.display = 'none'; |
|
|
|
|
|
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'; |
|
|
|
|
|
alert(data.message); |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('historyModal').addEventListener('hidden.bs.modal', function () { |
|
document.body.classList.remove('modal-open'); |
|
const backdrop = document.querySelector('.modal-backdrop'); |
|
if (backdrop) { |
|
backdrop.remove(); |
|
} |
|
}); |
|
|