new_car / index.html
junaid17's picture
Initial commit: DamageLens project
c5377b5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Car Damage AI</title>
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
:root {
--bg-dark: #09090b;
--bg-card: #18181b;
--text-primary: #e2e8f0;
--text-secondary: #a1a1aa;
--accent: #00c6ff;
--accent-hover: #0072ff;
--glass: rgba(255, 255, 255, 0.03);
--card-border: #27272a;
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', sans-serif; }
body {
background-color: var(--bg-dark);
color: var(--text-primary);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
background-image: radial-gradient(circle at top right, rgba(0, 198, 255, 0.05) 0%, transparent 40%);
}
.container {
width: 100%;
max-width: 850px;
background: var(--bg-card);
border-radius: 20px;
padding: 35px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
animation: slideUpFade 0.6s ease-out forwards;
border: 1px solid var(--card-border);
}
@keyframes slideUpFade { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
/* Shimmering Main Title */
.shimmer-text {
text-align: center;
font-size: 2.5rem;
font-weight: 800;
background: linear-gradient(90deg, #e2e8f0 0%, #ffffff 25%, #00c6ff 50%, #e2e8f0 75%, #e2e8f0 100%);
background-size: 200% auto;
color: transparent;
-webkit-background-clip: text;
background-clip: text;
animation: shimmer 4s linear infinite;
margin-bottom: 0.2rem;
}
@keyframes shimmer { 0% { background-position: -200% center; } 100% { background-position: 200% center; } }
.subtitle { text-align: center; color: var(--text-secondary); font-size: 1rem; margin-bottom: 25px; }
/* Warning Box */
.warning-box {
background: rgba(0, 198, 255, 0.1);
border-left: 4px solid var(--accent);
color: var(--text-primary);
padding: 12px 15px;
border-radius: 8px;
margin-bottom: 25px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 12px;
}
/* Controls Section */
.controls-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 25px;
}
.file-wrapper {
position: relative; height: 160px; border: 2px dashed #444; border-radius: 16px;
display: flex; justify-content: center; align-items: center; cursor: pointer;
transition: all 0.3s ease; background: var(--glass); overflow: hidden;
}
.file-wrapper:hover { border-color: var(--accent); background: rgba(0, 198, 255, 0.05); }
.file-wrapper input { position: absolute; width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 2; }
.settings-card {
background: rgba(0,0,0,0.2);
border-radius: 16px;
padding: 20px;
border: 1px solid var(--card-border);
display: flex;
flex-direction: column;
justify-content: center;
}
select {
width: 100%; background: #27272a; border: 1px solid #3f3f46; padding: 14px;
border-radius: 12px; color: white; outline: none; margin-top: 10px; font-size: 1rem;
}
select:focus { border-color: var(--accent); }
/* Preview Area & Animations */
.image-area {
width: 100%; height: 350px; background: #09090b; border-radius: 16px;
margin-bottom: 25px; display: none; justify-content: center; align-items: center;
overflow: hidden; position: relative; border: 1px solid var(--card-border);
}
.image-area img { max-width: 100%; max-height: 100%; object-fit: contain; z-index: 1;}
/* Scanner Animation */
.scan-line {
position: absolute; top: -10%; left: 0; width: 100%; height: 5px;
background: var(--accent); box-shadow: 0 0 15px var(--accent), 0 0 30px var(--accent);
z-index: 5; opacity: 0.8; display: none; animation: scanMove 2s ease-in-out infinite; filter: blur(1px);
}
@keyframes scanMove { 0% { top: -10%; opacity: 0.5; } 50% { opacity: 1; } 100% { top: 110%; opacity: 0.5; } }
/* Loader Overlay */
.loader-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.65); backdrop-filter: blur(4px);
display: none; flex-direction: column; justify-content: center; align-items: center; z-index: 10;
}
.spinner {
width: 50px; height: 50px; border: 4px solid rgba(0, 198, 255, 0.2);
border-top: 4px solid var(--accent); border-radius: 50%;
animation: spin 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite; margin-bottom: 15px;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
/* Buttons */
.btn {
width: 100%; padding: 16px; background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
color: white; border: none; border-radius: 12px; cursor: pointer; font-weight: 700; font-size: 1rem;
transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0, 114, 255, 0.3);
}
.btn:hover:not(:disabled) { transform: scale(1.02); box-shadow: 0 8px 25px rgba(0, 198, 255, 0.5); }
.btn:disabled { background: #444; color: #888; box-shadow: none; transform: none; cursor: not-allowed;}
/* Results Tabs */
.results-section { display: none; margin-top: 30px; animation: slideUpFade 0.5s ease-out; }
.tabs { display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid var(--card-border); padding-bottom: 10px; overflow-x: auto; }
.tab {
padding: 10px 20px; cursor: pointer; border-radius: 8px; color: var(--text-secondary);
font-weight: 600; transition: all 0.3s ease; white-space: nowrap;
}
.tab.active { background: rgba(0, 198, 255, 0.1); color: var(--accent); }
.tab-content { display: none; }
.tab-content.active { display: block; animation: slideUpFade 0.4s ease-out; }
/* Progress Bar */
.progress-wrapper { background: #27272a; border-radius: 20px; overflow: hidden; height: 12px; margin: 10px 0 20px 0; box-shadow: inset 0 2px 4px rgba(0,0,0,0.5); }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-hover)); border-radius: 20px; width: 0%; transition: width 1.5s cubic-bezier(0.22, 1, 0.36, 1); }
/* Final Prediction Text */
.big-text { font-size: 2.5rem; font-weight: 800; background: -webkit-linear-gradient(45deg, #00c6ff, #0072ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 5px; }
/* Images Grid (Attention Maps) */
.img-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
.img-card { background: rgba(0,0,0,0.3); border: 1px solid var(--card-border); border-radius: 12px; padding: 10px; text-align: center; }
.img-card img { width: 100%; border-radius: 8px; margin-top: 10px; }
/* YOLO Grid */
.yolo-grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 20px; }
.log-box { background: rgba(0,0,0,0.3); border: 1px solid var(--card-border); border-radius: 12px; padding: 20px; height: 100%; }
.detection-item { background: #27272a; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid var(--accent); box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
@media (max-width: 768px) {
.controls-grid, .img-grid, .yolo-grid { grid-template-columns: 1fr; }
.shimmer-text { font-size: 2rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="shimmer-text">🚗 Car Damage AI</div>
<div class="subtitle">Fusion Intelligence: ResNet + ConvNeXt Fusion + YOLO</div>
<div class="warning-box">
<span style="font-size: 1.2rem;">⏱️</span>
<span><b>Note:</b> The first analysis may take up to 3-4 mins while models warm up. Subsequent requests are faster!</span>
</div>
<div class="controls-grid">
<div class="file-wrapper">
<input type="file" id="fileInput" accept="image/jpeg, image/png, image/jpg">
<div style="text-align: center;">
<p style="font-size: 2.5rem; margin-bottom: 5px;">📷</p>
<p style="color:#a1a1aa; font-weight: 500;">Tap or Drag & Drop Vehicle Image</p>
</div>
</div>
<div class="settings-card">
<h3 style="font-size: 1.1rem; margin-bottom: 5px;">⚙️ Analysis Settings</h3>
<p style="font-size: 0.85rem; color: var(--text-secondary);">Select the neural network pipeline.</p>
<select id="engineMode">
<option value="fusion">Fusion (Recommended)</option>
<option value="resnet">ResNet Only</option>
</select>
</div>
</div>
<div class="image-area" id="previewBox">
<img id="displayImage" src="" alt="Car Image">
<div class="scan-line" id="scanLine"></div>
<div class="loader-overlay" id="loader">
<div class="spinner"></div>
<p style="color:white; font-weight:600; letter-spacing: 1px; margin-bottom: 5px;">🧠 ANALYZING...</p>
<p id="loaderStatusText" style="color:#00c6ff; font-size:0.9rem;">Extracting features...</p>
</div>
</div>
<button class="btn" id="analyzeBtn" onclick="analyze()">🚀 Run AI Analysis</button>
<div class="results-section" id="resultsSection">
<div class="tabs">
<div class="tab active" onclick="switchResultTab('tab-pred')">📊 Prediction</div>
<div class="tab" onclick="switchResultTab('tab-attention')">👀 Attention Maps</div>
<div class="tab" onclick="switchResultTab('tab-yolo')">🎯 Localization</div>
</div>
<div id="tab-pred" class="tab-content active">
<div class="settings-card">
<div id="finalPredText" class="big-text">--</div>
<div style="font-weight: 600; margin-top: 5px;" id="confText">Confidence Score: 0%</div>
<div class="progress-wrapper">
<div class="progress-fill" id="confBar"></div>
</div>
<h3 style="margin: 15px 0 5px 0; font-size: 1.1rem;">Probability Distribution</h3>
<div id="plotlyChart" style="width:100%; height:300px;"></div>
</div>
</div>
<div id="tab-attention" class="tab-content">
<div class="img-grid">
<div class="img-card">
<div style="font-weight:600; color:#e2e8f0;">Original</div>
<img id="camOriginal" src="" alt="Original">
</div>
<div class="img-card">
<div style="font-weight:600; color:#e2e8f0;">ResNet Focus</div>
<img id="camResnet" src="" alt="ResNet">
</div>
<div class="img-card">
<div style="font-weight:600; color:#e2e8f0;">Fusion Focus</div>
<img id="camFusion" src="" alt="Fusion">
</div>
</div>
</div>
<div id="tab-yolo" class="tab-content">
<div class="yolo-grid">
<div class="settings-card">
<h3 style="margin-bottom: 10px;">Bounding Boxes</h3>
<img id="yoloImage" src="" alt="YOLO Output" style="width: 100%; border-radius: 8px;">
</div>
<div class="log-box">
<h3 style="margin-bottom: 15px;">Detection Log</h3>
<div id="yoloLogContainer">
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const API_URL = "http://127.0.0.1:8000";
let currentFile = null;
// DOM Elements
const fileInput = document.getElementById('fileInput');
const displayImage = document.getElementById('displayImage');
const previewBox = document.getElementById('previewBox');
const resultsSection = document.getElementById('resultsSection');
const loader = document.getElementById('loader');
const loaderStatusText = document.getElementById('loaderStatusText');
const scanLine = document.getElementById('scanLine');
const analyzeBtn = document.getElementById('analyzeBtn');
fileInput.addEventListener('change', e => {
if(e.target.files[0]) {
currentFile = e.target.files[0];
const reader = new FileReader();
reader.onload = x => {
displayImage.src = x.target.result;
previewBox.style.display = 'flex';
resultsSection.style.display = 'none'; // Hide old results
};
reader.readAsDataURL(currentFile);
}
});
// --- BUG FIX IS HERE ---
function switchResultTab(tabId) {
// 1. Remove active state from all tabs and panels
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// 2. Find the tab button that corresponds to this panel and make it active
const tabButton = document.querySelector(`.tab[onclick*="${tabId}"]`);
if(tabButton) {
tabButton.classList.add('active');
}
// 3. Make the specific panel active
document.getElementById(tabId).classList.add('active');
// 4. Resize Plotly chart if switching back to its tab to prevent layout squash
if(tabId === 'tab-pred') {
window.dispatchEvent(new Event('resize'));
}
}
// Plotly Chart Helper
function drawChart(dataObj, title) {
const labels = Object.keys(dataObj);
const values = Object.values(dataObj);
const trace = {
x: labels,
y: values,
type: 'bar',
marker: { color: '#00c6ff', line: { color: '#0072ff', width: 1.5 } },
opacity: 0.85
};
const layout = {
title: title || '',
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
font: { family: 'Inter', color: '#a1a1aa' },
margin: { l: 40, r: 10, t: 30, b: 40 },
xaxis: { title: 'Classes' },
yaxis: { title: 'Probability', range: [0, 1] }
};
Plotly.newPlot('plotlyChart', [trace], layout, {displayModeBar: false, responsive: true});
}
async function analyze() {
if(!currentFile) return alert("Please upload an image first.");
const engineMode = document.getElementById('engineMode').value; // fusion or resnet
// UI Prep
loader.style.display = 'flex';
scanLine.style.display = 'block';
analyzeBtn.disabled = true;
analyzeBtn.innerText = "Processing...";
resultsSection.style.display = 'none';
const formData = new FormData();
formData.append('image', currentFile); // Using 'image' for pred endpoint
try {
// --- STEP 1: Feature Extraction (Main Prediction) ---
loaderStatusText.innerText = "Extracting features...";
const predRes = await fetch(`${API_URL}/predict/${engineMode}`, { method: 'POST', body: formData });
if(!predRes.ok) throw new Error("Prediction API Failed");
const predData = await predRes.json();
// --- STEP 2: GradCAM ---
loaderStatusText.innerText = "Generating attention maps...";
// Note: your python code sent 'file' instead of 'image' for GradCAM and YOLO endpoints
const fileFormData = new FormData();
fileFormData.append('file', currentFile);
const camRes = await fetch(`${API_URL}/predict`, { method: 'POST', body: fileFormData });
if(!camRes.ok) throw new Error("GradCAM API Failed");
const camData = await camRes.json();
// --- STEP 3: YOLO ---
loaderStatusText.innerText = "Running YOLO object detection...";
const yoloRes = await fetch(`${API_URL}/predict/yolo`, { method: 'POST', body: fileFormData });
if(!yoloRes.ok) throw new Error("YOLO API Failed");
const yoloData = await yoloRes.json();
// === POPULATE UI ===
// 1. Prediction Tab
let confVal = 0;
// Determine highest class and confidence from the returned probability map
let highestClass = Object.keys(predData).reduce((a, b) => predData[a] > predData[b] ? a : b);
confVal = (predData[highestClass] * 100).toFixed(2);
document.getElementById('finalPredText').innerText = highestClass;
document.getElementById('confText').innerText = `Confidence Score: ${confVal}%`;
drawChart(predData, `${engineMode === 'fusion' ? 'Fusion' : 'ResNet'} Output`);
// Animate Progress Bar
setTimeout(() => { document.getElementById('confBar').style.width = confVal + '%'; }, 100);
// 2. Attention Maps Tab
document.getElementById('camOriginal').src = `${API_URL}${camData.original_image}`;
document.getElementById('camResnet').src = `${API_URL}${camData.resnet_viz}`;
document.getElementById('camFusion').src = `${API_URL}${camData.fusion_viz}`;
// 3. YOLO Tab
document.getElementById('yoloImage').src = `${API_URL}${yoloData.yolo_image}`;
const logContainer = document.getElementById('yoloLogContainer');
if (yoloData.total_detections === 0) {
logContainer.innerHTML = `<div style="color: #a1a1aa; padding: 10px;">🟢 No specific damage bounding boxes detected.</div>`;
} else {
let logHTML = `<div style="color: #ffcc00; margin-bottom: 10px; font-weight:600;">🔴 Found ${yoloData.total_detections} damage region(s).</div>`;
yoloData.detections.forEach((det, idx) => {
let detConf = (det.confidence * 100).toFixed(1);
logHTML += `
<div class="detection-item">
<b style="color: #e2e8f0;">Region ${idx + 1}</b><br>
<span style="color: #a1a1aa; font-size: 0.9em;">Confidence: ${detConf}%</span>
</div>
`;
});
logContainer.innerHTML = logHTML;
}
// Show Results
resultsSection.style.display = 'block';
switchResultTab('tab-pred'); // Force the first tab to be active without crashing
} catch(e) {
alert(`Error connecting to AI server. It might be waking up or offline. Details: ${e.message}`);
console.error(e);
} finally {
loader.style.display = 'none';
scanLine.style.display = 'none';
analyzeBtn.disabled = false;
analyzeBtn.innerText = "🚀 Run AI Analysis";
}
}
</script>
</body>
</html>