Signal processing chain

How Radar turns a data cube into range profiles, range-Doppler maps, angle estimates, detections, point clouds and tracks, and the conventions each stage uses.

witwin.radar.processing turns a simulated cube into the products a radar pipeline uses. Every stage takes a typed record that carries its own axes, so no stage has to guess whether a fast axis holds samples or range bins. This page explains the chain, its records and its conventions.

The chain#

Result.frame(i)  ->  ProcessingCube [TX, RX, slow, fast]
                 ->  range_profile        ->  RangeProfile    [..., slow, range]
                 ->  range_doppler_map    ->  RangeDopplerMap [..., doppler, range]
                 ->  combine_coherent     ->  real map [doppler, range]
                 ->  ca_cfar / os_cfar    ->  Detections (mask, threshold)
                 ->  point_cloud          ->  PointCloud (xyz, velocity, energy, range)
                 ->  DetectionFrame       ->  TrackHandoff

Side branches take the same records: music_image and music_spectrum take a range profile, beam_cube takes a range-Doppler map and steering weights, and microdoppler_spectrogram takes a slow-time signal.

Frame: the starting point#

result.frame(i) returns a Frame: one frame's cube [TX, RX, slow, fast] paired with the ProcessingAxes built from the waveform and the array that produced it. Its methods call the processing functions and add no arithmetic:

MethodCalls
frame.processing_cube()Packs the cube and axes as a ProcessingCube
frame.range_profile(window=None, remove_dc=False)range_profile
frame.range_doppler(window="hann", range_window=None, remove_dc=False)range_profile, then range_doppler_map
frame.array()ArrayGeometry.from_axes
frame.points(...)range_doppler, combine_coherent, ca_cfar, point_cloud

Use the functions directly when you need a different detector or estimator.

ProcessingAxes#

ProcessingAxes is the one place a bin index becomes a physical quantity. It holds, in SI units:

  • range_m and velocity_mps: float64 axis tensors.
  • range_bin_m, range_origin_m, max_unambiguous_range_m, velocity_bin_mps, max_unambiguous_speed_mps.
  • slow_time_period_s: the slow-time sample period. For TDM-MIMO FMCW it is the slot period, chirp_period · num_tx.
  • wavelength_m, reference_frequency_hz, phasor, doppler_sign.
  • The array layout: num_tx, num_rx, element_spacing_m, tx_loc_half_wavelength, rx_loc_half_wavelength.

axes.units maps every field to its unit.

Range#

range_profile dispatches on the waveform stored in the axes:

  • FMCW spectrum output: already in range. No second transform runs.
  • FMCW beat output: a normalized DFT over the sample axis.
  • OFDM: an inverse DFT over subcarriers, which gives the channel impulse response.
  • Pulsed: a matched filter against the analytic pulse replica.

window tapers the input of the final transform. The default is rectangular, and a non-rectangular window on an FMCW spectrum cube is refused, because the range transform has already run. Use Fmcw(output="beat") if you want a range window. remove_dc=True subtracts the fast-time mean; it is off by default because it removes clutter.

Doppler#

range_doppler_map transforms the slow axis and keeps range. The Doppler axis is fftshifted and ascending.

Doppler sign

For every waveform, a positive Doppler bin is a closing target (moving toward the radar). The FMCW beat cube and the OFDM and pulsed cubes have opposite phasor conventions, and range_doppler_map is the one place that reconciles them.

The windows are "rectangular", "hann", "hamming" and "blackman". range_doppler_map(window=None) uses rectangular; Frame.range_doppler() uses Hann on the slow axis by default.

Amplitude convention#

Every transform is amplitude-normalized. An isolated path that falls exactly on a bin peaks at its own complex coefficient magnitude times the window's coherent gain (mean(w)), not at a value scaled by the number of samples. RangeProfile.window_coherent_gain and RangeDopplerMap.window_coherent_gain publish that gain. For pulsed waveforms with a window, the identity holds only approximately, because the window tapers the correlation spectrum.

Detection and point clouds#

  1. combine_coherent(rd.data) sums the sensor pairs with their phases (the boresight beam) and returns a real magnitude map. One threshold then means the same thing across the virtual array.
  2. ca_cfar (cell-averaging) or os_cfar (ordered-statistic) returns Detections: a boolean mask and the threshold it beat. ca_cfar_1d works on a range profile.
  3. point_cloud(detections, rd, array, route=...) estimates the angle of each detected cell with "phase_comparison" (default, coarse elevation) or "fft2" (a 2-D FFT over a planar virtual array), compensates the TDM phase, and returns a PointCloud.

Point cloud coordinates are in the array's local frame: x is the array's horizontal axis, y is the component along the boresight, and z is elevation. This differs from the radar pose frame, where the boresight is -z. velocity_mps is closing-positive and energy is in dB.

Angle and imaging#

  • conventional_steering and mvdr_weights build beamforming weights. Both satisfy w^H a = 1, so they are interchangeable in beam_cube.
  • phase_comparison_aoa and fft2_aoa return direction cosines for a set of cells.
  • music_spectrum and music_image compute MUSIC pseudo-spectra over a planar array. MUSIC needs a noise subspace, so add receiver noise (for example Noise(figure=10.0)) when you use it on simulated data.
  • tdm_compensate removes the phase a moving target writes across TDM transmitters before angle estimation.

Micro-Doppler and tracking#

microdoppler_spectrogram runs a short-time Fourier transform over a SlowTimeSignal: one sensor pair and one range gate, with uniform timestamps. Frame gaps are not uniform samples, so segment or resample them first. The frequency is the physical Doppler f_D = -f_ref · d(tau)/dt, which is negative for a receding target.

DetectionFrame.from_point_cloud timestamps a point cloud for a tracker, and TrackHandoff sequences frames through an associator. The built-in nearest_neighbour_associator is a reference implementation, not a tracker: it has no track initiation, death or covariance.

Which stages are differentiable#

Range profile, range-Doppler map, beamforming, the matched filter, TDM compensation and MUSIC are differentiable. CFAR, point cloud, angle-of-arrival peak picking and tracking are not: they make discrete decisions, and they refuse inputs that carry a gradient instead of returning a detached answer. See Differentiability.