import radarsimpy
print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0
Interferometric Radar: Phase-Based Motion Detection¶
Introduction¶
This notebook demonstrates interferometric radar—a sensitive technique that measures extremely small motions by detecting phase changes in reflected signals. Unlike conventional radar that focuses on range and velocity, interferometric radar excels at detecting sub-millimeter displacements, making it ideal for vital signs monitoring, structural health monitoring, and precision motion sensing.
Key Principle¶
When a target moves by distance $\Delta r$, the phase of the reflected signal changes by:
$$\Delta\phi = \frac{4\pi \Delta r}{\lambda}$$
At 24 GHz (λ ≈ 1.24 cm), a 1 mm displacement → 1.01 rad (58°) phase change—extremely sensitive to small motions.
CW Interferometry¶
- Transmit continuous single-tone signal at $f_c$
- Reflect off target; phase encodes range: $\phi_0 = 4\pi r_0/\lambda$
- Motion shifts phase: $\phi(t) = \phi_0 + 4\pi \Delta r(t)/\lambda$
- Demodulate I/Q to recover $\Delta r(t)$
CW interferometric radar cannot measure absolute distance—only phase changes—but provides extreme sensitivity to motion. Filtering separates respiration (< 1 Hz) from heartbeat (1–2 Hz) components.
Phase Demodulation¶
From complex baseband $s(t) = I(t) + jQ(t)$, phase is extracted as:
$$\phi(t) = \arctan\left(\frac{Q(t)}{I(t)}\right)$$
The I/Q constellation shows the signal as a rotating phasor. For pure phase modulation, it traces an arc (or full circle) at constant radius.
Displacement Recovery¶
$$\Delta r(t) = \frac{\lambda \cdot \phi(t)}{4\pi}$$
For displacements > λ/4, phase wraps at ±π and requires unwrapping.
This Example¶
This notebook uses RadarSimPy to simulate:
- 24.125 GHz CW radar (K-band, λ = 1.24 cm), 20-second observation
- 20 Hz sampling (adequate for vital signs up to 10 Hz)
- Target: 1 mm sinusoidal motion at 1 Hz (simulates respiration)
- Processing: I/Q visualization, constellation, arctangent phase demodulation
Radar System Configuration¶
Transmitter Configuration¶
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 Parameters:
| Parameter | Value | Description |
|---|---|---|
| Frequency | 24.125 GHz | K-band, single-tone (λ = 1.24 cm) |
| Pulse Length | 20 s | Continuous observation; captures 20 motion cycles |
| Transmit Power | 10 dBm | ~10 mW, safe for close-range operation |
| Pulses | 1 | Single continuous transmission |
Unlike FMCW, CW interferometric radar transmits a constant frequency—no range resolution, only phase-based motion detection.
Phase sensitivity: $4\pi/\lambda = 10.1$ rad/mm at 24 GHz
# Configure CW interferometric transmitter
tx = Transmitter(
f=24.125e9, # Carrier frequency: 24.125 GHz (K-band, single-tone)
t=20, # Pulse length: 20 seconds (long continuous observation)
tx_power=10, # Transmit power: 10 dBm (~10 mW)
pulses=1, # Single continuous pulse (no modulation)
channels=[dict(location=(0, 0, 0))], # Single antenna at origin
)
Receiver Configuration¶
Receiver Parameters:
| Parameter | Value | Description |
|---|---|---|
| Sampling Rate | 20 Hz | Nyquist: 10 Hz → captures motion up to 10 Hz; 400 samples total |
| Noise Figure | 12 dB | Standard performance |
| RF Gain | 20 dB | LNA amplification |
| Baseband Gain | 50 dB | High gain for phase sensitivity (total: 70 dB) |
| Load Resistor | 1000 Ω | High impedance |
# Configure interferometric radar receiver
rx = Receiver(
fs=20, # Sampling rate: 20 Hz (slow, adequate for vital signs)
noise_figure=12, # Noise figure: 12 dB
rf_gain=20, # RF gain: 20 dB
baseband_gain=50, # Baseband gain: 50 dB (high sensitivity for phase)
load_resistor=1000, # Load resistance: 1000 Ω (high impedance)
channels=[dict(location=(0, 0, 0))], # Receiver at origin (monostatic)
)
Create Radar System¶
Combine transmitter and receiver to form the complete CW interferometric radar.
# Create complete CW interferometric radar system
radar = Radar(transmitter=tx, receiver=rx)
Target Configuration with Time-Varying Motion¶
RadarSimPy supports time-varying target positions by defining location as a function of the timestamp array. This simulates a chest wall at 1.4 m oscillating with 1 mm amplitude at 1 Hz:
$$x(t) = 1.4 + 0.001 \times \sin(2\pi \times 1 \times t) \text{ m}$$
Expected phase deviation:
$$\Delta\phi_{max} = \frac{4\pi \times 0.001}{\lambda} = \frac{4\pi \times 0.001}{0.0124} \approx 1.01 \text{ rad} \approx 58°$$
# Configure target with time-varying sinusoidal motion
target = dict(
# Location: 1.4m base + 1mm sinusoidal displacement at 1 Hz
# x(t) = 1.4 + 0.001*sin(2π*1*t) meters
location=(
1.4 + 1e-3 * np.sin(2 * np.pi * 1 * radar.time_prop["timestamp"]), # X: sinusoidal motion
0, # Y: no motion
0, # Z: no motion
),
rcs=-10, # Radar cross section: -10 dBsm (small target, chest wall)
phase=0, # Initial phase: 0 degrees
)
# Single target for clean interferometric signal
targets = [target]
Simulate Baseband Signals¶
Generate interferometric baseband I/Q signals containing phase modulation from target motion.
Output Structure:
- Dimensions: [channels, pulses, samples] = [1, 1, 400]
- Phase: $\phi(t) = \phi_0 + 1.01 \times \sin(2\pi t)$ rad — I/Q traces a circular arc
- Note: noise is not added here to demonstrate clean phase modulation
# Import radar simulator
from radarsimpy.simulator import sim_radar
# Simulate interferometric radar with time-varying target
data = sim_radar(radar, targets)
# Extract timestamp and baseband signals
timestamp = data["timestamp"] # Time axis [1, 1, 400]
baseband = data["baseband"] # Complex I/Q (no noise added) [1, 1, 400]
Visualize Baseband I/Q Signals¶
Display time-domain baseband waveforms showing phase-modulated I and Q components.
# Create figure for time-domain I/Q visualization
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=timestamp[0, 0, :],
y=np.real(baseband[0, 0, :]),
name="I (In-phase)",
line=dict(color='blue', width=2),
)
)
fig.add_trace(
go.Scatter(
x=timestamp[0, 0, :],
y=np.imag(baseband[0, 0, :]),
name="Q (Quadrature)",
line=dict(color='red', width=2),
)
)
fig.update_layout(
title="Baseband I/Q Signals: Phase Modulation from 1 Hz Sinusoidal Motion",
yaxis=dict(title="Amplitude (V)", gridcolor='lightgray'),
xaxis=dict(title="Time (s)", gridcolor='lightgray'),
height=500,
legend=dict(x=0.02, y=0.98),
)
show(fig)
Visualize I/Q Constellation¶
The I/Q constellation plots the complex baseband in the I-Q plane. For pure phase modulation (constant amplitude):
- Shape: Arc segment (or full circle if phase spans full 360°)
- Radius: Constant signal amplitude
- Angular position: Instantaneous phase φ(t)
- Arc extent: ±58° from 1 mm motion at 24 GHz
Points are colored by time. Contrast with pure Doppler, which creates continuous rotation instead of oscillation along an arc.
# Create figure for I/Q constellation
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=np.real(baseband[0, 0, :]),
y=np.imag(baseband[0, 0, :]),
name="I/Q Trajectory",
)
)
fig.update_layout(
title="I/Q Constellation: Circular Trajectory from Phase Modulation",
xaxis=dict(
range=[-1, 1],
tickformat=".2f",
title="In-phase (I) [V]",
gridcolor='lightgray',
),
yaxis=dict(
range=[-1, 1],
tickformat=".2f",
title="Quadrature (Q) [V]",
scaleanchor="x",
scaleratio=1,
gridcolor='lightgray',
),
height=600,
width=650,
)
show(fig)
Phase Demodulation¶
Extract phase from I/Q baseband to recover target motion.
Arctangent demodulation:
$$\phi(t) = \arctan\left(\frac{Q(t)}{I(t)}\right) = \angle s(t)$$
np.angle() computes the phase of complex numbers in $[-\pi, +\pi]$ rad.
Expected result:
$$\phi(t) = \phi_0 + 1.01 \times \sin(2\pi \times 1 \times t)$$
Converting phase back to displacement:
$$\Delta r(t) = \frac{\lambda}{4\pi} \phi(t) = \frac{0.0124}{4\pi} \times 1.01 \times \sin(2\pi t) \approx 0.001 \times \sin(2\pi t) \text{ m}$$
# Perform phase demodulation using arctangent
# np.angle() extracts phase from complex I/Q signal
demod = np.angle(baseband[0, 0, :]) # Phase in radians [-π, +π]
Visualize Demodulated Phase¶
The recovered phase shows a clean 1 Hz sinusoid with ~1 rad amplitude, confirming sub-millimeter motion detection.
# Create figure for demodulated phase
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=timestamp[0, 0, :],
y=demod,
line=dict(color='purple', width=2),
name="Demodulated Phase",
)
)
fig.update_layout(
title="Phase Demodulation: 1 Hz Sinusoidal Motion (1 mm Amplitude)",
xaxis=dict(tickformat=".2f", title="Time (s)", gridcolor='lightgray'),
yaxis=dict(tickformat=".2f", title="Phase (rad)", gridcolor='lightgray'),
height=500,
showlegend=False,
)
fig.add_annotation(
text=f"Phase amplitude ≈ 1 rad<br>Displacement = λφ/(4π) ≈ 1 mm",
xref="paper", yref="paper",
x=0.05, y=0.95,
showarrow=False,
bgcolor="rgba(255,255,200,0.8)",
bordercolor="purple",
borderwidth=2,
)
show(fig)
Summary¶
This example demonstrated CW interferometric radar for phase-based motion detection:
- Phase sensitivity $\Delta\phi = 4\pi\Delta r/\lambda$ enables sub-millimeter displacement detection
- CW transmission provides simple hardware with no range capability—motion only
- Time-varying target position is modeled using the timestamp array
- I/Q constellation traces a circular arc for pure phase modulation
- Arctangent demodulation recovers clean sinusoidal motion at 1 Hz
- Displacement recovery $\Delta r = \lambda\phi/(4\pi)$ confirms 1 mm amplitude
Things to Try¶
| Experiment | What to Change | What to Observe |
|---|---|---|
| Motion amplitude | 0.1 mm, 0.5 mm, 5 mm | Phase deviation scaling; wrapping at > λ/4 |
| Motion frequency | 0.2–0.5 Hz (respiration), 1–2 Hz (heartbeat) | Frequency resolution; combined vital signs |
| Carrier frequency | 10, 60, 77 GHz | Sensitivity $\Delta\phi \propto 1/\lambda$ |
| Sampling rate | 10, 100 Hz | Nyquist limits; aliasing effects |
| Observation time | 10 s, 60 s | Frequency resolution vs. update rate |
| Phase unwrapping | Increase motion to 5 mm | Phase wrap at ±π; unwrapping algorithm |
| Add noise | Include data["noise"] |
Phase noise impact; SNR requirements |
| Multiple targets | Add 2nd target at different range | Phase interference; single-target assumption |