From fa7caa16b1af9d653098c3d4a1463269d85e6dd1 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Tue, 14 Apr 2026 15:36:56 +0530 Subject: [PATCH] feat(dashboard): implement structured Shenron telemetry HUD and progress tracking --- dashboard/static/app.js | 108 ++++++++++++++++++++++++- dashboard/static/style.css | 74 +++++++++++++++++ dashboard/templates/index.html | 19 ++++- scripts/generate_shenron.py | 142 +++++++++++++++++++++++++++------ 4 files changed, 318 insertions(+), 25 deletions(-) diff --git a/dashboard/static/app.js b/dashboard/static/app.js index 5f9dc72..b780af7 100644 --- a/dashboard/static/app.js +++ b/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 += ''; + 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 += ''; + 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 += ''; + 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 `${label}${value}`; + } + function setLoadingState(isLoading) { launchBtn.disabled = isLoading; launchBtn.style.display = isLoading ? 'none' : 'block'; diff --git a/dashboard/static/style.css b/dashboard/static/style.css index a12cc6e..e6888a4 100644 --- a/dashboard/static/style.css +++ b/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); diff --git a/dashboard/templates/index.html b/dashboard/templates/index.html index 537e164..ff170de 100644 --- a/dashboard/templates/index.html +++ b/dashboard/templates/index.html @@ -142,7 +142,7 @@ - + + + + +
> Application initialized. Fetching presets...
diff --git a/scripts/generate_shenron.py b/scripts/generate_shenron.py index bdb7193..a43b336 100644 --- a/scripts/generate_shenron.py +++ b/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() + + 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: - # 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,15 +141,61 @@ 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) - 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: 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" @@ -103,7 +203,6 @@ def main(): 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!")