delete me
This commit is contained in:
@@ -36,10 +36,17 @@ let isWebcamActive = false;
|
||||
let webcamStream = null;
|
||||
let simulatedStreamInterval = null;
|
||||
let currentFrameBase64 = null;
|
||||
let activeCameraDeviceId = null; // stores selected camera device ID
|
||||
let isDetecting = false;
|
||||
let autoDetectEnabled = true;
|
||||
let trainingPollInterval = null;
|
||||
|
||||
// Learn/Anlern-Modus State
|
||||
let learnBackgroundFrame = null; // stores ImageData or Image object for canvas subtraction
|
||||
let learnSelectedClass = null; // selected article key for training
|
||||
let learnSessionImages = []; // list of filenames in the current session
|
||||
let isSerialCapturing = false;
|
||||
|
||||
// Scanner Emulator State
|
||||
let autoScanEnabled = true;
|
||||
let stableClass = null;
|
||||
@@ -56,6 +63,9 @@ let shoppingCart = [];
|
||||
const videoEl = document.getElementById("webcam");
|
||||
const canvasEl = document.getElementById("camera-canvas");
|
||||
const ctx = canvasEl.getContext("2d");
|
||||
const learnCanvasEl = document.getElementById("learn-camera-canvas");
|
||||
const learnCtx = learnCanvasEl ? learnCanvasEl.getContext("2d") : null;
|
||||
|
||||
const toggleCameraBtn = document.getElementById("toggle-camera-btn");
|
||||
const triggerScaleBtn = document.getElementById("trigger-scale-btn");
|
||||
const autoDetectToggle = document.getElementById("auto-detect-toggle");
|
||||
@@ -86,19 +96,121 @@ function switchTab(tabName) {
|
||||
if (tabName === 'articles') {
|
||||
loadArticlesDatabase();
|
||||
}
|
||||
|
||||
if (tabName === 'learn') {
|
||||
checkBackgroundCalibration();
|
||||
loadLearnArticles();
|
||||
updateLearnBadge();
|
||||
}
|
||||
}
|
||||
|
||||
// --- CAMERA & DETECTION LOGIC ---
|
||||
async function startWebcam() {
|
||||
async function initCameraSelection() {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||
|
||||
const selectCheckout = document.getElementById("camera-select");
|
||||
const selectLearn = document.getElementById("learn-camera-select");
|
||||
|
||||
if (selectCheckout && selectLearn) {
|
||||
selectCheckout.innerHTML = "";
|
||||
selectLearn.innerHTML = "";
|
||||
|
||||
if (videoDevices.length === 0) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = "";
|
||||
opt.textContent = "Keine Kamera gefunden";
|
||||
selectCheckout.appendChild(opt);
|
||||
selectLearn.appendChild(opt.cloneNode(true));
|
||||
return;
|
||||
}
|
||||
|
||||
videoDevices.forEach((device, idx) => {
|
||||
const label = device.label || `Kamera ${idx + 1}`;
|
||||
const opt = document.createElement("option");
|
||||
opt.value = device.deviceId;
|
||||
opt.textContent = label;
|
||||
|
||||
selectCheckout.appendChild(opt);
|
||||
selectLearn.appendChild(opt.cloneNode(true));
|
||||
});
|
||||
|
||||
// Sync values to current active camera
|
||||
if (activeCameraDeviceId) {
|
||||
selectCheckout.value = activeCameraDeviceId;
|
||||
selectLearn.value = activeCameraDeviceId;
|
||||
} else {
|
||||
activeCameraDeviceId = videoDevices[0].deviceId;
|
||||
selectCheckout.value = activeCameraDeviceId;
|
||||
selectLearn.value = activeCameraDeviceId;
|
||||
}
|
||||
|
||||
// Hook up change handlers
|
||||
selectCheckout.onchange = (e) => switchCameraDevice(e.target.value);
|
||||
selectLearn.onchange = (e) => switchCameraDevice(e.target.value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Fehler beim Abrufen der Kameras:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function switchCameraDevice(deviceId) {
|
||||
activeCameraDeviceId = deviceId;
|
||||
|
||||
// Sync dropdown values across tabs
|
||||
const selectCheckout = document.getElementById("camera-select");
|
||||
const selectLearn = document.getElementById("learn-camera-select");
|
||||
if (selectCheckout) selectCheckout.value = deviceId;
|
||||
if (selectLearn) selectLearn.value = deviceId;
|
||||
|
||||
if (isWebcamActive) {
|
||||
// Stop current tracks and restart webcam stream with the new device
|
||||
if (webcamStream) {
|
||||
webcamStream.getTracks().forEach(track => track.stop());
|
||||
webcamStream = null;
|
||||
}
|
||||
await startWebcam(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebcam(deviceId = null) {
|
||||
try {
|
||||
const targetDeviceId = deviceId || activeCameraDeviceId;
|
||||
const videoConstraints = {
|
||||
width: 640,
|
||||
height: 480
|
||||
};
|
||||
|
||||
if (targetDeviceId) {
|
||||
videoConstraints.deviceId = { exact: targetDeviceId };
|
||||
} else {
|
||||
videoConstraints.facingMode = "environment";
|
||||
}
|
||||
|
||||
webcamStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { width: 640, height: 480, facingMode: "environment" }
|
||||
video: videoConstraints
|
||||
});
|
||||
|
||||
videoEl.srcObject = webcamStream;
|
||||
videoEl.style.display = "none";
|
||||
isWebcamActive = true;
|
||||
|
||||
// Show camera selector dropdowns
|
||||
const selectCheckout = document.getElementById("camera-select");
|
||||
const selectLearn = document.getElementById("learn-camera-select");
|
||||
if (selectCheckout) selectCheckout.style.display = "inline-block";
|
||||
if (selectLearn) selectLearn.style.display = "inline-block";
|
||||
|
||||
cameraTypeBadge.textContent = "LIVE-WEBCAM";
|
||||
cameraTypeBadge.style.background = "var(--color-primary)";
|
||||
|
||||
const learnStreamBadge = document.getElementById("learn-camera-type-badge");
|
||||
if (learnStreamBadge) {
|
||||
learnStreamBadge.textContent = "LIVE-WEBCAM";
|
||||
learnStreamBadge.style.background = "var(--color-primary)";
|
||||
}
|
||||
|
||||
toggleCameraBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" stroke="currentColor" fill="none" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/></svg>
|
||||
Kamera deaktivieren
|
||||
@@ -106,6 +218,9 @@ async function startWebcam() {
|
||||
document.getElementById("stream-status").textContent = "LIVE";
|
||||
document.getElementById("stream-status").classList.add("live");
|
||||
|
||||
// Initialize options list
|
||||
await initCameraSelection();
|
||||
|
||||
// Stop simulated stream if active
|
||||
if (simulatedStreamInterval) {
|
||||
clearInterval(simulatedStreamInterval);
|
||||
@@ -128,25 +243,42 @@ function stopWebcam() {
|
||||
videoEl.srcObject = null;
|
||||
videoEl.style.display = "none";
|
||||
isWebcamActive = false;
|
||||
|
||||
// Hide camera selectors when webcam is inactive
|
||||
const selectCheckout = document.getElementById("camera-select");
|
||||
const selectLearn = document.getElementById("learn-camera-select");
|
||||
if (selectCheckout) selectCheckout.style.display = "none";
|
||||
if (selectLearn) selectLearn.style.display = "none";
|
||||
|
||||
toggleCameraBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||||
Webcam aktivieren
|
||||
`;
|
||||
startSimulatedStream();
|
||||
}
|
||||
|
||||
function processWebcamFrame() {
|
||||
if (!isWebcamActive) return;
|
||||
|
||||
// Draw current video frame to canvas
|
||||
ctx.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
|
||||
|
||||
// Get frame as base64
|
||||
currentFrameBase64 = canvasEl.toDataURL("image/jpeg", 0.7);
|
||||
|
||||
// Auto-detect if enabled
|
||||
if (autoDetectEnabled && !isDetecting) {
|
||||
runDetection();
|
||||
if (activeTab === 'checkout') {
|
||||
// Draw current video frame to canvas
|
||||
ctx.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
|
||||
// Get frame as base64
|
||||
currentFrameBase64 = canvasEl.toDataURL("image/jpeg", 0.7);
|
||||
|
||||
// Auto-detect if enabled
|
||||
if (autoDetectEnabled && !isDetecting) {
|
||||
runDetection();
|
||||
}
|
||||
} else if (activeTab === 'learn') {
|
||||
if (learnCanvasEl && learnCtx) {
|
||||
// Draw current video frame to learn canvas
|
||||
learnCtx.drawImage(videoEl, 0, 0, learnCanvasEl.width, learnCanvasEl.height);
|
||||
// Get frame as base64
|
||||
currentFrameBase64 = learnCanvasEl.toDataURL("image/jpeg", 0.7);
|
||||
|
||||
// Draw dynamic green bounding box
|
||||
drawLearnFrameGreenBox();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -155,10 +287,22 @@ function processWebcamFrame() {
|
||||
}
|
||||
|
||||
function startSimulatedStream() {
|
||||
cameraTypeBadge.textContent = "SIMULATIONS-MODUS";
|
||||
cameraTypeBadge.style.background = "var(--color-bg-dark)";
|
||||
document.getElementById("stream-status").textContent = "Simuliert";
|
||||
document.getElementById("stream-status").classList.add("live");
|
||||
const streamBadge = document.getElementById("camera-type-badge");
|
||||
const learnStreamBadge = document.getElementById("learn-camera-type-badge");
|
||||
if (streamBadge) {
|
||||
streamBadge.textContent = "SIMULATIONS-MODUS";
|
||||
streamBadge.style.background = "var(--color-bg-dark)";
|
||||
}
|
||||
if (learnStreamBadge) {
|
||||
learnStreamBadge.textContent = "SIMULATIONS-MODUS";
|
||||
learnStreamBadge.style.background = "var(--color-bg-dark)";
|
||||
}
|
||||
|
||||
const streamStatus = document.getElementById("stream-status");
|
||||
if (streamStatus) {
|
||||
streamStatus.textContent = "Simuliert";
|
||||
streamStatus.classList.add("live");
|
||||
}
|
||||
|
||||
const loadSimulatedFrame = async () => {
|
||||
try {
|
||||
@@ -167,11 +311,21 @@ function startSimulatedStream() {
|
||||
if (data.status === "success" && data.image) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, canvasEl.width, canvasEl.height);
|
||||
currentFrameBase64 = data.image;
|
||||
|
||||
if (autoDetectEnabled && !isDetecting) {
|
||||
runDetection();
|
||||
if (activeTab === 'checkout') {
|
||||
ctx.drawImage(img, 0, 0, canvasEl.width, canvasEl.height);
|
||||
currentFrameBase64 = data.image;
|
||||
|
||||
if (autoDetectEnabled && !isDetecting) {
|
||||
runDetection();
|
||||
}
|
||||
} else if (activeTab === 'learn') {
|
||||
if (learnCanvasEl && learnCtx) {
|
||||
learnCtx.drawImage(img, 0, 0, learnCanvasEl.width, learnCanvasEl.height);
|
||||
currentFrameBase64 = data.image;
|
||||
|
||||
// Draw dynamic green bounding box
|
||||
drawLearnFrameGreenBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
img.src = data.image;
|
||||
@@ -248,14 +402,14 @@ function drawDetections(predictions) {
|
||||
function updateQuickButtons(predictions) {
|
||||
quickSelectButtons.innerHTML = "";
|
||||
|
||||
// Filter predictions with confidence > 0.10
|
||||
const candidates = predictions.filter(p => p.confidence > 0.10);
|
||||
// Filter predictions with confidence > 0.85
|
||||
const candidates = predictions.filter(p => p.confidence > 0.85);
|
||||
|
||||
if (candidates.length === 0) {
|
||||
quickSelectButtons.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<p>Keine Objekte erkannt</p>
|
||||
<span>Confidence Schwellenwert unter 10%</span>
|
||||
<span>Confidence Schwellenwert unter 85%</span>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -446,6 +600,23 @@ async function startTraining() {
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel active model training
|
||||
async function cancelTraining() {
|
||||
const cancelBtn = document.getElementById("cancel-training-btn");
|
||||
if (cancelBtn) cancelBtn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/cancel_training", { method: "POST" });
|
||||
const data = await response.json();
|
||||
alert(data.message);
|
||||
} catch (err) {
|
||||
console.error("Failed to cancel training:", err);
|
||||
alert("Fehler beim Abbrechen des Trainings.");
|
||||
} finally {
|
||||
if (cancelBtn) cancelBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll training API
|
||||
function startTrainingPolling() {
|
||||
if (trainingPollInterval) return;
|
||||
@@ -497,6 +668,19 @@ function updateTrainingStatusUI(data) {
|
||||
statusBox.classList.add("active-pulse");
|
||||
}
|
||||
|
||||
// Toggle start and cancel buttons based on status
|
||||
const startBtn = document.getElementById("start-training-btn");
|
||||
const cancelBtn = document.getElementById("cancel-training-btn");
|
||||
if (startBtn && cancelBtn) {
|
||||
if (data.status === "training") {
|
||||
startBtn.style.display = "none";
|
||||
cancelBtn.style.display = "inline-block";
|
||||
} else {
|
||||
startBtn.style.display = "inline-block";
|
||||
cancelBtn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Plot Chart if loss history is available
|
||||
if (data.train_loss && data.train_loss.length > 0) {
|
||||
updateChart(data.train_loss, data.val_loss || []);
|
||||
@@ -900,7 +1084,7 @@ async function processStabilityAndScan(predictions) {
|
||||
const progressWrapper = document.getElementById("stability-progress-wrapper");
|
||||
const progressFill = document.getElementById("stability-progress-fill");
|
||||
|
||||
const STABLE_CONF_THRESHOLD = 0.75;
|
||||
const STABLE_CONF_THRESHOLD = 0.85;
|
||||
|
||||
if (topPrediction && topPrediction.confidence >= STABLE_CONF_THRESHOLD) {
|
||||
const detectedClass = topPrediction.class;
|
||||
@@ -986,6 +1170,518 @@ async function triggerManualScan() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- LEARN ARTICLE TAB (ANLERN-MODUS) LOGIC ---
|
||||
let learnCurrentBBox = null; // stores {xmin, ymin, xmax, ymax}
|
||||
|
||||
function computeBBoxFromDiff(currData, bgData, width, height, threshold = 25) {
|
||||
let minX = width, minY = height, maxX = 0, maxY = 0;
|
||||
let changedCount = 0;
|
||||
|
||||
for (let y = 0; y < height; y += 4) { // sample every 4th pixel for speed
|
||||
for (let x = 0; x < width; x += 4) {
|
||||
const idx = (y * width + x) * 4;
|
||||
const rDiff = Math.abs(currData[idx] - bgData[idx]);
|
||||
const gDiff = Math.abs(currData[idx+1] - bgData[idx+1]);
|
||||
const bDiff = Math.abs(currData[idx+2] - bgData[idx+2]);
|
||||
|
||||
if (rDiff + gDiff + bDiff > threshold * 3) {
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (y > maxY) maxY = y;
|
||||
changedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If too few pixels changed, it's probably noise or empty
|
||||
if (changedCount < 100) return null;
|
||||
|
||||
// Add padding
|
||||
const padding = 15;
|
||||
minX = Math.max(0, minX - padding);
|
||||
minY = Math.max(0, minY - padding);
|
||||
maxX = Math.min(width, maxX + padding);
|
||||
maxY = Math.min(height, maxY + padding);
|
||||
|
||||
return { xmin: minX, ymin: minY, xmax: maxX, ymax: maxY };
|
||||
}
|
||||
|
||||
function drawLearnFrameGreenBox() {
|
||||
if (!learnCanvasEl || !learnCtx || !currentFrameBase64) return;
|
||||
|
||||
if (learnBackgroundFrame) {
|
||||
try {
|
||||
const width = learnCanvasEl.width;
|
||||
const height = learnCanvasEl.height;
|
||||
|
||||
// Get current frame image data
|
||||
const currImgData = learnCtx.getImageData(0, 0, width, height);
|
||||
|
||||
// Calculate bbox
|
||||
const bbox = computeBBoxFromDiff(currImgData.data, learnBackgroundFrame.data, width, height);
|
||||
learnCurrentBBox = bbox;
|
||||
|
||||
if (bbox) {
|
||||
// Draw green bounding box outline
|
||||
learnCtx.lineWidth = 3;
|
||||
learnCtx.strokeStyle = "#10b981"; // Green
|
||||
learnCtx.strokeRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin);
|
||||
|
||||
// Draw label box background
|
||||
learnCtx.fillStyle = "rgba(16, 185, 129, 0.85)";
|
||||
const labelText = "Ware erkannt (Auto-Crop)";
|
||||
learnCtx.font = "bold 12px sans-serif";
|
||||
const textWidth = learnCtx.measureText(labelText).width;
|
||||
learnCtx.fillRect(bbox.xmin - 1, bbox.ymin - 22, textWidth + 12, 22);
|
||||
|
||||
// Label text
|
||||
learnCtx.fillStyle = "#ffffff";
|
||||
learnCtx.fillText(labelText, bbox.xmin + 5, bbox.ymin - 6);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error drawing green box:", e);
|
||||
}
|
||||
} else {
|
||||
learnCurrentBBox = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function calibrateBackground() {
|
||||
if (!currentFrameBase64) {
|
||||
alert("Warte auf Kamerabild...");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/calibrate_background", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ image: currentFrameBase64 })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
// Save background frame locally as ImageData for real-time diffing
|
||||
const width = learnCanvasEl.width;
|
||||
const height = learnCanvasEl.height;
|
||||
learnBackgroundFrame = learnCtx.getImageData(0, 0, width, height);
|
||||
|
||||
// Update UI indicators
|
||||
const statusIndicator = document.getElementById("learn-calibration-indicator");
|
||||
if (statusIndicator) {
|
||||
statusIndicator.textContent = "Kalibriert";
|
||||
statusIndicator.style.color = "var(--color-success)";
|
||||
statusIndicator.classList.add("live");
|
||||
}
|
||||
const checkbox = document.getElementById("learn-bg-ok-indicator");
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
|
||||
alert(data.message);
|
||||
} else {
|
||||
alert("Kalibrierung fehlgeschlagen: " + data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Calibration error:", err);
|
||||
alert("Serverfehler bei Kalibrierung.");
|
||||
}
|
||||
}
|
||||
|
||||
async function checkBackgroundCalibration() {
|
||||
try {
|
||||
const response = await fetch("/api/has_background");
|
||||
const data = await response.json();
|
||||
const statusIndicator = document.getElementById("learn-calibration-indicator");
|
||||
const checkbox = document.getElementById("learn-bg-ok-indicator");
|
||||
|
||||
if (data.has_background) {
|
||||
if (statusIndicator) {
|
||||
statusIndicator.textContent = "Kalibriert";
|
||||
statusIndicator.style.color = "var(--color-success)";
|
||||
statusIndicator.classList.add("live");
|
||||
}
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
|
||||
// Load background image from backend and draw it on a hidden canvas to get ImageData
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.onload = () => {
|
||||
const hiddenCanvas = document.createElement("canvas");
|
||||
hiddenCanvas.width = learnCanvasEl.width;
|
||||
hiddenCanvas.height = learnCanvasEl.height;
|
||||
const hiddenCtx = hiddenCanvas.getContext("2d");
|
||||
hiddenCtx.drawImage(img, 0, 0, hiddenCanvas.width, hiddenCanvas.height);
|
||||
learnBackgroundFrame = hiddenCtx.getImageData(0, 0, hiddenCanvas.width, hiddenCanvas.height);
|
||||
};
|
||||
img.src = "/api/background_image?t=" + Date.now(); // Cache busting
|
||||
} else {
|
||||
learnBackgroundFrame = null;
|
||||
if (statusIndicator) {
|
||||
statusIndicator.textContent = "Nicht kalibriert";
|
||||
statusIndicator.style.color = "var(--text-muted)";
|
||||
statusIndicator.classList.remove("live");
|
||||
}
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check background calibration:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetBackgroundCalibration() {
|
||||
if (!confirm("Hintergrundkalibrierung wirklich zurücksetzen?")) return;
|
||||
try {
|
||||
const response = await fetch("/api/reset_background", { method: "POST" });
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
learnBackgroundFrame = null;
|
||||
const statusIndicator = document.getElementById("learn-calibration-indicator");
|
||||
if (statusIndicator) {
|
||||
statusIndicator.textContent = "Nicht kalibriert";
|
||||
statusIndicator.style.color = "var(--text-muted)";
|
||||
statusIndicator.classList.remove("live");
|
||||
}
|
||||
const checkbox = document.getElementById("learn-bg-ok-indicator");
|
||||
if (checkbox) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
alert(data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Reset calibration error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function loadLearnArticles() {
|
||||
const listContainer = document.getElementById("learn-articles-list");
|
||||
if (!listContainer) return;
|
||||
listContainer.innerHTML = "";
|
||||
|
||||
// Check if database loaded, if not, wait
|
||||
if (Object.keys(articlesDatabase).length === 0) {
|
||||
setTimeout(loadLearnArticles, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(articlesDatabase).forEach(key => {
|
||||
const art = articlesDatabase[key];
|
||||
const item = document.createElement("div");
|
||||
item.className = "learn-article-item";
|
||||
item.id = `learn-item-${key}`;
|
||||
if (learnSelectedClass === key) {
|
||||
item.classList.add("selected");
|
||||
}
|
||||
item.onclick = () => selectLearnArticle(key);
|
||||
|
||||
item.innerHTML = `
|
||||
<span>${art.name}</span>
|
||||
<span class="item-plu">EAN: ${art.ean || 'Keine'}</span>
|
||||
`;
|
||||
listContainer.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function filterLearnArticles() {
|
||||
const query = document.getElementById("learn-search-input").value.toLowerCase().trim();
|
||||
const items = document.querySelectorAll(".learn-article-item");
|
||||
|
||||
items.forEach(item => {
|
||||
const text = item.textContent.toLowerCase();
|
||||
if (text.includes(query)) {
|
||||
item.style.display = "flex";
|
||||
} else {
|
||||
item.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectLearnArticle(classKey) {
|
||||
learnSelectedClass = classKey;
|
||||
document.querySelectorAll(".learn-article-item").forEach(el => el.classList.remove("selected"));
|
||||
|
||||
const selectedEl = document.getElementById(`learn-item-${classKey}`);
|
||||
if (selectedEl) {
|
||||
selectedEl.classList.add("selected");
|
||||
}
|
||||
|
||||
const display = document.getElementById("learn-selected-article-display");
|
||||
if (display) {
|
||||
const name = articlesDatabase[classKey]?.name || classKey;
|
||||
display.textContent = name;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLearnBadge() {
|
||||
fetch("/api/capture_count")
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const badge = document.getElementById("learn-session-count-badge");
|
||||
if (badge) badge.textContent = `Eigene Bilder Gesamt: ${data.count}`;
|
||||
const displayCheckout = document.getElementById("capture-count-display");
|
||||
if (displayCheckout) displayCheckout.textContent = `Eigene Bilder: ${data.count}`;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
|
||||
async function startSerialCapture() {
|
||||
if (!learnSelectedClass) {
|
||||
alert("Bitte wählen Sie zuerst einen Artikel aus der Liste aus.");
|
||||
return;
|
||||
}
|
||||
if (!learnBackgroundFrame) {
|
||||
alert("Bitte führen Sie zuerst die Hintergrund-Kalibrierung durch (Hintergrund speichern).");
|
||||
return;
|
||||
}
|
||||
if (isSerialCapturing) return;
|
||||
|
||||
isSerialCapturing = true;
|
||||
|
||||
// Clear session gallery UI and list
|
||||
const galleryGrid = document.getElementById("learn-gallery-grid");
|
||||
galleryGrid.innerHTML = "";
|
||||
learnSessionImages = [];
|
||||
|
||||
const startBtn = document.getElementById("learn-start-capture-btn");
|
||||
startBtn.disabled = true;
|
||||
startBtn.innerHTML = `<span>Aufnahme läuft...</span>`;
|
||||
|
||||
const progressText = document.getElementById("learn-progress-text");
|
||||
const progressFill = document.getElementById("learn-progress-fill");
|
||||
|
||||
let count = 0;
|
||||
const total = 10;
|
||||
|
||||
const captureNext = async () => {
|
||||
// Sound feedback
|
||||
playScannerBeep();
|
||||
|
||||
// Capture frame
|
||||
const frameData = learnCanvasEl.toDataURL("image/jpeg", 0.95);
|
||||
const currentBbox = learnCurrentBBox ? { ...learnCurrentBBox } : null;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/capture_training_image", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
image: frameData,
|
||||
class_name: learnSelectedClass,
|
||||
bbox: currentBbox
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.status === "success" && data.filename) {
|
||||
learnSessionImages.push(data.filename);
|
||||
addPhotoToGallery(data.filename);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Serial capture step failed:", err);
|
||||
}
|
||||
|
||||
count++;
|
||||
if (progressText) progressText.textContent = `${count} / ${total} Bilder`;
|
||||
if (progressFill) progressFill.style.width = `${(count / total) * 100}%`;
|
||||
|
||||
if (count >= total) {
|
||||
clearInterval(captureInterval);
|
||||
isSerialCapturing = false;
|
||||
startBtn.disabled = false;
|
||||
startBtn.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>
|
||||
START (SERIENAUFNAHME)
|
||||
`;
|
||||
updateLearnBadge();
|
||||
alert("Serienaufnahme abgeschlossen! Sie können fehlerhafte Bilder unten in der Galerie löschen.");
|
||||
}
|
||||
};
|
||||
|
||||
// Start interval
|
||||
await captureNext(); // First capture immediately
|
||||
const captureInterval = setInterval(captureNext, 1000);
|
||||
}
|
||||
|
||||
function addPhotoToGallery(filename) {
|
||||
const galleryGrid = document.getElementById("learn-gallery-grid");
|
||||
const emptyState = document.getElementById("learn-gallery-empty");
|
||||
if (emptyState) emptyState.remove();
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "gallery-item-wrapper";
|
||||
wrapper.id = `gallery-item-${filename.replace(".", "_")}`;
|
||||
|
||||
wrapper.innerHTML = `
|
||||
<img class="gallery-item-img" src="/api/captured_images/${filename}" alt="${filename}">
|
||||
<button class="gallery-item-delete-btn" onclick="deleteSessionImage('${filename}')" title="Bild löschen">X</button>
|
||||
`;
|
||||
|
||||
galleryGrid.appendChild(wrapper);
|
||||
}
|
||||
|
||||
async function deleteSessionImage(filename) {
|
||||
if (!confirm("Dieses Bild aus der Session löschen?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/delete_captured_image", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ filename: filename })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.status === "success") {
|
||||
// Remove from UI
|
||||
const wrapper = document.getElementById(`gallery-item-${filename.replace(".", "_")}`);
|
||||
if (wrapper) wrapper.remove();
|
||||
|
||||
// Remove from list
|
||||
learnSessionImages = learnSessionImages.filter(f => f !== filename);
|
||||
|
||||
// If empty, show empty state
|
||||
const galleryGrid = document.getElementById("learn-gallery-grid");
|
||||
if (galleryGrid && galleryGrid.children.length === 0) {
|
||||
galleryGrid.innerHTML = `
|
||||
<div class="empty-state" id="learn-gallery-empty" style="grid-column: 1 / -1; padding: 40px 0;">
|
||||
<p>Noch keine Aufnahmen in dieser Session</p>
|
||||
<span>Wähle ein Produkt und starte die Serienaufnahme</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
updateLearnBadge();
|
||||
console.log(data.message);
|
||||
} else {
|
||||
alert("Löschen fehlgeschlagen: " + data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Delete image error:", err);
|
||||
alert("Serverfehler beim Löschen des Bildes.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- RESET ALL CAPTURED TRAINING DATA ---
|
||||
async function resetAllCapturedData() {
|
||||
const confirmed = confirm(
|
||||
"⚠️ Alle selbst erfassten Trainingsbilder löschen?\n\n" +
|
||||
"Dies entfernt alle user_capture_* Bilder und Labels sowie die Hintergrundkalibrierung.\n" +
|
||||
"Das vortrainierte Basis-Modell bleibt erhalten.\n\n" +
|
||||
"Dieser Vorgang kann NICHT rückgängig gemacht werden!"
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
const resetBtnLearn = document.getElementById("reset-all-captures-btn");
|
||||
const resetBtnDash = document.getElementById("dashboard-reset-btn");
|
||||
if (resetBtnLearn) { resetBtnLearn.disabled = true; resetBtnLearn.textContent = "Wird gelöscht..."; }
|
||||
if (resetBtnDash) { resetBtnDash.disabled = true; resetBtnDash.textContent = "Wird gelöscht..."; }
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/reset_dataset", { method: "POST" });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === "success") {
|
||||
// 1. Clear gallery UI
|
||||
const galleryGrid = document.getElementById("learn-gallery-grid");
|
||||
if (galleryGrid) {
|
||||
galleryGrid.innerHTML = `
|
||||
<div class="empty-state" id="learn-gallery-empty" style="grid-column: 1 / -1; padding: 40px 0;">
|
||||
<p>Noch keine Aufnahmen in dieser Session</p>
|
||||
<span>Wähle ein Produkt und starte die Serienaufnahme</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
learnSessionImages = [];
|
||||
|
||||
// 2. Reset background calibration state
|
||||
learnBackgroundFrame = null;
|
||||
learnCurrentBBox = null;
|
||||
const statusIndicator = document.getElementById("learn-calibration-indicator");
|
||||
if (statusIndicator) {
|
||||
statusIndicator.textContent = "Nicht kalibriert";
|
||||
statusIndicator.style.color = "var(--text-muted)";
|
||||
statusIndicator.classList.remove("live");
|
||||
}
|
||||
const checkbox = document.getElementById("learn-bg-ok-indicator");
|
||||
if (checkbox) checkbox.checked = false;
|
||||
|
||||
// 3. Update count badges
|
||||
const badge = document.getElementById("learn-session-count-badge");
|
||||
if (badge) badge.textContent = "Eigene Bilder Gesamt: 0";
|
||||
const countDisplay = document.getElementById("capture-count-display");
|
||||
if (countDisplay) countDisplay.textContent = "Eigene Bilder: 0";
|
||||
|
||||
alert("✅ " + data.message);
|
||||
} else {
|
||||
alert("Fehler beim Zurücksetzen: " + (data.detail || data.message));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Reset error:", err);
|
||||
alert("Serverfehler beim Zurücksetzen der Trainingsdaten.");
|
||||
} finally {
|
||||
if (resetBtnLearn) {
|
||||
resetBtnLearn.disabled = false;
|
||||
resetBtnLearn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
Alle Trainingsbilder löschen
|
||||
`;
|
||||
}
|
||||
if (resetBtnDash) {
|
||||
resetBtnDash.disabled = false;
|
||||
resetBtnDash.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
Eigene Bilder & Kalibrierung zurücksetzen
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- RESET TRAINED AI MODEL ---
|
||||
async function resetAIModel() {
|
||||
const confirmed = confirm(
|
||||
"⚠️ Trainiertes KI-Modell wirklich zurücksetzen?\n\n" +
|
||||
"Dies löscht das trainierte PyTorch- und ONNX-Modell vom Server.\n" +
|
||||
"Das System läuft danach im Simulations-Modus, bis Sie ein neues Modell trainieren.\n\n" +
|
||||
"Dieser Vorgang kann NICHT rückgängig gemacht werden!"
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
const resetBtn = document.getElementById("dashboard-reset-model-btn");
|
||||
if (resetBtn) {
|
||||
resetBtn.disabled = true;
|
||||
resetBtn.textContent = "Zurücksetzen...";
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/reset_model", { method: "POST" });
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === "success") {
|
||||
alert("✅ " + data.message);
|
||||
// Refresh dashboard training status if polling is active
|
||||
if (activeTab === 'dashboard') {
|
||||
const statusResponse = await fetch("/api/train_status");
|
||||
const statusData = await statusResponse.json();
|
||||
updateTrainingStatusUI(statusData);
|
||||
}
|
||||
} else {
|
||||
alert("Fehler beim Zurücksetzen des Modells: " + (data.detail || data.message));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Reset model error:", err);
|
||||
alert("Serverfehler beim Zurücksetzen des KI-Modells.");
|
||||
} finally {
|
||||
if (resetBtn) {
|
||||
resetBtn.disabled = false;
|
||||
resetBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"/></svg>
|
||||
Trainiertes KI-Modell zurücksetzen
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- INTERACTIVE EVENT LISTENERS ---
|
||||
toggleCameraBtn.addEventListener("click", () => {
|
||||
if (isWebcamActive) {
|
||||
@@ -1022,4 +1718,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Initialize capture classes
|
||||
initCapturePanel();
|
||||
|
||||
// Check background calibration on startup
|
||||
checkBackgroundCalibration();
|
||||
updateLearnBadge();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user