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. 142
      scripts/generate_shenron.py

108
dashboard/static/app.js

@ -360,11 +360,35 @@ document.addEventListener('DOMContentLoaded', () => {
appendLog(`> Simulation Finished. ${data}`, 'info'); appendLog(`> Simulation Finished. ${data}`, 'info');
setLoadingState(false); setLoadingState(false);
document.getElementById('sim-progress-container').style.display = 'none'; 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; isSimulationActive = false;
resetIdleTimer(); 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 { } else {
// Intercept progress bar logs
// Intercept CARLA simulation progress bar
if (data.includes('SIMULATING:')) { if (data.includes('SIMULATING:')) {
const match = data.match(/(\d+)%\|.*\|\s*(\d+)\/(\d+)/); const match = data.match(/(\d+)%\|.*\|\s*(\d+)\/(\d+)/);
if (match) { 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 // Prevent carriage return visual artifacts
if (data.includes('\r')) { if (data.includes('\r')) {
const parts = data.split('\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) { function setLoadingState(isLoading) {
launchBtn.disabled = isLoading; launchBtn.disabled = isLoading;
launchBtn.style.display = isLoading ? 'none' : 'block'; 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); } 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 */
.loader-spinner { .loader-spinner {
border: 3px solid rgba(255,255,255,0.3); border: 3px solid rgba(255,255,255,0.3);

19
dashboard/templates/index.html

@ -142,7 +142,7 @@
</button> </button>
</div> </div>
</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 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;"> <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> <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 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>
</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="terminal-body" id="terminal-output">
<div class="log-line system">> Application initialized. Fetching presets...</div> <div class="log-line system">> Application initialized. Fetching presets...</div>
</div> </div>

142
scripts/generate_shenron.py

@ -1,7 +1,7 @@
import os import os
import sys import sys
import time
import numpy as np import numpy as np
import tqdm
import json import json
from pathlib import Path 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})") print(f"Error: Failed to import ShenronRadarModel. Ensure scripts/ISOLATE/model_wrapper.py exists. ({e})")
sys.exit(1) 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): def process_session(session_path):
print(f"\n>>> Processing session: {session_path.name}") print(f"\n>>> Processing session: {session_path.name}")
@ -34,9 +58,12 @@ def process_session(session_path):
radar_types = ['awrl1432', 'radarbook'] radar_types = ['awrl1432', 'radarbook']
models = {} models = {}
# -----------------------------------------------------------------------
# DIAGNOSTIC: Step 1 - Model Initialization
# -----------------------------------------------------------------------
print(f" [DIAGNOSTIC] Step 1: Initializing models...", flush=True)
for r_type in radar_types: for r_type in radar_types:
try: try:
print(f" Initializing ShenronRadarModel ({r_type})...")
models[r_type] = ShenronRadarModel(radar_type=r_type) models[r_type] = ShenronRadarModel(radar_type=r_type)
(session_path / r_type).mkdir(exist_ok=True) (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}") print(f" [WARNING] Failed to init {r_type}: {e}")
continue 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()
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)
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)
data = np.load(lidar_file)
# -----------------------------------------------------------------------
# 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: 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 = 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 data = padded_data
frame_metrics = {}
for r_type, model in models.items(): for r_type, model in models.items():
try: try:
# 2. Process through the physics-based model # 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) rich_pcd = model.process(data)
# 3. Save to disk # 3. Save to disk
@ -87,15 +141,61 @@ def process_session(session_path):
np.save(met_base / "ra" / f"{frame_name}.npy", met['ra_heatmap']) np.save(met_base / "ra" / f"{frame_name}.npy", met['ra_heatmap'])
np.save(met_base / "cfar" / f"{frame_name}.npy", met['threshold_matrix']) np.save(met_base / "cfar" / f"{frame_name}.npy", met['threshold_matrix'])
# 5. Save Signal Metrics (Telemetry)
metrics = model.get_signal_metrics()
if metrics:
metrics_file = met_base / "metrics.jsonl"
with open(metrics_file, "a") as f:
f.write(json.dumps({"frame": lidar_file.stem, **metrics}) + "\n")
# 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, **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: except Exception as e:
print(f"\n [ERROR] Failed to process {lidar_file.name} for {r_type}: {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(): def main():
data_root = project_root / "data" data_root = project_root / "data"
@ -103,7 +203,6 @@ def main():
print(f"Error: {data_root} not found.") print(f"Error: {data_root} not found.")
return return
# Get all session folders
sessions = sorted([d for d in data_root.iterdir() if d.is_dir()]) sessions = sorted([d for d in data_root.iterdir() if d.is_dir()])
if not sessions: if not sessions:
@ -113,11 +212,8 @@ def main():
print(f"Found {len(sessions)} sessions.") print(f"Found {len(sessions)} sessions.")
for session in 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(): if (session / "frames.jsonl").exists():
process_session(session) process_session(session)
else:
print(f"Skipping {session.name} (no frames.jsonl found).")
print("\n" + "="*50) print("\n" + "="*50)
print("SHENRON BATCH PROCESSING COMPLETE!") print("SHENRON BATCH PROCESSING COMPLETE!")

Loading…
Cancel
Save