|
|
|
@ -169,7 +169,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
classes: ["plate", "person", "car", "motor", "bus", "truck"],
|
|
|
|
classes: ["plate", "person", "car", "motor", "bus", "truck"],
|
|
|
|
defaults: ["plate", "car", "motor", "bus", "truck"]
|
|
|
|
defaults: ["plate", "car", "motor", "bus", "truck"]
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'VIPTRACK': {name: "관심 인물", classes: ["person"], defaults: ["person"]}
|
|
|
|
'VIPTRACK': {name: "관심 인물", classes: ["person", "face"], defaults: ["person", "face"]}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let currentModelCode = 'OBJDET';
|
|
|
|
let currentModelCode = 'OBJDET';
|
|
|
|
@ -177,6 +177,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
const zoomInContainer = document.getElementById('zoom-in-container');
|
|
|
|
const zoomInContainer = document.getElementById('zoom-in-container');
|
|
|
|
const zoomToggle = document.getElementById('zoom-toggle');
|
|
|
|
const zoomToggle = document.getElementById('zoom-toggle');
|
|
|
|
const showLabelToggle = document.getElementById('show-label-toggle');
|
|
|
|
const showLabelToggle = document.getElementById('show-label-toggle');
|
|
|
|
|
|
|
|
const showTidToggle = document.getElementById('show-tid-toggle');
|
|
|
|
|
|
|
|
const riskSummaryContainer = document.getElementById('risk-summary-container');
|
|
|
|
|
|
|
|
|
|
|
|
if (zoomToggle) {
|
|
|
|
if (zoomToggle) {
|
|
|
|
zoomToggle.addEventListener('change', (e) => {
|
|
|
|
zoomToggle.addEventListener('change', (e) => {
|
|
|
|
@ -200,6 +202,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (showTidToggle) {
|
|
|
|
|
|
|
|
showTidToggle.addEventListener('change', () => {
|
|
|
|
|
|
|
|
if (lastFrameMeta) {
|
|
|
|
|
|
|
|
showDetections(lastFrameMeta);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
|
|
navItems.forEach(item => {
|
|
|
|
navItems.forEach(item => {
|
|
|
|
@ -212,13 +222,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
currentModelCode = modelCode;
|
|
|
|
currentModelCode = modelCode;
|
|
|
|
changeModel(modelCode);
|
|
|
|
changeModel(modelCode);
|
|
|
|
updateFilterBar(modelCode);
|
|
|
|
updateFilterBar(modelCode);
|
|
|
|
// 모델 변경 시 Summary 패널 초기화
|
|
|
|
|
|
|
|
|
|
|
|
if (modelCode === 'CROWD') {
|
|
|
|
|
|
|
|
riskSummaryContainer.classList.remove('hidden');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
riskSummaryContainer.classList.add('hidden');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (modelCode === 'FACEATTR' || modelCode === 'ABNORM' || modelCode === 'LPR') {
|
|
|
|
if (modelCode === 'FACEATTR' || modelCode === 'ABNORM' || modelCode === 'LPR') {
|
|
|
|
summaryListEl.className = 'summary-content card-list';
|
|
|
|
summaryListEl.className = 'summary-content card-list';
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">Waiting for card data...</div>';
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">Waiting for card data...</div>';
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
summaryListEl.className = 'summary-content';
|
|
|
|
summaryListEl.className = 'summary-content';
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">No detection</div>';
|
|
|
|
summaryListEl.innerHTML = '';
|
|
|
|
|
|
|
|
detectedClasses.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -254,7 +271,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
|
|
|
|
|
|
const defaults = def.defaults || def.classes;
|
|
|
|
const defaults = def.defaults || def.classes;
|
|
|
|
|
|
|
|
|
|
|
|
// classes와 defaults가 같은지 확인 (순서 무관하게 내용 비교)
|
|
|
|
|
|
|
|
const isAllDefault = (def.classes.length === defaults.length) &&
|
|
|
|
const isAllDefault = (def.classes.length === defaults.length) &&
|
|
|
|
def.classes.every(c => defaults.includes(c));
|
|
|
|
def.classes.every(c => defaults.includes(c));
|
|
|
|
|
|
|
|
|
|
|
|
@ -346,6 +362,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
let lastFpsCheckTime = performance.now();
|
|
|
|
let lastFpsCheckTime = performance.now();
|
|
|
|
let lastFrameMeta = null;
|
|
|
|
let lastFrameMeta = null;
|
|
|
|
let viewConfig = {r: 1, dx: 0, dy: 0};
|
|
|
|
let viewConfig = {r: 1, dx: 0, dy: 0};
|
|
|
|
|
|
|
|
let detectedClasses = new Set(); // 한 번이라도 탐지된 클래스 목록
|
|
|
|
|
|
|
|
let mosaicState = {}; // TID별 모자이크 상태 저장 (true: 모자이크, false: 해제)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 모자이크용 오프스크린 캔버스
|
|
|
|
|
|
|
|
const mosaicCanvas = document.createElement('canvas');
|
|
|
|
|
|
|
|
const mosaicCtx = mosaicCanvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyMosaic(x, y, width, height) {
|
|
|
|
|
|
|
|
if (!ctx || width <= 0 || height <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const blockSize = 20; // 모자이크 블록 크기
|
|
|
|
|
|
|
|
const smallW = Math.floor(width / blockSize);
|
|
|
|
|
|
|
|
const smallH = Math.floor(height / blockSize);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (smallW <= 0 || smallH <= 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mosaicCanvas.width = smallW;
|
|
|
|
|
|
|
|
mosaicCanvas.height = smallH;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 원본 캔버스의 영역을 축소해서 임시 캔버스에 그림
|
|
|
|
|
|
|
|
mosaicCtx.drawImage(canvasEl, x, y, width, height, 0, 0, smallW, smallH);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 임시 캔버스의 내용을 다시 원본 캔버스에 확대해서 그림 (픽셀화)
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
|
|
ctx.imageSmoothingEnabled = false; // 픽셀화 효과를 위해 스무딩 끄기
|
|
|
|
|
|
|
|
ctx.drawImage(mosaicCanvas, 0, 0, smallW, smallH, x, y, width, height);
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function connect() {
|
|
|
|
function connect() {
|
|
|
|
const ws = new WebSocket(uri);
|
|
|
|
const ws = new WebSocket(uri);
|
|
|
|
@ -362,6 +406,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
ws.onmessage = (event) => {
|
|
|
|
ws.onmessage = (event) => {
|
|
|
|
const data = event.data;
|
|
|
|
const data = event.data;
|
|
|
|
if (typeof data === "string") {
|
|
|
|
if (typeof data === "string") {
|
|
|
|
|
|
|
|
console.log("-----" + data);
|
|
|
|
updateLogPanel(data);
|
|
|
|
updateLogPanel(data);
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const meta = JSON.parse(data);
|
|
|
|
const meta = JSON.parse(data);
|
|
|
|
@ -369,13 +414,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
lastFrameMeta = meta;
|
|
|
|
lastFrameMeta = meta;
|
|
|
|
showDetections(meta);
|
|
|
|
showDetections(meta);
|
|
|
|
} else if (meta.type === "card" && (currentModelCode === 'FACEATTR' || currentModelCode === 'ABNORM' || currentModelCode === 'LPR')) {
|
|
|
|
} else if (meta.type === "card" && (currentModelCode === 'FACEATTR' || currentModelCode === 'ABNORM' || currentModelCode === 'LPR')) {
|
|
|
|
lastFrameMeta = meta; // 카드 데이터도 BBox를 포함할 수 있으므로 저장
|
|
|
|
lastFrameMeta = meta;
|
|
|
|
showDetections(meta); // BBox 그리기
|
|
|
|
showDetections(meta);
|
|
|
|
updateCardPanel(meta.items); // 카드 패널 업데이트
|
|
|
|
updateCardPanel(meta.items);
|
|
|
|
|
|
|
|
|
|
|
|
if (meta.type === "card" && currentModelCode === 'LPR') {
|
|
|
|
|
|
|
|
console.log("-----" + JSON.stringify(meta));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
console.error(e);
|
|
|
|
@ -404,6 +445,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
viewConfig = {r, dx, dy};
|
|
|
|
viewConfig = {r, dx, dy};
|
|
|
|
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
|
|
|
|
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
|
|
|
|
ctx.drawImage(bmp, dx, dy, dw, dh);
|
|
|
|
ctx.drawImage(bmp, dx, dy, dw, dh);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentModelCode === 'VIPTRACK' && lastFrameMeta && lastFrameMeta.items) {
|
|
|
|
|
|
|
|
lastFrameMeta.items.forEach(item => {
|
|
|
|
|
|
|
|
const tagId = item.tag !== undefined ? item.tag : -1;
|
|
|
|
|
|
|
|
const clsId = item.cls !== undefined ? item.cls : -1;
|
|
|
|
|
|
|
|
let displayClassName = '';
|
|
|
|
|
|
|
|
if (LABEL_MAP[tagId] && LABEL_MAP[tagId].classes[clsId]) {
|
|
|
|
|
|
|
|
displayClassName = LABEL_MAP[tagId].classes[clsId];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (displayClassName === 'face') {
|
|
|
|
|
|
|
|
const { r, dx, dy } = viewConfig;
|
|
|
|
|
|
|
|
const x1 = item.x1 || 0, y1 = item.y1 || 0, x2 = item.x2 || 0, y2 = item.y2 || 0;
|
|
|
|
|
|
|
|
const screenX = dx + (x1 * r), screenY = dy + (y1 * r);
|
|
|
|
|
|
|
|
const screenW = (x2 - x1) * r, screenH = (y2 - y1) * r;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tid = item.tid;
|
|
|
|
|
|
|
|
const isMosaicOn = (tid !== undefined && mosaicState[tid] !== undefined) ? mosaicState[tid] : true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isMosaicOn && screenW > 0 && screenH > 0) {
|
|
|
|
|
|
|
|
applyMosaic(screenX, screenY, screenW, screenH);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bmp.close();
|
|
|
|
bmp.close();
|
|
|
|
updateBboxContainerPosition();
|
|
|
|
updateBboxContainerPosition();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -411,11 +478,118 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (canvasEl) {
|
|
|
|
|
|
|
|
canvasEl.addEventListener('click', (e) => {
|
|
|
|
|
|
|
|
if (currentModelCode !== 'VIPTRACK' || !lastFrameMeta || !lastFrameMeta.items) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rect = canvasEl.getBoundingClientRect();
|
|
|
|
|
|
|
|
const clickX = e.clientX - rect.left;
|
|
|
|
|
|
|
|
const clickY = e.clientY - rect.top;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { r, dx, dy } = viewConfig;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lastFrameMeta.items.forEach(item => {
|
|
|
|
|
|
|
|
const tagId = item.tag !== undefined ? item.tag : -1;
|
|
|
|
|
|
|
|
const clsId = item.cls !== undefined ? item.cls : -1;
|
|
|
|
|
|
|
|
let displayClassName = '';
|
|
|
|
|
|
|
|
if (LABEL_MAP[tagId] && LABEL_MAP[tagId].classes[clsId]) {
|
|
|
|
|
|
|
|
displayClassName = LABEL_MAP[tagId].classes[clsId];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (displayClassName === 'face') {
|
|
|
|
|
|
|
|
const x1 = item.x1 || 0;
|
|
|
|
|
|
|
|
const y1 = item.y1 || 0;
|
|
|
|
|
|
|
|
const x2 = item.x2 || 0;
|
|
|
|
|
|
|
|
const y2 = item.y2 || 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const screenX = dx + (x1 * r);
|
|
|
|
|
|
|
|
const screenY = dy + (y1 * r);
|
|
|
|
|
|
|
|
const screenW = (x2 - x1) * r;
|
|
|
|
|
|
|
|
const screenH = (y2 - y1) * r;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (clickX >= screenX && clickX <= screenX + screenW &&
|
|
|
|
|
|
|
|
clickY >= screenY && clickY <= screenY + screenH) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tid = item.tid;
|
|
|
|
|
|
|
|
if (tid !== undefined && tid !== 65535) {
|
|
|
|
|
|
|
|
if (mosaicState[tid] === undefined) {
|
|
|
|
|
|
|
|
mosaicState[tid] = false;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
mosaicState[tid] = !mosaicState[tid];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
canvasEl.addEventListener('mousemove', (e) => {
|
|
|
|
|
|
|
|
if (currentModelCode !== 'VIPTRACK' || !lastFrameMeta || !lastFrameMeta.items) {
|
|
|
|
|
|
|
|
canvasEl.style.cursor = 'default';
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rect = canvasEl.getBoundingClientRect();
|
|
|
|
|
|
|
|
const mouseX = e.clientX - rect.left;
|
|
|
|
|
|
|
|
const mouseY = e.clientY - rect.top;
|
|
|
|
|
|
|
|
const { r, dx, dy } = viewConfig;
|
|
|
|
|
|
|
|
let onFace = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const item of lastFrameMeta.items) {
|
|
|
|
|
|
|
|
const tagId = item.tag !== undefined ? item.tag : -1;
|
|
|
|
|
|
|
|
const clsId = item.cls !== undefined ? item.cls : -1;
|
|
|
|
|
|
|
|
let displayClassName = '';
|
|
|
|
|
|
|
|
if (LABEL_MAP[tagId] && LABEL_MAP[tagId].classes[clsId]) {
|
|
|
|
|
|
|
|
displayClassName = LABEL_MAP[tagId].classes[clsId];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (displayClassName === 'face' && item.tid !== 65535) {
|
|
|
|
|
|
|
|
const x1 = item.x1 || 0, y1 = item.y1 || 0, x2 = item.x2 || 0, y2 = item.y2 || 0;
|
|
|
|
|
|
|
|
const screenX = dx + (x1 * r), screenY = dy + (y1 * r);
|
|
|
|
|
|
|
|
const screenW = (x2 - x1) * r, screenH = (y2 - y1) * r;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (mouseX >= screenX && mouseX <= screenX + screenW &&
|
|
|
|
|
|
|
|
mouseY >= screenY && mouseY <= screenY + screenH) {
|
|
|
|
|
|
|
|
onFace = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
canvasEl.style.cursor = onFace ? 'pointer' : 'default';
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showDetections(meta) {
|
|
|
|
function showDetections(meta) {
|
|
|
|
bboxContainerEl.innerHTML = "";
|
|
|
|
bboxContainerEl.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentModelCode === 'CROWD' && meta.trajectory) {
|
|
|
|
|
|
|
|
const personColor = getBoxColor('person');
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
|
|
ctx.lineWidth = 2;
|
|
|
|
|
|
|
|
ctx.strokeStyle = personColor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object.values(meta.trajectory).forEach(points => {
|
|
|
|
|
|
|
|
if (points.length < 2) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
|
|
const startX = viewConfig.dx + (points[0][0] * viewConfig.r);
|
|
|
|
|
|
|
|
const startY = viewConfig.dy + (points[0][1] * viewConfig.r);
|
|
|
|
|
|
|
|
ctx.moveTo(startX, startY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < points.length; i++) {
|
|
|
|
|
|
|
|
const px = viewConfig.dx + (points[i][0] * viewConfig.r);
|
|
|
|
|
|
|
|
const py = viewConfig.dy + (points[i][1] * viewConfig.r);
|
|
|
|
|
|
|
|
ctx.lineTo(px, py);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const items = meta.items || [];
|
|
|
|
const items = meta.items || [];
|
|
|
|
const currentCounts = {};
|
|
|
|
const currentCounts = {};
|
|
|
|
const shouldShowLabel = showLabelToggle ? showLabelToggle.checked : false;
|
|
|
|
const shouldShowLabel = showLabelToggle ? showLabelToggle.checked : false;
|
|
|
|
|
|
|
|
const shouldShowTid = showTidToggle ? showTidToggle.checked : false;
|
|
|
|
|
|
|
|
|
|
|
|
items.forEach((it) => {
|
|
|
|
items.forEach((it) => {
|
|
|
|
const tagId = it.tag !== undefined ? it.tag : -1;
|
|
|
|
const tagId = it.tag !== undefined ? it.tag : -1;
|
|
|
|
@ -457,7 +631,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
boxDiv.style.height = `${screenH}px`;
|
|
|
|
boxDiv.style.height = `${screenH}px`;
|
|
|
|
boxDiv.style.borderColor = boxColor;
|
|
|
|
boxDiv.style.borderColor = boxColor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let labelText = '';
|
|
|
|
if (shouldShowLabel) {
|
|
|
|
if (shouldShowLabel) {
|
|
|
|
|
|
|
|
labelText += displayClassName;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldShowTid && it.tid !== undefined) {
|
|
|
|
|
|
|
|
labelText += ` ${it.tid}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (labelText) {
|
|
|
|
const label = document.createElement('div');
|
|
|
|
const label = document.createElement('div');
|
|
|
|
label.style.position = 'absolute';
|
|
|
|
label.style.position = 'absolute';
|
|
|
|
label.style.top = '-20px';
|
|
|
|
label.style.top = '-20px';
|
|
|
|
@ -467,7 +649,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
label.style.fontSize = '11px';
|
|
|
|
label.style.fontSize = '11px';
|
|
|
|
label.style.fontWeight = 'bold';
|
|
|
|
label.style.fontWeight = 'bold';
|
|
|
|
label.style.padding = '1px 4px';
|
|
|
|
label.style.padding = '1px 4px';
|
|
|
|
label.textContent = displayClassName;
|
|
|
|
label.textContent = labelText.trim();
|
|
|
|
boxDiv.appendChild(label);
|
|
|
|
boxDiv.appendChild(label);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -475,31 +657,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (currentModelCode !== 'FACEATTR' && currentModelCode !== 'ABNORM' && currentModelCode !== 'LPR') {
|
|
|
|
if (currentModelCode !== 'FACEATTR' && currentModelCode !== 'ABNORM' && currentModelCode !== 'LPR') {
|
|
|
|
updateSummaryPanel(currentCounts);
|
|
|
|
updateSummaryPanel(currentCounts, meta.risk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateSummaryPanel(counts) {
|
|
|
|
function updateSummaryPanel(counts, riskData) {
|
|
|
|
if (!summaryListEl) return;
|
|
|
|
if (!summaryListEl) return;
|
|
|
|
summaryListEl.innerHTML = '';
|
|
|
|
|
|
|
|
const keys = Object.keys(counts);
|
|
|
|
Object.keys(counts).forEach(key => detectedClasses.add(key));
|
|
|
|
if (keys.length === 0) {
|
|
|
|
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">No detection</div>';
|
|
|
|
summaryListEl.innerHTML = '';
|
|
|
|
return;
|
|
|
|
detectedClasses.forEach(key => {
|
|
|
|
}
|
|
|
|
const count = counts[key] || 0;
|
|
|
|
keys.forEach(key => {
|
|
|
|
|
|
|
|
const row = document.createElement('div');
|
|
|
|
const row = document.createElement('div');
|
|
|
|
row.className = 'summary-row';
|
|
|
|
row.className = 'summary-row';
|
|
|
|
const color = getBoxColor(key);
|
|
|
|
const color = getBoxColor(key);
|
|
|
|
const colorBox = `<span style="display: inline-block; width: 12px; height: 12px; background-color: ${color}; margin-right: 8px; vertical-align: middle;"></span>`;
|
|
|
|
const colorBox = `<span style="display: inline-block; width: 12px; height: 12px; background-color: ${color}; margin-right: 8px; vertical-align: middle;"></span>`;
|
|
|
|
row.innerHTML = `<span class="label">${colorBox}${key}</span><span class="count">${counts[key]}</span>`;
|
|
|
|
row.innerHTML = `<span class="label">${colorBox}${key}</span><span class="count">${count}</span>`;
|
|
|
|
summaryListEl.appendChild(row);
|
|
|
|
summaryListEl.appendChild(row);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentModelCode === 'CROWD' && riskData) {
|
|
|
|
|
|
|
|
const distRisk = (riskData.dist_risk !== undefined) ? (riskData.dist_risk * 100).toFixed(2) : '-';
|
|
|
|
|
|
|
|
const motionRisk = (riskData.motion_risk !== undefined) ? (riskData.motion_risk * 100).toFixed(2) : '-';
|
|
|
|
|
|
|
|
const totalRisk = (riskData.risk_total !== undefined) ? (riskData.risk_total * 100).toFixed(2) : '-';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
riskSummaryContainer.innerHTML = `
|
|
|
|
|
|
|
|
<div class="summary-row">
|
|
|
|
|
|
|
|
<span class="label">거리 위험도(%)</span>
|
|
|
|
|
|
|
|
<span class="count">${distRisk}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="summary-row">
|
|
|
|
|
|
|
|
<span class="label">이동 위험도(%)</span>
|
|
|
|
|
|
|
|
<span class="count">${motionRisk}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="summary-row">
|
|
|
|
|
|
|
|
<span class="label" style="color: #ff5555; font-weight: bold;">종합 위험도(%)</span>
|
|
|
|
|
|
|
|
<span class="count" style="color: #ff5555; font-weight: bold;">${totalRisk}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateCardPanel(items) {
|
|
|
|
function updateCardPanel(items) {
|
|
|
|
if (!summaryListEl) return;
|
|
|
|
if (!summaryListEl) return;
|
|
|
|
summaryListEl.innerHTML = ''; // 기존 내용 초기화
|
|
|
|
summaryListEl.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (!items || items.length === 0) {
|
|
|
|
if (!items || items.length === 0) {
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">No card data</div>';
|
|
|
|
summaryListEl.innerHTML = '<div style="color:#777; text-align:center; padding:10px;">No card data</div>';
|
|
|
|
@ -528,7 +730,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
card.innerHTML = `
|
|
|
|
card.innerHTML = `
|
|
|
|
<img src="${fallenImgSrc}" class="card-person-img" style="width:120px; height:auto; max-height:150px;">
|
|
|
|
<img src="${fallenImgSrc}" class="card-person-img" style="width:120px; height:auto; max-height:150px;">
|
|
|
|
<div class="card-right">
|
|
|
|
<div class="card-right">
|
|
|
|
<div class="card-appear-info">쓰러짐 발생</div>
|
|
|
|
<div class="card-appear-info">Event ID: ${item.tid || '-'}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
} else if (currentModelCode === 'LPR') {
|
|
|
|
} else if (currentModelCode === 'LPR') {
|
|
|
|
|