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 @@
-
+
SIMULATION STATUS
@@ -152,6 +152,23 @@
+
+
+
+ ⚡ SHENRON SYNTHESIS
+ 0% (0 / 0 frames)
+
+
+
ETA: --
+
+
+
> 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!")