FMCW Intro - Grouping

You can open this workbook in Google Colab to experiment with mmWrt image0

Below is an intro to mmWrt for simple targets position estimation

For a generic introduction to mmWave sensors: Watch Here

The problem

As distance estimation is based on FFT, spectral leakage even after CFAR may lead to detection of too many targets.

Changing synthetic target distances in order to make this more visible in the below code

The solution

Pass the index of FFT bin which are over the CFAR threshold to a peak_grouping function which groups them.

Multiple grouping algorithms are possible: * 1D: Adjacent grouping to lead or tail, or interpolation grouping to find the more likely position of the point target

[1]:
# Install a pip package in the current Jupyter kernel
import sys
from os.path import abspath, basename, join, pardir
import datetime

# hack to handle if running from git cloned folder or stand alone (like Google Colab)
cw = basename(abspath(join(".")))
dp = abspath(join(".",pardir))
if cw=="docs" and basename(dp) == "mmWrt":
    # running from cloned folder
    print("running from git folder, using local path (latest) mmWrt code", dp)
    sys.path.insert(0, dp)
else:
    print("running standalone, need to ensure mmWrt is installed")
    !{sys.executable} -m pip install mmWrt
print(datetime.datetime.now())
running from git folder, using local path (latest) mmWrt code c:\git\mmWrt
2024-05-23 13:57:54.525423
[2]:
from os.path import abspath, join, pardir
import sys
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib import colors
from numpy import where, expand_dims
from numpy import complex_ as complex

from mmWrt.Raytracing import rt_points  # noqa: E402
from mmWrt.Scene import Radar, Transmitter, Receiver, Target  # noqa: E402
from mmWrt import RadarSignalProcessing as rsp  # noqa: E402

Exposing the problem

[17]:
c = 3e8

debug_ON = False
test = 0
radar = Radar(transmitter=Transmitter(bw=0.2e9, slope=70e8, chirps_count=64, t_inter_chirp=0.15),
              receiver=Receiver(fs=5e3, n_adc=128, max_adc_buffer_size=1024,
                                debug=debug_ON), debug=debug_ON)

target1 = Target(7.2)
target2 = Target(12.1)

targets = [target1, target2]

bb = rt_points(radar, targets, datatype=complex, debug=debug_ON)
Distances, range_profile = rsp.range_fft(bb, debug=debug_ON, full_FFT=False)
ca_cfar = rsp.cfar_ca_1d(range_profile)

mag_r = abs(range_profile)
mag_c = abs(ca_cfar)
# little hack to remove small FFT ripples : mag_r> 5
target_filter = ((mag_r > mag_c) & (mag_r > 20))

index_peaks = where(target_filter)[0]
# grouped_peaks = rsp.peak_grouping_1d(index_peaks)

found_targets = [Target(Distances[i]) for i in index_peaks]
error = rsp.error([target1, target2], found_targets)
print("synthetic targets", [t.distance() for t in targets])
print("found targets", [t.distance() for t in found_targets])
print("amplitude found targets", [mag_r[i] for i in index_peaks])
print("error is", error)

# 2D representation of the FFT and CFAR
# plot on X,Y axis the FFT and CFAR
figure, axes = plt.subplots()
plt.plot(Distances, mag_r, '-o')
plt.plot(Distances, mag_c)
plt.title("2D plots FFT w/ CFAR")

# Add illustration of spectral leakage
# annotate
# xy: position of arrow
# xytext: position of text
xytext = (10, 110)
leak1_xy = (6.7, 70)
leak2_xy = (12.6, 79)

leakage_1 = plt.Circle( leak1_xy, 1, fill=False)
leakage_2 = plt.Circle( leak2_xy, 1, fill=False)
axes.add_artist(leakage_1)
axes.add_artist(leakage_2)

plt.annotate("spectral leakage", xy=leak1_xy,xytext=xytext,
             horizontalalignment="center",
             # Custom arrow
             arrowprops=dict(arrowstyle='->',lw=1)
             )

plt.annotate("", xy=leak2_xy,xytext=xytext,
             horizontalalignment="center",
             # Custom arrow
             arrowprops=dict(arrowstyle='->',lw=1)
             )

plt.show()
synthetic targets [7.2, 12.1]
found targets [6.696428571428571, 7.533482142857142, 11.71875, 12.555803571428571]
amplitude found targets [69.56788153259075, 91.09148943355741, 82.76276086905901, 79.16993976450007]
error is 5.070089285714286
_images/Grouping_5_1.png

The solution: peak grouping

In order to reduce the error, it is necessasry to proceed to peak grouping

below code shows how to do it by calling peak_grouping_1d

The result can be seen in that the error (distance between synthetic targets and found targets) is reduced

from 5.1 to 0.8

as demonstrated in the next code cell.

Note: this only selects the index with maximum amplitude, a further improvement would be to interpolate so instead of having an integer index, a float index which would translate in a more accurate position.

[23]:
index_peaks = where(target_filter)[0]
grouped_peaks = rsp.peak_grouping_1d(index_peaks, mag_r)

found_targets = [Target(Distances[i]) for i in grouped_peaks]
error_grouped = rsp.error([target1, target2], found_targets)
print("synthetic targets", [t.distance() for t in targets])
print("found targets", [t.distance() for t in found_targets])
print(f"RMS error reduced from: {error} to: {error_grouped}")


synthetic targets [7.2, 12.1]
found targets [7.533482142857142, 12.555803571428571]
RMS error reduced from: 5.070089285714286 to: 0.7892857142857137

NON REGRESSION

[24]:
try:
    assert error_grouped <= 0.8
except Exception as ex:
    log_msg = f"error should be <0.8, found to be : {error}"
    print(log_msg)
    raise