Skip to content

Commit d945a16

Browse files
committed
Add PPE detection summary panel and on-canvas badge
Construction preset now shows: - PPE Summary panel with 4 stats: People, With Helmet, No Helmet, With Vest - Per-person detail rows showing what each person is wearing/missing - On-canvas summary badge (top-right) with helmet/vest counts - Panel auto-shows when Construction preset selected, hides for others - Color-coded: green for compliant, red for non-compliant
1 parent 1536808 commit d945a16

File tree

2 files changed

+166
-3
lines changed

2 files changed

+166
-3
lines changed

16-ai-safety/app.js

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,10 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
442442
}
443443
}
444444

445-
// Step 3: Draw visual overlays
445+
// Step 3: Draw visual overlays + update summary
446446
drawPersonOverlays(people, imageDataUrl);
447+
drawCanvasSummaryBadge(people);
448+
updatePPESummary(people);
447449

448450
// Step 4: Derive overall safety rating
449451
const unsafeCount = people.filter(p => !p.safe).length;
@@ -629,6 +631,83 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
629631
}
630632
}
631633

634+
// ── PPE Summary Panel + On-canvas badge ──
635+
function updatePPESummary(people) {
636+
const panel = document.getElementById('ppeSummary');
637+
if (!panel) return;
638+
639+
const total = people.length;
640+
const hasHelmet = people.filter(p => p.wearing.some(w => /hat|helmet/i.test(w))).length;
641+
const hasVest = people.filter(p => p.wearing.some(w => /vest|hi-vis/i.test(w))).length;
642+
const noHelmet = total - hasHelmet;
643+
644+
document.getElementById('ppeTotalPeople').textContent = total;
645+
document.getElementById('ppeSafeCount').textContent = hasHelmet;
646+
document.getElementById('ppeUnsafeCount').textContent = noHelmet;
647+
document.getElementById('ppeVestCount').textContent = hasVest;
648+
649+
// Per-person detail rows
650+
const details = document.getElementById('ppeDetails');
651+
if (total === 0) {
652+
details.innerHTML = '<div style="text-align:center;opacity:0.6;">No people detected</div>';
653+
} else {
654+
details.innerHTML = people.map((p, i) => {
655+
const icon = p.safe ? '\u2705' : '\u274c';
656+
const cls = p.safe ? 'ppe-detail-safe' : 'ppe-detail-unsafe';
657+
const wearing = p.wearing.length > 0 ? p.wearing.join(', ') : 'none detected';
658+
const missing = p.missing.length > 0 ? p.missing.join(', ') : '';
659+
return `<div class="ppe-detail-row ${cls}">
660+
${icon} <strong>Person ${i + 1}</strong>: ${wearing}${missing ? ' — <span style="color:#E63946;">Missing: ' + missing + '</span>' : ''}
661+
</div>`;
662+
}).join('');
663+
}
664+
}
665+
666+
function drawCanvasSummaryBadge(people) {
667+
if (people.length === 0) return;
668+
669+
const total = people.length;
670+
const safe = people.filter(p => p.safe).length;
671+
const unsafe = total - safe;
672+
const hasHelmet = people.filter(p => p.wearing.some(w => /hat|helmet/i.test(w))).length;
673+
const hasVest = people.filter(p => p.wearing.some(w => /vest|hi-vis/i.test(w))).length;
674+
675+
// Draw summary badge in top-right corner
676+
const badgeX = overlayCanvas.width - 220;
677+
const badgeY = 10;
678+
const badgeW = 210;
679+
const badgeH = 80;
680+
681+
// Background
682+
overlayCtx.fillStyle = 'rgba(0, 0, 0, 0.8)';
683+
overlayCtx.beginPath();
684+
overlayCtx.roundRect(badgeX, badgeY, badgeW, badgeH, 8);
685+
overlayCtx.fill();
686+
687+
// Title
688+
overlayCtx.fillStyle = '#fff';
689+
overlayCtx.font = 'bold 13px sans-serif';
690+
overlayCtx.fillText('PPE Summary', badgeX + 10, badgeY + 18);
691+
692+
// Stats line 1: People count
693+
overlayCtx.font = '12px sans-serif';
694+
overlayCtx.fillStyle = '#ccc';
695+
overlayCtx.fillText(`\ud83d\udc64 ${total} people detected`, badgeX + 10, badgeY + 36);
696+
697+
// Stats line 2: Helmets
698+
overlayCtx.fillStyle = hasHelmet === total ? '#2A9D8F' : '#E63946';
699+
overlayCtx.fillText(`\u26d1 Helmets: ${hasHelmet}/${total}`, badgeX + 10, badgeY + 54);
700+
701+
// Stats line 3: Vests
702+
overlayCtx.fillStyle = hasVest === total ? '#2A9D8F' : '#E76F51';
703+
overlayCtx.fillText(`\ud83e\udda6 Vests: ${hasVest}/${total}`, badgeX + 10, badgeY + 72);
704+
705+
// Safe/unsafe indicator
706+
overlayCtx.fillStyle = unsafe === 0 ? '#2A9D8F' : '#E63946';
707+
overlayCtx.font = 'bold 12px sans-serif';
708+
const statusText = unsafe === 0 ? '\u2713 ALL CLEAR' : `\u26a0 ${unsafe} NON-COMPLIANT`;
709+
overlayCtx.fillText(statusText, badgeX + 115, badgeY + 18);
710+
}
632711
// ── UI Update functions ──
633712
function updateStatus(message, isError = false) {
634713
statusBar.textContent = message;
@@ -1216,9 +1295,12 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
12161295
presetSelect.addEventListener('change', () => {
12171296
updatePresetInfo();
12181297
saveSettings();
1219-
// Show/hide PPE legend for construction mode
1298+
// Show/hide PPE legend + summary for construction mode
12201299
const ppeLegend = document.getElementById('ppeLegend');
1221-
if (ppeLegend) ppeLegend.style.display = presetSelect.value === 'construction' ? '' : 'none';
1300+
const ppeSummary = document.getElementById('ppeSummary');
1301+
const isConstruction = presetSelect.value === 'construction';
1302+
if (ppeLegend) ppeLegend.style.display = isConstruction ? '' : 'none';
1303+
if (ppeSummary) ppeSummary.style.display = isConstruction ? '' : 'none';
12221304
// Clear overlays when switching presets
12231305
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
12241306
window.reasoningConsole.logInfo(`Preset changed to: ${PRESETS[presetSelect.value].name}`);

16-ai-safety/index.html

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,63 @@
373373
padding: 0;
374374
background: transparent;
375375
}
376+
377+
/* PPE Detection Summary */
378+
.ppe-summary {
379+
background: var(--surface);
380+
border: 1px solid var(--surface-light);
381+
border-radius: 8px;
382+
padding: 10px;
383+
margin-top: 8px;
384+
}
385+
.ppe-summary-title {
386+
font-size: 0.75rem;
387+
font-weight: 700;
388+
text-transform: uppercase;
389+
letter-spacing: 0.5px;
390+
color: var(--text-muted);
391+
margin-bottom: 8px;
392+
}
393+
.ppe-summary-grid {
394+
display: grid;
395+
grid-template-columns: 1fr 1fr 1fr 1fr;
396+
gap: 6px;
397+
}
398+
.ppe-stat {
399+
text-align: center;
400+
padding: 6px 4px;
401+
background: var(--background);
402+
border-radius: 6px;
403+
}
404+
.ppe-stat-value {
405+
font-size: 1.3rem;
406+
font-weight: 700;
407+
color: var(--text);
408+
}
409+
.ppe-stat-label {
410+
font-size: 0.65rem;
411+
color: var(--text-muted);
412+
margin-top: 2px;
413+
}
414+
.ppe-stat.ppe-safe .ppe-stat-value { color: #2A9D8F; }
415+
.ppe-stat.ppe-unsafe .ppe-stat-value { color: #E63946; }
416+
.ppe-details {
417+
margin-top: 8px;
418+
font-size: 0.75rem;
419+
color: var(--text-muted);
420+
max-height: 100px;
421+
overflow-y: auto;
422+
}
423+
.ppe-detail-row {
424+
display: flex;
425+
align-items: center;
426+
gap: 6px;
427+
padding: 3px 0;
428+
border-bottom: 1px solid var(--surface-light);
429+
}
430+
.ppe-detail-row:last-child { border-bottom: none; }
431+
.ppe-detail-safe { color: #2A9D8F; }
432+
.ppe-detail-unsafe { color: #E63946; }
376433
</style>
377434
</head>
378435
<body>
@@ -453,6 +510,30 @@ <h2>Settings</h2>
453510

454511
<div id="status" class="status-bar">Ready - Configure preset and start monitoring</div>
455512

513+
<!-- PPE Detection Summary (construction mode only) -->
514+
<div id="ppeSummary" class="ppe-summary" style="display:none;">
515+
<div class="ppe-summary-title">PPE Detection</div>
516+
<div class="ppe-summary-grid">
517+
<div class="ppe-stat">
518+
<div class="ppe-stat-value" id="ppeTotalPeople">0</div>
519+
<div class="ppe-stat-label">👤 People</div>
520+
</div>
521+
<div class="ppe-stat ppe-safe">
522+
<div class="ppe-stat-value" id="ppeSafeCount">0</div>
523+
<div class="ppe-stat-label">✅ With Helmet</div>
524+
</div>
525+
<div class="ppe-stat ppe-unsafe">
526+
<div class="ppe-stat-value" id="ppeUnsafeCount">0</div>
527+
<div class="ppe-stat-label">❌ No Helmet</div>
528+
</div>
529+
<div class="ppe-stat">
530+
<div class="ppe-stat-value" id="ppeVestCount">0</div>
531+
<div class="ppe-stat-label">🦦 With Vest</div>
532+
</div>
533+
</div>
534+
<div id="ppeDetails" class="ppe-details"></div>
535+
</div>
536+
456537
<!-- Preset selection with emoji buttons -->
457538
<div class="control-group">
458539
<label>Environment Preset</label>

0 commit comments

Comments
 (0)