import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
FMCW Radar RCS Measurement: Rotating Flat Plate¶
Radar cross section (RCS) depends critically on aspect angle — the target's orientation relative to the radar. A flat plate illuminated at normal incidence (0°) produces maximum RCS, while edge-on (90°) yields minimal return. The RCS varies as:
$$\sigma_{max} = \frac{4\pi a^2 b^2}{\lambda^2}, \qquad \sigma(\theta) \propto \left|\frac{\sin(k a \sin\theta)}{k a \sin\theta}\right|^2$$
where $a, b$ are plate dimensions, $\lambda$ is wavelength, and $k = 2\pi/\lambda$. The sinc² pattern produces nulls at:
$$\theta_{null} = \arcsin\left(\frac{\lambda}{2a}\right)$$
For a 5 m plate at 1 GHz (λ = 0.3 m): $\theta_{null} \approx 1.7°$.
This Example¶
- Radar: 1 GHz FMCW (L-band), 100 MHz BW → ΔR = 1.5 m
- Target: 5×5 m flat plate at 200 m, rotating at 1°/s
- Measurement: 180 chirps over 90 s (0.5 s PRP) → 0° to 90° coverage
- Processing: Range FFT → extract peak amplitude → RCS vs. angle curve
Expected: σ_max ≈ 40 dBsm at 0°, first null near 1.7°, ~20-30 dB drop at 90°.
Radar System Configuration¶
Import Required Modules¶
import numpy as np
import plotly.graph_objs as go
import plotly.express as px
from IPython.display import Image, display
from radarsimpy import Radar, Transmitter, Receiver
# Set to True for interactive plots; False renders a static JPEG (e.g. for HTML export)
INTERACTIVE = False
def show(fig):
if INTERACTIVE:
fig.show()
else:
display(Image(fig.to_image(format="jpg", scale=2)))
Transmitter Configuration¶
| Parameter | Value | Notes |
|---|---|---|
| Frequency | 950 MHz–1.05 GHz | Center 1 GHz, BW = 100 MHz |
| Range resolution | ΔR = 1.5 m | c/(2B) |
| Chirp duration | 80 µs | |
| TX power | 15 dBm | ~32 mW |
| PRP | 0.5 s | One chirp every 0.5 s (slow for rotation) |
| Pulses | 180 | 90 s total; 0.5° per chirp; 0°→90° |
With 1°/s rotation and 0.5 s PRP, each chirp captures a 0.5° step.
# Define transmitter antenna location
tx_channel = dict(
location=(0, 0, 0), # Position at origin
)
# Configure FMCW transmitter
tx = Transmitter(
f=[1e9 - 50e6, 1e9 + 50e6], # Frequency sweep: 950 MHz - 1.05 GHz (100 MHz BW)
t=[0, 80e-6], # Chirp duration: 0-80 μs
tx_power=15, # Transmit power: 15 dBm
prp=0.5, # Pulse repetition period: 0.5 seconds (slow for rotation)
pulses=180, # Number of chirps: 180 (90° rotation at 0.5° steps)
channels=[tx_channel], # Transmitter antenna configuration
)
Receiver Configuration¶
| Parameter | Value | Notes |
|---|---|---|
| Sampling rate | 2 MHz | R_max ≈ 240 m |
| Noise figure | 8 dB | |
| RF gain | 20 dB | |
| BB gain | 30 dB | Total 50 dB |
| Load resistor | 500 Ω |
# Define receiver antenna location
rx_channel = dict(
location=(0, 0, 0), # Co-located with transmitter (monostatic)
)
# Configure radar receiver
rx = Receiver(
fs=2e6, # Sampling rate: 2 MHz
noise_figure=8, # Noise figure: 8 dB
rf_gain=20, # RF gain: 20 dB
load_resistor=500, # Load resistance: 500 Ω
baseband_gain=30, # Baseband gain: 30 dB
channels=[rx_channel], # Receiver antenna configuration
)
Create Radar System¶
# Create complete radar system
radar = Radar(transmitter=tx, receiver=rx)
Rotating Plate Target Model¶
A 5×5 m square metal plate at 200 m rotates continuously at 1°/s around the x-axis (yaw). Over 90 seconds, it sweeps from 0° (normal incidence) to 90° (edge-on).
Expected RCS:
- At 0° (normal): $\sigma_{max} = 4\pi a^2 b^2/\lambda^2 \approx 10\,000\,\text{m}^2 = 40\,\text{dBsm}$
- At 90° (edge): $\sigma_{edge} \ll \sigma_{max}$ (only edge diffraction)
- First null: $\theta \approx \arcsin(\lambda/2a) \approx 1.7°$
# Configure rotating flat plate target
target_1 = {
"model": "../models/plate5x5.stl", # 5m × 5m square plate
"unit": "m", # Model units in meters
"location": (200, 0, 0), # Position: 200m along x-axis
"speed": (0, 0, 0), # No translational motion
"rotation_rate": (1, 0, 0), # Rotation: 1°/s around x-axis (yaw)
}
# Create target list for simulation
targets = [target_1]
Visualize Plate Model¶
import pymeshlab
# Load plate 3D mesh
ms = pymeshlab.MeshSet()
ms.load_new_mesh(target_1["model"])
t_mesh = ms.current_mesh()
# Extract vertex positions and face connectivity
v_matrix = np.array(t_mesh.vertex_matrix()) # Vertex coordinates (N x 3)
f_matrix = np.array(t_mesh.face_matrix()) # Face indices (M x 3)
# Create 3D mesh visualization
fig = go.Figure()
fig.add_trace(
go.Mesh3d(
x=v_matrix[:, 0],
y=v_matrix[:, 1],
z=v_matrix[:, 2],
i=f_matrix[:, 0],
j=f_matrix[:, 1],
k=f_matrix[:, 2],
color="lightsteelblue",
flatshading=False,
lighting=dict(ambient=0.4, diffuse=0.9, specular=0.3, roughness=0.5),
lightposition=dict(x=1000, y=500, z=1000),
)
)
# Configure 3D plot layout
fig.update_layout(
scene=dict(
aspectmode="data",
xaxis=dict(visible=False),
yaxis=dict(visible=False),
zaxis=dict(visible=False),
),
height=500,
margin=dict(l=0, r=0, b=0, t=0),
)
show(fig)
Radar Scene Simulation¶
Pulse-level ray tracing: Each of the 180 chirps updates the plate orientation by 0.5°, recomputes RCS, and captures the return. Output dimensions: [1 channel × 180 pulses × 160 samples]. This approach is much faster than sample-level tracing while accurately capturing rotation dynamics.
# Import radar simulator and timing module
from radarsimpy.simulator import sim_radar
import time
# Start timing
tic = time.time()
# Simulate radar returns from rotating plate
# density=1: Standard ray tracing density for flat surfaces
# level='pulse': Ray tracing per chirp (captures rotation efficiently)
data = sim_radar(radar, targets, density=1, level="pulse")
# Extract baseband I/Q signals (no noise added for clean RCS measurement)
baseband = data["baseband"] # Complex samples [1, 180, 160]
# End timing
toc = time.time()
# Display execution time
print("Exec time:", toc - tic, "s")
Exec time: 0.44066953659057617 s
Radar Signal Processing¶
Apply range FFT with Chebyshev window (60 dB sidelobes) to compress each chirp. For each pulse (aspect angle), the peak amplitude at the plate's range bin (~133) indicates RCS at that orientation. 180 measurements build the complete RCS vs. angle curve.
# Import signal processing modules
from scipy import signal
import radarsimpy.processing as proc
# Create Chebyshev window for range FFT (60 dB sidelobe suppression)
range_window = signal.windows.chebwin(radar.sample_prop["samples_per_pulse"], at=60)
# Perform range FFT to compress chirp
# Input: baseband [1 channel, 180 pulses, 160 samples]
# Output: range_profile [1 channel, 180 pulses, 160 range_bins]
range_profile = proc.range_fft(baseband, range_window)
Range-Time Heatmap¶
Expected: a horizontal line at range bin 133 (200 m) with brightness varying vertically — maximum at top (chirp 0, 0°), minimum at bottom (chirp 180, 90°), with periodic lobing from the sinc² pattern.
# Create heatmap of range profiles over time
fig = px.imshow(
20 * np.log10(np.abs(range_profile[0, :, :]) + 0.0001),
color_continuous_scale="hot",
labels=dict(color="Amplitude (dB)"),
aspect="auto",
)
fig.update_xaxes(title="Range Bin (bin 133 ≈ 200m)")
fig.update_yaxes(title="Chirp Index (0-180 = 0°-90° rotation)")
fig.update_layout(
title="Range Profiles vs. Time: RCS Variation During Rotation",
height=600,
)
show(fig)
RCS vs. Aspect Angle¶
The classic flat plate RCS pattern: maximum at 0°, first null near 1.7° (matches $\theta_{null} = \arcsin(\lambda/2a)$), side lobes from sinc² interference, and a 20–30 dB drop at 90° (edge-on, dominated by edge diffraction).
# Create aspect angle array (0° to 90° in 0.5° steps)
obs_angle = np.arange(0, 90, 0.5)
# Extract peak amplitude at plate range bin (bin 133)
rcs_vs_angle = 20 * np.log10(np.abs(range_profile[0, :, :]) + 0.0001)[:, 133]
# Create line plot of RCS vs. aspect angle
fig = px.line(
x=obs_angle,
y=rcs_vs_angle,
labels={"x": "Aspect Angle (degrees)", "y": "Peak Amplitude (dB)"},
)
fig.update_layout(
title="RCS vs. Aspect Angle: Flat Plate (5m × 5m at 1 GHz)",
xaxis=dict(title="Observation Angle (degrees)", range=[0, 90]),
yaxis=dict(title="Peak Amplitude (dB)"),
height=500,
showlegend=False,
)
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='LightGray')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='LightGray')
show(fig)
Summary¶
- A 5×5 m flat plate rotates continuously (1°/s) while an FMCW radar at 1 GHz captures 180 chirps over 90 s
- Pulse-level ray tracing updates target orientation at each chirp, efficiently capturing aspect angle variation
- Range FFT isolates the target return; peak amplitude at each pulse indicates RCS at that angle
- The RCS vs. angle curve matches physical optics predictions: maximum at 0°, first null near 1.7°, sinc² lobing, and 20–30 dB drop at 90°
- This measurement technique validates flat plate RCS theory and demonstrates radar calibration with known targets
Things to Try¶
| Experiment | How |
|---|---|
| Change frequency | Test 300 MHz, 3 GHz, 10 GHz; observe null position θ_null = arcsin(λ/2a) |
| Vary plate size | Use 2 m, 10 m, 20 m plates; verify σ_max ∝ a⁴/λ² |
| Adjust rotation rate | Try 0.5°/s (finer sampling) or 2°/s (faster); adapt PRP accordingly |
| Extend angular range | Rotate to 180° or 360°; observe front-back symmetry |
| Increase bandwidth | Use 500 MHz or 1 GHz BW; improve range resolution |
| Compare window functions | Hamming, Hanning, Blackman with varying sidelobe levels |
| Add second plate | Place at different range; test range resolution |
| Test other shapes | Cylinder, sphere, corner reflector; compare RCS patterns |