import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
Micro-Doppler Signatures: Rotating Turbine¶
Micro-Doppler refers to the additional Doppler frequency shifts from rotating or vibrating target components (turbine blades, propellers, human limbs), superimposed on the bulk motion Doppler. For a point at radius $R$ rotating at angular velocity $\omega$, the radial velocity is:
$$v_r(t) = \omega R \sin(\theta(t)), \qquad f_d = \frac{2v_r f_c}{c}$$
Maximum Doppler from blade tips:
$$f_{d,max} = \frac{2\omega R f_c}{c}$$
This creates a time-varying sinusoidal signature, characteristic of rotation. Time-frequency analysis (STFT, spectrogram) reveals how Doppler changes over time — standard FFT loses this temporal information.
This Example¶
- Radar: 24 GHz FMCW (K-band), 8 GHz BW → ΔR = 1.875 cm
- Target: 3-blade turbine at 5 m, rotating at 30°/s (5 RPM)
- Observation: 1,280 chirps over 3.2 s (2.5 ms PRP) → PRF = 400 Hz, Δf_d = 0.3125 Hz
- Processing: Range-Doppler FFT with Chebyshev windows → 3D visualization
- Expected: Sinusoidal Doppler modulation; max Doppler ~83 Hz (blade tips: v_tip ≈ 0.52 m/s); three flashes per rotation (3 blades); zero Doppler from stationary hub
Radar System Configuration¶
Import Required Modules¶
import numpy as np
import plotly.graph_objs as go
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 | 20–28 GHz | Center 24 GHz, BW = 8 GHz |
| Range resolution | ΔR = 1.875 cm | c/(2B) |
| Chirp duration | 300 µs | |
| TX power | 25 dBm | ~316 mW |
| PRP | 2.5 ms | PRF = 400 Hz |
| Pulses | 1,280 | 3.2 s observation; captures >1 rotation |
| Doppler resolution | 0.3125 Hz | 1/(N×PRP) |
| Unambiguous velocity | ±31.25 m/s | More than sufficient for turbine tips |
For blade tip at R = 1 m, ω = 30°/s: v_tip ≈ 0.52 m/s → f_d ≈ 83 Hz (well within range).
# Define transmitter antenna location
tx_channel = dict(
location=(0, 0, 0), # Position at origin
)
# Configure FMCW transmitter
tx = Transmitter(
f=[24e9 - 4000e6, 24e9 + 4000e6], # Frequency sweep: 20-28 GHz (8 GHz BW)
t=300e-6, # Chirp duration: 300 μs
tx_power=25, # Transmit power: 25 dBm
prp=0.0025, # Pulse repetition period: 2.5 ms (400 Hz PRF)
pulses=1280, # Number of chirps: 1280 (3.2s observation)
channels=[tx_channel], # Transmitter antenna configuration
)
Receiver Configuration¶
| Parameter | Value | Notes |
|---|---|---|
| Sampling rate | 2 MHz | R_max ≈ 30 m |
| Noise figure | 6 dB | Low noise for sensitivity |
| 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=6, # Noise figure: 6 dB (low noise for sensitivity)
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 Turbine Target Model¶
A 3-blade turbine at 5 m rotates continuously at 30°/s around the pitch axis (Y-axis). rotation_rate=(0, 30, 0) in deg/s. Over 3.2 s, it rotates 96° — enough to capture the full micro-Doppler pattern.
Expected micro-Doppler:
- Blade tip velocity: $v_{tip} = \omega R = (30°/s \times \pi/180) \times 1\,\text{m} \approx 0.52\,\text{m/s}$
- Max Doppler: $f_d = 2v_{tip}f_c/c \approx 83\,\text{Hz}$
- Modulation period: 12 s/rotation
- Blade passage: 3 flashes per rotation
# Configure rotating turbine target
target_1 = {
"model": "../models/turbine.stl", # 3-blade turbine model
"unit": "m", # Model units in meters
"location": (5, 0, 0), # Position: 5m along x-axis
"rotation": (0, 0, 0), # Initial orientation: (yaw, pitch, roll) degrees
"rotation_rate": (0, 30, 0), # Rotation: 30°/s around pitch (Y-axis)
"speed": (0, 0, 0), # No translational motion
}
# Create target list for simulation
targets = [target_1]
Visualize Turbine Model¶
import pymeshlab
# Load turbine 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())
f_matrix = np.array(t_mesh.face_matrix())
# 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 chirp (every 2.5 ms) updates the turbine orientation by 0.075°, recomputes RCS, and captures the return. Output: [1 channel × 1,280 pulses × 600 samples]. System noise is added for realistic SNR.
# Import radar simulator and timing module
from radarsimpy.simulator import sim_radar
import time
# Start timing
tic = time.time()
# Simulate radar returns from rotating turbine
# density=1: Standard ray tracing density
# level='pulse': Ray tracing per chirp (efficient for rotation)
data = sim_radar(radar, targets, density=1, level="pulse")
# Extract baseband I/Q signals and add system noise
baseband = data["baseband"] + data["noise"] # Complex samples [1, 1280, 600]
# End timing
toc = time.time()
# Display execution time
print("Exec time:", toc - tic, "s")
Exec time: 7.820748567581177 s
Range-Doppler Processing¶
Apply 2D FFT with Chebyshev windows (60 dB sidelobes) to generate a range-Doppler map:
- Range FFT: Compress chirp → range profiles
- Doppler FFT: Extract velocity across slow-time
- fftshift: Center zero-Doppler for symmetric display
Expected features: hub at zero Doppler (stationary), blade returns spread across ±83 Hz, periodic bright flashes (blade perpendicular to radar), sinusoidal time-varying pattern.
# Import signal processing modules
from scipy import signal
import radarsimpy.processing as proc
# Create Chebyshev windows for sidelobe suppression (60 dB)
range_window = signal.windows.chebwin(radar.sample_prop["samples_per_pulse"], at=60)
doppler_window = signal.windows.chebwin(
radar.radar_prop["transmitter"].waveform_prop["pulses"], at=60
)
# Perform combined range-Doppler FFT
# Input: baseband [1 channel, 1280 pulses, 600 samples]
# Output: range_doppler [1, 1280 Doppler bins, 600 range bins]
range_doppler = np.fft.fftshift(
proc.range_doppler_fft(baseband, rwin=range_window, dwin=doppler_window),
axes=1, # Shift only Doppler axis (axis 1) to center zero frequency
)
Micro-Doppler Signature Visualization¶
3D surface plot (range 4.5–5.5 m) showing amplitude vs. range and Doppler. Expected: zero-Doppler ridge (hub), ±83 Hz spread (blade tips), three-fold repeating pattern (3 blades), characteristic "butterfly" or "propeller" shape in Doppler space.
# Extract magnitude and convert to dB scale
temp = np.abs(range_doppler[0, :, :])
temp = 20 * np.log10(temp)
# Calculate maximum unambiguous range
max_range = (
3e8
* radar.radar_prop["receiver"].bb_prop["fs"]
* radar.radar_prop["transmitter"].waveform_prop["pulse_length"]
/ radar.radar_prop["transmitter"].waveform_prop["bandwidth"]
/ 2
)
# Calculate unambiguous velocity
unambiguous_speed = (
3e8
/ radar.radar_prop["transmitter"].waveform_prop["prp"][0]
/ 24e9
/ 2
)
# Create range axis
range_axis = np.linspace(
0, max_range, radar.sample_prop["samples_per_pulse"], endpoint=False
)
# Focus on turbine range (4.5-5.5m)
rng_idx = np.where(np.logical_and(range_axis > 4.5, range_axis < 5.5))
# Create Doppler axis (centered at zero)
doppler_axis = np.linspace(
-unambiguous_speed / 2,
unambiguous_speed / 2,
radar.radar_prop["transmitter"].waveform_prop["pulses"],
endpoint=False,
)
# Create 3D surface plot
fig = go.Figure()
fig.add_trace(
go.Surface(
x=range_axis[rng_idx[0]],
y=doppler_axis,
z=temp[:, rng_idx[0]],
colorscale="Rainbow",
colorbar=dict(title="Amplitude (dB)"),
)
)
# Configure camera angle for optimal view
camera = dict(
up=dict(x=0, y=0, z=1),
center=dict(x=0, y=0, z=0),
eye=dict(x=-1, y=1, z=2),
)
fig.update_layout(
title="Micro-Doppler Signature: Rotating Turbine (24 GHz)",
height=800,
scene=dict(
xaxis=dict(title="Range (m)", range=[4.5, 5.5]),
yaxis=dict(title="Velocity (m/s)"),
zaxis=dict(title="Amplitude (dB)"),
aspectmode="cube",
camera=camera,
),
margin=dict(l=0, r=0, b=60, t=100),
)
show(fig)
Summary¶
- Micro-Doppler signatures from rotating components create time-varying sinusoidal patterns distinct from bulk motion Doppler
- A 3-blade turbine at 5 m rotating at 30°/s produces max Doppler ~83 Hz from blade tips (v_tip ≈ 0.52 m/s)
- 24 GHz FMCW radar with 1,280 chirps (3.2 s, 2.5 ms PRP) provides 0.3125 Hz Doppler resolution
- Pulse-level ray tracing efficiently captures rotation dynamics by updating orientation at each chirp
- Range-Doppler FFT reveals the characteristic "butterfly" pattern: zero-Doppler hub, ±83 Hz spread, three-fold blade structure
- Applications: drone/UAV detection, aircraft classification, machinery diagnostics, human gait recognition
Things to Try¶
| Experiment | How |
|---|---|
| Vary rotation rate | Try 10°/s, 60°/s, 120°/s; observe Doppler spread f_d ∝ ω |
| Change frequency | Test 10 GHz, 77 GHz; note Doppler sensitivity f_d ∝ f_c |
| Adjust blade count | Model 2-, 4-, or 5-blade turbines; observe pattern repetition |
| Extend observation | Use 10 s (multiple full rotations); improve Doppler resolution |
| Optimize PRF | Try 1 kHz PRF; observe unambiguous velocity trade-off |
| Vary range | Place turbine at 10 m, 20 m; observe SNR vs. distance |
| Add second turbine | Different rotation rate; test multi-target separation |
| Extract STFT | Compute spectrogram from single range bin; detailed time-frequency analysis |