In [14]:
import radarsimpy

print("`RadarSimPy` used in this example is version: " + str(radarsimpy.__version__))
`RadarSimPy` used in this example is version: 15.2.0

CFAR Detection¶

CFAR (Constant False Alarm Rate) adjusts the detection threshold based on local noise, keeping the false alarm probability constant regardless of clutter or interference level.

How it works:

  1. Estimate background noise power from neighboring training cells
  2. Scale estimate by a threshold factor derived from pfa
  3. Declare detection if the test cell exceeds the threshold

RadarSimPy CFAR algorithms:

Algorithm Function Method Best for
CA-CFAR cfar_ca_1d / cfar_ca_2d Averages all training cells Homogeneous noise
OS-CFAR cfar_os_1d / cfar_os_2d Uses k-th ordered statistic Cluttered / multi-target environments

Key parameters: guard — cells adjacent to test cell (excluded from estimate); trailing — training cells each side; pfa — false alarm probability; detector — "squarelaw" or "linear"; k — OS-CFAR rank.

Processing pipeline: Baseband I/Q → Range FFT → CFAR thresholding → detections.

This Example¶

  • Radar: 24.125 GHz center, 100 MHz BW, single chirp
  • Targets: 2 point targets at ~95 m (20 dBsm) and ~30 m (10 dBsm)
  • Algorithms: CA-CFAR vs OS-CFAR comparison on a 1D range profile

Radar System Configuration¶

Import Required Modules¶

In [15]:
import numpy as np
import scipy.constants as const
from radarsimpy import Radar, Transmitter, Receiver
import plotly.graph_objs as go
from IPython.display import Image, display

# 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 Channel Configuration¶

Define transmitter antenna location (monostatic, co-located at origin).

In [16]:
tx_channel = dict(location=(0, 0, 0))

Transmitter Configuration¶

f and t define the frequency sweep. For a linear FMCW chirp: f = [f_start, f_end], t = [t_start, t_end] (or a single value t0 → [0, t0]). Bandwidth = |f[1] − f[0]|. prp ≥ t_end.

|                       prp
|                  +-----------+
|
|            +---f[1]--->  /            /
|                         /            /
|                        /            /    ...
|                       /            /
|      +---f[0]--->    /            /
|
|                  +-------+
|                 t[0]    t[1]
Parameter Value Notes
Frequency sweep 24.075–24.175 GHz 100 MHz BW, 1.5 m range resolution
Chirp duration 80 μs
TX power 30 dBm
PRP 100 μs
Pulses 1 Single chirp (no Doppler processing)
In [17]:
tx = Transmitter(
    f=[24.075e9, 24.175e9],
    t=80e-6,
    tx_power=30,
    prp=100e-6,
    pulses=1,
    channels=[tx_channel],
)

Receiver Channel Configuration¶

Receiver antenna co-located with transmitter (monostatic).

In [18]:
rx_channel = dict(location=(0, 0, 0))

Receiver Configuration¶

Sampling rate sets maximum unambiguous range: $R_{max} = \frac{f_s c T_c}{2B} = 240$ m.

Parameter Value
Sampling rate fs 2 MHz
Noise figure 6 dB
RF gain 20 dB
Load resistor 500 Ω
Baseband gain 30 dB
In [19]:
rx = Receiver(
    fs=2e6,
    noise_figure=6,
    rf_gain=20,
    load_resistor=500,
    baseband_gain=30,
    channels=[rx_channel],
)

Create Radar System¶

Combine transmitter and receiver into the complete radar.

In [20]:
radar = Radar(transmitter=tx, receiver=rx)

Target Configuration¶

Target Location (m) Speed (m/s) RCS (dBsm)
1 (95, 20, 0) (−50, 0, 0) 20
2 (30, −5, 0) (−22, 0, 0) 10
In [21]:
target_1 = dict(location=(95, 20, 0), speed=(-50, 0, 0), rcs=20, phase=0)
target_2 = dict(location=(30, -5, 0), speed=(-22, 0, 0), rcs=10, phase=0)

targets = [target_1, target_2]

Simulate Baseband Signals¶

sim_radar computes the complex I/Q beat signal, incorporating propagation delay, Doppler shift, RCS weighting, and thermal noise.

Output shape: [channels, pulses, samples] → [1, 1, 160].

In [22]:
from radarsimpy.simulator import sim_radar

data = sim_radar(radar, targets)
timestamp = data["timestamp"]
baseband = data["baseband"] + data["noise"]

Visualize Baseband I/Q Signals¶

Display time-domain beat signal for the chirp. Two targets produce two overlapping beat frequencies.

In [23]:
fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=timestamp[0, 0, :] * 1e6,
        y=np.real(baseband[0, 0, :]),
        name="I",
    )
)
fig.add_trace(
    go.Scatter(
        x=timestamp[0, 0, :] * 1e6,
        y=np.imag(baseband[0, 0, :]),
        name="Q",
    )
)
fig.update_layout(
    title="I/Q Beat Signals for the First Chirp",
    yaxis=dict(title="Amplitude (V)"),
    xaxis=dict(title="Time (μs)"),
)

show(fig)
No description has been provided for this image

Range FFT (Fast-Time Processing)¶

A Chebyshev window (60 dB sidelobe suppression) is applied before the FFT to reduce spectral leakage. Output: range profile [1, 1, 160] — peaks at target ranges.

In [24]:
from scipy import signal
import radarsimpy.processing as proc

range_window = signal.windows.chebwin(radar.sample_prop["samples_per_pulse"], at=60)
range_profile = proc.range_fft(baseband, range_window)

Apply CFAR Detection¶

radarsimpy.processing provides 1D and 2D CFAR detectors. This example applies both 1D algorithms to the range profile.

CA-CFAR (cfar_ca_1d): The threshold is the mean of all 2 × trailing training cells scaled by a factor derived from pfa. Simple and efficient, but the threshold rises when other targets fall inside the training window.

OS-CFAR (cfar_os_1d): Sorts the 2 × trailing training cells and uses the k-th value (rank) as the noise estimate. More robust to target contamination — one or a few strong targets in the training window don't inflate the threshold.

In [25]:
ca_cfar = proc.cfar_ca_1d(
    np.abs(range_profile[0, 0, :]) ** 2,
    guard=2,
    trailing=10,
    pfa=1e-4,
    detector="squarelaw",
)
os_cfar = proc.cfar_os_1d(
    np.abs(range_profile[0, 0, :]) ** 2,
    guard=0,
    trailing=10,
    k=14,
    pfa=1e-4,
    detector="squarelaw",
)

Compare CFAR Algorithms¶

Range profile with CA-CFAR and OS-CFAR adaptive thresholds. Detection occurs wherever the profile exceeds the threshold.

In [26]:
max_range = (
    const.c
    * radar.radar_prop["receiver"].bb_prop["fs"]
    * radar.radar_prop["transmitter"].waveform_prop["pulse_length"]
    / radar.radar_prop["transmitter"].waveform_prop["bandwidth"]
    / 2
)
range_axis = np.linspace(
    0, max_range, radar.sample_prop["samples_per_pulse"], endpoint=False
)

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=range_axis,
        y=20 * np.log10(np.abs(range_profile[0, 0, :])),
        name="Range profile",
    )
)
fig.add_trace(go.Scatter(x=range_axis, y=10 * np.log10(ca_cfar), name="CA-CFAR"))
fig.add_trace(go.Scatter(x=range_axis, y=10 * np.log10(os_cfar), name="OS-CFAR"))
fig.update_layout(
    title="Range Profile with CA-CFAR and OS-CFAR Thresholds",
    yaxis=dict(title="Amplitude (dB)"),
    xaxis=dict(title="Range (m)"),
)

show(fig)
No description has been provided for this image

Key observations:

  • Both thresholds adapt to the local noise floor and rise near strong peaks.
  • CA-CFAR threshold may be elevated in the vicinity of a strong target (target contamination of training cells).
  • OS-CFAR threshold is stabler because a few outliers in the training window don't dominate the estimate.
  • Guard cells (guard=2 for CA) prevent target energy from leaking into the training estimate.

Summary¶

  • CA-CFAR averages all training cells — simple and efficient in homogeneous noise.
  • OS-CFAR uses the k-th ordered statistic — robust when other targets contaminate the training window.
  • guard cells prevent target energy from biasing the noise estimate; pfa sets the false-alarm floor.
  • The same algorithms extend to range-Doppler maps with cfar_ca_2d / cfar_os_2d.

Things to Try¶

Experiment Parameter to change Observable effect
Wider training window Increase trailing Smoother, more stable threshold
More guard cells Increase guard Threshold unaffected by target sidelobes
Tighter false-alarm Decrease pfa Threshold rises; weaker targets missed
CA vs OS robustness Place targets close together OS threshold stays low; CA inflates
2D detection Use cfar_ca_2d() on a range-Doppler map 2D adaptive thresholding