Radar Detection Tutorial
This tutorial covers Constant False Alarm Rate (CFAR) detection algorithms for radar signal processing.
CFAR Detection Overview
CFAR detectors maintain a constant false alarm probability by adapting the detection threshold to local noise conditions. This is essential for radar systems operating in non-uniform clutter environments.
Cell-Averaging CFAR
The most common CFAR algorithm averages reference cells to estimate the noise floor.
import numpy as np
from pytcl.mathematical_functions.signal_processing import cfar_ca
# Simulated radar range profile
np.random.seed(42)
n_cells = 500
# Background noise (exponential for Swerling 0/1 targets)
signal = np.random.exponential(scale=1.0, size=n_cells)
# Add targets
signal[100] = 15.0 # Strong target
signal[250] = 8.0 # Medium target
signal[400] = 5.0 # Weak target
# CA-CFAR detection
result = cfar_ca(
signal,
guard_cells=3, # Guard cells on each side
ref_cells=10, # Reference cells on each side
pfa=1e-4 # Probability of false alarm
)
# result.detections: boolean mask
# result.threshold: adaptive threshold
# result.detection_indices: indices of detections
print(f"Detected targets at: {result.detection_indices}")
CFAR Parameters
guard_cells: Cells adjacent to cell under test (CUT) that are excluded from noise estimation. Prevents target energy from biasing threshold.
ref_cells: Number of cells on each side used to estimate noise level.
pfa: Desired probability of false alarm.
Greatest-Of CFAR (GO-CFAR)
GO-CFAR uses the maximum of leading and lagging averages. Better for clutter edges but higher detection loss.
from pytcl.mathematical_functions.signal_processing import cfar_go
result = cfar_go(signal, guard_cells=3, ref_cells=10, pfa=1e-4)
Smallest-Of CFAR (SO-CFAR)
SO-CFAR uses the minimum of leading and lagging averages. Better detection in clutter but higher false alarms at edges.
from pytcl.mathematical_functions.signal_processing import cfar_so
result = cfar_so(signal, guard_cells=3, ref_cells=10, pfa=1e-4)
Order-Statistic CFAR (OS-CFAR)
OS-CFAR selects the k-th smallest value from reference cells. Robust to interfering targets in the reference window.
from pytcl.mathematical_functions.signal_processing import cfar_os
# Use 3/4 order statistic (robust to one interferer)
k = int(0.75 * 20) # 75% of total reference cells
result = cfar_os(signal, guard_cells=3, ref_cells=10, pfa=1e-4, k=k)
2D CFAR Detection
For range-Doppler or image-based detection:
from pytcl.mathematical_functions.signal_processing import cfar_2d
# Simulated range-Doppler map
n_range = 100
n_doppler = 64
# Noise floor
rdm = np.random.exponential(scale=1.0, size=(n_range, n_doppler))
# Add targets
rdm[30, 20] = 20.0 # Target 1
rdm[70, 45] = 15.0 # Target 2
# 2D CA-CFAR
result = cfar_2d(
rdm,
guard=(2, 2), # Guard cells (range, Doppler)
ref=(5, 5), # Reference cells
pfa=1e-5,
method='ca'
)
# Find detection coordinates
det_range, det_doppler = np.where(result.detections)
print(f"Detections at (range, Doppler):")
for r, d in zip(det_range, det_doppler):
print(f" ({r}, {d})")
Threshold Factor Calculation
Compute the CFAR threshold multiplier for a given Pfa:
from pytcl.mathematical_functions.signal_processing import threshold_factor
# For CA-CFAR with 20 reference cells
alpha = threshold_factor(pfa=1e-4, n_ref=20, method='ca')
print(f"Threshold factor: {alpha:.2f}")
# Threshold = alpha * noise_estimate
Detection Probability
Compute probability of detection for given SNR:
from pytcl.mathematical_functions.signal_processing import detection_probability
# Detection probability vs SNR
snr_db = np.linspace(0, 20, 50)
snr_linear = 10 ** (snr_db / 10)
pd = [detection_probability(s, pfa=1e-4, n_ref=20, method='ca')
for s in snr_linear]
# Find required SNR for Pd = 0.9
snr_90 = snr_db[np.argmin(np.abs(np.array(pd) - 0.9))]
print(f"Required SNR for 90% detection: {snr_90:.1f} dB")
Complete Example: Radar Processing Chain
import numpy as np
from pytcl.mathematical_functions.signal_processing import (
matched_filter, cfar_ca
)
from pytcl.mathematical_functions.transforms import fft
# Radar parameters
fs = 1e6 # Sample rate (1 MHz)
pri = 1e-3 # Pulse repetition interval
n_pulses = 32 # Number of pulses for Doppler processing
n_range = 1000 # Range bins
np.random.seed(42)
# Transmit waveform (LFM chirp)
bw = 5e6 # Bandwidth
t_pulse = 10e-6 # Pulse duration
t = np.arange(0, t_pulse, 1/fs)
chirp = np.exp(1j * np.pi * bw / t_pulse * t**2)
# Simulate received data (n_range x n_pulses)
# Noise
rx_data = (np.random.randn(n_range, n_pulses) +
1j * np.random.randn(n_range, n_pulses)) * 0.1
# Add targets with range and Doppler
targets = [
{'range': 200, 'doppler': 5}, # Moving target
{'range': 500, 'doppler': -3}, # Approaching target
{'range': 750, 'doppler': 0}, # Stationary target
]
for tgt in targets:
r_idx = tgt['range']
doppler_phase = np.exp(1j * 2 * np.pi * tgt['doppler'] *
np.arange(n_pulses) / n_pulses)
rx_data[r_idx:r_idx+len(chirp), :] += (
5.0 * np.outer(chirp, doppler_phase)[:n_range-r_idx, :]
)
# Step 1: Pulse compression (matched filtering per pulse)
compressed = np.zeros((n_range, n_pulses), dtype=complex)
for p in range(n_pulses):
mf = matched_filter(rx_data[:, p], chirp)
compressed[:len(mf.output), p] = mf.output[:n_range]
# Step 2: Doppler processing (FFT across pulses)
rdm = np.abs(fft(compressed, axis=1))
# Step 3: CFAR detection on range-Doppler map
from pytcl.mathematical_functions.signal_processing import cfar_2d
detections = cfar_2d(rdm, guard=(2, 2), ref=(5, 3), pfa=1e-6, method='ca')
det_range, det_doppler = np.where(detections.detections)
print(f"Detected {len(det_range)} targets:")
for r, d in zip(det_range, det_doppler):
print(f" Range bin {r}, Doppler bin {d}")
Next Steps
See Signal Processing Tutorial for filter design and spectral analysis
Explore Signal Processing for the complete API reference