Browse Source

feat(radar): Major Update - Shenron Modularity and Physics Refinement (Iteration 18)

Transitioned the Shenron radar engine from a rigid architecture to a modular,
tunable "Knobs and Dials" framework. This update establishes a physics-first
baseline derived from real-world electromagnetic behavior.

Core Changes:
- Modular Radar Profiles: Pre-configured profiles for TI Cascade, Radarbook,
  and AWRL1432 with hardware-specific Bandwidth, Chirp, and nRx parameters.
- Physics Core (Sceneset.py):
    - Full implementation of Fresnel and Beckmann-Spizzichino scattering.
    - Pure 1/R^4 power law (1/R^2 transmit, 1/R^2 receive) via legacy scaling removal.
    - Fixed cos(cos(theta)) bug in CARLA semantic lidar mapping.
- Antenna Gain Integration:
    - Implemented separable Azimuth/Elevation gain patterns.
    - Added Symmetric Azimuth LUT interpolation and Vertical FOV Hard Cutoff.
- Signal Processing:
    - Optimized GPU-accelerated signal synthesis using PyTorch.
    - Standardized 110 dB System Calibration Constant for hardware SNR matching.

Additional Documentation:
- intel/radar/SHENRON_MODULAR_ARCHITECTURE.md: Architecture and "Knobs/Dials" overview.
- intel/radar/SHENRON_ANTENNA_GAIN_CALIBRATION.md: Physics of Antenna Gain and 1/R^4 logic.

Note: Remaining magic numbers in get_loss_3 (K_sq, scat_normalization, lobe_frac)
are noted for future migration into ConfigureRadar.py.
main
RUSHIL AMBARISH KADU 1 month ago
parent
commit
a367026622
  1. 227
      intel/radar/Azimuth_Elevation_Gain_Implementation.md
  2. 28
      intel/radar/SHENRON_ANTENNA_GAIN_CALIBRATION.md
  3. 30
      intel/radar/SHENRON_MODULAR_ARCHITECTURE.md
  4. 74
      intel/radar/radar_architecture_answers.md
  5. 62
      scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/ConfigureRadar.py
  6. 1
      scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/lidar.py
  7. 53
      scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/shenron/Sceneset.py

227
intel/radar/Azimuth_Elevation_Gain_Implementation.md

@ -0,0 +1,227 @@
Below is a **precise, implementation‑ready plan** tailored to your current Shenron architecture. This plan assumes **both Azimuth and Elevation LUTs already exist** and focuses on *minimal, safe, radar‑local integration*.
***
# Implementation Plan: Azimuth & Elevation Radiation Gain Integration
## Goal
Introduce **radar‑specific azimuth and elevation antenna gains** into Shenron such that:
* Each radar applies its own antenna pattern
* No shared energy values are contaminated
* Existing energy models and ADC pipelines remain intact
* Integration aligns with current physics and code structure
***
## 1. Data Model Extensions (ConfigureRadar.py)
### 1.1 Add Antenna LUT Fields to Radar Configuration
Extend the radar configuration object to carry antenna patterns.
**New Radar Attributes**
* `radar.azimuth_lut`
* `radar.elevation_lut`
* `radar.azimuth_lut_angles` (degrees or radians)
* `radar.elevation_lut_angles`
* `radar.antenna_gain_mode` (e.g., `"separable"`)
These must be **radar‑instance local**, not global.
***
### 1.2 LUT Normalization Convention (Decide Once)
Choose and enforce one convention:
* **Linear gain** (recommended, to match existing loss math)
* LUT values ∈ ℝ⁺
* OR
* **dB gain**, converted to linear on load
✅ Best practice:
Convert LUTs to **linear gain at load time** and store only linear values.
***
## 2. Angle Availability Contract
### 2.1 Confirm Angle Variables
From your architecture:
* Azimuth (`theta`)
* Elevation (`elev_angle`)
are already computed in `specularpoints()`.
**Action**
* Ensure both angles are:
* In a known unit (prefer radians internally)
* Passed directly to `get_loss_3()` with no recomputation
No structural changes needed here.
***
## 3. Loss Chain Integration Point (Sceneset.py)
### 3.1 Insert Antenna Gain in get\_loss\_3()
Modify `get_loss_3()` to include antenna LUT gain.
**Exact placement**
* After range loss
* Before material / roughness losses
* Adjacent to existing vertical antenna gain logic
This preserves physical correctness:
> Antenna attenuates the incident and received wave, not the material physics.
***
### 3.2 Compute Antenna Gain Per Point
For each point $$i$$:
1. Clamp / wrap angles to LUT domain
* Azimuth: e.g. $$[-90°, +90°]$$
* Elevation: e.g. $$[-45°, +45°]$$
2. Lookup gains:
* `G_az[i] = interp(radar.az_lut, theta[i])`
* `G_el[i] = interp(radar.el_lut, elev_angle[i])`
3. Combine gains (separable model):
$$
G_{ant}[i] = G_{az}[i] \times G_{el}[i]
$$
4. Apply:
$$
loss[i] \leftarrow loss[i] \times G_{ant}[i]
$$
✅ Fully vectorized NumPy operation.
***
## 4. Multi‑Radar Safety Guarantees
No special handling required **if the following rules are followed**:
* Never modify shared `semantic_lidar_data`
* Antenna gain applied only to:
* Local loss arrays inside `get_loss_3()`
* No mutation of radar‑agnostic structures
Your architecture already satisfies these conditions.
***
## 5. Configuration Loading & Switching
### 5.1 Attach LUTs per Radar Profile
In `ConfigureRadar.py`:
* Load LUTs as part of radar initialization
* Support per‑profile antenna selection:
* `"awrl1432_front"`
* `"awrl1432_corner"`
* `"generic_patch"`
### 5.2 Runtime Safety
Since each `ShenronRadarModel` owns its radar instance:
* Switching patterns between radars is safe
* No cross‑profile contamination possible
***
## 6. ADC Pipeline Interaction (No Changes Required)
Because:
* Antenna gain is applied **before ADC synthesis**
* Noise is added later in `heatmap_gen_fast.py`
You automatically get:
* Angle‑dependent SNR
* Realistic CFAR behavior
* Hardware‑like edge‑of‑FOV noise dominance
✅ Do **not** touch ADC or noise code.
***
## 7. Validation Hooks (Strongly Recommended)
### 7.1 Internal Diagnostics
Add (optional but useful):
* Store `G_az`, `G_el`, `G_ant` in `last_metrology`
* Log min / max / mean gain per frame
### 7.2 Visual Validation
Confirm:
* Point density decreases smoothly at FOV edges
* Peak intensity aligns at boresight
* Multiple radars show different angular falloff in the same scene
***
## 8. Performance Impact
* Operations: O(N) per radar, vectorized
* N ≈ 5k–15k points
* LUT interpolation cost negligible
✅ Safe for real‑time simulation with multiple radars.
***
## 9. Extension‑Ready Design
This structure naturally supports later:
* 2D az‑el LUTs
* Per‑virtual‑antenna offsets
* Beamforming‑aware gain shaping
No redesign required.
***
## ✅ Final Summary
| Aspect | Status |
| ----------------------- | ------ |
| Radar‑specific | ✅ Yes |
| Multi‑radar safe | ✅ Yes |
| Minimal code changes | ✅ Yes |
| Physically correct | ✅ Yes |
| ADC‑correct integration | ✅ Yes |
**This plan integrates azimuth & elevation radiation patterns into Shenron cleanly, deterministically, and without architectural risk.**
If you want, next I can:
* Write **exact pseudocode for get\_loss\_3()**
* Define **LUT file formats**
* Suggest **default az/el cutoffs for AWRL1432**
* Design a **radar antenna profile schema**
Just say what’s next.

28
intel/radar/SHENRON_ANTENNA_GAIN_CALIBRATION.md

@ -0,0 +1,28 @@
# Shenron Antenna Gain & Compensation Logic
## 📡 Azimuth & Elevation Gain Integration
The latest iteration implements a **separable antenna pattern model**. This ensures that targets detected at the edges of the radar's Field of View (FOV) are correctly attenuated, matching real-world hardware behavior.
### 1. Implementation Details (`Sceneset.py`)
In `get_loss_3()`, the antenna gain $G_{ant}$ is calculated as the product of horizontal and vertical patterns:
$$G_{ant}(\theta, \phi) = G_{az}(\theta) \times G_{el}(\phi)$$
- **Azimuth ($\theta$):** Uses a Look-Up Table (LUT) for symmetric gain interpolation. Points outside the precise LUT range are clamped to the nearest edge value.
- **Elevation ($\phi$):** Implements a **Hard FOV Cutoff**. Points beyond the mechanical vertical beamwidth receive zero gain, accurately simulating physical sensor limitations.
- **Physics Order:** Gain is applied to the incident power *after* the path loss but *before* the material interaction, ensuring that the antenna pattern does not affect target reflectivity.
### 2. High-Level System Gain (`gain = 110 dB`)
In `ConfigureRadar.py`, the parameter `self.gain` is set to values like `10 ** (110 / 10)`.
**Why 110 dB?**
This is a **System-Level Calibration Constant** that encapsulates several physical and simulation factors:
1. **Transmit Power ($P_t$):** The actual chirping power emitted (typically ~10-15 dBm).
2. **Peak Antenna Gain ($G_t, G_r$):** The boresight gain of the TX and RX patches.
3. **Signal Chain Amplification:** Hardware-specific gains in the LNA and Mixer.
4. **Simulation Scaling:** In physics-based simulations, units computed for unit scatterers ($1/R^2$) result in extremely small floating-point values. The 110 dB factor "lifts" these signals into a dynamic range that maps correctly to real 16-bit or 32-bit ADC data formats.
By tuning this single "dial," the simulation can be calibrated to match the peak Signal-to-Noise Ratio (SNR) seen in real-world hardware datasets.
## ⚙️ Calibration Flow
- **Standard Baseline:** 110 dB (Calibrated for Iteration 16/18 physics).
- **Match Hardware:** Adjust this dial ±5 dB if targets at 10m in simulation appear significantly stronger/weaker than in real sensor captures.

30
intel/radar/SHENRON_MODULAR_ARCHITECTURE.md

@ -0,0 +1,30 @@
# Shenron Modular Radar Architecture
## Overview
Shenron is a high-fidelity, physics-based radar simulation engine designed for multi-modal ADAS research. The latest iteration (Iteration 18) introduces a modular "Knobs and Dials" approach, allowing the engine to be tuned to match specific hardware characteristics via a centralized configuration system.
## 🏗️ Core Components
### 1. Radar Configuration (`ConfigureRadar.py`)
This is the "Control Panel" of the simulation. It provides:
- **Modular Profiles:** Pre-configured settings for `ti_cascade`, `radarbook`, and `awrl1432`.
- **Tunable Parameters:**
- `B` (Bandwidth): Impacts range resolution.
- `chirps`: Impact Doppler resolution and processing gain.
- `nRx`: Number of receiving antennas (virtual antennas).
- `gain`: System-level calibration constant ($P_t G_t G_r$ and scaling).
- `noise_amp`: High-level noise floor control.
### 2. Physics Modeling (`Sceneset.py`)
The engine simulates electromagnetic interactions using deterministic physical laws:
- **Material Interactions:** Uses Fresnel equations for reflection and Beckmann-Spizzichino for scattering, indexed by material type (Metal, Concrete, Wood, etc.).
- **1/R⁴ Power Law:** Implements a pure physical $1/R^2$ transmit and $1/R^2$ receive path loss, ensuring signal strength decays naturally with range.
- **Occlusion & Hidden Points:** Uses Open3D's hidden point removal and KD-Trees to simulate line-of-sight and density.
### 3. Signal Generation (`heatmap_gen_fast.py`)
- **GPU Acceleration:** High-performance signal synthesis using PyTorch.
- **MIMO Processing:** Simulates multi-chirp frames and doppler shifts.
- **ADC Synthesis:** Multiplies the physics-based voltage signal by the hardware calibration constant to produce raw I/Q-like data.
## 🎛️ The "Knobs and Dials" Philosophy
The engine is built to be "physics-first, tuning-second." By maintaining a rigid physical baseline (1/R⁴, Fresnel), we can trust that the simulator's spatial and temporal behavior is correct. The "dials" (Bandwidth, Gain, Noise) are used solely to align the simulation with real-world sensor specifications.

74
intel/radar/radar_architecture_answers.md

@ -0,0 +1,74 @@
# Shenron Radar Architecture Breakdown
This document provides a precise architectural analysis of the Shenron radar simulation pipeline, focusing on ownership, energy flow, and multi-radar extensibility.
### 🏗️ Radar Abstraction & Ownership
1. **Radar Instance Representation**: Represented as a `radar` class in `ConfigureRadar.py`. It is a configuration-heavy object containing both hardware specs (frequency, bandwidth) and physics parameters (beamwidth, gain).
2. **Signal Generation Ownership**: Each `ShenronRadarModel` (in `model_wrapper.py`) own its own `radar_obj`. The pipeline is private to the instance; radars share the input **Semantic LiDAR** but do not share intermediate artifacts like ADC cubes or calculated losses.
3. **Metadata Storage**: All sensor-specific metadata (mount pose, FOV, waveform, noise floor) is stored as attributes of the `radar` class. It is accessed by name (e.g., `radar_type='awrl1432'`) during model initialization.
4. **Sequential vs Parallel**: Processing is currently **sequential**. Within a single frame, the orchestrator iterates through defined radar profiles, generating one set of ADC samples and one point cloud at a time.
### ⚡ Energy Flow & Reuse
5. **Reflected Energy Stage**: First computed in `Sceneset.py` within the `get_loss_3` function.
6. **Energy Form**: Linear power (normalized unitless factor representing RCS + Path Loss).
7. **Storage Granularity**: Stored **per-point** in a 1D array of length $N_{pts}$.
8. **Reuse vs Recompute**: It is currently **recomputed** for every radar profile. Even if the points are identical, `get_loss_3` is called fresh for each radar instance to account for radar-specific gains.
9. **Radar-Agnostic Stages**: Only the raw `semantic_lidar_data` ingestion and the basic axis-swap in `lidar.py` are truly radar-agnostic. Once the data enters `Cropped_forRadar`, it becomes radar-dependent.
### 🌐 Coordinate Frames & Angular Knowledge
10. **Transformation Point**: Points are transformed into radar-local coordinates in `lidar.py` inside `Cropped_forRadar` via the `rotate_points` function and mount-offset subtraction (e.g., `CARLA_X - 2.0`).
11. **Derived Quantities**: Azimuth (`theta`) and Elevation (`elev_angle`) are computed derivationally in `specularpoints()` (Lines 221-222) before being passed to the loss function. They are not currently stored in the point structure.
12. **Re-casting Rays**: Points are represented in Cartesian XYZ; azimuth and elevation can be (and are) computed analytically without re-casting rays.
13. **Occlusion & Incidence**: Occlusion is strictly **radar-dependent** (calculated from the `radar.center` viewpoint). Incidence angle is calculated relative to surface normals and is partially shared as it is scene-dependent, but its power-contribution is radar-dependent.
### 📉 Loss / Gain Modeling
14. **Modular Support**: Modular support is limited. Code uses fixed functions (`get_loss_3`); there is no plug-in architecture for gain blocks yet.
15. **Application Relative Order**: Applied in the following order: Range Loss ($1/R^2$ transmit) → Material Permittivity Loss → Surface Roughness Loss → Vertical Antenna Gain. Horizontal gain is currently missing.
16. **Mathematical Domain**: Applied **multiplicatively in linear space**.
17. **Scaling Mechanism**: Scaling is currently injected via the `radar.gain` and `radar.vertical_beamwidth` properties passed into the physics functions.
### 🛡️ Multi‑Radar Safety
18. **Shared Tick Structures**: The incoming `semantic_lidar_data` numpy array is the primary shared structure.
19. **Contamination Risk**: Low. `lidar.py` and `Sceneset` operate on slices or copies (e.g., `skew_pc`, `new_pc`). Internal modifications to `pc[:, 4]` exist but usually target local copies.
20. **Immutability**: Not strictly enforced by Python, but the workflow treats the input LiDAR as a read-only source.
21. **Attribute Cloning**: Introducing per-radar antenna loss would **not** require cloning the whole attribute list; it simply requires a new temporary array produced by the radar-specific loss function.
### 📡 ADC & Noise Injection
22. **Thermal Noise Stage**: Added during the ADC synthesis in `heatmap_gen_fast.py` (Line 146). It is added to the clean signal after beamforming.
23. **ADC Sharing**: Strictly **per-radar**. ADC cubes are not shared across profiles.
24. **SNR Variation**: Easily introduced via the `radar.noise_amp` property in the profile.
25. **Power Preservation**: Signal power and noise power are calculated separately and summed; they are not tracked as separate streams after summation.
### ⚙️ Configuration & Extensibility
26. **Profile Loading**: Hardcoded Python classes/dictionaries in `ConfigureRadar.py`.
27. **Sensor LUTs**: Existing precedent in `lidar.py` for semantic-to-material mapping tables.
28. **Radiation Patterns**: Can be attached as scalar beamwidths (current) or arrays/LUTs (feasible).
29. **Runtime Switching**: Fully supported via the `ShenronRadarModel` wrapper.
### 🚀 Performance & Scaling
30. **Point Budget**: 5,000 – 15,000 points per radar per frame (post-cropping).
31. **LUT Performance**: Highly acceptable; NumPy vectorized lookups for material/angle coefficients are not a bottleneck.
32. **Memory Pressure**: Low for point data. High for ADC cubes and large Range-Doppler FFT matrices if many radars run concurrently.
### 🐞 Validation & Debugging
33. **Logging Hooks**: The `last_metrology` dictionary in `model_wrapper.py` serves as the primary hook for internal diagnostics.
34. **Energy Annotation**: The internal `loss` array is calculated but usually discarded; it is not currently appended to the return point cloud.
35. **Comparison Hook**: Radars can be compared frame-by-frame by running two model instances on the same `semantic_lidar_data` buffer.
### 🔮 Future‑Proofing
36. **MIMO/Virtual Antennas**: Logic lives in `heatmap_gen_fast.py` inside the torch-accelerated phase-delay loop.
37. **Physics Separation**: A partial separation exists. `Sceneset.py` is "Scene Physics" (Reflection/Scattering), while `ConfigureRadar.py` is "Sensor Physics."
38. **Antenna Pattern Logic**: Logically belongs to the **Sensor Model** (`ConfigureRadar`) but must be applied during the **Scene Physics** stage to attenuate incident rays before they become ADC signals.
***
*Generated by Antigravity AI | Fox Radar ADAS Pipeline Architecture Review*

62
scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/ConfigureRadar.py

@ -47,6 +47,15 @@ class radar():
self.voxel_phi = 2.0 # 0.5 # 0.1 self.voxel_phi = 2.0 # 0.5 # 0.1
self.voxel_rho = 0.05 # 0.1 # 0.05 self.voxel_rho = 0.05 # 0.1 # 0.05
# Antenna Pattern LUTs (loaded via load_antenna_luts — radar-local)
# Mode defaults to "gaussian" (Iteration 14a fallback) until LUTs are loaded.
self.azimuth_lut_angles_deg = None
self.azimuth_lut_gain_dB = None
self.elevation_lut_angles_deg = None
self.elevation_lut_gain_dB = None
self.elevation_fov_cutoff_deg = None
self.antenna_gain_mode = "gaussian"
elif radartype == "radarbook": elif radartype == "radarbook":
self.center = np.array([0.0, 4.0]) # center of radar self.center = np.array([0.0, 4.0]) # center of radar
self.elv = np.array([0.5]) # self position in z-axis self.elv = np.array([0.5]) # self position in z-axis
@ -84,6 +93,14 @@ class radar():
self.voxel_phi = 2 # 0.5 # 0.1 self.voxel_phi = 2 # 0.5 # 0.1
self.voxel_rho = 0.05 # 0.1 # 0.05 self.voxel_rho = 0.05 # 0.1 # 0.05
# Antenna Pattern LUTs (loaded via load_antenna_luts — radar-local)
self.azimuth_lut_angles_deg = None
self.azimuth_lut_gain_dB = None
self.elevation_lut_angles_deg = None
self.elevation_lut_gain_dB = None
self.elevation_fov_cutoff_deg = None
self.antenna_gain_mode = "gaussian"
elif radartype == "awrl1432": elif radartype == "awrl1432":
self.center = np.array([0.0, 4.0]) # center of radar self.center = np.array([0.0, 4.0]) # center of radar
self.elv = np.array([0.5]) # self position in z-axis self.elv = np.array([0.5]) # self position in z-axis
@ -120,6 +137,17 @@ class radar():
self.voxel_phi = 2.0 # 0.5 # 0.1 self.voxel_phi = 2.0 # 0.5 # 0.1
self.voxel_rho = 0.05 # 0.1 # 0.05 self.voxel_rho = 0.05 # 0.1 # 0.05
# --- Native Antenna Pattern LUTs (AWRL1432BOOST-BSD TX/RX Averaged) ---
# Azimuth: Symmetric -75 to +75 deg
self.azimuth_lut_angles_deg = np.array([-75, -70, -65, -60, -55, -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75])
self.azimuth_lut_gain_dB = np.array([-34, -24, -17, -14, -12, -10, -8, -6, -4, -3, -2, -2, -1, -1, 0, 0, 0, -1, -1, -2, -2, -3, -4, -6, -8, -10, -12, -14, -20, -32, -42])
# Elevation: Symmetric 0 to 20 deg (indexed by abs off-boresight in physics engine)
self.elevation_lut_angles_deg = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
self.elevation_lut_gain_dB = np.array([0, -1, -1, -2, -3, -4, -5, -7, -9, -11, -14, -17, -20, -23, -26, -29, -31, -33, -35, -36, -38])
self.elevation_fov_cutoff_deg = 20.0
self.antenna_gain_mode = "lut"
else: else:
raise Exception("Incorrect radartype selected") raise Exception("Incorrect radartype selected")
@ -137,3 +165,37 @@ class radar():
raise Exception("Incorrect radartype selected") raise Exception("Incorrect radartype selected")
return signal_Noisy return signal_Noisy
def load_antenna_luts(self, az_csv_path, el_csv_path):
"""
Load normalized antenna gain LUTs (dB) from CSV files.
Expected CSV format (two columns, with header row):
angle_deg,gain_db_norm
Azimuth LUT : full symmetric range (e.g. -75° to +75°).
Points outside the LUT range are clamped to the nearest edge value.
Elevation LUT: positive half only [0° to cutoff°], indexed by abs(off-boresight angle).
Points outside the FOV cutoff receive gain = 0 (hard physical cutoff).
dB values are stored for dB-domain interpolation; linear conversion happens
inside get_loss_3() after the interpolation step (as per interpolation rules).
"""
az_data = np.loadtxt(az_csv_path, delimiter=',', skiprows=1)
el_data = np.loadtxt(el_csv_path, delimiter=',', skiprows=1)
# Azimuth: store full range as-is
self.azimuth_lut_angles_deg = az_data[:, 0]
self.azimuth_lut_gain_dB = az_data[:, 1]
# Elevation: expects positive-half table [0..cutoff] for abs-valued lookup
self.elevation_lut_angles_deg = el_data[:, 0]
self.elevation_lut_gain_dB = el_data[:, 1]
self.elevation_fov_cutoff_deg = float(el_data[-1, 0]) # hard cutoff = last entry
self.antenna_gain_mode = "lut"
print(f"[ConfigureRadar] Antenna LUTs loaded for '{self.radartype}'"
f"AZ: {len(self.azimuth_lut_angles_deg)} pts "
f"[{self.azimuth_lut_angles_deg[0]:.0f}° to {self.azimuth_lut_angles_deg[-1]:.0f}°], "
f"EL: {len(self.elevation_lut_angles_deg)} pts "
f"[0° to {self.elevation_fov_cutoff_deg:.0f}° FOV cutoff]")

1
scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/lidar.py

@ -131,6 +131,7 @@ def run_lidar(sim_config, sem_lidar_frame, radarobj=None):
#Lidar specific settings #Lidar specific settings
if radarobj is None: if radarobj is None:
radarobj = radar(sim_config["RADAR_TYPE"]) radarobj = radar(sim_config["RADAR_TYPE"])
# radarobj.chirps = 128 # radarobj.chirps = 128
radarobj.center = np.array([0.0, 0.0]) # center of radar radarobj.center = np.array([0.0, 0.0]) # center of radar
radarobj.elv = np.array([0.0]) radarobj.elv = np.array([0.0])

53
scripts/ISOLATE/e2e_agent_sem_lidar2shenron_package/shenron/Sceneset.py

@ -220,6 +220,10 @@ class Sceneset():
rho = np.linalg.norm(spec_mask[:, 0:3] - np.hstack((radar.center, radar.elv)), axis=1) rho = np.linalg.norm(spec_mask[:, 0:3] - np.hstack((radar.center, radar.elv)), axis=1)
theta = math.pi / 2 - np.arctan(((spec_mask[:, 0] - radar.center[0]) / (spec_mask[:, 1] - radar.center[1]))) theta = math.pi / 2 - np.arctan(((spec_mask[:, 0] - radar.center[0]) / (spec_mask[:, 1] - radar.center[1])))
elev_angle = np.arccos((spec_mask[:, 2] - radar.elv)/rho) elev_angle = np.arccos((spec_mask[:, 2] - radar.elv)/rho)
# Off-boresight azimuth angle for antenna LUT lookup (0 = boresight forward, +ve = right)
# Uses arctan2 for quadrant safety. Distinct from theta (which feeds the heatmap beamformer).
az_boresight = np.arctan2(spec_mask[:, 0] - radar.center[0],
spec_mask[:, 1] - radar.center[1])
# loss = np.zeros((len(theta),), dtype=float) # loss = np.zeros((len(theta),), dtype=float)
# for i in range(len(theta)): # for i in range(len(theta)):
# if self.rad_scene[i, 4] == 0.1: # if self.rad_scene[i, 4] == 0.1:
@ -234,7 +238,7 @@ class Sceneset():
# pc = o3d.io.read_point_cloud(file_path) # pc = o3d.io.read_point_cloud(file_path)
# pc.points = o3d.utility.Vector3dVector(self.rad_scene[:, 0:3]) # pc.points = o3d.utility.Vector3dVector(self.rad_scene[:, 0:3])
# o3d.visualization.draw_geometries([pc]) # o3d.visualization.draw_geometries([pc])
loss_att,_,_ = get_loss_3(self.rad_scene, rho, elev_angle , angles_carla, radar, use_spec = False, use_diffused = True, no_material=False)
loss_att,_,_ = get_loss_3(self.rad_scene, rho, az_boresight, elev_angle, angles_carla, radar, use_spec=False, use_diffused=True, no_material=False)
return rho, theta, loss_att, speed, angles return rho, theta, loss_att, speed, angles
@ -357,7 +361,7 @@ def get_loss_2(points, rho, angles, radar, use_spec = True, use_diffused = True,
return loss, P_scat_inc, P_spec_inc return loss, P_scat_inc, P_spec_inc
def get_loss_3(points, rho, elev_angle, angles, radar, use_spec = True, use_diffused = True, no_material = False):
def get_loss_3(points, rho, az_boresight, elev_angle, angles, radar, use_spec = True, use_diffused = True, no_material = False):
''' '''
Note: angles should be used for power calculation as it is in reflecting surface coordinate system while theta is in radar coordinate system Note: angles should be used for power calculation as it is in reflecting surface coordinate system while theta is in radar coordinate system
@ -409,18 +413,43 @@ def get_loss_3(points, rho, elev_angle, angles, radar, use_spec = True, use_diff
spec_angle_thresh = 2.0*np.pi/180 # Tightened back to 2.0 degrees for physical realism spec_angle_thresh = 2.0*np.pi/180 # Tightened back to 2.0 degrees for physical realism
# --- Iteration 14a: Vertical Antenna Gain (Gaussian Damping) ---
# Determine elevation in degrees relative to boresight (horizontal).
phi_deg = np.rad2deg(np.abs(np.pi/2 - elev_angle))
G_vertical = np.exp(-2.77 * np.power(phi_deg / radar.vertical_beamwidth, 2))
# --- Iteration 18: Measured Azimuth & Elevation Antenna Gain LUTs ---
# Separable model: G_ant(az, el) = G_az(az) * G_el(el) — both linear, both normalized.
# Interpolation rules: clamp → interp in dB → convert dB→linear → floor at 1e-6.
# Azimuth: clamp to LUT edges (physical edge value preserved, not zeroed).
# Elevation: hard zero outside FOV cutoff (physical vertical FOV enforcement).
if radar.antenna_gain_mode == "lut" and radar.azimuth_lut_gain_dB is not None:
# -- Azimuth gain --
az_deg = np.rad2deg(az_boresight)
az_deg_clamped = np.clip(az_deg,
radar.azimuth_lut_angles_deg[0],
radar.azimuth_lut_angles_deg[-1])
G_az_dB = np.interp(az_deg_clamped,
radar.azimuth_lut_angles_deg,
radar.azimuth_lut_gain_dB)
G_az = np.maximum(10.0 ** (G_az_dB / 10.0), 1e-6) # dB → linear, floor at -60 dB
# -- Elevation gain (hard cutoff outside physical FOV) --
el_deg = np.rad2deg(np.abs(np.pi / 2 - elev_angle)) # 0° = boresight
in_fov = el_deg <= radar.elevation_fov_cutoff_deg
el_deg_clamped = np.clip(el_deg, 0.0, radar.elevation_fov_cutoff_deg)
G_el_dB = np.interp(el_deg_clamped,
radar.elevation_lut_angles_deg,
radar.elevation_lut_gain_dB)
G_el = np.where(in_fov, np.maximum(10.0 ** (G_el_dB / 10.0), 1e-6), 0.0)
# Separable antenna gain model
G_ant = G_az * G_el
# --- Iteration 17: Pure Physical 1/R^4 Alignment ---
# We have removed all legacy normalizations (DENSITY_REF, rho^2 Area scaling).
# Each LiDAR point now acts as a raw unit scatterer.
else:
# Fallback: Gaussian approximation (Iteration 14a — active until LUTs are loaded)
phi_deg = np.rad2deg(np.abs(np.pi / 2 - elev_angle))
G_ant = np.exp(-2.77 * np.power(phi_deg / radar.vertical_beamwidth, 2))
# Calculate Incident Power (FMCW transmitter path loss: 1/R^2)
# Powerhitting the surface = Pt * Gt / (4 * pi * R^2)
P_incident = (1 / np.power(rho, tx_dist_loss_exponent)) * K_sq * G_vertical
# --- Iteration 17 preserved: Pure Physical 1/R^2 Tx path loss ---
# Each LiDAR point acts as a raw unit scatterer. No legacy density normalization.
P_incident = (1 / np.power(rho, tx_dist_loss_exponent)) * K_sq * G_ant
# DEBUG: Monitor Signal Trends # DEBUG: Monitor Signal Trends
# P_inc print suppressed — data captured via model.get_signal_metrics() telemetry # P_inc print suppressed — data captured via model.get_signal_metrics() telemetry

Loading…
Cancel
Save