Skip to content

RF Datacube

RF datacube creation and processing.

Provides helpers to allocate a pulse-Doppler datacube, compute range and frequency axes, apply a matched filter via fast convolution, and Doppler-process the slow-time dimension with an FFT.

data_cube(fs, prf, N_p)

Creates an empty, complex-valued datacube.

This function initializes a 2D NumPy array (datacube) with zeros, representing the raw data collected over a coherent processing interval (CPI). The dimensions are determined by the number of range bins and the number of pulses.

Parameters:

Name Type Description Default
fs float

The sampling frequency in Hertz [Hz].

required
prf float

The pulse repetition frequency in Hertz [Hz].

required
N_p int

The number of pulses in the coherent processing interval (CPI).

required

Returns:

Type Description
ndarray

np.ndarray: A 2D NumPy array of shape (N_range_bins, N_pulses) initialized with complex zeros.

Source code in src/rad_lab/rf_datacube.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def data_cube(fs: float, prf: float, N_p: int) -> np.ndarray:
    """Creates an empty, complex-valued datacube.

    This function initializes a 2D NumPy array (datacube) with zeros,
    representing the raw data collected over a coherent processing interval (CPI).
    The dimensions are determined by the number of range bins and the number of pulses.

    Args:
        fs (float): The sampling frequency in Hertz [Hz].
        prf (float): The pulse repetition frequency in Hertz [Hz].
        N_p (int): The number of pulses in the coherent processing interval (CPI).

    Returns:
        np.ndarray: A 2D NumPy array of shape (N_range_bins, N_pulses)
                    initialized with complex zeros.
    """
    Nr = number_range_bins(fs, prf)
    dc = np.zeros((Nr, N_p), dtype=np.complex64)
    return dc

doppler_process(datacube, fs)

Performs Doppler processing on a radar datacube.

This function applies a Fast Fourier Transform (FFT) across the slow-time (pulse) dimension of the datacube to transform the data into the Range-Doppler domain. The operation is performed in-place on the input datacube. It also generates the corresponding Doppler frequency and range axes.

Parameters:

Name Type Description Default
datacube ndarray

A 2D NumPy array representing the time-domain datacube, with shape (N_range_bins, N_pulses). This array will be modified in-place.

required
fs float

The sampling frequency in Hertz [Hz].

required

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - f_axis (np.ndarray): The Doppler frequency axis, [-PRF/2, PRF/2) [Hz]. - R_axis (np.ndarray): The range axis [delta_r, R_ambigious][m].

Source code in src/rad_lab/rf_datacube.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def doppler_process(datacube: np.ndarray, fs: float) -> tuple[np.ndarray, np.ndarray]:
    """Performs Doppler processing on a radar datacube.

    This function applies a Fast Fourier Transform (FFT) across the slow-time
    (pulse) dimension of the datacube to transform the data into the
    Range-Doppler domain. The operation is performed in-place on the input
    datacube. It also generates the corresponding Doppler frequency and range axes.

    Args:
        datacube (np.ndarray): A 2D NumPy array representing the time-domain
                             datacube, with shape (N_range_bins, N_pulses).
                             This array will be modified in-place.
        fs (float): The sampling frequency in Hertz [Hz].

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - f_axis (np.ndarray): The Doppler frequency axis, [-PRF/2, PRF/2) [Hz].
            - R_axis (np.ndarray): The range axis [delta_r, R_ambigious] [m].
    """
    N_r, N_p = datacube.shape
    dR_grid = c.C / (2 * fs)
    prf = fs / datacube.shape[0]
    R_axis = np.arange(1, N_r + 1) * dR_grid  # Process fast time
    f_axis = fft.fftshift(fft.fftfreq(N_p, 1 / prf))  # process slow time
    datacube[:] = fft.fftshift(fft.fft(datacube, axis=1), axes=1)
    return f_axis, R_axis

matchfilter(datacube, pulse_wvf, pedantic=True)

Applies a matched filter to a datacube for pulse compression.

This function processes each pulse (column) in the datacube with a matched filter to perform pulse compression, which improves signal-to-noise ratio and range resolution. The operation is performed in-place.

Two implementations are available: - Pedantic (True): Iteratively applies the matched filter to each pulse using a time-domain helper function. This is typically slower but can be clearer to understand. - Non-pedantic (False): Uses a more efficient frequency-domain approach by performing convolution via FFT. This involves a single FFT of the waveform kernel and is generally faster for large datacubes.

Parameters:

Name Type Description Default
datacube ndarray

The 2D time-domain datacube to be processed, with shape (N_range_bins, N_pulses). This array is modified in-place.

required
pulse_wvf ndarray

A 1D array representing the transmitted pulse waveform samples.

required
pedantic bool

If True, uses the iterative, time-domain filtering approach. If False, uses the faster frequency-domain convolution. Defaults to True.

True

Returns:

Name Type Description
None None

The datacube is modified in-place.

Source code in src/rad_lab/rf_datacube.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def matchfilter(datacube: np.ndarray, pulse_wvf: np.ndarray, pedantic: bool = True) -> None:
    """Applies a matched filter to a datacube for pulse compression.

    This function processes each pulse (column) in the datacube with a matched
    filter to perform pulse compression, which improves signal-to-noise ratio
    and range resolution. The operation is performed in-place.

    Two implementations are available:
    - Pedantic (True): Iteratively applies the matched filter to each pulse
      using a time-domain helper function. This is typically slower but can
      be clearer to understand.
    - Non-pedantic (False): Uses a more efficient frequency-domain approach
      by performing convolution via FFT. This involves a single FFT of the
      waveform kernel and is generally faster for large datacubes.

    Args:
        datacube (np.ndarray): The 2D time-domain datacube to be processed, with
                               shape (N_range_bins, N_pulses). This array is
                               modified in-place.
        pulse_wvf (np.ndarray): A 1D array representing the transmitted pulse
                                waveform samples.
        pedantic (bool, optional): If True, uses the iterative, time-domain
                                   filtering approach. If False, uses the
                                   faster frequency-domain convolution.
                                   Defaults to True.

    Returns:
        None: The `datacube` is modified in-place.
    """
    if pedantic:
        for j in range(datacube.shape[1]):
            _, mf = matchfilter_with_waveform(datacube[:, j], pulse_wvf)
            datacube[:, j] = mf
    else:
        # FFT-based matched filter applied to all pulses at once
        kernel = np.conj(pulse_wvf)[::-1]
        datacube[:] = signal.fftconvolve(datacube, kernel.reshape(-1, 1), mode="same", axes=0)

number_range_bins(fs, prf)

Calculates the number of range bins.

The number of range bins is determined by the number of samples collected during one pulse repetition interval (PRI). PRI is the reciprocal of the pulse repetition frequency (PRF).

Parameters:

Name Type Description Default
fs float

The sampling frequency [Hz].

required
prf float

The pulse repetition frequency [Hz].

required

Returns:

Name Type Description
int int

The total number of range bins.

Source code in src/rad_lab/rf_datacube.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def number_range_bins(fs: float, prf: float) -> int:
    """Calculates the number of range bins.

    The number of range bins is determined by the number of samples collected
    during one pulse repetition interval (PRI). PRI is the reciprocal of the
    pulse repetition frequency (PRF).

    Args:
        fs (float): The sampling frequency [Hz].
        prf (float): The pulse repetition frequency [Hz].

    Returns:
        int: The total number of range bins.
    """
    return int(fs / prf)

range_axis(fs, N_r)

Generates the range axis for a radar datacube.

This function calculates the range corresponding to each range bin based on the sampling frequency. The range resolution is determined by the speed of light and the sampling rate.

Parameters:

Name Type Description Default
fs float

The sampling frequency in Hertz [Hz].

required
N_r int

The number of range bins (samples in fast-time).

required

Returns:

Type Description
ndarray

np.ndarray: A 1D NumPy array representing the range axis in meters [m].

Source code in src/rad_lab/rf_datacube.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def range_axis(fs: float, N_r: int) -> np.ndarray:
    """Generates the range axis for a radar datacube.

    This function calculates the range corresponding to each range bin
    based on the sampling frequency. The range resolution is determined
    by the speed of light and the sampling rate.

    Args:
        fs (float): The sampling frequency in Hertz [Hz].
        N_r (int): The number of range bins (samples in fast-time).

    Returns:
        np.ndarray: A 1D NumPy array representing the range axis in meters [m].
    """
    dR_grid = c.C / (2 * fs)
    R_axis = np.arange(1, N_r + 1) * dR_grid  # Process fast time
    return R_axis