├── README.md ├── config └── get_params_value.py ├── generate_ra_3dfft.py ├── modules ├── fft │ ├── fft_angle.py │ ├── fft_doppler.py │ ├── fft_range.py │ └── fftshiftfreqgrid.py └── plot │ └── plot_rangeAng.py ├── read_bin.py ├── read_data └── readDCA1000.py ├── setup.py └── utils ├── Normalize.py └── matlab_hanning.py /README.md: -------------------------------------------------------------------------------- 1 | this repo is a Python version of [***mmWave-radar-signal-processing-and-microDoppler-classification***](https://github.com/Xiangyu-Gao/mmWave-radar-signal-processing-and-microDoppler-classification) 2 | translated from Matlab code, and implement parallel computing for better efficiency 3 | -------------------------------------------------------------------------------- /config/get_params_value.py: -------------------------------------------------------------------------------- 1 | import scipy.constants as constant 2 | import numpy as np 3 | from modules.fft.fftshiftfreqgrid import fftshiftfreqgrid 4 | 5 | class GetParamsValue(): 6 | def __init__(self) -> None: 7 | # constant parameters 8 | self.c = constant.c 9 | self.fc = 77e9 10 | self._lambda = self.c/self.fc 11 | self.Rx = 4 12 | self.Tx = 2 13 | 14 | # configuration parameters 15 | self.Fs = 4*10**6 16 | self.sweepSlope = 21.0017e12 17 | self.samples = 128 18 | self.loop = 255 19 | 20 | 21 | self.Tc = 120e-6 22 | self.fft_Rang = 134 23 | self.fft_Vel = 256 24 | self.fft_Ang = 128 25 | self.num_crop = 3 26 | self.max_value = 1e+04 27 | 28 | # Creat grid table 29 | freq_res = self.Fs/self.fft_Rang 30 | freq_grid = np.arange(self.fft_Rang) * freq_res 31 | self.rng_grid = freq_grid*self.c/self.sweepSlope/2 # d=frediff_grid*c/sweepSlope/2;\n', 32 | 33 | w = np.linspace(-1,1,self.fft_Ang) # angle_grid\n', 34 | self.agl_grid = np.arcsin(w)*180/np.pi # [-1,1]->[-pi/2,pi/2]\n', 35 | 36 | # velocity_grid 37 | dop_grid = fftshiftfreqgrid(self.fft_Vel,1/self.Tc) # now fs is equal to 1/Tc\n', 38 | self.vel_grid = dop_grid*self._lambda/2; # unit: m/s, v = lamda/4*[-fs,fs], dopgrid = [-fs/2,fs/2]\n', 39 | 40 | if __name__ == "__main__": 41 | data = GetParamsValue() 42 | print(data.rng_grid.shape) 43 | print(data.agl_grid.shape) 44 | print(data.vel_grid.shape) -------------------------------------------------------------------------------- /generate_ra_3dfft.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from config.get_params_value import GetParamsValue 5 | from modules.fft.test import * 6 | from utils.Normalize import Normalize 7 | from modules.plot.plot_rangeAng import plot_rangeAng 8 | 9 | params = GetParamsValue() 10 | 11 | c = params.c # Speed of light in air (m/s) 12 | fc = params.fc # Center frequency (Hz) 13 | _lambda = params._lambda 14 | Rx = params.Rx 15 | Tx = params.Tx 16 | 17 | # configuration parameters 18 | Fs = params.Fs 19 | sweepSlope = params.sweepSlope 20 | samples = params.samples 21 | loop = params.loop 22 | 23 | Tc = params.Tc # us 24 | fft_Rang = params.fft_Rang 25 | fft_Vel = params.fft_Vel 26 | fft_Ang = params.fft_Ang 27 | num_crop = params.num_crop 28 | max_value = params.max_value # normalization the maximum of data WITH 1843 29 | 30 | # Creat grid table 31 | rng_grid = params.rng_grid 32 | agl_grid = params.agl_grid 33 | vel_grid = params.vel_grid 34 | 35 | # Algorithm parameters 36 | data_each_frame = samples*loop*Tx 37 | set_frame_number = 30 38 | frame_start = 1 39 | frame_end = set_frame_number 40 | Is_Windowed = 1# 1==> Windowing before doing range and angle fft 41 | Is_plot_rangeDop = 1 42 | 43 | load_mat = loadmat('/home/ray/Desktop/mmWave-radar-signal-processing-and-microDoppler-classification/template data/bms1000_30fs.mat') 44 | # test_mat = loadmat('/home/ray/Desktop/mmWave-radar-signal-processing-and-microDoppler-classification/template data/cms1000_30fs.mat') 45 | # test_mat = loadmat('/home/ray/Desktop/mmWave-radar-signal-processing-and-microDoppler-classification/template data/pms1000_30fs.mat') 46 | data_frames = load_mat['data_frames'] 47 | 48 | # for i in range(set_frame_number): 49 | i = 0 50 | 51 | data_frame = data_frames[:, i*data_each_frame:(i+1)*data_each_frame] 52 | # for cj in range(Tx*loop): 53 | # temp_data = data_frame[:, cj*samples:(cj+1)*samples] 54 | # data_chirp[:,:,cj] = temp_data 55 | 56 | # parallel 57 | data_chirp = np.transpose(np.reshape(data_frame, (Rx, -1, samples)), (0,2,1)) 58 | 59 | chirp_odd = data_chirp[:, :, ::2] 60 | chirp_even = data_chirp[:, :, 1::2] 61 | 62 | # Range FFT for odd chirps 63 | chirp_odd = np.transpose(chirp_odd, (1, 0, 2)) 64 | chirp_even = np.transpose(chirp_even, (1, 0, 2)) 65 | 66 | # Range FFT for odd chirps 67 | Rangedata_odd = fft_range(chirp_odd,fft_Rang,Is_Windowed) 68 | # Range FFT for even chirps 69 | Rangedata_even = fft_range(chirp_even,fft_Rang,Is_Windowed) 70 | 71 | #Doppler FFT 72 | # Dopplerdata_odd = fft_doppler(Rangedata_odd, fft_Vel, False) 73 | # Dopplerdata_even = fft_doppler(Rangedata_even, fft_Vel, False) 74 | # Dopdata_sum = np.squeeze(np.mean(np.abs(Dopplerdata_odd), axis=1)) 75 | 76 | Rangedata_merge = np.concatenate((Rangedata_odd, Rangedata_even), axis=1) 77 | # Angle FFt 78 | Angdata = fft_angle(Rangedata_merge,fft_Ang,Is_Windowed) 79 | Angdata_crop = Angdata[num_crop:fft_Rang - num_crop, :, :] 80 | Angdata_crop = Normalize(Angdata_crop, max_value) 81 | 82 | # Xpow = np.abs(Angdata_crop) 83 | # Xpow = np.squeeze(np.sum(Xpow, axis=2) / Xpow.shape[2]) 84 | 85 | # Xsnr = Xpow 86 | 87 | # fig = plt.figure(frameon=False) 88 | # ax = plt.Axes(fig, [0., 0., 1., 1.]) 89 | # ax.set_axis_off() 90 | # fig.add_axes(ax) 91 | # ax.imshow(Xsnr[::-1], aspect='auto') 92 | # plt.savefig('RF_image.png') 93 | # plt.close(fig) 94 | 95 | plot_rangeAng(Angdata_crop,rng_grid[num_crop:fft_Rang-num_crop],agl_grid) -------------------------------------------------------------------------------- /modules/fft/fft_angle.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import windows 3 | 4 | def fft_angle(Xcube, fft_Ang, Is_Windowed): 5 | # Nr = Xcube.shape[0] # length of Chirp 6 | Ne = Xcube.shape[1] # length of receiver 7 | # Nd = Xcube.shape[2] # length of chirp loop 8 | 9 | # AngData = np.zeros((Nr, fft_Ang, Nd), dtype=np.complex128) 10 | # for i in range(Nd): 11 | # for j in range(Nr): 12 | # if Is_Windowed: 13 | # win_xcube = np.reshape(Xcube[j, :, i], (Ne)) * windows.taylor(Ne) 14 | # else: 15 | # win_xcube = np.reshape(Xcube[j, :, i], (Ne)) * 1 16 | 17 | # AngData[j, :, i] = np.fft.fftshift(np.fft.fft(win_xcube, axis=0, n=fft_Ang)) 18 | # parallel 19 | win_xcube = Xcube * np.reshape(windows.taylor(Ne), (1,-1,1)) 20 | AngData = np.fft.fftshift(np.fft.fft(win_xcube, axis=1, n=fft_Ang), axes=1) 21 | 22 | 23 | return AngData 24 | -------------------------------------------------------------------------------- /modules/fft/fft_doppler.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from utils.matlab_hanning import hanning 3 | 4 | def fft_doppler(Xcube, fft_Vel, Is_Windowed): 5 | # Nr = Xcube.shape[0] # length of Chirp 6 | # Ne = Xcube.shape[1] # number of receiver 7 | Nd = Xcube.shape[2] # number of chirp loop 8 | 9 | # DopData = np.zeros((Nr, Ne, fft_Vel), dtype=np.complex128) 10 | 11 | # # Second fft on dopper dimension 12 | # for i in range(Ne): 13 | # for j in range(Nr): 14 | # if Is_Windowed: 15 | # win_dop = np.reshape(Xcube[j, i, :], (Nd)) * np.hanning(Nd) 16 | # else: 17 | # win_dop = np.reshape(Xcube[j, i, :], (Nd)) 18 | 19 | # DopData[j, i, :] = np.fft.fftshift(np.fft.fft(win_dop, fft_Vel)) 20 | # parallel 21 | if Is_Windowed: 22 | win_dop = Xcube * np.reshape(hanning(Nd), (1,1,-1)) 23 | else: 24 | win_dop = Xcube 25 | DopData = np.fft.fftshift(np.fft.fft(win_dop, fft_Vel, axis=2), axes=2) 26 | 27 | return DopData 28 | -------------------------------------------------------------------------------- /modules/fft/fft_range.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from utils.matlab_hanning import hanning 3 | 4 | def fft_range(Xcube,fft_Rang,Is_Windowed): 5 | Nr = Xcube.shape[0] # length of Chirp (number of samples) 6 | # Ne = Xcube.shape[1] # number of receiver 7 | # Nd = Xcube.shape[2] # length of chirp loop 8 | 9 | # Rangedata = np.zeros((fft_Rang, Ne, Nd), dtype=np.complex128) 10 | 11 | # for i in range(Ne): 12 | # for j in range(Nd): 13 | # if Is_Windowed: 14 | # win_rng = Xcube[:, i, j] * np.hanning(Nr) 15 | # else: 16 | # win_rng = Xcube[:, i, j] 17 | 18 | # Rangedata[:, i, j] = np.fft.fft(win_rng, fft_Rang) 19 | 20 | # parallel 21 | win_rng = Xcube* np.reshape(hanning(Nr), (-1,1,1)) 22 | Rangedata = np.fft.fft(win_rng, fft_Rang, axis=0) 23 | return Rangedata -------------------------------------------------------------------------------- /modules/fft/fftshiftfreqgrid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def fftshiftfreqgrid(N,Fs): 4 | freq_res = Fs/N 5 | freq_grid = np.arange(N) * freq_res 6 | Nyq = Fs/2 7 | half_res = freq_res/2 8 | if N % 2: # odd 9 | pass 10 | # idx = 1:(N-1)/2; 11 | # halfpts = (N+1)/2; 12 | # freq_grid(halfpts) = Nyq-half_res; 13 | # freq_grid(halfpts+1) = Nyq+half_res; 14 | else: 15 | idx = np.arange(N//2) 16 | hafpts = N//2 17 | freq_grid[hafpts] = Nyq 18 | freq_grid = np.fft.fftshift(freq_grid) 19 | freq_grid[idx] = freq_grid[idx]-Fs 20 | return freq_grid -------------------------------------------------------------------------------- /modules/plot/plot_rangeAng.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | def plot_rangeAng(Xcube, rng_grid, agl_grid): 4 | Xpow = np.abs(Xcube) 5 | Xpow = np.squeeze(np.sum(Xpow, axis=2) / Xpow.shape[2]) 6 | 7 | Xsnr = Xpow 8 | 9 | fig = plt.figure(figsize=(7, 5)) 10 | ax = fig.add_subplot(111, projection='3d') 11 | # ax.plot_surface(yvalue, xvalue, np.transpose(Xsnr), cmap='jet') 12 | agl_grid2,rng_grid2 = np.meshgrid(agl_grid,rng_grid) 13 | ax.plot_surface(agl_grid2,rng_grid2, Xsnr, cmap='jet') 14 | # ax.view_init(90, 0) 15 | # ax.set_xlim(-60, 60) 16 | # ax.set_ylim(2, 25) 17 | ax.set_xlabel('Angle of arrival (degrees)') 18 | ax.set_ylabel('Range (meters)') 19 | ax.set_zlabel('Amplitude') 20 | ax.set_title('Range-Angle heatmap') 21 | plt.savefig('RF_image1.png') 22 | plt.close(fig) 23 | 24 | 25 | index = [] 26 | index_amount = 20 27 | tick = (agl_grid.size-1) / index_amount 28 | for i in range(index_amount+1): 29 | index.append(round(tick*i)) 30 | 31 | plt.figure() 32 | plt.imshow(Xsnr[::-1], cmap='jet') 33 | plt.xticks(index, agl_grid[index].round().astype('int'), rotation=90); 34 | plt.yticks(index, rng_grid[index[::-1]].round().astype('int'), rotation=0); 35 | plt.xlabel('Angle of arrival (degrees)') 36 | plt.ylabel('Range (meters)') 37 | plt.title('Range-Angle heatmap') 38 | plt.savefig('RF_image2.png') 39 | plt.close() -------------------------------------------------------------------------------- /read_bin.py: -------------------------------------------------------------------------------- 1 | from read_data.readDCA1000 import readDCA1000 2 | 3 | samples = 128# num of samples per chirp 4 | loop = 255 5 | Tx = 2 6 | 7 | 8 | folder_location = '/home/ray/Desktop/mmWave-radar-signal-processing-and-microDoppler-classification/template data/bin_data/adc_data_0.bin' 9 | data = readDCA1000(folder_location, samples) 10 | data_length = data.shape[1] 11 | data_each_frame = samples*loop*Tx 12 | Frame_num = data_length/data_each_frame 13 | 14 | if __name__ == '__main__': 15 | print(data.shape) -------------------------------------------------------------------------------- /read_data/readDCA1000.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def readDCA1000(folder_locaion, numADCSamples): 3 | numADCBits = 16 4 | numRX = 4 5 | umLanes = 2 6 | max_numChirps = 459000 7 | isReal = 0 8 | 9 | with open(folder_locaion, 'rb') as bin: 10 | data = bin.read() 11 | adcData = np.frombuffer(data, dtype=np.int16) 12 | 13 | if numADCBits != 16: 14 | pass 15 | 16 | fileSize = adcData.size 17 | if isReal: 18 | pass 19 | else: 20 | numChirps = fileSize//2//numADCSamples//numRX 21 | LVDS = np.zeros((1, fileSize//2), dtype=np.complex128) 22 | #combine real and imaginary part into complex data 23 | #read in file: 2I is followed by 2Q 24 | # counter = 0 25 | # for i in range(0, fileSize, 4): 26 | # # for i in range(0, 20, 4): 27 | # LVDS[0,counter] = adcData[i] + 1j*adcData[i+2] 28 | # LVDS[0,counter+1] = adcData[i+1] + 1j*adcData[i+3] 29 | # counter += 2 30 | 31 | # parallel 32 | LVDS[0, ::2] = adcData[::4]+ 1j*adcData[2::4] 33 | LVDS[0, 1::2] = adcData[1::4]+ 1j*adcData[3::4] 34 | 35 | LVDS = LVDS.reshape(numADCSamples*numRX, numChirps) 36 | 37 | adcData = np.zeros((numRX,numChirps*numADCSamples), dtype=np.complex128) 38 | if numChirps <= max_numChirps: 39 | for row in range(numRX): 40 | for i in range(numChirps): 41 | adcData[row, i*numADCSamples:(i+1)*numADCSamples] = LVDS[row*numADCSamples:(row+1)*numADCSamples, i] 42 | return adcData 43 | 44 | 45 | if __name__ == '__main__': 46 | import matplotlib.pyplot as plt 47 | 48 | data = readDCA1000('/home/ray/Desktop/mmWave-radar-signal-processing-and-microDoppler-classification/template data/bin_data/adc_data_0.bin', 49 | 128) 50 | fig = plt.figure() 51 | plt.plot(np.transpose(data[:,:1000])) 52 | plt.legend([f"Rx_{i}" for i in range(4)]) 53 | plt.savefig(f'/home/ray/Desktop/adcData.png') 54 | plt.close(fig) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='mmWave_python', version='1.0', packages=find_packages()) 4 | -------------------------------------------------------------------------------- /utils/Normalize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def Normalize(Xcube, max_val): 3 | Xcube = Xcube / max_val 4 | Angdata = Xcube.astype(np.float32) 5 | return Angdata -------------------------------------------------------------------------------- /utils/matlab_hanning.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def hanning(n): 4 | ret = np.arange(1,n+1) 5 | return .5 * (1-np.cos(2*np.pi*ret/(n+1))) --------------------------------------------------------------------------------