Browse Source

feat(dashboard): implement structured Shenron telemetry HUD and progress tracking

main
RUSHIL AMBARISH KADU 1 month ago
parent
commit
fa7caa16b1
  1. 108
      dashboard/static/app.js
  2. 74
      dashboard/static/style.css
  3. 19
      dashboard/templates/index.html
  4. 132
      scripts/generate_shenron.py

108
dashboard/static/app.js

@ -360,11 +360,35 @@ document.addEventListener('DOMContentLoaded', () => {
appendLog(`> Simulation Finished. ${data}`, 'info');
setLoadingState(false);
document.getElementById('sim-progress-container').style.display = 'none';
document.getElementById('shenron-progress-container').style.display = 'none';
document.getElementById('shenron-telemetry-hud').style.display = 'none';
isSimulationActive = false;
resetIdleTimer();
// ---- SHENRON INIT TELEMETRY ----
} else if (data.includes('[SHENRON_INIT]')) {
try {
const jsonStr = data.substring(data.indexOf('[SHENRON_INIT]') + 14);
const tel = JSON.parse(jsonStr);
renderShenronTelemetry(tel);
appendLog(`> [SHENRON] Radar synthesis started — ${tel.total_frames} frames`, 'success');
} catch(e) {
appendLog(data);
}
// ---- SHENRON STEP PROGRESS ----
} else if (data.includes('[SHENRON_STEP]')) {
try {
const jsonStr = data.substring(data.indexOf('[SHENRON_STEP]') + 14);
const step = JSON.parse(jsonStr);
updateShenronProgress(step);
} catch(e) {
appendLog(data);
}
} else {
// Intercept progress bar logs
// Intercept CARLA simulation progress bar
if (data.includes('SIMULATING:')) {
const match = data.match(/(\d+)%\|.*\|\s*(\d+)\/(\d+)/);
if (match) {
@ -375,6 +399,20 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// Filter out tqdm-style progress bars (containing |###)
if (data.includes('|#') || data.includes('|█') || data.match(/\d+%\|/)) {
continue; // Skip tqdm bars entirely
}
// Filter out suppressed internal logs
if (data.includes('Simulating Radar:') ||
data.includes('------- Using') ||
data.includes('Number of points =') ||
data.includes('[ITER 17]') ||
data.includes('Synced global config:')) {
continue; // These are now handled by telemetry
}
// Prevent carriage return visual artifacts
if (data.includes('\r')) {
const parts = data.split('\r');
@ -397,6 +435,74 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// ---- SHENRON TELEMETRY RENDERING (Compact Chips) ----
function renderShenronTelemetry(tel) {
const hud = document.getElementById('shenron-telemetry-hud');
const hwRow = document.getElementById('shenron-telemetry-hw');
const radarRow = document.getElementById('shenron-telemetry-radars');
const shenronContainer = document.getElementById('shenron-progress-container');
hud.style.display = 'flex';
shenronContainer.style.display = 'block';
// Row 1: Hardware + Session
let hw = '';
hw += chip('GPU', tel.gpu.name, 'c-emerald');
hw += chip('VRAM', `${tel.gpu.vram_gb} GB`, 'c-blue');
hw += chip('Backend', tel.gpu.backend, 'c-blue');
hw += '<span class="tel-separator"></span>';
hw += chip('Session', tel.session, 'c-rose');
hw += chip('Frames', tel.total_frames, 'c-emerald');
hwRow.innerHTML = hw;
// Row 2: Radar specs (compact, separated by type)
let rd = '';
const radarEntries = Object.entries(tel.radars);
radarEntries.forEach(([rType, specs], idx) => {
if (idx > 0) rd += '<span class="tel-separator"></span>';
rd += chip('Radar', rType.toUpperCase(), 'c-amber');
rd += chip('Freq', `${specs.freq_ghz}GHz`, 'c-blue');
rd += chip('BW', `${specs.bw_mhz}MHz`, 'c-cyan');
rd += chip('Chirps', specs.chirps, 'c-emerald');
rd += chip('Ant', `${specs.antennas}vRx`, 'c-emerald');
rd += chip('RRes', `${specs.range_res_m}m`, 'c-blue');
rd += chip('Gain', `${specs.gain_db}dB`, 'c-amber');
});
radarRow.innerHTML = rd;
}
function updateShenronProgress(step) {
const fill = document.getElementById('shenron-progress-fill');
const text = document.getElementById('shenron-progress-text');
const eta = document.getElementById('shenron-eta');
const container = document.getElementById('shenron-progress-container');
const liveRow = document.getElementById('shenron-telemetry-live');
container.style.display = 'block';
fill.style.width = step.pct + '%';
text.textContent = `${step.pct}% (${step.frame} / ${step.total} frames)`;
eta.textContent = `${step.fps} fps · ETA: ${step.eta}`;
// Update live signal metrics as inline chips
if (step.metrics && liveRow) {
let live = '';
const entries = Object.entries(step.metrics);
entries.forEach(([rType, m], idx) => {
if (idx > 0) live += '<span class="tel-separator"></span>';
live += chip(rType, '', 'c-amber');
live += chip('SNR', `${m.snr}dB`, 'c-amber');
live += chip('Pts', m.pts, 'c-emerald');
live += chip('Bins', m.bins, 'c-blue');
});
liveRow.innerHTML = live;
}
}
function chip(label, value, colorClass) {
return `<span class="tel-chip"><span class="chip-label">${label}</span><span class="chip-value ${colorClass}">${value}</span></span>`;
}
function setLoadingState(isLoading) {
launchBtn.disabled = isLoading;
launchBtn.style.display = isLoading ? 'none' : 'block';

74
dashboard/static/style.css

@ -514,6 +514,80 @@ input:checked + .slider:before {
to { opacity: 1; transform: translateY(0); }
}
/* Shenron Progress Bar */
#shenron-progress-container {
border-bottom: 1px solid var(--panel-border) !important;
animation: slideDown 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
#shenron-progress-fill {
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
background: linear-gradient(90deg, #059669, #34d399) !important;
}
/* Shenron Telemetry HUD — Compact Inline Layout */
.telemetry-hud {
padding: 6px 15px 8px;
border-bottom: 1px solid rgba(255,255,255,0.05);
background: rgba(0,0,0,0.2);
animation: slideDown 0.3s cubic-bezier(0.16, 1, 0.3, 1);
display: flex;
flex-direction: column;
gap: 4px;
}
.telemetry-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
line-height: 1;
}
.tel-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 4px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
white-space: nowrap;
}
.tel-chip .chip-label {
color: #6b7280;
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.tel-chip .chip-value {
font-weight: 600;
font-size: 0.72rem;
}
.chip-value.c-emerald { color: #6ee7b7; }
.chip-value.c-blue { color: #93c5fd; }
.chip-value.c-amber { color: #fcd34d; }
.chip-value.c-rose { color: #fda4af; }
.chip-value.c-cyan { color: #67e8f9; }
.tel-separator {
width: 1px;
height: 14px;
background: rgba(255,255,255,0.1);
margin: 0 2px;
}
.live-row .tel-chip {
border-color: rgba(52, 211, 153, 0.15);
background: rgba(52, 211, 153, 0.05);
}
/* Loader Spinner */
.loader-spinner {
border: 3px solid rgba(255,255,255,0.3);

19
dashboard/templates/index.html

@ -142,7 +142,7 @@
</button>
</div>
</div>
<!-- Interactive Progress Bar -->
<!-- Interactive Progress Bar: Simulation -->
<div id="sim-progress-container" style="display: none; padding: 12px 15px; border-bottom: 1px solid rgba(255,255,255,0.05); background: rgba(0,0,0,0.15);">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: #a0aec0; letter-spacing: 0.5px;">
<span>SIMULATION STATUS</span>
@ -152,6 +152,23 @@
<div id="sim-progress-fill" style="width: 0%; height: 100%; background: linear-gradient(90deg, #3b82f6, #60a5fa); box-shadow: 0 0 10px rgba(96,165,250,0.5); transition: width 0.15s ease-out;"></div>
</div>
</div>
<!-- Interactive Progress Bar: Shenron Radar Synthesis -->
<div id="shenron-progress-container" style="display: none; padding: 12px 15px; border-bottom: 1px solid rgba(255,255,255,0.05); background: rgba(0,0,0,0.15);">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px; font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: #6ee7b7; letter-spacing: 0.5px;">
<span>⚡ SHENRON SYNTHESIS</span>
<span id="shenron-progress-text">0% (0 / 0 frames)</span>
</div>
<div style="width: 100%; background: #1a202c; border-radius: 4px; overflow: hidden; height: 10px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);">
<div id="shenron-progress-fill" style="width: 0%; height: 100%; background: linear-gradient(90deg, #059669, #34d399); box-shadow: 0 0 10px rgba(52,211,153,0.5); transition: width 0.15s ease-out;"></div>
</div>
<div id="shenron-eta" style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; color: #6b7280; margin-top: 4px;">ETA: --</div>
</div>
<!-- Shenron Telemetry HUD (Compact) -->
<div id="shenron-telemetry-hud" class="telemetry-hud" style="display: none;">
<div id="shenron-telemetry-hw" class="telemetry-row"></div>
<div id="shenron-telemetry-radars" class="telemetry-row"></div>
<div id="shenron-telemetry-live" class="telemetry-row live-row"></div>
</div>
<div class="terminal-body" id="terminal-output">
<div class="log-line system">> Application initialized. Fetching presets...</div>
</div>

132
scripts/generate_shenron.py

@ -1,7 +1,7 @@
import os
import sys
import time
import numpy as np
import tqdm
import json
from pathlib import Path
@ -17,6 +17,30 @@ except ImportError as e:
print(f"Error: Failed to import ShenronRadarModel. Ensure scripts/ISOLATE/model_wrapper.py exists. ({e})")
sys.exit(1)
def _get_gpu_info():
"""Retrieve GPU hardware info for telemetry display."""
try:
import torch
if not torch.cuda.is_available():
return {"name": "CPU Fallback", "vram_gb": 0, "backend": "CPU"}
# Try to get device name safely
try:
name = torch.cuda.get_device_name(0)
except:
name = "NVIDIA Device"
# Try to get properties safely
try:
props = torch.cuda.get_device_properties(0)
vram_total = props.total_memory / (1024**3)
except:
vram_total = 0
return {"name": name, "vram_gb": round(vram_total, 1), "backend": "CUDA"}
except Exception as e:
return {"name": f"Detection Error ({type(e).__name__})", "vram_gb": 0, "backend": "Unknown"}
def process_session(session_path):
print(f"\n>>> Processing session: {session_path.name}")
@ -34,9 +58,12 @@ def process_session(session_path):
radar_types = ['awrl1432', 'radarbook']
models = {}
# -----------------------------------------------------------------------
# DIAGNOSTIC: Step 1 - Model Initialization
# -----------------------------------------------------------------------
print(f" [DIAGNOSTIC] Step 1: Initializing models...", flush=True)
for r_type in radar_types:
try:
print(f" Initializing ShenronRadarModel ({r_type})...")
models[r_type] = ShenronRadarModel(radar_type=r_type)
(session_path / r_type).mkdir(exist_ok=True)
@ -52,26 +79,53 @@ def process_session(session_path):
print(f" [WARNING] Failed to init {r_type}: {e}")
continue
print(f" Generating Shenron Radar data for {len(lidar_files)} frames...")
# -----------------------------------------------------------------------
# TELEMETRY: Emit structured init payload
# -----------------------------------------------------------------------
print(f" [DIAGNOSTIC] Step 2: Collecting metadata...", flush=True)
gpu_info = _get_gpu_info()
for lidar_file in tqdm.tqdm(lidar_files, desc=" Simulating Radar", unit="frame"):
# 1. Load Semantic LiDAR data once per frame
# Expected raw: [x, y, z, cos, obj, tag] (6 cols)
# Expected Shenron input: [x, y, z, intensity, cos, obj, tag] (7 cols)
radar_specs = {}
for r_type, model in models.items():
radar_specs[r_type] = model.get_radar_specs()
telemetry_init = {
"gpu": gpu_info,
"radars": radar_specs,
"total_frames": len(lidar_files),
"session": session_path.name,
}
print(f"[SHENRON_INIT]{json.dumps(telemetry_init)}", flush=True)
# -----------------------------------------------------------------------
# MAIN PROCESSING LOOP
# -----------------------------------------------------------------------
print(f" [DIAGNOSTIC] Step 3: Starting main loop for {len(lidar_files)} frames...", flush=True)
total_frames = len(lidar_files)
frame_times = []
for frame_idx, lidar_file in enumerate(lidar_files):
frame_start = time.time()
# 1. Load Semantic LiDAR data
try:
data = np.load(lidar_file)
except Exception as e:
print(f"\n [ERROR] Failed to load {lidar_file.name}: {e}")
continue
if data.shape[1] == 6:
# Pad with a dummy intensity column at index 3
# This aligns 'tag' to index 6 as expected by our lidar.py mapping
padded_data = np.zeros((data.shape[0], 7), dtype=np.float32)
padded_data[:, 0:3] = data[:, 0:3] # x, y, z
padded_data[:, 4:7] = data[:, 3:6] # cos, obj, tag
padded_data[:, 0:3] = data[:, 0:3]
padded_data[:, 4:7] = data[:, 3:6]
data = padded_data
frame_metrics = {}
for r_type, model in models.items():
try:
# 2. Process through the physics-based model
# returns rich PCD: [M, 5] (x, y, z, velocity, magnitude)
# print(f" [DEBUG] Processing {r_type} frame {frame_idx+1}...", end='\r', flush=True)
rich_pcd = model.process(data)
# 3. Save to disk
@ -87,23 +141,68 @@ def process_session(session_path):
np.save(met_base / "ra" / f"{frame_name}.npy", met['ra_heatmap'])
np.save(met_base / "cfar" / f"{frame_name}.npy", met['threshold_matrix'])
# 5. Save Signal Metrics (Telemetry)
# 5. Save Signal Metrics
try:
metrics = model.get_signal_metrics()
if metrics:
# Clean metrics for JSON (handle NaN/Inf)
clean_metrics = {}
for k, v in metrics.items():
if isinstance(v, float) and (np.isnan(v) or np.isinf(v)):
clean_metrics[k] = 0.0
else:
clean_metrics[k] = v
metrics_file = met_base / "metrics.jsonl"
with open(metrics_file, "a") as f:
f.write(json.dumps({"frame": lidar_file.stem, **metrics}) + "\n")
f.write(json.dumps({"frame": lidar_file.stem, **clean_metrics}) + "\n")
# CAPTURE UNIQUE POINT COUNT FOR TELEMETRY
clean_metrics["pts"] = int(rich_pcd.shape[0]) if hasattr(rich_pcd, 'shape') else 0
frame_metrics[r_type] = clean_metrics
except Exception as e:
pass # Metrics failure shouldn't crash the loop
except Exception as e:
print(f"\n [ERROR] Failed to process {lidar_file.name} for {r_type}: {e}")
# Timing
frame_elapsed = time.time() - frame_start
frame_times.append(frame_elapsed)
avg_time = sum(frame_times[-10:]) / len(frame_times[-10:])
remaining = total_frames - (frame_idx + 1)
eta_seconds = remaining * avg_time
eta_str = f"{int(eta_seconds // 60)}m {int(eta_seconds % 60)}s" if eta_seconds > 60 else f"{int(eta_seconds)}s"
progress_pct = int(((frame_idx + 1) / total_frames) * 100)
telemetry_frame = {
"frame": frame_idx + 1,
"total": total_frames,
"pct": progress_pct,
"fps": round(1.0 / frame_elapsed, 2) if frame_elapsed > 0 else 0,
"elapsed": round(frame_elapsed, 2),
"eta": eta_str,
"metrics": {}
}
for r_type, m in frame_metrics.items():
telemetry_frame["metrics"][r_type] = {
"snr": round(m.get("peak_snr_db", 0), 1),
"pts": m.get("pts", 0),
"peak": round(m.get("peak_magnitude", 0), 1),
"bins": m.get("active_bins", 0),
}
print(f"[SHENRON_STEP]{json.dumps(telemetry_frame)}", flush=True)
def main():
data_root = project_root / "data"
if not data_root.exists():
print(f"Error: {data_root} not found.")
return
# Get all session folders
sessions = sorted([d for d in data_root.iterdir() if d.is_dir()])
if not sessions:
@ -113,11 +212,8 @@ def main():
print(f"Found {len(sessions)} sessions.")
for session in sessions:
# Check if the session has frames.jsonl to confirm it's a valid data folder
if (session / "frames.jsonl").exists():
process_session(session)
else:
print(f"Skipping {session.name} (no frames.jsonl found).")
print("\n" + "="*50)
print("SHENRON BATCH PROCESSING COMPLETE!")

Loading…
Cancel
Save