"A range-Doppler map tells me how far you are and how fast you are leaving. A micro-Doppler spectrogram tells me you are lying about standing still, because your arms keep swinging."
A Suspicious AI Agent
The Big Picture
The signal chain of Section 44.2 hands you a cube of complex samples: one axis for the samples within each chirp, one for the chirps within a frame, one for the receive antennas. That cube is not yet perception. It becomes perception when you fold it into the right two-dimensional picture. Fold across the two time scales and you get the range-Doppler map, the workhorse image on which every detection, every tracker, and every occupancy grid in the rest of this chapter is built. Fold across a sliding window of many frames and you get the micro-Doppler spectrogram, the signature that separates a walking human from a dog from a spinning drone rotor without ever forming an image of them. These two representations are how a radar stops being an oscilloscope and starts being a sensor an AI model can read.
This section assumes the FMCW chirp geometry and the two-time-scale sampling model from Section 44.1 and Section 44.2, and comfort with the short-time Fourier transform and spectrograms from Chapter 7. Our job here is narrow and load-bearing: derive the two folds precisely, explain the resolution and ambiguity that govern them, and show why deep models treat one as an image and the other as a texture.
Fast time and slow time: the two axes that matter
An FMCW radar transmits a burst of \(N_c\) identical chirps. Within one chirp, the analog-to-digital converter takes \(N_s\) samples; the index along those samples is fast time, sampled at the ADC rate (megahertz). The index across successive chirps in the frame is slow time, sampled once per chirp repetition interval (kilohertz). After dechirping, a target at range \(R\) moving with radial velocity \(v\) produces a beat signal whose phase advances along both axes at rates you can read off directly:
$$ s[n, m] \approx A \, \exp\!\Big( j 2\pi \big(\underbrace{\tfrac{2 S R}{c\,f_s}}_{\text{fast-time tone}}\, n \;+\; \underbrace{\tfrac{2 v}{\lambda}\,T_c}_{\text{slow-time tone}}\, m \big) \Big), $$where \(n\) indexes fast time, \(m\) indexes slow time, \(S\) is the chirp slope, \(f_s\) the ADC sampling rate, \(T_c\) the chirp interval, and \(\lambda\) the carrier wavelength. The structure is the whole trick: range hides in a tone along \(n\), velocity hides in a tone along \(m\), and the two are separable. Estimating two frequencies means two Fourier transforms.
The range-Doppler map: a 2D FFT
Apply an FFT along fast time and each target collapses to a peak at its range bin: this is the range FFT. Apply a second FFT along slow time and each target further collapses to a peak at its Doppler (velocity) bin: the Doppler FFT. Stacking the two gives the range-Doppler map (RD map), a complex 2D array whose magnitude, in decibels, is the image every downstream stage consumes. A stationary wall lands in the zero-Doppler column; an approaching car sits at its range and a negative velocity bin; clutter piles up near zero-Doppler where you can notch it out.
The resolutions follow from the transform lengths, and they are worth memorizing because they dictate what the map can and cannot see. Range resolution depends only on the swept bandwidth \(B\), and velocity resolution only on the total frame duration \(T_f = N_c T_c\):
$$ \Delta R = \frac{c}{2B}, \qquad \Delta v = \frac{\lambda}{2 T_f}. $$Wider bandwidth sharpens range; a longer frame sharpens velocity. The unambiguous velocity span is \(v_{\max} = \pm \lambda / (4 T_c)\), set by the chirp repetition rate, so a target moving faster than that aliases and wraps around the map, exactly the Nyquist wrap you met in Chapter 3. This is the design tension of every automotive radar: shorten \(T_c\) to chase fast vehicles and you coarsen \(\Delta v\); lengthen the frame to resolve slow pedestrians and you shrink \(v_{\max}\).
Key Insight
Range and Doppler are separable because they live on orthogonal time axes, so the joint estimation is two 1D FFTs rather than one expensive 2D search. That factorization is why a range-Doppler map costs almost nothing to compute on an embedded DSP and why it, not the raw ADC cube, is the native input to radar neural networks. When you feed a CNN a range-Doppler map you are handing it the same kind of grid-structured, translation-covariant tensor an image model expects, which is why the backbones of Chapter 42 transfer so readily.
import numpy as np
def range_doppler_map(cube, win=True):
"""cube: complex (n_slow, n_fast) for one RX channel -> RD map in dB."""
n_slow, n_fast = cube.shape
if win: # taper both axes to suppress sidelobes
cube = cube * np.hanning(n_fast)[None, :]
cube = cube * np.hanning(n_slow)[:, None]
rng = np.fft.fft(cube, axis=1) # fast-time FFT -> range bins
rng = rng[:, : n_fast // 2] # keep positive-range half
rd = np.fft.fftshift(np.fft.fft(rng, axis=0), axes=0) # slow-time FFT -> Doppler
return 20 * np.log10(np.abs(rd) + 1e-6) # magnitude in dB
# synthetic: one target at range bin 40, Doppler bin +8
n_slow, n_fast = 128, 256
n = np.arange(n_fast); m = np.arange(n_slow)
sig = np.exp(1j * 2 * np.pi * (40 / n_fast * n)[None, :]
+ 1j * 2 * np.pi * (8 / n_slow * m)[:, None])
rd = range_doppler_map(sig)
peak = np.unravel_index(np.argmax(rd), rd.shape)
print("peak (doppler_bin, range_bin):", peak) # ~ (72, 40) after fftshift
The code above makes the fold concrete: two FFTs, a window on each axis to keep strong targets from smearing sidelobes over weak ones, and a magnitude image. Detection then runs a constant-false-alarm-rate (CFAR) threshold over this map, a classical change-detection idea covered generally in Chapter 12, before the angle FFT across antennas lifts each surviving peak into a 3D or 4D detection.
Library Shortcut
You rarely hand-roll this stack in production. Open-source radar toolkits such as pymmw and the OpenRadar library expose range_doppler(...) plus windowing, static-clutter removal, CFAR, and angle estimation as composable calls, turning the roughly 20 lines above plus a hand-written CFAR (another 40 or so) into three or four function calls. The library owns the fftshift bookkeeping, the mixed-radix FFT sizes real chips use, and the multi-RX bookkeeping that the toy example skips. Reach for the from-scratch version only when you are teaching the transform or targeting a microcontroller where you cannot afford the dependency.
Micro-Doppler: motion writes its own signature
A range-Doppler map assigns one velocity to a target. Real bodies are not rigid points. A walking person has a torso moving at one speed while arms, legs, and feet swing through a range of instantaneous velocities; a rotating drone propeller sweeps its blade tips through hundreds of meters per second; a beating heart and breathing chest displace the skin by millimeters. These micro-motions modulate the returned phase around the bulk Doppler, and that modulation is the micro-Doppler signature. To see it you stop collapsing slow time to a single Doppler bin and instead watch how the Doppler spectrum of one range bin evolves over time.
The tool is exactly the short-time Fourier transform from Chapter 7. Fix the range bin (or sum a few bins covering the target), take the slow-time complex series across many frames, and slide a windowed FFT along it. The result is a spectrogram: time on the horizontal axis, Doppler (velocity) on the vertical, energy as color. Human gait paints an unmistakable picture, a steady torso line with rhythmic sinusoidal "wings" from the swinging limbs, the cadence and amplitude of which encode who and what is moving. The same representation is what contactless vital-sign radars in Section 44.6 mine for the tiny periodic modulation of breathing and heartbeat.
import numpy as np
from scipy.signal import spectrogram
def micro_doppler(slow_series, prf, nperseg=64, overlap=0.9):
"""slow_series: complex slow-time samples at one range bin, sampled at PRF (Hz)."""
noverlap = int(nperseg * overlap)
f, t, Sxx = spectrogram(slow_series, fs=prf, nperseg=nperseg,
noverlap=noverlap, return_onesided=False,
mode='complex')
f = np.fft.fftshift(f) # center zero-Doppler
Sxx = np.fft.fftshift(Sxx, axes=0)
return f, t, 20 * np.log10(np.abs(Sxx) + 1e-6)
prf = 1000.0 # one frame Doppler line per ms
tt = np.arange(4096) / prf
torso = np.exp(1j * 2 * np.pi * 20 * tt) # steady 20 Hz Doppler
limb = np.exp(1j * 2 * np.pi * (20 + 30 * np.sin(2 * np.pi * 1.8 * tt)) * tt)
f, t, S = micro_doppler(torso + 0.6 * limb, prf) # torso + swinging limb
print("spectrogram shape (doppler, time):", S.shape)
The code above shows the one structural difference from the range-Doppler map: micro-Doppler is a time-frequency image, not a range-frequency image. The horizontal axis is now real elapsed time over hundreds of frames, so the representation trades all range information for a rich picture of how motion evolves. Feeding these spectrograms to a 2D CNN or a temporal model from Chapter 14 is the standard recipe for radar activity recognition, closely mirroring the accelerometer-based approach of Chapter 26 but with a privacy-preserving, no-camera sensor.
Practical Example: fall detection in an assisted-living apartment
A gerontechnology vendor mounts a 60 GHz radar in the ceiling corner of an elderly resident's living room. It never forms an image, which is the selling point to families worried about cameras in the bedroom. The pipeline continuously computes micro-Doppler spectrograms over a two-second sliding window. Ordinary activity, walking, sitting, reaching for a cup, produces the familiar rhythmic gait wings and gentle low-Doppler smears. A fall produces a signature no other activity matches: a brief, high-amplitude burst spanning a wide Doppler band (the whole body accelerating downward at once) followed by a near-silent flatline as the person comes to rest on the floor. A lightweight CNN trained on labeled spectrograms flags that burst-then-silence pattern. Crucially, the team split train and test by resident, not by clip, following the leakage-safe protocol of Chapter 5; an earlier random split had leaked each person's idiosyncratic gait across the split and inflated accuracy by nine points, a number that evaporated on a new resident.
Choosing and combining the two views
Reach for the range-Doppler map when you need to localize and count targets: detection, tracking, occupancy, and any task where "where and how fast" is the answer. Reach for the micro-Doppler spectrogram when you need to classify motion: human versus vehicle versus animal, gesture recognition, gait biometrics, drone-type identification, or vital signs. The two are complementary rather than competing, and the strongest systems keep both. A security radar first detects an intruder as a peak on the range-Doppler map, then extracts that target's range bin and runs micro-Doppler to decide whether it is a person, a swaying branch, or a wandering deer, sharply cutting false alarms. This detect-then-classify cascade is the practical shape of most deployed radar AI, and it sets up the learned end-to-end detectors of the next section, which increasingly operate on the range-Doppler tensor directly rather than on post-CFAR point lists.
Research Frontier
Classical pipelines threshold the range-Doppler map early and throw away everything below the CFAR line, discarding weak returns from pedestrians and low-RCS targets. Recent work keeps the full complex tensor and learns from it. RADIal and its associated FFT-RadNet showed that a network reading the raw range-Doppler spectrum detects and free-space-segments better than the CFAR-then-cluster baseline, and T-FFTRadNet and follow-ups push toward learning even from the pre-FFT ADC cube so the transform itself becomes part of the model. On the micro-Doppler side, self-supervised pretraining on unlabeled spectrograms, in the spirit of Chapter 17, is closing the gap that scarce labeled radar data has long imposed. The open question is how much of the hand-built FFT-and-CFAR chain survives once data is plentiful.
Exercise
A 77 GHz automotive radar (\(\lambda \approx 3.9\) mm) uses chirps of duration \(T_c = 50\ \mu\text{s}\), sweeps \(B = 400\) MHz, and sends \(N_c = 128\) chirps per frame. (a) Compute the range resolution \(\Delta R\), the velocity resolution \(\Delta v\), and the unambiguous velocity \(v_{\max}\). (b) A motorcycle approaches at 40 m/s; does it alias on this map, and if so, to what apparent velocity? (c) You must now also resolve a pedestrian's 0.3 m/s gait difference from the background. Which parameter do you change, what is the cost to \(v_{\max}\), and how could interleaving two chirp configurations recover both?
Self-Check
- Why does range resolution depend only on bandwidth while velocity resolution depends only on the total frame duration?
- What single processing choice distinguishes forming a range-Doppler map from forming a micro-Doppler spectrogram, and what information does each keep or discard?
- A range-Doppler-based tracker keeps flagging a windblown bush at the edge of a yard as an intruder. Which representation would you add to suppress the false alarm, and what feature of the signature would you key on?
What's Next
In Section 44.4, we move from these two-dimensional images to full 4D imaging radar, where a dense antenna array adds elevation and the range-Doppler-angle tensor feeds learned detection and occupancy networks such as RadarOcc and RCBEVDet, the radar counterparts to the lidar and BEV models you have already met.