diff --git a/scripts/ISOLATE/model_wrapper.py b/scripts/ISOLATE/model_wrapper.py index e330f2e..d95251e 100644 --- a/scripts/ISOLATE/model_wrapper.py +++ b/scripts/ISOLATE/model_wrapper.py @@ -116,6 +116,7 @@ class ShenronRadarModel: # 5. Capture Advanced Metrology # Calculate SNR and basic noise stats for the Frame Metrics + self.last_pcd = rich_pcd self.last_metrology = metrology return rich_pcd @@ -148,17 +149,76 @@ class ShenronRadarModel: rd = self.last_metrology['rd_heatmap'] noise = self.last_metrology['threshold_matrix'] + ra = self.last_metrology['ra_heatmap'] peak_mag = np.max(rd) avg_noise = np.mean(noise) snr = 10 * np.log10(peak_mag / avg_noise) if avg_noise > 0 else 0 - return { + metrics = { "peak_magnitude": float(peak_mag), "avg_noise_floor": float(avg_noise), "peak_snr_db": float(snr), - "active_bins": int(np.sum(rd > avg_noise)) + "active_bins": int(np.sum(rd > noise)) } + + try: + # 1. Point Cloud Metrics + pcd = getattr(self, 'last_pcd', np.array([])) + cfar_count = len(pcd) + + if cfar_count > 0: + ranges = np.linalg.norm(pcd[:, :2], axis=1) + vels = pcd[:, 3] + farthest = np.max(ranges) + closest = np.min(ranges) + mean_doppler = np.mean(np.abs(vels)) + doppler_var = np.var(vels) + else: + farthest = 0.0 + closest = 0.0 + mean_doppler = 0.0 + doppler_var = 0.0 + + metrics["cfar_target_count"] = int(cfar_count) + metrics["farthest_target_m"] = float(farthest) + metrics["closest_target_m"] = float(closest) + metrics["mean_absolute_doppler"] = float(mean_doppler) + metrics["doppler_variance"] = float(doppler_var) + + # 2. Physical Array Metrics + min_hit = np.min(rd[rd > noise]) if cfar_count > 0 else avg_noise + dyn_range = 10 * np.log10(peak_mag / min_hit) if peak_mag > 0 and min_hit > 0 else 0.0 + + r_axis = self.processor.rangeAxis + v_axis = self.processor.velAxis + r_mask = r_axis < 5.0 + v_mask = np.abs(v_axis) < 1.0 + + if len(r_axis) == rd.shape[0] and len(v_axis) == rd.shape[1]: + ego_power = np.sum(rd[np.ix_(r_mask, v_mask)]) + else: + ego_power = 0.0 + + clutter_bins = (rd > avg_noise) & (rd <= noise) + clutter_ratio = np.sum(clutter_bins) / rd.size + avg_clutter = np.mean(rd[clutter_bins]) if np.any(clutter_bins) else avg_noise + scr = 10 * np.log10(peak_mag / avg_clutter) if avg_clutter > 0 else snr + + metrics["dynamic_range_db"] = float(dyn_range) + metrics["ego_vicinity_power"] = float(ego_power) + metrics["clutter_ratio"] = float(clutter_ratio) + metrics["signal_to_clutter_ratio_db"] = float(scr) + + # 3. Array Health + az_var = np.mean(np.var(ra, axis=1)) if ra is not None else 0.0 + metrics["azimuth_variance"] = float(az_var) + + except Exception as e: + print(f"[WARNING] Advanced metrology calculation failed: {e}") + pass + + return metrics def get_radar_specs(self): """Return radar hardware specs for dashboard telemetry."""