summaryrefslogtreecommitdiffstats
path: root/util/_signal_processing/krakenSDR_signal_processor.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/_signal_processing/krakenSDR_signal_processor.py')
-rwxr-xr-xutil/_signal_processing/krakenSDR_signal_processor.py713
1 files changed, 0 insertions, 713 deletions
diff --git a/util/_signal_processing/krakenSDR_signal_processor.py b/util/_signal_processing/krakenSDR_signal_processor.py
deleted file mode 100755
index 31f1836..0000000
--- a/util/_signal_processing/krakenSDR_signal_processor.py
+++ /dev/null
@@ -1,713 +0,0 @@
-# KrakenSDR Signal Processor
-#
-# Copyright (C) 2018-2021 Carl Laufer, Tamás Pető
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
-#
-#
-# - coding: utf-8 -*-
-
-# Import built-in modules
-import sys
-import os
-import time
-import logging
-import threading
-import queue
-import math
-
-# Import optimization modules
-import numba as nb
-from numba import jit, njit
-from functools import lru_cache
-
-# Math support
-import numpy as np
-import numpy.linalg as lin
-#from numba import jit
-import pyfftw
-
-# Signal processing support
-import scipy
-from scipy import fft
-from scipy import signal
-from scipy.signal import correlate
-from scipy.signal import convolve
-
-from pyapril import channelPreparation as cp
-from pyapril import clutterCancellation as cc
-from pyapril import detector as det
-
-c_dtype = np.complex64
-
-#import socket
-# UDP is useless to us because it cannot work over mobile internet
-
-# Init UDP
-#server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
-#server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
-# Enable broadcasting mode
-#server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
-# Set a timeout so the socket does not block
-# indefinitely when trying to receive data.
-#server.settimeout(0.2)
-
-class SignalProcessor(threading.Thread):
-
- def __init__(self, data_que, module_receiver, logging_level=10):
-
- """
- Parameters:
- -----------
- :param: data_que: Que to communicate with the UI (web iface/Qt GUI)
- :param: module_receiver: Kraken SDR DoA DSP receiver modules
- """
- super(SignalProcessor, self).__init__()
- self.logger = logging.getLogger(__name__)
- self.logger.setLevel(logging_level)
-
- root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
- doa_res_file_path = os.path.join(os.path.join(root_path,"_android_web","DOA_value.html"))
- self.DOA_res_fd = open(doa_res_file_path,"w+")
-
- self.module_receiver = module_receiver
- self.data_que = data_que
- self.en_spectrum = False
- self.en_record = False
- self.en_DOA_estimation = True
- self.first_frame = 1 # Used to configure local variables from the header fields
- self.processed_signal = np.empty(0)
-
- # Squelch feature
- self.data_ready = False
- self.en_squelch = False
- self.squelch_threshold = 0.1
- self.squelch_trigger_channel = 0
- self.raw_signal_amplitude = np.empty(0)
- self.filt_signal = np.empty(0)
- self.squelch_mask = np.empty(0)
-
- # DOA processing options
- self.en_DOA_Bartlett = False
- self.en_DOA_Capon = False
- self.en_DOA_MEM = False
- self.en_DOA_MUSIC = False
- self.en_DOA_FB_avg = False
- self.DOA_offset = 0
- self.DOA_inter_elem_space = 0.5
- self.DOA_ant_alignment = "ULA"
- self.DOA_theta = np.linspace(0,359,360)
-
- # PR processing options
- self.PR_clutter_cancellation = "Wiener MRE"
- self.max_bistatic_range = 128
- self.max_doppler = 256
- self.en_PR = True
-
-
- # Processing parameters
- self.spectrum_window_size = 2048 #1024
- self.spectrum_window = "hann"
- self.run_processing = False
- self.is_running = False
-
-
- self.channel_number = 4 # Update from header
-
- # Result vectors
- self.DOA_Bartlett_res = np.ones(181)
- self.DOA_Capon_res = np.ones(181)
- self.DOA_MEM_res = np.ones(181)
- self.DOA_MUSIC_res = np.ones(181)
- self.DOA_theta = np.arange(0,181,1)
-
- self.max_index = 0
- self.max_frequency = 0
- self.fft_signal_width = 0
-
- self.DOA_theta = np.linspace(0,359,360)
-
- self.spectrum = None #np.ones((self.channel_number+2,N), dtype=np.float32)
- self.spectrum_upd_counter = 0
-
-
- def run(self):
- """
- Main processing thread
- """
-
- pyfftw.config.NUM_THREADS = 4
- scipy.fft.set_backend(pyfftw.interfaces.scipy_fft)
- pyfftw.interfaces.cache.enable()
-
- while True:
- self.is_running = False
- time.sleep(1)
- while self.run_processing:
- self.is_running = True
-
- que_data_packet = []
- start_time = time.time()
-
- #-----> ACQUIRE NEW DATA FRAME <-----
- self.module_receiver.get_iq_online()
-
- # Check frame type for processing
- en_proc = (self.module_receiver.iq_header.frame_type == self.module_receiver.iq_header.FRAME_TYPE_DATA)# or \
- #(self.module_receiver.iq_header.frame_type == self.module_receiver.iq_header.FRAME_TYPE_CAL)# For debug purposes
- """
- You can enable here to process other frame types (such as call type frames)
- """
-
- que_data_packet.append(['iq_header',self.module_receiver.iq_header])
- self.logger.debug("IQ header has been put into the data que entity")
-
- # Configure processing parameteres based on the settings of the DAQ chain
- if self.first_frame:
- self.channel_number = self.module_receiver.iq_header.active_ant_chs
- self.spectrum_upd_counter = 0
- self.spectrum = np.ones((self.channel_number+2, self.spectrum_window_size), dtype=np.float32)
- self.first_frame = 0
-
- decimation_factor = 1
-
- self.data_ready = False
-
- if en_proc:
- self.processed_signal = self.module_receiver.iq_samples
- self.data_ready = True
-
- first_decimation_factor = 1 #480
-
- # TESTING: DSP side main decimation - significantly slower than NE10 but it works ok-ish
- #decimated_signal = signal.decimate(self.processed_signal, first_decimation_factor, n = 584, ftype='fir', zero_phase=True) #first_decimation_factor * 2, ftype='fir')
- #self.processed_signal = decimated_signal #.copy()
- #spectrum_signal = decimated_signal.copy()
-
- max_amplitude = -100
-
- #max_ch = np.argmax(np.max(self.spectrum[1:self.module_receiver.iq_header.active_ant_chs+1,:], axis=1)) # Find the channel that had the max amplitude
- max_amplitude = 0 #np.max(self.spectrum[1+max_ch, :]) #Max amplitude out of all 5 channels
- #max_spectrum = self.spectrum[1+max_ch, :] #Send max ch to channel centering
-
- que_data_packet.append(['max_amplitude',max_amplitude])
-
- #-----> SQUELCH PROCESSING <-----
-
- if self.en_squelch:
- self.data_ready = False
-
- self.processed_signal, decimation_factor, self.fft_signal_width, self.max_index = \
- center_max_signal(self.processed_signal, self.spectrum[0,:], max_spectrum, self.module_receiver.daq_squelch_th_dB, self.module_receiver.iq_header.sampling_freq)
-
- #decimated_signal = []
- #if(decimation_factor > 1):
- # decimated_signal = signal.decimate(self.processed_signal, decimation_factor, n = decimation_factor * 2, ftype='fir')
- # self.processed_signal = decimated_signal #.copy()
-
-
- #Only update if we're above the threshold
- if max_amplitude > self.module_receiver.daq_squelch_th_dB:
- self.data_ready = True
-
-
- #-----> SPECTRUM PROCESSING <-----
-
- if self.en_spectrum and self.data_ready:
-
- spectrum_samples = self.module_receiver.iq_samples #spectrum_signal #self.processed_signal #self.module_receiver.iq_samples #self.processed_signal
-
-
- N = self.spectrum_window_size
-
- N_perseg = 0
- N_perseg = min(N, len(self.processed_signal[0,:])//25)
- N_perseg = N_perseg // 1
-
- # Get power spectrum
- f, Pxx_den = signal.welch(self.processed_signal, self.module_receiver.iq_header.sampling_freq//first_decimation_factor,
- nperseg=N_perseg,
- nfft=N,
- noverlap=int(N_perseg*0.25),
- detrend=False,
- return_onesided=False,
- window= ('tukey', 0.25), #tukey window gives better time resolution for squelching #self.spectrum_window, #('tukey', 0.25), #self.spectrum_window,
- #window=self.spectrum_window,
- scaling="spectrum")
-
- self.spectrum[1:self.module_receiver.iq_header.active_ant_chs+1,:] = np.fft.fftshift(10*np.log10(Pxx_den))
-
- self.spectrum[0,:] = np.fft.fftshift(f)
-
-
- # Create signal window for plot
-# signal_window = np.ones(len(self.spectrum[1,:])) * -100
- # signal_window[max(self.max_index - self.fft_signal_width//2, 0) : min(self.max_index + self.fft_signal_width//2, len(self.spectrum[1,:]))] = max(self.spectrum[1,:])
- #signal_window = np.ones(len(max_spectrum)) * -100
- #signal_window[max(self.max_index - self.fft_signal_width//2, 0) : min(self.max_index + self.fft_signal_width//2, len(max_spectrum))] = max(max_spectrum)
-
- #self.spectrum[self.channel_number+1, :] = signal_window #np.ones(len(spectrum[1,:])) * self.module_receiver.daq_squelch_th_dB # Plot threshold line
- que_data_packet.append(['spectrum', self.spectrum])
-
- #-----> Passive Radar <-----
- conf_val = 0
- theta_0 = 0
- if self.en_PR and self.data_ready and self.channel_number > 1:
-
- ref_ch = self.module_receiver.iq_samples[0,:]
- surv_ch = self.module_receiver.iq_samples[1,:]
-
- td_filter_dimension = self.max_bistatic_range
-
- start = time.time()
-
- if self.PR_clutter_cancellation == "Wiener MRE":
- surv_ch, w = Wiener_SMI_MRE(ref_ch, surv_ch, td_filter_dimension)
- #surv_ch, w = cc.Wiener_SMI_MRE(ref_ch, surv_ch, td_filter_dimension)
-
- surv_ch = det.windowing(surv_ch, "Hamming") #surv_ch * signal.tukey(surv_ch.size, alpha=0.25) #det.windowing(surv_ch, "hamming")
-
- max_Doppler = self.max_doppler #256
- max_range = self.max_bistatic_range
-
- #RD_matrix = det.cc_detector_ons(ref_ch, surv_ch, self.module_receiver.iq_header.sampling_freq, max_Doppler, max_range, verbose=0, Qt_obj=None)
- RD_matrix = cc_detector_ons(ref_ch, surv_ch, self.module_receiver.iq_header.sampling_freq, max_Doppler, max_range)
-
- end = time.time()
- print("Time: " + str((end-start) * 1000))
-
- que_data_packet.append(['RD_matrix', RD_matrix])
-
- # Record IQ samples
- if self.en_record:
- # TODO: Implement IQ frame recording
- self.logger.error("Saving IQ samples to npy is obsolete, IQ Frame saving is currently not implemented")
-
- stop_time = time.time()
- que_data_packet.append(['update_rate', stop_time-start_time])
- que_data_packet.append(['latency', int(stop_time*10**3)-self.module_receiver.iq_header.time_stamp])
-
- # If the que is full, and data is ready (from squelching), clear the buffer immediately so that useful data has the priority
- if self.data_que.full() and self.data_ready:
- try:
- #self.logger.info("BUFFER WAS NOT EMPTY, EMPTYING NOW")
- self.data_que.get(False) #empty que if not taken yet so fresh data is put in
- except queue.Empty:
- #self.logger.info("DIDNT EMPTY")
- pass
-
- # Put data into buffer, but if there is no data because its a cal/trig wait frame etc, then only write if the buffer is empty
- # Otherwise just discard the data so that we don't overwrite good DATA frames.
- try:
- self.data_que.put(que_data_packet, False) # Must be non-blocking so DOA can update when dash browser window is closed
- except:
- # Discard data, UI couldn't consume fast enough
- pass
-
- """
- start = time.time()
- end = time.time()
- thetime = ((end - start) * 1000)
- print ("Time elapsed: ", thetime)
- """
-@jit(fastmath=True)
-def Wiener_SMI_MRE(ref_ch, surv_ch, K):
- """
- Description:
- ------------
- Performs Wiener filtering with applying the Minimum Redundance Estimation (MRE) technique.
- When using MRE, the autocorrelation matrix is not fully estimated, but only the first column.
- With this modification the required calculations can be reduced from KxK to K element.
-
- Parameters:
- -----------
- :param K : Filter tap number
- :param ref_ch : Reference signal array
- :param surv_ch: Surveillance signal array
-
- :type K : int
- :type ref_ch : 1 x N complex numpy array
- :type surv_ch: 1 x N complex numpy array
- Return values:
- --------------
- :return filt: Filtered surveillance channel
- :rtype filt: 1 x N complex numpy array
-
- :return None: Input parameters are not consistent
- """
-
- N = ref_ch.shape[0] # Number of time samples
- R, r = pruned_correlation(ref_ch, surv_ch, K, N)
- R_mult = R_eye_memoize(K)
- w = fast_w(R, r, K, R_mult)
-
- #return surv_ch - np.convolve(ref_ch, w)[0:N], w # subtract the zero doppler clutter
- return surv_ch - signal.oaconvolve(ref_ch, w)[0:N], w # subtract the zero doppler clutter #oaconvolve saves us about 100-200 ms
-
-@njit(fastmath=True, parallel=True, cache=True)
-def fast_w(R, r, K, R_mult):
- # Complete the R matrix based on its Hermitian and Toeplitz property
-
- for k in range(1, K):
- R[:, k] = shift(R[:, 0], k)
- #R[:, K] = shift(R[:,0], K)
-
- R += np.transpose(np.conjugate(R))
- R *= R_mult #(np.ones(K) - np.eye(K) * 0.5)
-
- #w = np.dot(lin.inv(R), r) # weight vector
- w = lin.inv(R) @ r #np.dot(lin.inv(R), r) # weight vector #matmul (@) may be slightly faster that np.dot for 1D, 2D arrays.
- # inverse and dot product run time : 1.1s for 2048*2048 matrix
-
- return w
-
-#Memoize ~50ms speedup?
-@lru_cache(maxsize=2)
-def R_eye_memoize(K):
- return (np.ones(K) - np.eye(K) * 0.5)
-
-#Modified pruned correlation, returns R and r directly and saves one FFT
-@jit(fastmath=True, cache=True)
-def pruned_correlation(ref_ch, surv_ch, clen, N):
- """
- Description:
- -----------
- Calculates the part of the correlation function of arrays with same size
- The total length of the cross-correlation function is 2*N-1, but this
- function calculates the values of the cross-correlation between [N-1 : N+clen-1]
- Parameters:
- -----------
- :param x : input array
- :param y : input array
- :param clen: correlation length
-
- :type x: 1 x N complex numpy array
- :type y: 1 x N complex numpy array
- :type clen: int
- Return values:
- --------------
- :return corr : part of the cross-correlation function
- :rtype corr : 1 x clen complex numpy array
-
- :return None : inconsistent array size
- """
- R = np.zeros((clen, clen), dtype=c_dtype) # Autocorrelation mtx.
-
- # --calculation--
- # set up input matrices pad zeros if not multiply of the correlation length
- cols = clen - 1 #(clen = Filter drowsimension)
- rows = np.int32(N / (cols)) + 1
-
- zeropads = cols * rows - N
- x = np.pad(ref_ch, (0, zeropads))
-
- # shaping inputs into matrices
- xp = np.reshape(x, (rows, cols))
-
- # padding matrices for FFT
- ypp = np.vstack([xp[1:, :], np.zeros(cols, dtype=c_dtype)]) #vstack appears to be faster than pad
- yp = np.concatenate([xp, ypp], axis=1)
-
- # execute FFT on the matrices
- xpw = fft.fft(xp, n = 2*cols, axis=1, workers=4, overwrite_x=True)
- bpw = fft.fft(yp, axis=1, workers=4, overwrite_x=True)
-
- # magic formula which describes the unified equation of the universe
- # corr_batches = np.fliplr(fft.fftshift(fft.ifft(corr_mult(xpw, bpw), axis=1, workers=4, overwrite_x=True)).conj()[:, 0:clen])
- corr_batches = fft.fftshift(fft.ifft(corr_mult(xpw, bpw), axis=1, workers=4, overwrite_x=True)).conj()[:, 0:clen]
-
- # sum each value in a column of the batched correlation matrix
- R[:,0] = np.fliplr([np.sum(corr_batches, axis=0)])[0]
-
- #calc r
- y = np.pad(surv_ch, (0, zeropads))
- yp = np.reshape(y, (rows, cols))
- ypp = np.vstack([yp[1:, :], np.zeros(cols, dtype=c_dtype)]) #vstack appears to be faster than pad
- yp = np.concatenate([yp, ypp], axis=1)
- bpw = fft.fft(yp, axis=1, workers=4, overwrite_x=True)
- #corr_batches = np.fliplr(fft.fftshift(fft.ifft(corr_mult(xpw, bpw), axis=1, workers=4, overwrite_x=True)).conj()[:, 0:clen])
- corr_batches = fft.fftshift(fft.ifft(corr_mult(xpw, bpw), axis=1, workers=4, overwrite_x=True)).conj()[:, 0:clen]
- #r = np.sum(corr_batches, axis=0)
- r = np.fliplr([np.sum(corr_batches, axis=0)])[0]
-
- return R, r
-
-@njit(fastmath=True, cache=True)
-def shift(x, i):
- """
- Description:
- -----------
- Similar to np.roll function, but not circularly shift values
- Example:
- x = |x0|x1|...|xN-1|
- y = shift(x,2)
- x --> y: |0|0|x0|x1|...|xN-3|
- Parameters:
- -----------
- :param:x : input array on which the roll will be performed
- :param i : delay value [sample]
-
- :type i :int
- :type x: N x 1 complex numpy array
- Return values:
- --------------
- :return shifted : shifted version of x
- :rtype shifted: N x 1 complex numpy array
- """
-
- N = x.shape[0]
- if np.abs(i) >= N:
- return np.zeros(N, dtype=c_dtype)
- if i == 0:
- return x
- shifted = np.roll(x, i)
- if i < 0:
- shifted[np.mod(N + i, N):] = np.zeros(np.abs(i), dtype=c_dtype)
- if i > 0:
- shifted[0:i] = np.zeros(np.abs(i), dtype=c_dtype)
- return shifted
-
-
-@njit(fastmath=True, parallel=True, cache=True)
-def resize_and_align(no_sub_tasks, ref_ch, surv_ch, fs, fD_max, r_max):
- surv_ch_align = np.reshape(surv_ch,(no_sub_tasks, r_max)) # shaping surveillance signal array into a matrix
- pad_zeros = np.expand_dims(np.zeros(r_max, dtype=c_dtype), axis=0)
- surv_ch_align = np.vstack((surv_ch_align, pad_zeros)) # padding one row of zeros into the surv matrix
- surv_ch_align = np.concatenate((surv_ch_align[0 : no_sub_tasks,:], surv_ch_align[1 : no_sub_tasks +1, :]), axis = 1)
-
- ref_ch_align = np.reshape(ref_ch, (no_sub_tasks, r_max)) # shaping reference signal array into a matrix
- pad_zeros = np.zeros((no_sub_tasks, r_max),dtype = c_dtype)
- ref_ch_align = np.concatenate((ref_ch_align, pad_zeros),axis = 1) # shaping
-
- return ref_ch_align, surv_ch_align
-
-@njit(fastmath=True, cache=True)
-def corr_mult(surv_fft, ref_fft):
- return np.multiply(surv_fft, ref_fft.conj())
-
-@jit(fastmath=True, cache=True)
-def cc_detector_ons(ref_ch, surv_ch, fs, fD_max, r_max):
- """
- Parameters:
- -----------
- :param N: Range resolution - N must be a divisor of the input length
- :param F: Doppler resolution, F has a theoretical limit. If you break the limit, the output may repeat
- itself and get wrong results. F should be less than length/N otherwise use other method!
- Return values:
- --------------
- :return None: Improper input parameters
-
- """
- N = ref_ch.size
-
- # --> Set processing parameters
- fD_step = fs / (2 * N) # Doppler frequency step size (with zero padding)
- Doppler_freqs_size = int(fD_max / fD_step)
- no_sub_tasks = N // r_max
-
- # Allocate range-Doppler maxtrix
- mx = np.zeros((2*Doppler_freqs_size+1, r_max),dtype = c_dtype) #memoize_zeros((2*Doppler_freqs_size+1, r_max), c_dtype) #np.zeros((2*Doppler_freqs_size+1, r_max),dtype = nb.c8)
-
- ref_ch_align, surv_ch_align = resize_and_align(no_sub_tasks, ref_ch, surv_ch, fs, fD_max, r_max)
-
- # row wise fft on both channels
- ref_fft = fft.fft(ref_ch_align, axis = 1, overwrite_x=True, workers=4) #pyfftw.interfaces.numpy_fft.fft(ref_ch_align_a, axis = 1, overwrite_input=True, threads=4) #fft.fft(ref_ch_align_a, axis = 1, overwrite_x=True, workers=4)
- surv_fft = fft.fft(surv_ch_align, axis = 1, overwrite_x=True, workers=4) #pyfftw.interfaces.numpy_fft.fft(surv_ch_align_a, axis = 1, overwrite_input=True, threads=4) #fft.fft(surv_ch_align_a, axis = 1, overwrite_x=True, workers=4)
-
- corr = corr_mult(surv_fft, ref_fft) #np.multiply(surv_fft, ref_fft.conj())
-
- corr = fft.ifft(corr,axis = 1, workers=4, overwrite_x=True)
-
- corr_a = pyfftw.empty_aligned(np.shape(corr), dtype=c_dtype)
- corr_a[:] = corr #.copy()
-
- # This is the most computationally intensive part ~120ms, overwrite_x=True gives a big speedup, not sure if it changes the result though...
- corr = fft.fft(corr_a, n=2* no_sub_tasks, axis = 0, workers=4, overwrite_x=True) # Setting the output size with "n=.." is faster than doing a concat first.
-
- # crop and fft shift
- mx[ 0 : Doppler_freqs_size, 0 : r_max] = corr[2*no_sub_tasks - Doppler_freqs_size : 2*no_sub_tasks, 0 : r_max]
- mx[Doppler_freqs_size : 2 * Doppler_freqs_size+1, 0 : r_max] = corr[ 0 : Doppler_freqs_size+1 , 0 : r_max]
-
- return mx
-
-
-
-
-
-
-#NUMBA optimized center tracking. Gives a mild speed boost ~25% faster.
-@njit(fastmath=True, cache=True, parallel=True)
-def center_max_signal(processed_signal, frequency, fft_spectrum, threshold, sample_freq):
-
- # Where is the max frequency? e.g. where is the signal?
- max_index = np.argmax(fft_spectrum)
- max_frequency = frequency[max_index]
-
- # Auto decimate down to exactly the max signal width
- fft_signal_width = np.sum(fft_spectrum > threshold) + 25
- decimation_factor = max((sample_freq // fft_signal_width) // 2, 1)
-
- # Auto shift peak frequency center of spectrum, this frequency will be decimated:
- # https://pysdr.org/content/filters.html
- f0 = -max_frequency #+10
- Ts = 1.0/sample_freq
- t = np.arange(0.0, Ts*len(processed_signal[0, :]), Ts)
- exponential = np.exp(2j*np.pi*f0*t) # this is essentially a complex sine wave
-
- return processed_signal * exponential, decimation_factor, fft_signal_width, max_index
-
-
-# NUMBA optimized MUSIC function. About 100x faster on the Pi 4
-@njit(fastmath=True, cache=True)
-def DOA_MUSIC(R, scanning_vectors, signal_dimension, angle_resolution=1):
- # --> Input check
- if R[:,0].size != R[0,:].size:
- print("ERROR: Correlation matrix is not quadratic")
- return np.ones(1, dtype=nb.c16)*-1 #[(-1, -1j)]
-
- if R[:,0].size != scanning_vectors[:,0].size:
- print("ERROR: Correlation matrix dimension does not match with the antenna array dimension")
- return np.ones(1, dtype=nb.c16)*-2
-
- #ADORT = np.zeros(scanning_vectors[0,:].size, dtype=np.complex) #CHANGE TO nb.c16 for NUMBA
- ADORT = np.zeros(scanning_vectors[0,:].size, dtype=nb.c16)
- M = R[:,0].size #np.size(R, 0)
-
- # --- Calculation ---
- # Determine eigenvectors and eigenvalues
- sigmai, vi = lin.eig(R)
- sigmai = np.abs(sigmai)
-
- idx = sigmai.argsort()[::1] # Sort eigenvectors by eigenvalues, smallest to largest
- #sigmai = sigmai[idx] # Eigenvalues not used again
- vi = vi[:,idx]
-
- # Generate noise subspace matrix
- noise_dimension = M - signal_dimension
- #E = np.zeros((M, noise_dimension),dtype=np.complex)
- E = np.zeros((M, noise_dimension),dtype=nb.c16)
- for i in range(noise_dimension):
- E[:,i] = vi[:,i]
-
- theta_index=0
- for i in range(scanning_vectors[0,:].size):
- S_theta_ = scanning_vectors[:, i]
- S_theta_ = S_theta_.T
- ADORT[theta_index] = 1/np.abs(S_theta_.conj().T @ (E @ E.conj().T) @ S_theta_)
- theta_index += 1
-
- return ADORT
-
-# Numba optimized version of pyArgus corr_matrix_estimate with "fast". About 2x faster on Pi4
-@njit(fastmath=True, cache=True) #(nb.c8[:,:](nb.c16[:,:]))
-def corr_matrix(X):
- M = X[:,0].size
- N = X[0,:].size
- #R = np.zeros((M, M), dtype=nb.c8)
- R = np.dot(X, X.conj().T)
- R = np.divide(R, N)
- return R
-
-# Numba optimized scanning vectors generation for UCA arrays. About 10x faster on Pi4
-# LRU cache memoize about 1000x faster.
-@lru_cache(maxsize=8)
-def uca_scanning_vectors(M, DOA_inter_elem_space):
-
- thetas = np.linspace(0,359,360) # Remember to change self.DOA_thetas too, we didn't include that in this function due to memoization cannot work with arrays
-
- x = DOA_inter_elem_space * np.cos(2*np.pi/M * np.arange(M))
- y = -DOA_inter_elem_space * np.sin(2*np.pi/M * np.arange(M)) # For this specific array only
-
- scanning_vectors = np.zeros((M, thetas.size), dtype=np.complex)
- for i in range(thetas.size):
- scanning_vectors[:,i] = np.exp(1j*2*np.pi* (x*np.cos(np.deg2rad(thetas[i])) + y*np.sin(np.deg2rad(thetas[i]))))
-
- return scanning_vectors
- # scanning_vectors = de.gen_scanning_vectors(M, x, y, self.DOA_theta)
-
-@njit(fastmath=True, cache=True)
-def DOA_plot_util(DOA_data, log_scale_min=-100):
- """
- This function prepares the calulcated DoA estimation results for plotting.
-
- - Noramlize DoA estimation results
- - Changes to log scale
- """
-
- DOA_data = np.divide(np.abs(DOA_data), np.max(np.abs(DOA_data))) # Normalization
- DOA_data = 10*np.log10(DOA_data) # Change to logscale
-
- for i in range(len(DOA_data)): # Remove extremely low values
- if DOA_data[i] < log_scale_min:
- DOA_data[i] = log_scale_min
-
- return DOA_data
-
-@njit(fastmath=True, cache=True)
-def calculate_doa_papr(DOA_data):
- return 10*np.log10(np.max(np.abs(DOA_data))/np.mean(np.abs(DOA_data)))
-
-# Old time-domain squelch algorithm (Unused as freq domain FFT with overlaps gives significantly better sensitivity with acceptable time resolution expense
-"""
- K = 10
- self.filtered_signal = self.raw_signal_amplitude #convolve(np.abs(self.raw_signal_amplitude),np.ones(K), mode = 'same')/K
-
- # Burst is always started at the begining of the processed block, ensured by the squelch module in the DAQ FW
- burst_stop_index = len(self.filtered_signal) # CARL FIX: Initialize this to the length of the signal, incase the signal is active the entire time
- self.logger.info("Original burst stop index: {:d}".format(burst_stop_index))
-
- min_burst_size = K
- burst_stop_amp_val = 0
- for n in np.arange(K, len(self.filtered_signal), 1):
- if self.filtered_signal[n] < self.squelch_threshold:
- burst_stop_amp_val = self.filtered_signal[n]
- burst_stop_index = n
- burst_stop_index-=K # Correction with the length of filter
- break
-
- #burst_stop_index-=K # Correction with the length of filter
-
-
- self.logger.info("Burst stop index: {:d}".format(burst_stop_index))
- self.logger.info("Burst stop ampl val: {:f}".format(burst_stop_amp_val))
- self.logger.info("Processed signal length: {:d}".format(len(self.processed_signal[0,:])))
-
- # If sign
- if burst_stop_index < min_burst_size:
- self.logger.debug("The length of the captured burst size is under the minimum: {:d}".format(burst_stop_index))
- burst_stop_index = 0
-
- if burst_stop_index !=0:
- self.logger.info("INSIDE burst_stop_index != 0")
-
- self.logger.debug("Burst stop index: {:d}".format(burst_stop_index))
- self.logger.debug("Burst stop ampl val: {:f}".format(burst_stop_amp_val))
- self.squelch_mask = np.zeros(len(self.filtered_signal))
- self.squelch_mask[0 : burst_stop_index] = np.ones(burst_stop_index)*self.squelch_threshold
- # Next line removes the end parts of the samples after where the signal ended, truncating the array
- self.processed_signal = self.module_receiver.iq_samples[: burst_stop_index, self.squelch_mask == self.squelch_threshold]
- self.logger.info("Raw signal length when burst_stop_index!=0: {:d}".format(len(self.module_receiver.iq_samples[0,:])))
- self.logger.info("Processed signal length when burst_stop_index!=0: {:d}".format(len(self.processed_signal[0,:])))
-
- #self.logger.info(' '.join(map(str, self.processed_signal)))
-
- self.data_ready=True
- else:
- self.logger.info("Signal burst is not found, try to adjust the threshold levels")
- #self.data_ready=True
- self.squelch_mask = np.ones(len(self.filtered_signal))*self.squelch_threshold
- self.processed_signal = np.zeros([self.channel_number, len(self.filtered_signal)])
-"""
-
-