Rename tabs: Camera, Calibration, Trajectory — remove overlay
- Tab 1: Camera (raw feeds) - Tab 2: Calibration (3D court + calibrate button, as before) - Tab 3: Trajectory (disabled until calibrated) - Remove unnecessary calibration overlay from Camera tab Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -293,59 +293,6 @@
|
||||
.info-item .label { color: #666; }
|
||||
.info-item .value { color: #4ecca3; font-weight: 600; margin-left: 4px; }
|
||||
|
||||
/* Calibration overlay */
|
||||
.calibration-overlay {
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
background: rgba(10, 10, 26, 0.85);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.calibration-overlay.hidden { display: none; }
|
||||
.cal-overlay-content {
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
padding: 40px;
|
||||
}
|
||||
.cal-overlay-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #4ecca3;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.cal-overlay-desc {
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.cal-overlay-params {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.cal-overlay-params b { color: #4ecca3; }
|
||||
.btn-calibrate-big {
|
||||
padding: 14px 48px;
|
||||
background: #4ecca3;
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.btn-calibrate-big:hover { background: #3dbb92; }
|
||||
.btn-calibrate-big:disabled { background: #333; color: #666; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -353,8 +300,8 @@
|
||||
<div class="header">
|
||||
<div class="logo">Pickle Vision</div>
|
||||
<div class="tabs">
|
||||
<button class="tab active" data-tab="detection">Detection</button>
|
||||
<button class="tab" data-tab="court" id="tabBtnCourt" disabled>Court</button>
|
||||
<button class="tab active" data-tab="camera">Camera</button>
|
||||
<button class="tab" data-tab="calibration">Calibration</button>
|
||||
<button class="tab" data-tab="trajectory" id="tabBtnTrajectory" disabled>Trajectory</button>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
@@ -365,44 +312,25 @@
|
||||
|
||||
<div class="event-banner" id="eventBanner">VAR: Close Call</div>
|
||||
|
||||
<!-- Tab 1: Detection -->
|
||||
<div class="tab-content active" id="tab-detection">
|
||||
<!-- Calibration overlay — shown until system is calibrated -->
|
||||
<div class="calibration-overlay" id="calibrationOverlay">
|
||||
<div class="cal-overlay-content">
|
||||
<div class="cal-overlay-title">Calibration Required</div>
|
||||
<div class="cal-overlay-desc">
|
||||
Position cameras so that court lines are clearly visible, then press Calibrate.
|
||||
The system will auto-detect court geometry and determine camera positions.
|
||||
</div>
|
||||
<div class="cal-overlay-params">
|
||||
<span>Sensor: <b>IMX219</b></span>
|
||||
<span>HFOV: <b>128°</b></span>
|
||||
<span>Stereo gap: <b>~6 cm</b></span>
|
||||
</div>
|
||||
<button class="btn-calibrate-big" id="btnCalibrateBig" onclick="doCalibrate()">Calibrate</button>
|
||||
<div class="calibrate-status" id="calStatusOverlay" style="margin-top:8px">
|
||||
<span id="calStatusTextOverlay">Waiting for calibration...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab 1: Camera -->
|
||||
<div class="tab-content active" id="tab-camera">
|
||||
<div class="cameras">
|
||||
<div class="cam-box">
|
||||
<img id="det-cam1" alt="Camera 1">
|
||||
<img id="cam-cam1" alt="Camera 1">
|
||||
</div>
|
||||
<div class="cam-box">
|
||||
<img id="det-cam0" alt="Camera 0">
|
||||
<img id="cam-cam0" alt="Camera 0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Court — 3D main, cameras small bottom center -->
|
||||
<div class="tab-content" id="tab-court">
|
||||
<!-- Tab 2: Calibration — 3D main, cameras small bottom center -->
|
||||
<div class="tab-content" id="tab-calibration">
|
||||
<div class="tab-full">
|
||||
<div class="viewport-3d" id="court-3d"></div>
|
||||
<div class="viewport-3d" id="calibration-3d"></div>
|
||||
<div class="bottom-bar">
|
||||
<div class="bottom-card"><img id="court-cam1" alt="Camera 1"></div>
|
||||
<div class="bottom-card"><img id="court-cam0" alt="Camera 0"></div>
|
||||
<div class="bottom-card"><img id="cal-cam1" alt="Camera 1"></div>
|
||||
<div class="bottom-card"><img id="cal-cam0" alt="Camera 0"></div>
|
||||
<div class="cam-card">
|
||||
<div class="cc-title">Base Setup</div>
|
||||
<div class="cc-item">Distance <b id="paramPosY">1.0</b>m</div>
|
||||
@@ -450,7 +378,7 @@
|
||||
|
||||
<script>
|
||||
// ===================== Tab switching =====================
|
||||
var activeTab = '{{ active_tab | default("detection") }}';
|
||||
var activeTab = '{{ active_tab | default("camera") }}';
|
||||
|
||||
function switchTab(target) {
|
||||
activeTab = target;
|
||||
@@ -459,7 +387,7 @@ function switchTab(target) {
|
||||
document.querySelector('.tab[data-tab="' + target + '"]').classList.add('active');
|
||||
document.getElementById('tab-' + target).classList.add('active');
|
||||
document.getElementById('infoPanel').style.display = (target === 'trajectory') ? 'flex' : 'none';
|
||||
if (target === 'court' && !courtSceneInitialized) initCourtScene();
|
||||
if (target === 'calibration' && !courtSceneInitialized) initCourtScene();
|
||||
if (target === 'trajectory' && !trajSceneInitialized) initTrajectoryScene();
|
||||
history.pushState(null, '', '/' + target);
|
||||
}
|
||||
@@ -473,34 +401,32 @@ document.querySelectorAll('.tab').forEach(function(tab) {
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function() {
|
||||
var path = location.pathname.replace('/', '') || 'detection';
|
||||
var path = location.pathname.replace('/', '') || 'camera';
|
||||
switchTab(path);
|
||||
});
|
||||
|
||||
// Init active tab from server
|
||||
if (activeTab !== 'detection') switchTab(activeTab);
|
||||
if (activeTab !== 'camera') switchTab(activeTab);
|
||||
|
||||
// ===================== Calibration =====================
|
||||
function doCalibrate() {
|
||||
var btn = document.getElementById('btnCalibrate');
|
||||
var btnBig = document.getElementById('btnCalibrateBig');
|
||||
[btn, btnBig].forEach(function(b) { if (b) { b.disabled = true; b.textContent = 'Calibrating...'; } });
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Calibrating...';
|
||||
|
||||
fetch('/api/calibration/trigger', { method: 'POST' })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
[btn, btnBig].forEach(function(b) { if (b) b.disabled = false; });
|
||||
btn.disabled = false;
|
||||
if (data.ok) {
|
||||
[btn, btnBig].forEach(function(b) { if (b) b.textContent = 'Re-calibrate'; });
|
||||
btn.textContent = 'Re-calibrate';
|
||||
updateCalibrationStatus();
|
||||
|
||||
// Fetch camera positions and add to 3D scene
|
||||
fetch('/api/calibration/data')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(camData) { addCamerasToScene(camData); });
|
||||
} else {
|
||||
[btn, btnBig].forEach(function(b) { if (b) b.textContent = 'Calibrate'; });
|
||||
// Show per-camera errors
|
||||
btn.textContent = 'Calibrate';
|
||||
var errors = [];
|
||||
if (data.result) {
|
||||
for (var sid in data.result) {
|
||||
@@ -511,7 +437,8 @@ function doCalibrate() {
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
[btn, btnBig].forEach(function(b) { if (b) { b.disabled = false; b.textContent = 'Calibrate'; } });
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Calibrate';
|
||||
alert('Error: ' + e);
|
||||
});
|
||||
}
|
||||
@@ -523,39 +450,22 @@ function updateCalibrationStatus() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var el = document.getElementById('calStatusText');
|
||||
var elOverlay = document.getElementById('calStatusTextOverlay');
|
||||
var ok0 = data['0'], ok1 = data['1'];
|
||||
var statusText, statusClass;
|
||||
|
||||
if (ok0 && ok1) {
|
||||
statusText = 'Calibrated';
|
||||
statusClass = 'ok';
|
||||
el.textContent = 'Calibrated';
|
||||
el.className = 'ok';
|
||||
} else if (ok0 || ok1) {
|
||||
statusText = 'Partially calibrated';
|
||||
statusClass = 'ok';
|
||||
el.textContent = 'Partially calibrated';
|
||||
el.className = 'ok';
|
||||
} else {
|
||||
statusText = 'Not calibrated';
|
||||
statusClass = '';
|
||||
el.textContent = 'Not calibrated';
|
||||
el.className = '';
|
||||
}
|
||||
|
||||
if (el) { el.textContent = statusText; el.className = statusClass; }
|
||||
if (elOverlay) { elOverlay.textContent = statusText; elOverlay.className = statusClass; }
|
||||
|
||||
// Enable/disable system based on calibration
|
||||
systemReady = data.system_ready || false;
|
||||
var overlay = document.getElementById('calibrationOverlay');
|
||||
var tabCourt = document.getElementById('tabBtnCourt');
|
||||
var tabTraj = document.getElementById('tabBtnTrajectory');
|
||||
|
||||
if (systemReady) {
|
||||
if (overlay) overlay.classList.add('hidden');
|
||||
if (tabCourt) tabCourt.disabled = false;
|
||||
if (tabTraj) tabTraj.disabled = false;
|
||||
} else {
|
||||
if (overlay) overlay.classList.remove('hidden');
|
||||
if (tabCourt) tabCourt.disabled = true;
|
||||
if (tabTraj) tabTraj.disabled = true;
|
||||
}
|
||||
if (tabTraj) tabTraj.disabled = !systemReady;
|
||||
});
|
||||
}
|
||||
// Check on load and periodically
|
||||
@@ -590,7 +500,7 @@ function addCamerasToScene(camData) {
|
||||
}
|
||||
|
||||
// ===================== Camera frame polling =====================
|
||||
var camPrefixes = { 'detection': 'det', 'court': 'court', 'trajectory': 'traj' };
|
||||
var camPrefixes = { 'camera': 'cam', 'calibration': 'cal', 'trajectory': 'traj' };
|
||||
|
||||
function refreshCam(tabName, camId) {
|
||||
var prefix = camPrefixes[tabName];
|
||||
@@ -608,7 +518,7 @@ function refreshCam(tabName, camId) {
|
||||
newImg.src = '/frame/' + camId + '?' + Date.now();
|
||||
}
|
||||
|
||||
['detection', 'court', 'trajectory'].forEach(function(tab) {
|
||||
['camera', 'calibration', 'trajectory'].forEach(function(tab) {
|
||||
refreshCam(tab, 0);
|
||||
refreshCam(tab, 1);
|
||||
});
|
||||
@@ -627,7 +537,7 @@ var courtScene, courtCamera, courtRenderer, courtBallMesh;
|
||||
|
||||
function initCourtScene() {
|
||||
courtSceneInitialized = true;
|
||||
var container = document.getElementById('court-3d');
|
||||
var container = document.getElementById('calibration-3d');
|
||||
var w = container.clientWidth;
|
||||
var h = container.clientHeight;
|
||||
|
||||
@@ -935,7 +845,7 @@ function drawCourtLines(scene) {
|
||||
// ===================== Trajectory data polling =====================
|
||||
setInterval(function() {
|
||||
if (!systemReady) return;
|
||||
if (activeTab !== 'trajectory' && activeTab !== 'court') return;
|
||||
if (activeTab !== 'trajectory' && activeTab !== 'calibration') return;
|
||||
|
||||
fetch('/api/trajectory').then(function(r) { return r.json(); }).then(function(data) {
|
||||
var points = data.points || [];
|
||||
|
||||
Reference in New Issue
Block a user