project / static /js /app.js
tyriaa's picture
Initial commit
8078d22
raw
history blame
26 kB
// 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 = '<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.");
}
});
// 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 = '<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.");
}
});
// 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 = '<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.");
}
});
// 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();
}
});