def beam_pattern_cartesian_multi_freq(self, save_fig=False): ''' Plot Array Response including several Frequencies and Azimuth Angle infomation ''' title = '{}_{}'.format('Beampattern_CartersianMultiFreq', self.beam_label) if save_fig: fig_name = self._set_fig_name(title) else: fig_name = None #frequency to plot freq_range = constants.get('freq_range_small') angle_range = np.degrees(constants.get('angle_range')) y_lim_lower = [] plot = Plot() plot.append_axes() for freq in freq_range: response_freq = self.beam_pattern(freq) y_lim_lower.append(np.min(response_freq)) plot.plot_vector(response_freq, angle_range, label='{} kHz'.format(freq / 1000)) y_lim_lower = np.maximum(-70, np.min(y_lim_lower)) plot.set_pipeline(title=title, ylim=(y_lim_lower, 1), xlim=(0, 180), legend='lower right', xlabel='Azimuth Angle[Deg]', ylabel='Beampattern[dB]', fig_name=fig_name)
def steering_vector_2D_from_point(self, frequency, source, attn=True, ff=False): """ Creates a steering vector for a particular frequency and source Args: frequency source: location in cartesian coordinates attn: include attenuation factor if True ff: uses far-field distance if true Return: A 2x1 ndarray containing the steering vector """ X = np.array(source) if X.ndim == 1: X = source[:,np.newaxis] # normalize for far-field if requested if (ff): X -= self.center Xn = np.sqrt(np.sum(X**2, axis=0)) X *= constants.get('ffdist')/Xn X += self.center D = distance(self.R, X) omega = 2 * np.pi * frequency if attn: # TO DO 1: This will mean slightly different absolute value for # every entry, even within the same steering vector. Perhaps a # better paradigm is far-field with phase carrier. return 1. / (4 * np.pi) / D * np.exp(-1j * omega * D / constants.get('c')) else: return np.exp(-1j * omega * D / constants.get('c'))
def beam_pattern_heatmap(self, save_fig=False): ''' Plot heatmap of Array Response including all Frequency and Azimuth Angle infomation ''' title = '{}_{}'.format('Beampattern_Heatmap', self.beam_label) if save_fig: fig_name = self._set_fig_name(title) else: fig_name = None freq = np.linspace(8000, 1, num=800, endpoint=True) response_matrix = np.zeros( (len(freq), len(constants.get('angle_range')))) for index, f in enumerate(freq): response_matrix[index:] = self.beam_pattern(f) #plot xticks = np.arange(0, len(constants.get('angle_range')), 60) yticks = np.arange(0, 900, 100) xticklabels = np.arange( 0, len(constants.get('angle_range')), 60, dtype=int) / 2.0 yticklabels = np.linspace(8, 0, num=9, endpoint=True, dtype=int) plot = Plot() plot.append_axes() plot.plot_heatmap(response_matrix) plot.set_pipeline(title=title, xticks=xticks, yticks=yticks, xticklabels=[int(tick) for tick in xticklabels], yticklabels=yticklabels, xlabel='Azimuth Angle[Deg]', ylabel='Freq[kHz]', fig_name=fig_name)
def heatmap_beam_pattern(self, save_fig=True): ''' beampattern heatmap only available for frequency domain beamforming case : draw beampatten for heatmap. There will be as many subplots as beamforming numbers. ''' if self._beamforming_cout == 0: raise ValueError( 'No beamforming to analysis! Please append one firstly!') beamforming_dict = self._beamforming_list[0] if beamforming_dict.get('beam_domain') == 'time': raise ValueError( 'Time domain beamforming donnot support heatmap plot!') print('This may take a few seconds. Please wating...') figsize = (10, 5) if self._beamforming_cout == 1 else ( 10, 3 * self._beamforming_cout) plot = Plot(figsize=figsize, subplot_rows=self._beamforming_cout) plot.append_axes_combo() xticks = np.arange(0, len(constants.get('angle_range')), 60) yticks = np.arange(0, 900, 100) xticklabels = np.arange( 0, len(constants.get('angle_range')), 60, dtype=int) / 2.0 yticklabels = np.linspace(8, 0, num=9, endpoint=True, dtype=int) freq_range = np.linspace(8000, 1, num=800, endpoint=True) for beam_index, beam in enumerate(self._beamforming_list): response_matrix = np.zeros( (len(freq_range), len(constants.get('angle_range')))) for freq_index, freq in enumerate(freq_range): response_matrix[freq_index:] = beam[ 'beam_instance'].beam_pattern(freq) plot.plot_heatmap(response_matrix, label_pad=-65, subplot_index=beam_index) plot.set_pipeline(title=beam['beam_instance'].beam_label, xticks=xticks, yticks=yticks, xticklabels=[int(tick) for tick in xticklabels], yticklabels=yticklabels, xlabel='Azimuth Angle[Deg]', ylabel='Freq[kHz]', wait_for_user=False, subplot_index=beam_index) if self._beamforming_cout > 1: plot.suptitle( 'Beampattern Heatmap Compare for Different Beamforming', hspace=0.5) if save_fig: fig_name = self.fig_name('Beampattern_Heatmap') plot.save_figure(fig_name) plot.wait_for_user()
def filter(self): g = self.steer_vector() theta_array_0_90 = np.array_split(constants.get('angle_range'), 2)[0] theta_array_90_180 = np.array_split(constants.get('angle_range'), 2)[1] gamma_0_half_pi = self.diffuse_noise_coherence(theta_array_0_90) gamma_half_pi_pi = self.diffuse_noise_coherence(theta_array_90_180) [t, _] = jeig(gamma_0_half_pi, gamma_half_pi_pi) t = t[:, 0: self.Q] filts = t.dot(pinv(t.T.dot(g).dot(g.T).dot(t))).dot(t.T).dot(g).dot(self.i_ell) return filts
def front_back_ratio(self): ''' return the front_back_ratio in TDB ''' theta_array_0_90 = np.array_split(constants.get('angle_range'), 2)[0] theta_array_90_180 = np.array_split(constants.get('angle_range'), 2)[1] filts = self.filter gamma_0_half_pi = self.diffuse_noise_coherence(theta_array_0_90) gamma_half_pi_pi = self.diffuse_noise_coherence(theta_array_90_180) front_back_ratio = (filts.T.dot(gamma_0_half_pi).dot(filts)) / ( filts.T.dot(gamma_half_pi_pi).dot(filts)) return dB(front_back_ratio, True)
def get_rir(self, mic, visibility, Fs, t0=0., t_max=None): ''' Compute the room impulse response between the source and the microphone whose position is given as an argument. ''' # fractional delay length fdl = constants.get('frac_delay_length') fdl2 = (fdl - 1) // 2 # compute the distance dist = self.distance(mic) time = dist / constants.get('c') + t0 alpha = self.damping / (4. * np.pi * dist) # the number of samples needed if t_max is None: # we give a little bit of time to the sinc to decay anyway N = np.ceil((1.05 * time.max() - t0) * Fs) else: N = np.ceil((t_max - t0) * Fs) N += fdl t = np.arange(N) / float(Fs) ir = np.zeros(t.shape) try: # Try to use the Cython extension from build_rir import fast_rir_builder fast_rir_builder(ir, time, alpha, visibility, Fs, fdl) except ImportError: print( "Cython-extension build_rir unavailable. Falling back to pure python" ) # fallback to pure Python implemenation from utilities import fractional_delay for i in range(time.shape[0]): if visibility[i] == 1: time_ip = int(np.round(Fs * time[i])) time_fp = (Fs * time[i]) - time_ip ir[time_ip - fdl2:time_ip + fdl2 + 1] += alpha[i] * fractional_delay(time_fp) return ir
def __init__(self, d_mic, pattern='Cardioid'): if pattern not in ['Quadrupole', 'Cardioid', 'Hypercardioid', 'Supercardioid']: raise ValueError('No this pattern in SecondOrder Beamforming!') super(SecondOrder, self).__init__(d_mic, M=3, phi=0) self.null_directions = constants.get('second_order_null_angle')[pattern] self.beam_label = '{}{}'.format(pattern, self.beam_label)
def steering_vector_2D(self, frequency, phi, dist, attn=False): phi = np.array([phi]).reshape(phi.size) # Assume phi and dist are measured from the array's center X = dist * np.array([np.cos(phi), np.sin(phi)]) + self.center D = distance(self.R, X) omega = 2 * np.pi * frequency if attn: # TO DO 1: This will mean slightly different absolute value for # every entry, even within the same steering vector. Perhaps a # better paradigm is far-field with phase carrier. return 1. / (4 * np.pi) / D * np.exp(-1j * omega * D / constants.get('c')) else: return np.exp(-1j * omega * D / constants.get('c'))
def __init__(self, d_mic, pattern='Diople'): if pattern not in ['Diople', 'Cardioid', 'Hypercardioid', 'Supercardioid']: raise ValueError('No this pattern in FirstOrder Beamforming!') super(FirstOrder, self).__init__(d_mic, M=2, phi=0) self.null_directions = constants.get('first_order_null_angle')[pattern] self.beam_label = '{}{}'.format(pattern, self.beam_label)
def __frequency_fixed_noise_improvement_performance( self, wng_or_df, save_fig=True): ''' Only for frequency domain or beamforming performace for directivity and white noise gain case : draw directivity or white noise gain in only one figure Parameter --------- wng_or_df: string White Noise Gain or Directivity ''' freq_range = constants.get('freq_range_large') plot = Plot(figsize=(8, 6)) plot.append_axes() axis_limit_max = [] axis_limit_min = [] for beam_index, beam in enumerate(self._beamforming_list): gain_array = np.zeros_like(freq_range, dtype=np.float32) for freq_index, freq in enumerate(freq_range): if wng_or_df == 'White Noise Gain': gain_array[freq_index] = beam[ 'beam_instance'].white_noise_gain(freq) elif wng_or_df == 'Directivity': gain_array[freq_index] = beam['beam_instance'].directivity( freq) else: raise ValueError('Please check DF or WNG are allowed') axis_limit_max.append(np.max(gain_array)) axis_limit_min.append(np.min(gain_array)) plot.plot_vector(gain_array, freq_range, label=beam['beam_instance'].beam_label) if save_fig: fig_name = self.fig_name(wng_or_df) else: fig_name = None beamforming_dict = self._beamforming_list[0] title = '{wng_or_df} for {domain}Domain {dtype}Beamforming'.format( wng_or_df=wng_or_df, domain=(beamforming_dict.get('beam_domain')).capitalize(), dtype=(beamforming_dict.get('beam_type')).capitalize()) plot.set_pipeline(title=title, xlim=(0, 8000), ylim=(np.min(axis_limit_min) - 1, np.max(axis_limit_max) + 1), xlabel='Freq[Hz]', ylabel='dB', legend='lower right', fig_name=fig_name)
def unit_delay(self, theta): ''' computing the delay between two successive microphones Parameters ---------- theta: float A given azimuth angle ''' return self.d_mic / constants.get('c') * np.cos(theta)
def comparePlot(signal1, signal2, Fs, fft_size=512, norm=False, equal=False, title1=None, title2=None): import matplotlib.pyplot as plt td_amp = np.maximum(np.abs(signal1).max(), np.abs(signal2).max()) if norm: if equal: signal1 /= np.abs(signal1).max() signal2 /= np.abs(signal2).max() else: signal1 /= td_amp signal2 /= td_amp td_amp = 1. plt.subplot(2,2,1) plt.plot(np.arange(len(signal1))/float(Fs), signal1) plt.axis('tight') plt.ylim(-td_amp, td_amp) if title1 is not None: plt.title(title1) plt.subplot(2,2,2) plt.plot(np.arange(len(signal2))/float(Fs), signal2) plt.axis('tight') plt.ylim(-td_amp, td_amp) if title2 is not None: plt.title(title2) import stft import windows eps = constants.get('eps') F1 = stft.stft(signal1, fft_size, fft_size / 2, win=windows.hann(fft_size)) F2 = stft.stft(signal2, fft_size, fft_size / 2, win=windows.hann(fft_size)) # try a fancy way to set the scale to avoid having the spectrum # dominated by a few outliers p_min = 1 p_max = 99.5 all_vals = np.concatenate((dB(F1+eps), dB(F2+eps))).flatten() vmin, vmax = np.percentile(all_vals, [p_min, p_max]) cmap = 'jet' interpolation='sinc' plt.subplot(2,2,3) stft.spectroplot(F1.T, fft_size, fft_size / 2, Fs, vmin=vmin, vmax=vmax, cmap=plt.get_cmap(cmap), interpolation=interpolation) plt.subplot(2,2,4) stft.spectroplot(F2.T, fft_size, fft_size / 2, Fs, vmin=vmin, vmax=vmax, cmap=plt.get_cmap(cmap), interpolation=interpolation)
def beam_pattern(self): """ 绘制波束形成的波束图 """ filts = self.filter performace = [] for theta in constants.get('angle_range'): g_tmp = self.steer_vector(theta) performace_theta = filts.T.dot(g_tmp).dot(g_tmp.T).dot(filts) performace.append(performace_theta) return dB(np.array(performace), True)
def _set_fig_name(self, title): ''' if save the fig, the directory and fig name should set Parameters ---------- titile: string fig title name ''' pic_path = os.path.join(constants.get('pic_path'), self.beam_type) make_directory_if_not_exists(pic_path) fig_name = os.path.join(pic_path, '{}.png'.format(title)) return fig_name
def response(self, phi_list, frequency): i_freq = np.argmin(np.abs(self.frequencies - frequency)) if self.weights is None and self.filters is not None: self.weightsFromFilters() elif self.weights is None and self.filters is None: raise NameError('Beamforming weights or filters need to be computed first.') # For the moment assume that we are in 2D bfresp = np.dot(H(self.weights[:,i_freq]), self.steering_vector_2D( self.frequencies[i_freq], phi_list, constants.get('ffdist'))) return self.frequencies[i_freq], bfresp
def plot_beam_response(self): if self.weights is None and self.filters is not None: self.weightsFromFilters() elif self.weights is None and self.filters is None: raise NameError('Beamforming weights or filters need to be computed first.') phi = np.linspace(-np.pi, np.pi-np.pi/180, 360) freq = self.frequencies resp = np.zeros((freq.shape[0], phi.shape[0]), dtype=complex) for i,f in enumerate(freq): # For the moment assume that we are in 2D resp[i,:] = np.dot(H(self.weights[:,i]), self.steering_vector_2D( f, phi, constants.get('ffdist'))) H_abs = np.abs(resp)**2 H_abs /= H_abs.max() H_abs = 10*np.log10(H_abs) p_min = 0 p_max = 100 vmin, vmax = np.percentile(H_abs.flatten(), [p_min, p_max]) import matplotlib.pyplot as plt plt.imshow(H_abs, aspect='auto', origin='lower', interpolation='sinc', vmax=vmax, vmin=vmin) plt.xlabel('Angle [rad]') xticks = [-np.pi, -np.pi/2, 0, np.pi/2, np.pi] for i,p in enumerate(xticks): xticks[i] = np.argmin(np.abs(p - phi)) xticklabels = ['$-\pi$', '$-\pi/2$', '0', '$\pi/2$', '$\pi$'] plt.setp(plt.gca(), 'xticks', xticks) plt.setp(plt.gca(), 'xticklabels', xticklabels) plt.ylabel('Freq [kHz]') yticks = np.zeros(4) f_0 = np.floor(self.Fs/8000.) for i in np.arange(1,5): yticks[i-1] = np.argmin(np.abs(freq - 1000.*i*f_0)) #yticks = np.array(plt.getp(plt.gca(), 'yticks'), dtype=np.int) plt.setp(plt.gca(), 'yticks', yticks) plt.setp(plt.gca(), 'yticklabels', np.arange(1,5)*f_0)
def beam_pattern_polar(self, f, save_fig=False): title = '{}_{}_f_{}'.format('Beampattern_Polar', self.beam_label, f) if save_fig: fig_name = self._set_fig_name(title) else: fig_name = None response = self.beam_pattern(f) #expand to pi to 2*pi, when plot polar sweep_angle = np.concatenate((constants.get('angle_range'), (constants.get('angle_range') + np.pi)), axis=0) response = np.concatenate((response, np.fliplr([response])[0]), axis=0) plot = Plot() plot.append_axes(polar=True) plot.plot_vector(response, sweep_angle) plot.set_pipeline(xlim=(0, 2 * np.pi), ylim=(-50, np.max(response) + 1), title=title, xlabel='Azimuth Angle[Deg]', fig_name=fig_name)
def farFieldWeights(self, phi): ''' This method computes weight for a far field at infinity phi: direction of beam ''' u = unit_vec2D(phi) proj = np.dot(u.T, self.R - self.center)[0] # normalize the first arriving signal to ensure a causal filter proj -= proj.max() self.weights = np.exp(2j * np.pi * self.frequencies[:, np.newaxis] * proj / constants.get('c')).T
def beam_pattern_cartesian(self, save_fig=False): title = '{}_{}'.format('Beampattern_Cartesian', self.beam_label) if save_fig: fig_name = self._set_fig_name(title) else: fig_name = None response = self.beam_pattern() plot = Plot() plot.append_axes() plot.plot_vector(response, np.degrees(constants.get('angle_range'))) plot.set_pipeline(xlim=(0, 180), ylim=(np.min(response) - 1, np.max(response) + 1), title=title, xlabel='Azimuth Angle[Deg]', ylabel='Beampattern[dB]', fig_name=fig_name)
def fig_name(self, fig_type): ''' fig name : fig_type combine all the beamformings' label Parameter --------- fig_type: string the fig type, such as beampattern polar or cartesian ''' title = '' # combine all beamforming instance label together for beam in self._beamforming_list: title = '{}_{}'.format(title, beam['beam_instance'].beam_label) title = '{}{}'.format(fig_type, title) pic_path = os.path.join(constants.get('pic_path'), 'beamforming_compare') make_directory_if_not_exists(pic_path) return os.path.join(pic_path, '{}.png'.format(title))
def _array_gain_freq_response(self, wng_or_df, plot=True, save_fig=False): ''' plot array gain agaist freq Parameters ---------- wng_or_df: string Directivity: draw directivity agaist frequency White_Noise_Gain: draw white_noise_gain agaist frequency ''' freq_range = constants.get('freq_range_large') array_gain = np.zeros_like(freq_range, dtype=np.float64) for freq_index, freq in enumerate(freq_range): if wng_or_df == 'White_Noise_Gain': array_gain[freq_index] = self.white_noise_gain(freq) elif wng_or_df == 'Directivity': array_gain[freq_index] = self.directivity(freq) if plot: title = '{}_{}'.format(wng_or_df, self.beam_label) if save_fig: fig_name = self._set_fig_name(title) else: fig_name = None # title = '{}_{}'.format(wng_or_df, self.title_tmp) # fig_name = os.path.join(self.pic_path ,'{}.png'.format(title)) plot = Plot() plot.append_axes() plot.plot_vector(array_gain, freq_range) plot.set_pipeline( title=title, xlabel='Freq[Hz]', ylabel=wng_or_df, xlim=[0, np.max(freq_range)], ylim=[np.min(array_gain) - 5, np.max(array_gain) + 5], fig_name=fig_name) return None return array_gain
def diffuse_noise_coherence(self, f, alpha=None): ''' Compute the spherically isotropic(diffuse) noise field coherence matrix Parameters ---------- f: float frequency alpha: float diffuse factor, if the value is zero it reprensents an full isotropic duffuse sound ''' [m_mat, n_mat] = np.meshgrid(np.arange(self.M), np.arange(self.M)) mat = 2 * f * (n_mat - m_mat) * (self.d_mic / constants.get('c')) noise_coherence = np.sinc(mat) if alpha: noise_coherence = ( 1 - alpha) * noise_coherence + alpha * np.identity(self.M) return noise_coherence
def highpass(signal, Fs, fc=None, plot=False): ''' Filter out the really low frequencies, default is below 50Hz ''' if fc is None: fc = constants.get('fc_hp') # have some predefined parameters rp = 5 # minimum ripple in dB in pass-band rs = 60 # minimum attenuation in dB in stop-band n = 4 # order of the filter type = 'butter' # normalized cut-off frequency wc = 2. * fc / Fs # design the filter from scipy.signal import iirfilter, lfilter, freqz b, a = iirfilter(n, Wn=wc, rp=rp, rs=rs, btype='highpass', ftype=type) # plot frequency response of filter if requested if (plot): import matplotlib.pyplot as plt w, h = freqz(b, a) plt.figure() plt.title('Digital filter frequency response') plt.plot(w, 20 * np.log10(np.abs(h))) plt.title('Digital filter frequency response') plt.ylabel('Amplitude Response [dB]') plt.xlabel('Frequency (rad/sample)') plt.grid() # apply the filter signal = lfilter(b, a, signal.copy()) return signal
def diffuse_noise_coherence(self, theta_list=constants.get('angle_range')): ''' The equivalent form of diffuse noise coherence in the time domain Parameter --------- theta_list : array default, 0-pi ''' g_phi = [] for theta in theta_list: g = self.steer_vector(theta) x = g.dot(g.T) g_phi.append(g.dot(g.T)) g_phi = np.array(g_phi) int_g = np.zeros((g_phi.shape[1], g_phi.shape[2])) d_theta = theta_list[1] - theta_list[0] for id1 in np.arange(g_phi.shape[1]): for id2 in np.arange(g_phi.shape[2]): int_g[id1, id2] = np.sum(g_phi[:, id1, id2] * np.sin(theta_list) * d_theta) return int_g / 2
def steer_vector(self, f, theta=None): ''' Computing steer vector[导向矢量] Parameters ---------- f: float frequency for computing theta : angle list or array return: steervector when theta is list or array; single exp function when theta is a single value ''' if theta is None: theta = constants.get('angle_range') # theta = self.phi if isinstance(theta, list): theta = np.asarray(theta) delay = np.outer(self.unit_delay(theta), np.arange(self.M)) steer_vector = np.exp(-2j * np.pi * f * delay) return steer_vector.T
def beam_pattern(self, polar, save_fig): ''' plot either beamforming or beamforming group's beampattern Case1 -- if the beamforming is a time domain beamforming, either ploar or catersian all the beam instance are plotted in the same figure, because it's frequency invariant. Case2-- if the beamforming is a frequency domain beamforming, then if polar, it will be plotted in 4 figures, every figure represents a single frequency. if catesian, the figure number is equal to the beamforming instance number. One figure represents a beamforming, the different frequency plotted in this same figure. Parameters ---------- polar: bool True or False save_fig: bool True of False ''' if self._beamforming_cout == 0: raise ValueError( 'No beamforming to analysis! Please append one firstly!') beamforming_dict = self._beamforming_list[0] coordinate = 'Polar' if polar else 'Cartesian' title = '{coordinate} Beampattern for {domain}Domain {dtype}Beamforming'.format( coordinate=coordinate, domain=(beamforming_dict.get('beam_domain')).capitalize(), dtype=(beamforming_dict.get('beam_type')).capitalize()) if polar: sweep_angle = np.concatenate( (constants.get('angle_range'), (constants.get('angle_range') + np.pi)), axis=0) xlim = (0, 2 * np.pi) else: sweep_angle = np.degrees(constants.get('angle_range')) xlim = (0, 180) if beamforming_dict.get('beam_domain') == 'time': plot = Plot(figsize=(6, 6)) if polar else Plot(figsize=(8, 5)) plot.append_axes_combo(polar=polar) axis_limit_max = 0 axis_limit_min = 0 for beam_index, beam in enumerate(self._beamforming_list): response = beam['beam_instance'].beam_pattern() axis_limit_max = np.maximum(axis_limit_max, np.max(response)) axis_limit_min = np.minimum(axis_limit_min, np.min(response)) if polar: response = np.concatenate( (response, np.fliplr([response])[0]), axis=0) plot.plot_vector(response, sweep_angle, label=beam['beam_instance'].beam_label) plot.set_pipeline(xlim=xlim, ylim=(axis_limit_min - 1, axis_limit_max + 1), title=title, xlabel='Azimuth Angle[Deg]', wait_for_user=False) plot.set_legend(loc=(0.7, 0.01), fontsize=6) elif beamforming_dict.get('beam_domain') == 'frequency': if polar: mini_freq_range = constants.get('freq_range_mini') plot = Plot(figsize=(8, 8), subplot_cols=2, subplot_rows=2) plot.append_axes_combo(polar=True) # axis_limit_max = 0 # axis_limit_min = 0 for beam_index, beam in enumerate(self._beamforming_list): for freq_index, freq in enumerate(mini_freq_range): # get response response = beam['beam_instance'].beam_pattern(freq) # axis_limit_max = np.maximum(axis_limit_max, np.max(response)) # axis_limit_min = np.minimum(axis_limit_min, np.min(response)) response = np.concatenate( (response, np.fliplr([response])[0]), axis=0) # plot plot.plot_vector( response, sweep_angle, label=beam['beam_instance'].beam_label, subplot_index=freq_index) plot.set_pipeline( xlim=(0, 2 * np.pi), ylim=(-50, 1), title='', xlabel='Azimuth Angle[Deg]_{}Hz'.format(freq), wait_for_user=False, subplot_index=freq_index) plot.set_legend(loc=(0.5, 0.01), fontsize=6, subplot_index=3) plot.suptitle(title) else: plot = Plot(figsize=(8, 4 * self._beamforming_cout), subplot_rows=self._beamforming_cout) plot.append_axes_combo() axis_limit_max = 0 axis_limit_min = 0 for beam_index, beam in enumerate(self._beamforming_list): for freq in constants.get('freq_range_small'): response = beam['beam_instance'].beam_pattern(freq) axis_limit_max = np.maximum(axis_limit_max, np.max(response)) axis_limit_min = np.minimum(axis_limit_min, np.min(response)) plot.plot_vector(response, np.degrees( constants.get('angle_range')), label='{} kHz'.format(freq / 1000), subplot_index=beam_index) plot.set_pipeline( title=beam['beam_instance'].beam_label, xlim=xlim, ylim=(axis_limit_min - 1, axis_limit_max + 1), xlabel='Azimuth Angle[Deg]', ylabel='Beampatten[dB]', wait_for_user=False, subplot_index=beam_index) plot.set_legend(subplot_index=self._beamforming_cout - 1) if self._beamforming_cout > 1: plot.suptitle(title, 0.5) if save_fig: fig_name = self.fig_name( 'Beampattern_{coordinate}'.format(coordinate=coordinate)) plot.save_figure(fig_name) plot.wait_for_user()
def rakeOneForcingFilters(self, sources, interferers, R_n, epsilon=5e-3): ''' Compute the time-domain filters of a beamformer with unit response towards multiple sources. ''' dist_mat = distance(self.R, sources.images) s_time = dist_mat / constants.get('c') s_dmp = 1./(4*np.pi*dist_mat) dist_mat = distance(self.R, interferers.images) i_time = dist_mat / constants.get('c') i_dmp = 1./(4*np.pi*dist_mat) # compute offset needed for decay of sinc by epsilon offset = np.maximum(s_dmp.max(), i_dmp.max())/(np.pi*self.Fs*epsilon) t_min = np.minimum(s_time.min(), i_time.min()) t_max = np.maximum(s_time.max(), i_time.max()) # adjust timing s_time -= t_min - offset i_time -= t_min - offset Lh = np.ceil((t_max - t_min + 2*offset)*float(self.Fs)) # the channel matrix K = sources.images.shape[1] Lg = self.Lg off = (Lg - Lh)/2 L = self.Lg + Lh - 1 H = np.zeros((Lg*self.M, 2*L)) As = np.zeros((Lg*self.M, K)) for r in np.arange(self.M): # build constraint matrix hs = u.lowPassDirac(s_time[r,:,np.newaxis], s_dmp[r,:,np.newaxis], self.Fs, Lh)[:,::-1] As[r*Lg+off:r*Lg+Lh+off,:] = hs.T # build interferer RIR matrix hx = u.lowPassDirac(s_time[r,:,np.newaxis], s_dmp[r,:,np.newaxis], self.Fs, Lh).sum(axis=0) H[r*Lg:(r+1)*Lg,:L] = u.convmtx(hx, Lg).T # build interferer RIR matrix hq = u.lowPassDirac(i_time[r,:,np.newaxis], i_dmp[r,:,np.newaxis], self.Fs, Lh).sum(axis=0) H[r*Lg:(r+1)*Lg,L:] = u.convmtx(hq, Lg).T ones = np.ones((K,1)) # We first assume the sample are uncorrelated K_x = np.dot(H[:,:L], H[:,:L].T) K_nq = np.dot(H[:,L:], H[:,L:].T) + R_n # Compute the TD filters K_nq_inv = np.linalg.inv(K_x+K_nq) C = np.dot(K_nq_inv, As) B = np.linalg.inv(np.dot(As.T, C)) g_val = np.dot(C, np.dot(B, ones)) self.filters = g_val.reshape((self.M,Lg)) # compute and return SNR A = np.dot(g_val.T, H[:,:L]) num = np.dot(A, A.T) denom = np.dot(np.dot(g_val.T, K_nq), g_val) return num/denom
def microphone_array(self, *, d_mic, M): phi = self.sound_field._desired_signal_dict['phi'] self.sound_field._uniform_array = FrequencyDomainULABase(d_mic, M, phi) self.sound_field.Ts = self.sound_field._uniform_array.d_mic / constants.get('c')
def build_rir_matrix(mics, sources, Lg, Fs, epsilon=5e-3, unit_damping=False): ''' A function to build the channel matrix for many sources and microphones Parameters ---------- mics: ndarray a dim-by-M ndarray where each column is the position of a microphone sources: list of pyroomacoustics.SoundSource list of sound sources for which we want to build the matrix Lg: int the length of the beamforming filters Fs: int the sampling frequency epsilon: float, optional minimum decay of the sinc before truncation. Defaults to epsilon=5e-3 unit_damping: bool, optional determines if the wall damping parameters are used or not. Default to false. Returns ------- the function returns the RIR matrix H = :: -------------------- | H_{11} H_{12} ... | ... | -------------------- where H_{ij} is channel matrix between microphone i and source j. H is of type (M*Lg)x((Lg+Lh-1)*S) where Lh is the channel length (determined by epsilon), and M, S are the number of microphones, sources, respectively. ''' from .beamforming import distance from .utilities import low_pass_dirac, convmtx # set the boundaries of RIR filter for given epsilon d_min = np.inf d_max = 0. dmp_max = 0. for s in range(len(sources)): dist_mat = distance(mics, sources[s].images) if unit_damping is True: dmp_max = np.maximum((1. / (4 * np.pi * dist_mat)).max(), dmp_max) else: dmp_max = np.maximum((sources[s].damping[np.newaxis, :] / (4 * np.pi * dist_mat)).max(), dmp_max) d_min = np.minimum(dist_mat.min(), d_min) d_max = np.maximum(dist_mat.max(), d_max) t_max = d_max / constants.get('c') t_min = d_min / constants.get('c') offset = dmp_max / (np.pi * Fs * epsilon) # RIR length Lh = int((t_max - t_min + 2 * offset) * float(Fs)) # build the channel matrix L = Lg + Lh - 1 H = np.zeros((Lg * mics.shape[1], len(sources) * L)) for s in range(len(sources)): for r in np.arange(mics.shape[1]): dist = sources[s].distance(mics[:, r]) time = dist / constants.get('c') - t_min + offset if unit_damping == True: dmp = 1. / (4 * np.pi * dist) else: dmp = sources[s].damping / (4 * np.pi * dist) h = low_pass_dirac(time[:, np.newaxis], dmp[:, np.newaxis], Fs, Lh).sum(axis=0) H[r * Lg:(r + 1) * Lg, s * L:(s + 1) * L] = convmtx(h, Lg).T return H