feat(aurora): add detection overlays with face/plate boxes in compare UI
This commit is contained in:
@@ -401,6 +401,69 @@
|
||||
padding: 2px 0;
|
||||
}
|
||||
.aurora-quality-line span:first-child { color: var(--muted); }
|
||||
.aurora-detect-wrap {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
background: var(--bg2);
|
||||
}
|
||||
.aurora-detect-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.aurora-detect-card {
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
.aurora-detect-stage {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
background: #000;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.aurora-detect-stage img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 240px;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
}
|
||||
.aurora-detect-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.aurora-bbox {
|
||||
position: absolute;
|
||||
border: 2px solid #00c67a;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,0.35) inset;
|
||||
}
|
||||
.aurora-bbox.face { border-color: #00c67a; }
|
||||
.aurora-bbox.plate { border-color: #f5a623; }
|
||||
.aurora-bbox-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translateY(-100%);
|
||||
font-size: 0.63rem;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
.aurora-chat-log {
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
@@ -871,6 +934,20 @@
|
||||
<div id="auroraQualityContent"></div>
|
||||
</div>
|
||||
|
||||
<div id="auroraDetectionsWrap" class="aurora-detect-wrap" style="display:none;">
|
||||
<div class="aurora-note" style="margin-top:0;">Detections (faces + plates)</div>
|
||||
<div class="aurora-detect-grid">
|
||||
<div class="aurora-detect-card">
|
||||
<div class="aurora-note" style="margin-top:0;">Original frame</div>
|
||||
<div id="auroraDetectionsBefore"></div>
|
||||
</div>
|
||||
<div class="aurora-detect-card">
|
||||
<div class="aurora-note" style="margin-top:0;">Aurora enhanced frame</div>
|
||||
<div id="auroraDetectionsAfter"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="auroraFacesRow" class="aurora-kv" style="display:none;">
|
||||
<span class="k">Виявлено облич</span><span class="v" id="auroraFacesCount">0</span>
|
||||
</div>
|
||||
@@ -2233,6 +2310,8 @@ function auroraSetSelectedFile(file) {
|
||||
if (audioCard) audioCard.style.display = 'none';
|
||||
const qualityWrap = document.getElementById('auroraQualityWrap');
|
||||
if (qualityWrap) qualityWrap.style.display = 'none';
|
||||
const detWrap = document.getElementById('auroraDetectionsWrap');
|
||||
if (detWrap) detWrap.style.display = 'none';
|
||||
const quickStartBtn = document.getElementById('auroraStartFromAnalysisBtn');
|
||||
if (quickStartBtn) quickStartBtn.disabled = !file;
|
||||
const reBtn = document.getElementById('auroraReprocessBtn');
|
||||
@@ -2698,6 +2777,93 @@ function auroraRenderQualityReport(report) {
|
||||
wrap.style.display = 'block';
|
||||
}
|
||||
|
||||
function auroraNormalizeBoxPct(bbox, frameW, frameH) {
|
||||
if (!Array.isArray(bbox) || bbox.length < 4) return null;
|
||||
const fw = Number(frameW || 0);
|
||||
const fh = Number(frameH || 0);
|
||||
if (!Number.isFinite(fw) || !Number.isFinite(fh) || fw <= 0 || fh <= 0) return null;
|
||||
let x1 = Number(bbox[0]); let y1 = Number(bbox[1]); let x2 = Number(bbox[2]); let y2 = Number(bbox[3]);
|
||||
if (![x1, y1, x2, y2].every(Number.isFinite)) return null;
|
||||
if (x2 < x1) [x1, x2] = [x2, x1];
|
||||
if (y2 < y1) [y1, y2] = [y2, y1];
|
||||
x1 = Math.max(0, Math.min(fw, x1));
|
||||
x2 = Math.max(0, Math.min(fw, x2));
|
||||
y1 = Math.max(0, Math.min(fh, y1));
|
||||
y2 = Math.max(0, Math.min(fh, y2));
|
||||
if ((x2 - x1) < 2 || (y2 - y1) < 2) return null;
|
||||
return {
|
||||
left: (x1 / fw) * 100,
|
||||
top: (y1 / fh) * 100,
|
||||
width: ((x2 - x1) / fw) * 100,
|
||||
height: ((y2 - y1) / fh) * 100,
|
||||
};
|
||||
}
|
||||
|
||||
function auroraRenderDetectionsPanel(containerId, imageUrl, payload) {
|
||||
const host = document.getElementById(containerId);
|
||||
if (!host) return false;
|
||||
const url = String(imageUrl || '').trim();
|
||||
const data = (payload && typeof payload === 'object') ? payload : {};
|
||||
const frame = (data.frame_size && typeof data.frame_size === 'object') ? data.frame_size : {};
|
||||
const frameW = Number(frame.width || 0);
|
||||
const frameH = Number(frame.height || 0);
|
||||
const faces = Array.isArray(data.faces) ? data.faces : [];
|
||||
const plates = Array.isArray(data.plates) ? data.plates : [];
|
||||
|
||||
if (!url) {
|
||||
host.innerHTML = '<div class="aurora-note">preview unavailable</div>';
|
||||
return false;
|
||||
}
|
||||
|
||||
const boxHtml = [];
|
||||
const pushBox = (kind, item) => {
|
||||
const norm = auroraNormalizeBoxPct(item?.bbox, frameW, frameH);
|
||||
if (!norm) return;
|
||||
const conf = Number(item?.confidence);
|
||||
const confText = Number.isFinite(conf) ? conf.toFixed(2) : '?';
|
||||
let label = `${kind} (${confText})`;
|
||||
if (kind === 'plate' && item?.text) {
|
||||
label = `plate ${String(item.text)} (${confText})`;
|
||||
}
|
||||
boxHtml.push(
|
||||
`<div class="aurora-bbox ${kind}" style="left:${norm.left}%;top:${norm.top}%;width:${norm.width}%;height:${norm.height}%;">` +
|
||||
`<span class="aurora-bbox-label">${auroraEsc(label)}</span>` +
|
||||
`</div>`
|
||||
);
|
||||
};
|
||||
faces.forEach((item) => pushBox('face', item));
|
||||
plates.forEach((item) => pushBox('plate', item));
|
||||
|
||||
host.innerHTML = `
|
||||
<div class="aurora-detect-stage">
|
||||
<img src="${auroraEsc(url)}" alt="detections preview">
|
||||
<div class="aurora-detect-overlay">${boxHtml.join('')}</div>
|
||||
</div>
|
||||
<div class="aurora-note">${boxHtml.length} boxes</div>
|
||||
`;
|
||||
return boxHtml.length > 0;
|
||||
}
|
||||
|
||||
function auroraRenderDetections(compare) {
|
||||
const wrap = document.getElementById('auroraDetectionsWrap');
|
||||
const beforeHost = document.getElementById('auroraDetectionsBefore');
|
||||
const afterHost = document.getElementById('auroraDetectionsAfter');
|
||||
if (!wrap || !beforeHost || !afterHost) return;
|
||||
if (!compare || typeof compare !== 'object' || !compare.frame_preview || !compare.detections) {
|
||||
wrap.style.display = 'none';
|
||||
beforeHost.innerHTML = '';
|
||||
afterHost.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const fp = compare.frame_preview || {};
|
||||
const beforeUrl = auroraAbsoluteUrl(fp.before_url || '');
|
||||
const afterUrl = auroraAbsoluteUrl(fp.after_url || '');
|
||||
const d = compare.detections || {};
|
||||
const beforeOk = auroraRenderDetectionsPanel('auroraDetectionsBefore', beforeUrl, d.before || {});
|
||||
const afterOk = auroraRenderDetectionsPanel('auroraDetectionsAfter', afterUrl, d.after || {});
|
||||
wrap.style.display = (beforeOk || afterOk) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function auroraShowCompare(beforeUrl, afterUrl) {
|
||||
const wrap = document.getElementById('auroraCompareWrap');
|
||||
if (!wrap || !beforeUrl || !afterUrl) return;
|
||||
@@ -3449,6 +3615,7 @@ async function auroraRenderResult(data, compare) {
|
||||
const afterUrl = auroraAbsoluteUrl(outputImage.url);
|
||||
auroraShowCompare(beforeUrl, afterUrl);
|
||||
}
|
||||
auroraRenderDetections(compare);
|
||||
|
||||
const forensicWrap = document.getElementById('auroraForensicLogWrap');
|
||||
const forensicPre = document.getElementById('auroraForensicLog');
|
||||
|
||||
Reference in New Issue
Block a user