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:
@@ -30,7 +30,7 @@ state = {
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@app.route('/<tab>')
|
@app.route('/<tab>')
|
||||||
def index(tab='detection'):
|
def index(tab='camera'):
|
||||||
return render_template('index.html', active_tab=tab)
|
return render_template('index.html', active_tab=tab)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -293,59 +293,6 @@
|
|||||||
.info-item .label { color: #666; }
|
.info-item .label { color: #666; }
|
||||||
.info-item .value { color: #4ecca3; font-weight: 600; margin-left: 4px; }
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -353,8 +300,8 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="logo">Pickle Vision</div>
|
<div class="logo">Pickle Vision</div>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" data-tab="detection">Detection</button>
|
<button class="tab active" data-tab="camera">Camera</button>
|
||||||
<button class="tab" data-tab="court" id="tabBtnCourt" disabled>Court</button>
|
<button class="tab" data-tab="calibration">Calibration</button>
|
||||||
<button class="tab" data-tab="trajectory" id="tabBtnTrajectory" disabled>Trajectory</button>
|
<button class="tab" data-tab="trajectory" id="tabBtnTrajectory" disabled>Trajectory</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
@@ -365,44 +312,25 @@
|
|||||||
|
|
||||||
<div class="event-banner" id="eventBanner">VAR: Close Call</div>
|
<div class="event-banner" id="eventBanner">VAR: Close Call</div>
|
||||||
|
|
||||||
<!-- Tab 1: Detection -->
|
<!-- Tab 1: Camera -->
|
||||||
<div class="tab-content active" id="tab-detection">
|
<div class="tab-content active" id="tab-camera">
|
||||||
<!-- 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>
|
|
||||||
<div class="cameras">
|
<div class="cameras">
|
||||||
<div class="cam-box">
|
<div class="cam-box">
|
||||||
<img id="det-cam1" alt="Camera 1">
|
<img id="cam-cam1" alt="Camera 1">
|
||||||
</div>
|
</div>
|
||||||
<div class="cam-box">
|
<div class="cam-box">
|
||||||
<img id="det-cam0" alt="Camera 0">
|
<img id="cam-cam0" alt="Camera 0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 2: Court — 3D main, cameras small bottom center -->
|
<!-- Tab 2: Calibration — 3D main, cameras small bottom center -->
|
||||||
<div class="tab-content" id="tab-court">
|
<div class="tab-content" id="tab-calibration">
|
||||||
<div class="tab-full">
|
<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-bar">
|
||||||
<div class="bottom-card"><img id="court-cam1" alt="Camera 1"></div>
|
<div class="bottom-card"><img id="cal-cam1" alt="Camera 1"></div>
|
||||||
<div class="bottom-card"><img id="court-cam0" alt="Camera 0"></div>
|
<div class="bottom-card"><img id="cal-cam0" alt="Camera 0"></div>
|
||||||
<div class="cam-card">
|
<div class="cam-card">
|
||||||
<div class="cc-title">Base Setup</div>
|
<div class="cc-title">Base Setup</div>
|
||||||
<div class="cc-item">Distance <b id="paramPosY">1.0</b>m</div>
|
<div class="cc-item">Distance <b id="paramPosY">1.0</b>m</div>
|
||||||
@@ -450,7 +378,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ===================== Tab switching =====================
|
// ===================== Tab switching =====================
|
||||||
var activeTab = '{{ active_tab | default("detection") }}';
|
var activeTab = '{{ active_tab | default("camera") }}';
|
||||||
|
|
||||||
function switchTab(target) {
|
function switchTab(target) {
|
||||||
activeTab = target;
|
activeTab = target;
|
||||||
@@ -459,7 +387,7 @@ function switchTab(target) {
|
|||||||
document.querySelector('.tab[data-tab="' + target + '"]').classList.add('active');
|
document.querySelector('.tab[data-tab="' + target + '"]').classList.add('active');
|
||||||
document.getElementById('tab-' + target).classList.add('active');
|
document.getElementById('tab-' + target).classList.add('active');
|
||||||
document.getElementById('infoPanel').style.display = (target === 'trajectory') ? 'flex' : 'none';
|
document.getElementById('infoPanel').style.display = (target === 'trajectory') ? 'flex' : 'none';
|
||||||
if (target === 'court' && !courtSceneInitialized) initCourtScene();
|
if (target === 'calibration' && !courtSceneInitialized) initCourtScene();
|
||||||
if (target === 'trajectory' && !trajSceneInitialized) initTrajectoryScene();
|
if (target === 'trajectory' && !trajSceneInitialized) initTrajectoryScene();
|
||||||
history.pushState(null, '', '/' + target);
|
history.pushState(null, '', '/' + target);
|
||||||
}
|
}
|
||||||
@@ -473,34 +401,32 @@ document.querySelectorAll('.tab').forEach(function(tab) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('popstate', function() {
|
window.addEventListener('popstate', function() {
|
||||||
var path = location.pathname.replace('/', '') || 'detection';
|
var path = location.pathname.replace('/', '') || 'camera';
|
||||||
switchTab(path);
|
switchTab(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init active tab from server
|
// Init active tab from server
|
||||||
if (activeTab !== 'detection') switchTab(activeTab);
|
if (activeTab !== 'camera') switchTab(activeTab);
|
||||||
|
|
||||||
// ===================== Calibration =====================
|
// ===================== Calibration =====================
|
||||||
function doCalibrate() {
|
function doCalibrate() {
|
||||||
var btn = document.getElementById('btnCalibrate');
|
var btn = document.getElementById('btnCalibrate');
|
||||||
var btnBig = document.getElementById('btnCalibrateBig');
|
btn.disabled = true;
|
||||||
[btn, btnBig].forEach(function(b) { if (b) { b.disabled = true; b.textContent = 'Calibrating...'; } });
|
btn.textContent = 'Calibrating...';
|
||||||
|
|
||||||
fetch('/api/calibration/trigger', { method: 'POST' })
|
fetch('/api/calibration/trigger', { method: 'POST' })
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
[btn, btnBig].forEach(function(b) { if (b) b.disabled = false; });
|
btn.disabled = false;
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
[btn, btnBig].forEach(function(b) { if (b) b.textContent = 'Re-calibrate'; });
|
btn.textContent = 'Re-calibrate';
|
||||||
updateCalibrationStatus();
|
updateCalibrationStatus();
|
||||||
|
|
||||||
// Fetch camera positions and add to 3D scene
|
|
||||||
fetch('/api/calibration/data')
|
fetch('/api/calibration/data')
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(camData) { addCamerasToScene(camData); });
|
.then(function(camData) { addCamerasToScene(camData); });
|
||||||
} else {
|
} else {
|
||||||
[btn, btnBig].forEach(function(b) { if (b) b.textContent = 'Calibrate'; });
|
btn.textContent = 'Calibrate';
|
||||||
// Show per-camera errors
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
if (data.result) {
|
if (data.result) {
|
||||||
for (var sid in data.result) {
|
for (var sid in data.result) {
|
||||||
@@ -511,7 +437,8 @@ function doCalibrate() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function(e) {
|
.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);
|
alert('Error: ' + e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -523,39 +450,22 @@ function updateCalibrationStatus() {
|
|||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
var el = document.getElementById('calStatusText');
|
var el = document.getElementById('calStatusText');
|
||||||
var elOverlay = document.getElementById('calStatusTextOverlay');
|
|
||||||
var ok0 = data['0'], ok1 = data['1'];
|
var ok0 = data['0'], ok1 = data['1'];
|
||||||
var statusText, statusClass;
|
|
||||||
|
|
||||||
if (ok0 && ok1) {
|
if (ok0 && ok1) {
|
||||||
statusText = 'Calibrated';
|
el.textContent = 'Calibrated';
|
||||||
statusClass = 'ok';
|
el.className = 'ok';
|
||||||
} else if (ok0 || ok1) {
|
} else if (ok0 || ok1) {
|
||||||
statusText = 'Partially calibrated';
|
el.textContent = 'Partially calibrated';
|
||||||
statusClass = 'ok';
|
el.className = 'ok';
|
||||||
} else {
|
} else {
|
||||||
statusText = 'Not calibrated';
|
el.textContent = 'Not calibrated';
|
||||||
statusClass = '';
|
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;
|
systemReady = data.system_ready || false;
|
||||||
var overlay = document.getElementById('calibrationOverlay');
|
|
||||||
var tabCourt = document.getElementById('tabBtnCourt');
|
|
||||||
var tabTraj = document.getElementById('tabBtnTrajectory');
|
var tabTraj = document.getElementById('tabBtnTrajectory');
|
||||||
|
if (tabTraj) tabTraj.disabled = !systemReady;
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Check on load and periodically
|
// Check on load and periodically
|
||||||
@@ -590,7 +500,7 @@ function addCamerasToScene(camData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===================== Camera frame polling =====================
|
// ===================== Camera frame polling =====================
|
||||||
var camPrefixes = { 'detection': 'det', 'court': 'court', 'trajectory': 'traj' };
|
var camPrefixes = { 'camera': 'cam', 'calibration': 'cal', 'trajectory': 'traj' };
|
||||||
|
|
||||||
function refreshCam(tabName, camId) {
|
function refreshCam(tabName, camId) {
|
||||||
var prefix = camPrefixes[tabName];
|
var prefix = camPrefixes[tabName];
|
||||||
@@ -608,7 +518,7 @@ function refreshCam(tabName, camId) {
|
|||||||
newImg.src = '/frame/' + camId + '?' + Date.now();
|
newImg.src = '/frame/' + camId + '?' + Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
['detection', 'court', 'trajectory'].forEach(function(tab) {
|
['camera', 'calibration', 'trajectory'].forEach(function(tab) {
|
||||||
refreshCam(tab, 0);
|
refreshCam(tab, 0);
|
||||||
refreshCam(tab, 1);
|
refreshCam(tab, 1);
|
||||||
});
|
});
|
||||||
@@ -627,7 +537,7 @@ var courtScene, courtCamera, courtRenderer, courtBallMesh;
|
|||||||
|
|
||||||
function initCourtScene() {
|
function initCourtScene() {
|
||||||
courtSceneInitialized = true;
|
courtSceneInitialized = true;
|
||||||
var container = document.getElementById('court-3d');
|
var container = document.getElementById('calibration-3d');
|
||||||
var w = container.clientWidth;
|
var w = container.clientWidth;
|
||||||
var h = container.clientHeight;
|
var h = container.clientHeight;
|
||||||
|
|
||||||
@@ -935,7 +845,7 @@ function drawCourtLines(scene) {
|
|||||||
// ===================== Trajectory data polling =====================
|
// ===================== Trajectory data polling =====================
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
if (!systemReady) return;
|
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) {
|
fetch('/api/trajectory').then(function(r) { return r.json(); }).then(function(data) {
|
||||||
var points = data.points || [];
|
var points = data.points || [];
|
||||||
|
|||||||
Reference in New Issue
Block a user