|
|
@ -116,6 +116,7 @@ class ShenronRadarModel: |
|
|
|
|
|
|
|
|
# 5. Capture Advanced Metrology |
|
|
# 5. Capture Advanced Metrology |
|
|
# Calculate SNR and basic noise stats for the Frame Metrics |
|
|
# Calculate SNR and basic noise stats for the Frame Metrics |
|
|
|
|
|
self.last_pcd = rich_pcd |
|
|
self.last_metrology = metrology |
|
|
self.last_metrology = metrology |
|
|
|
|
|
|
|
|
return rich_pcd |
|
|
return rich_pcd |
|
|
@ -148,17 +149,76 @@ class ShenronRadarModel: |
|
|
|
|
|
|
|
|
rd = self.last_metrology['rd_heatmap'] |
|
|
rd = self.last_metrology['rd_heatmap'] |
|
|
noise = self.last_metrology['threshold_matrix'] |
|
|
noise = self.last_metrology['threshold_matrix'] |
|
|
|
|
|
ra = self.last_metrology['ra_heatmap'] |
|
|
|
|
|
|
|
|
peak_mag = np.max(rd) |
|
|
peak_mag = np.max(rd) |
|
|
avg_noise = np.mean(noise) |
|
|
avg_noise = np.mean(noise) |
|
|
snr = 10 * np.log10(peak_mag / avg_noise) if avg_noise > 0 else 0 |
|
|
snr = 10 * np.log10(peak_mag / avg_noise) if avg_noise > 0 else 0 |
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
|
metrics = { |
|
|
"peak_magnitude": float(peak_mag), |
|
|
"peak_magnitude": float(peak_mag), |
|
|
"avg_noise_floor": float(avg_noise), |
|
|
"avg_noise_floor": float(avg_noise), |
|
|
"peak_snr_db": float(snr), |
|
|
"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): |
|
|
def get_radar_specs(self): |
|
|
"""Return radar hardware specs for dashboard telemetry.""" |
|
|
"""Return radar hardware specs for dashboard telemetry.""" |
|
|
|