Signal utilities
Matched filtering for pulsed radar, coherent and incoherent combining, and micro-Doppler spectrograms in witwin.radar.processing.
This page covers the lower-level signal functions of witwin.radar.processing.
from witwin.radar.processing import (SlowTimeSignal, combine_coherent, combine_incoherent, lag_axis,
matched_filter, microdoppler_spectrogram, pulse_samples)matched_filter#
matched_filter(signal, spec, *, oversample=1) -> torch.TensorCorrelate the fast-time axis of a pulsed train against the conjugate of the pulse replica. range_profile calls it for a pulsed cube; call it directly for raw trains.
Received train [..., num_samples], complex.
The pulsed synthesis spec, from radar.waveform_spec().
1Zero-padding factor in the lag spectrum. Gives exact band-limited interpolation of the correlation on a finer lag grid; values on the original grid do not change.
Returns complex tensor [..., num_samples · oversample], indexed by lag from the range-gate start, in the input's precision. With the unit-energy pulse, an isolated row peaks at its own transfer coefficient.
lag_axis#
lag_axis(spec, *, oversample=1, device='cpu') -> torch.TensorThe delay of each matched_filter output sample: range_gate_start + m · T_s / oversample, in seconds, float64.
spec = radar.waveform_spec()
y = matched_filter(frame.cube, spec, oversample=4)
delays = lag_axis(spec, oversample=4, device=y.device)pulse_samples#
pulse_samples(spec, device='cpu') -> torch.TensorThe transmitted pulse replica on the ADC grid, complex128[M_p] with M_p = round(pulse_width / T_s). The amplitude is 1 / sqrt(pulse_width). When pulse_width is not a whole number of samples (spec.pulse_grid_is_commensurate is False), its discrete energy is M_p · T_s / pulse_width instead of 1, and the matched-filter peak carries the same factor.
combine_coherent#
combine_coherent(cube) -> torch.Tensorabs(sum_p cube[p]): sum the sensor pairs with their phases, then take the magnitude. This is the boresight beam, used before a detector so one threshold applies across the virtual array.
Complex [..., D, R] with the sensor pairs in the leading axes, for example [TX, RX, D, R].
Returns real [D, R].
combine_incoherent#
combine_incoherent(cubes) -> torch.Tensorsum_j abs(cube_j)^2: the power sum of independently exported components. The result is real and has no phase, so it cannot go back into a coherent stage. It is differentiable, including at exact zeros.
Tensors of the same shape on the same device. Complex or real.
Returns real tensor of the same shape.
Raises ValueError for an empty sequence or mismatched shapes or devices. TypeError for an entry that is not a tensor.
SlowTimeSignal#
class SlowTimeSignal(samples, times_s, phasor)One slow-time sequence (one sensor pair and one range gate) with absolute timestamps and its phasor convention.
Complex [..., time].
Timestamp of each sample, s. At least two, finite, strictly increasing and uniformly spaced.
Phasor convention of the source cube, for example result.phasor.
Raises ValueError if the samples are not complex, the timestamps do not match the last axis, or the spacing is not uniform. TDM interleaving and gaps between frames are not uniform; split or resample them first.
microdoppler_spectrogram#
microdoppler_spectrogram(signal, *, window_slots, hop_slots, window='hann')
-> tuple[torch.Tensor, torch.Tensor, torch.Tensor]Short-time Fourier transform over slow time, with the physical Doppler f_D = -f_ref · d(tau)/dt. FMCW beat-convention bins are reversed from the declared phasor, as in range_doppler_map.
The slow-time sequence.
Window length, samples. Positive, at most the sequence length.
Step between windows, samples. Positive.
'hann'"hann" or "rectangular".
Returns (times, frequencies, spectrum): window-centre times in seconds (float64), the signed fftshifted Doppler frequency axis in Hz, and the complex spectrum [..., windows, frequency].
Raises TypeError if signal is not a SlowTimeSignal. ValueError for a non-positive or too-long window or hop.
rd_gate = 70 # range bin of interest
frame = result.frame(0)
profile = frame.range_profile() # [TX, RX, chirp, range]
samples = profile.data[0, 0, :, rd_gate] # one pair, one range bin
period = frame.axes.slow_time_period_s
signal = SlowTimeSignal(samples, tuple(i * period for i in range(samples.shape[-1])), result.phasor)
times, freqs, spec = microdoppler_spectrogram(signal, window_slots=32, hop_slots=8)