arihant3704's picture
Upload 14 files
ec0daf5 verified
document.addEventListener('DOMContentLoaded', () => {
// State management
let currentFile = null;
let isDrawing = false;
let startX, startY;
let roi = { x1: 0, y1: 0, x2: 100, y2: 100 };
let previewImage = new Image();
// Elements
const modelDropZone = document.getElementById('model-drop-zone');
const modelInput = document.getElementById('model-input');
const modelStatus = document.getElementById('model-status');
const statusText = document.getElementById('status-text');
const statusIcon = modelStatus.querySelector('i');
const mediaDropZone = document.getElementById('media-drop-zone');
const mediaInput = document.getElementById('media-input');
const previewSection = document.getElementById('preview-section');
const roiCanvas = document.getElementById('roi-canvas');
const ctx = roiCanvas.getContext('2d');
const thresholdInput = document.getElementById('threshold-input');
const confMaxInput = document.getElementById('conf-max-input');
const confRangeVal = document.getElementById('conf-range-val');
const roiX1 = document.getElementById('roi-x1');
const roiY1 = document.getElementById('roi-y1');
const roiX2 = document.getElementById('roi-x2');
const roiY2 = document.getElementById('roi-y2');
const resetRoiBtn = document.getElementById('reset-roi-btn');
const progressCard = document.getElementById('progress-card');
const loading = document.getElementById('loading');
const videoProgressContainer = document.getElementById('video-progress-container');
const videoProgressBar = document.getElementById('video-progress-bar');
const videoStatusMsg = document.getElementById('video-status-msg');
const videoPercentage = document.getElementById('video-percentage');
const analyzeBtn = document.getElementById('analyze-btn');
const resultSection = document.getElementById('result-section');
const resultImage = document.getElementById('result-image');
const resultCount = document.getElementById('result-count');
const downloadBtn = document.getElementById('download-btn');
const videoResultSection = document.getElementById('video-result-section');
const resultVideo = document.getElementById('result-video');
const videoDownloadBtn = document.getElementById('video-download-btn');
// Drag and Drop Setup
[modelDropZone, mediaDropZone].forEach(zone => {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
zone.addEventListener(eventName, e => {
e.preventDefault();
e.stopPropagation();
});
});
['dragenter', 'dragover'].forEach(eventName => {
zone.addEventListener(eventName, () => zone.classList.add('dragover'));
});
['dragleave', 'drop'].forEach(eventName => {
zone.addEventListener(eventName, () => zone.classList.remove('dragover'));
});
});
// Clicks
modelDropZone.addEventListener('click', () => modelInput.click());
mediaDropZone.addEventListener('click', () => mediaInput.click());
// File Handlers
modelInput.addEventListener('change', e => handleModelUpload(e.target.files[0]));
modelDropZone.addEventListener('drop', e => handleModelUpload(e.dataTransfer.files[0]));
mediaInput.addEventListener('change', e => handleMediaSelection(e.target.files[0]));
mediaDropZone.addEventListener('drop', e => handleMediaSelection(e.dataTransfer.files[0]));
// Settings
const updateConfLabel = () => {
const min = Math.round(thresholdInput.value * 100);
const max = Math.round(confMaxInput.value * 100);
confRangeVal.innerText = `${min}% - ${max}%`;
};
thresholdInput.addEventListener('input', updateConfLabel);
confMaxInput.addEventListener('input', updateConfLabel);
[roiX1, roiY1, roiX2, roiY2].forEach(input => {
input.addEventListener('change', updateROIFromInputs);
});
resetRoiBtn.addEventListener('click', () => {
roi = { x1: 0, y1: 0, x2: 100, y2: 100 };
updateInputsFromROI();
drawROI();
});
analyzeBtn.addEventListener('click', startInference);
// --- Functions ---
async function handleModelUpload(file) {
if (!file || !file.name.endsWith('.pt')) {
showToast('Please upload a valid YOLO .pt model.', 'error');
return;
}
const formData = new FormData();
formData.append('file', file);
statusText.innerText = 'Uploading model...';
modelStatus.classList.remove('loaded');
statusIcon.className = 'fas fa-spinner fa-spin';
try {
const resp = await fetch('/upload-model', { method: 'POST', body: formData });
const data = await resp.json();
if (data.status === 'success') {
statusText.innerText = `Model: ${file.name}`;
modelStatus.classList.add('loaded');
statusIcon.className = 'fas fa-check-circle';
showToast('Model loaded successfully!', 'success');
} else {
throw new Error(data.detail);
}
} catch (err) {
statusText.innerText = 'Error loading model';
statusIcon.className = 'fas fa-exclamation-circle';
showToast(err.message, 'error');
}
}
async function handleMediaSelection(file) {
if (!file) return;
currentFile = file;
// Reset state
resultSection.classList.add('hidden');
videoResultSection.classList.add('hidden');
progressCard.classList.add('hidden');
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = e => {
previewImage.onload = () => initCanvas();
previewImage.src = e.target.result;
};
reader.readAsDataURL(file);
} else if (file.type.startsWith('video/')) {
extractVideoFrame(file);
} else {
showToast('Unsupported file type.', 'error');
}
}
function extractVideoFrame(file) {
const video = document.createElement('video');
video.preload = 'metadata';
video.src = URL.createObjectURL(file);
video.onloadedmetadata = () => {
video.currentTime = 0.1; // Seek a bit in to avoid black frames
};
video.onseeked = () => {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = video.videoWidth;
tempCanvas.height = video.videoHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(video, 0, 0);
previewImage.onload = () => initCanvas();
previewImage.src = tempCanvas.toDataURL('image/jpeg');
URL.revokeObjectURL(video.src);
};
}
function initCanvas() {
previewSection.classList.remove('hidden');
previewSection.scrollIntoView({ behavior: 'smooth' });
// Scale canvas to fit container but keep aspect ratio
const containerWidth = roiCanvas.parentElement.clientWidth;
const scale = containerWidth / previewImage.width;
roiCanvas.width = previewImage.width * scale;
roiCanvas.height = previewImage.height * scale;
drawROI();
}
function drawROI() {
ctx.clearRect(0, 0, roiCanvas.width, roiCanvas.height);
ctx.drawImage(previewImage, 0, 0, roiCanvas.width, roiCanvas.height);
// Darken outside
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
const x1 = (roi.x1 / 100) * roiCanvas.width;
const y1 = (roi.y1 / 100) * roiCanvas.height;
const x2 = (roi.x2 / 100) * roiCanvas.width;
const y2 = (roi.y2 / 100) * roiCanvas.height;
const w = x2 - x1;
const h = y2 - y1;
// Draw overlay path with a "hole" for the ROI
ctx.beginPath();
ctx.rect(0, 0, roiCanvas.width, roiCanvas.height);
ctx.rect(x1, y1, w, h);
ctx.fill('evenodd');
// Draw border
ctx.strokeStyle = '#f59e0b';
ctx.lineWidth = 3;
ctx.setLineDash([5, 5]);
ctx.strokeRect(x1, y1, w, h);
// Optional: corner handles design look
ctx.fillStyle = '#f59e0b';
ctx.fillRect(x1-4, y1-4, 8, 8);
ctx.fillRect(x2-4, y1-4, 8, 8);
ctx.fillRect(x1-4, y2-4, 8, 8);
ctx.fillRect(x2-4, y2-4, 8, 8);
}
// Canvas Events
roiCanvas.addEventListener('mousedown', e => {
isDrawing = true;
const rect = roiCanvas.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
roi.x1 = (startX / roiCanvas.width) * 100;
roi.y1 = (startY / roiCanvas.height) * 100;
});
roiCanvas.addEventListener('mousemove', e => {
if (!isDrawing) return;
const rect = roiCanvas.getBoundingClientRect();
const curX = e.clientX - rect.left;
const curY = e.clientY - rect.top;
roi.x2 = (curX / roiCanvas.width) * 100;
roi.y2 = (curY / roiCanvas.height) * 100;
updateInputsFromROI();
drawROI();
});
roiCanvas.addEventListener('mouseup', () => {
isDrawing = false;
// Normalize coordinates (ensure x1 < x2, y1 < y2)
if (roi.x1 > roi.x2) [roi.x1, roi.x2] = [roi.x2, roi.x1];
if (roi.y1 > roi.y2) [roi.y1, roi.y2] = [roi.y2, roi.y1];
updateInputsFromROI();
drawROI();
});
function updateInputsFromROI() {
roiX1.value = Math.round(roi.x1);
roiY1.value = Math.round(roi.y1);
roiX2.value = Math.round(roi.x2);
roiY2.value = Math.round(roi.y2);
}
function updateROIFromInputs() {
roi.x1 = parseInt(roiX1.value);
roi.y1 = parseInt(roiY1.value);
roi.x2 = parseInt(roiX2.value);
roi.y2 = parseInt(roiY2.value);
drawROI();
}
async function startInference() {
if (!currentFile) return;
progressCard.classList.remove('hidden');
progressCard.scrollIntoView({ behavior: 'smooth' });
const isVideo = currentFile.type.startsWith('video/');
const formData = new FormData();
formData.append('file', currentFile);
formData.append('conf_min', thresholdInput.value);
formData.append('conf_max', confMaxInput.value);
formData.append('roi', JSON.stringify(roi));
if (isVideo) {
handleVideoInference(formData);
} else {
handleImageInference(formData);
}
}
async function handleImageInference(formData) {
loading.classList.remove('hidden');
videoProgressContainer.classList.add('hidden');
try {
const resp = await fetch('/inference', { method: 'POST', body: formData });
const data = await resp.json();
if (data.status === 'success') {
resultImage.src = data.image;
resultCount.innerText = `${data.count} Detections`;
resultSection.classList.remove('hidden');
resultSection.scrollIntoView({ behavior: 'smooth' });
} else {
throw new Error(data.detail);
}
} catch (err) {
showToast(err.message, 'error');
} finally {
progressCard.classList.add('hidden');
}
}
async function handleVideoInference(formData) {
loading.classList.add('hidden');
videoProgressContainer.classList.remove('hidden');
videoProgressBar.style.width = '0%';
videoPercentage.innerText = '0%';
videoStatusMsg.innerText = 'Uploading video...';
try {
const resp = await fetch('/inference-video', { method: 'POST', body: formData });
const data = await resp.json();
if (data.status === 'success') {
videoStatusMsg.innerText = 'Processing frames...';
pollVideoProgress(data.task_id);
} else {
throw new Error(data.detail);
}
} catch (err) {
showToast(err.message, 'error');
progressCard.classList.add('hidden');
}
}
function pollVideoProgress(taskId) {
const interval = setInterval(async () => {
try {
const resp = await fetch(`/video-progress/${taskId}`);
const data = await resp.json();
if (data.status === 'processing') {
videoProgressBar.style.width = `${data.progress}%`;
videoPercentage.innerText = `${data.progress}%`;
} else if (data.status === 'completed') {
clearInterval(interval);
videoProgressBar.style.width = '100%';
videoPercentage.innerText = '100%';
videoStatusMsg.innerText = 'Processing complete!';
showVideoResult(taskId);
} else if (data.status === 'error') {
clearInterval(interval);
showToast(data.message, 'error');
progressCard.classList.add('hidden');
}
} catch (err) {
console.error(err);
}
}, 1000);
}
function showVideoResult(taskId) {
const url = `/video-result/${taskId}`;
resultVideo.src = url;
videoDownloadBtn.href = url;
videoResultSection.classList.remove('hidden');
videoResultSection.scrollIntoView({ behavior: 'smooth' });
progressCard.classList.add('hidden');
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
Object.assign(toast.style, {
position: 'fixed', bottom: '20px', right: '20px', padding: '1rem 1.5rem',
borderRadius: '10px', color: 'white', zIndex: '1000',
background: type === 'error' ? '#ef4444' : '#10b981',
boxShadow: '0 4px 15px rgba(0,0,0,0.3)', animation: 'slideIn 0.3s ease forwards'
});
toast.innerText = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease forwards';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
});