Esempio n. 1
0
def plot_doa(x_t, title):
    x = psa.Signal(x_t, fs, 'acn', 'sn3d')
    X = psa.Stft.fromSignal(x,
                            window_size=window_size,
                            window_overlap=window_overlap,
                            nfft=nfft)
    X_doa = psa.compute_DOA(X)
    psa.plot_doa(X_doa, title)
    plt.show()
    return X_doa
Esempio n. 2
0
    s = np.random.normal(size=(num_channels, audio_length_samples))
    ambi0 = s[0][:, np.newaxis] * y[0]
    ambi1 = s[1][:, np.newaxis] * y[1]

    ambi = ambi0 + ambi1

    # # # # # # # # # # # # # # # # # #
    r = 1
    window_size = 256
    window_overlap = window_size // 2

    s_tot_ambi = psa.Signal(ambi.T, fs, 'acn', 'n3d')
    S_tot_ambi = psa.Stft.fromSignal(s_tot_ambi,
                                     window_size=window_size,
                                     window_overlap=window_overlap)
    doa = psa.compute_DOA(S_tot_ambi)
    # ksi = S_tot_ambi.compute_ksi(r=r)
    msc = S_tot_ambi.compute_msc(r=r)
    msw = S_tot_ambi.compute_msw(r=r)
    ksi = np.dot(msc, msw)
    m = np.asarray([
        msc.data[0] * msw.data[0], msc.data[1] * msw.data[1],
        msc.data[2] * msw.data[2]
    ])
    A = np.sqrt(msc.data[0] * msw.data[0] + msc.data[1] * msw.data[1] +
                msc.data[2] * msw.data[2])
    ksi = psa.Stft(msc.t, msc.f, A, msc.sample_rate)
    ita = S_tot_ambi.compute_ita(r=r)

    # ksi = np.sqrt(msc.data[0] * msw.data[0] + msc.data[1] * msw.data[1] + msc.data[2] * msw.data[2])
Esempio n. 3
0
            bformat[m] = scipy.signal.fftconvolve(s_dir, irs[m])[:audio_file_length_samples]  # keep original length


        r=4
        window_size = 256
        window_overlap = window_size//2

        _, _, S_dir = scipy.signal.stft(s_dir, fs, nperseg=window_size, noverlap=window_overlap )


        s_tot_ambi = psa.Signal(bformat, fs, 'acn', 'n3d')
        S_tot_ambi = psa.Stft.fromSignal(s_tot_ambi,
                                window_size=window_size,
                                window_overlap=window_overlap
                                )
        doa = psa.compute_DOA(S_tot_ambi)
        directivity = S_tot_ambi.compute_ita_re(r=r)

        psa.plot_signal(s_tot_ambi)
        psa.plot_magnitude_spectrogram(S_tot_ambi)
        psa.plot_doa(doa)
        psa.plot_directivity(directivity)
        # psa.plot_directivity(directivity.sqrt())

        est_S_dir_ambi = S_tot_ambi.apply_mask(directivity.sqrt())
        est_S_dir = est_S_dir_ambi.data[0]
        # psa.plot_magnitude_spectrogram(est_S_dir_ambi)
        doa_est_S_dir_ambi = psa.compute_DOA(est_S_dir_ambi)
        directivity_est_S_dir_ambi = est_S_dir_ambi.compute_ita_re(r=r)
        psa.plot_doa(doa_est_S_dir_ambi,title='after method')
        # psa.plot_directivity(directivity_est_S_dir_ambi)
Esempio n. 4
0
def estimate_doa(data, sr, params):
    """
    Given an input audio, get the most significant tf bins per frame
    :param data: np.array (num_frames, num_channels)
    :param sr:  sampling rate
    :param params: params dict
    :return: an array in the form :
        [frame, [class_id, azi, ele],[class_id, azi, ele]... ]
        without repeated frame instances, quantized at hop_size,
        containing all valid tf bins doas.
    """

    ### Preprocess data
    X = preprocess(data, sr, params)
    N = X.get_num_time_bins()
    K = X.get_num_frequency_bins()
    r = params['r']

    ### Diffuseness mask
    doa = psa.compute_DOA(X)
    directivity = X.compute_ita_re(r=r)
    directivity_mask = directivity.compute_mask(th=params['directivity_th'])


    ### Energy density mask
    e = psa.compute_energy_density(X)
    block_size = params['energy_density_local_th_size']
    tl = e.compute_threshold_local(block_size=block_size)
    e_mask = e.compute_mask(tl)


    ### DOA Variance mask (computed on azimuth variance)
    vicinity_radius = params['doa_std_vicinity_radius']
    if np.size(vicinity_radius) == 1:
        # Square!
        r_k = vicinity_radius
        r_n = vicinity_radius
    elif np.size(vicinity_radius) == 2:
        # Rectangle! [k, n]
        r_k = vicinity_radius[0]
        r_n = vicinity_radius[1]
    else:
        Warning.warn()

    # TODO: optimize the for loop
    std = np.zeros((K, N))
    doa0_k_array = []
    for r in range(-r_n,r_n+1):
        doa0_k_array.append(np.roll(doa.data[0,:,:],r))
    doa0_k = np.stack(doa0_k_array, axis=0)

    for k in range(r_k, K - r_k):
        std[k, :] = scipy.stats.circstd(doa0_k[:, k - r_k:k + r_k + 1, :], high=np.pi, low=-np.pi, axis=(0, 1))

    # not optimized version...
    # for k in range(r_k, K-r_k):
    #     for n in range(r_n, N-r_n):
    #         # azi
    #         std[k, n] = scipy.stats.circstd(doa.data[0, k-r_k:k+r_k+1, n-r_n:n+r_n+1], high=np.pi, low=-np.pi)
    #         # ele
    #         # std[k, n] = np.std(doa.data[1, k-r_k:k+r_k+1, n-r_n:n+r_n+1])

    # Edges: largest value
    std_max = np.max(std)
    std[0:r_k, :] = std_max
    std[K-r_k:K, :] = std_max
    std[:, 0:r_n] = std_max
    std[:, N - r_n:N] = std_max
    # Scale values to min/max
    std_scaled = std / std_max
    # Invert values
    std_scaled_inv = 1 - std_scaled

    # Compute mask
    doa_std = psa.Stft(doa.t, doa.f, std_scaled_inv, doa.sample_rate)
    doa_std_mask = doa_std.compute_mask(th=params['doa_std_th'])
    mask_all = doa_std_mask.apply_mask(directivity_mask).apply_mask(e_mask)
    doa_th = doa.apply_mask(mask_all)


    ## Median average
    median_averaged_doa = np.empty(doa.data.shape)
    median_averaged_doa.fill(np.nan)
    vicinity_size = (2*r_k-1) + (2*r_n-1)
    doa_median_average_nan_th = params['doa_median_average_nan_th']

    vicinity_radius = params['median_filter_vicinity_radius']
    if np.size(vicinity_radius) == 1:
        # Square!
        r_k = vicinity_radius
        r_n = vicinity_radius
    elif np.size(vicinity_radius) == 2:
        # Rectangle! [k, n]
        r_k = vicinity_radius[0]
        r_n = vicinity_radius[1]
    else:
        Warning.warn()

    # TODO: optimize the for loop
    for k in range(r_k, K - r_k):
        for n in range(r_n, N - r_n):
            azis = discard_nans(doa_th.data[0, k - r_k:k + r_k + 1, n - r_n:n + r_n + 1].flatten())
            if azis.size > vicinity_size * doa_median_average_nan_th:
                median_averaged_doa[0, k, n] = circmedian(azis, 'rad')
            eles = discard_nans(doa_th.data[1, k - r_k:k + r_k + 1, n - r_n:n + r_n + 1].flatten())
            if eles.size > vicinity_size * doa_median_average_nan_th:
                median_averaged_doa[1, k, n] = np.median(eles)
    doa_th_median = psa.Stft(doa.t, doa.f, median_averaged_doa, doa.sample_rate)


    ## Plot stuff
    if params['plot']:
        psa.plot_doa(doa, title='doa')
        psa.plot_doa(doa.apply_mask(e_mask), title='e mask')
        psa.plot_doa(doa.apply_mask(directivity_mask), title='directivity mask')
        psa.plot_doa(doa.apply_mask(doa_std_mask), title='doa std mask')
        psa.plot_doa(doa_th, title='doa mask all')
        psa.plot_doa(doa_th_median, title='doa circmedian')
        plt.show()


    ## Fold values into a vector

    # Get a list of bins with the position estimation according to the selected doa_method
    # TODO: OPTIMIZE
    active_windows = []
    position = []
    for n in range(N):
        azi = discard_nans(doa_th_median.data[0, :, n])
        ele = discard_nans(doa_th_median.data[1, :, n])
        if np.size(azi) < params['num_min_valid_bins']:
            # Empty! not enough suitable doa values in this analysis window
            pass
        else:
            active_windows.append(n)
            position.append([rad2deg(azi), rad2deg(ele)])

    # result = [bin, class_id, azi, ele] with likely repeated bin instances
    result = []
    label = params['default_class_id']
    for window_idx, window in enumerate(active_windows):
        num_bins = np.shape(position[window_idx])[1]
        for b in range(num_bins):
            azi = position[window_idx][0][b]
            ele = position[window_idx][1][b]
            result.append([window, label, azi, ele])


    # Perform the window transformation by averaging within frame
    ## TODO: assert our bins are smaller than required ones

    current_window_hop = (params['window_size'] - params['window_overlap']) / float(sr)
    window_factor = params['required_window_hop'] / current_window_hop

    # Since frames are ordered (at least they should), we can optimise that a little bit
    last_frame = -1
    # result_quantized = [frame, [class_id, azi, ele],[class_id, azi, ele]... ] without repeated bin instances
    result_quantized = []
    for row in result:
        frame = row[0]
        new_frame = int(np.floor(frame / window_factor))
        if new_frame == last_frame:
            result_quantized[-1].append([row[1], row[2], row[3]])
        else:
            result_quantized.append([new_frame, [row[1], row[2], row[3]]])
        last_frame = new_frame

    return result_quantized
    print(signal_limit.data[:, i])
psa.plot_signal(signal, 'Signal Waveform')
psa.plot_signal(signal_limit, 'limit')
plt.show()
#
# print(signal.data)
#
# doa_n = psa.compute_DOA(signal)
# print(doa_n.data)

# stft = psa.Stft(signal)

stft = psa.Stft.fromSignal(signal)
directivity = psa.compute_directivity(stft)

doa = psa.compute_DOA(stft)
h = psa.plot_doa_2d_histogram(doa)
print(h)
# masked1 = stft.apply_softmask(directivity,0.9)

# dpd = psa.compute_DPD_test(stft,1,1,2.)

# psa.plot_magnitude_spectrogram(stft)
# psa.plot_mask(directivity,"directivity")
# psa.plot_mask(masked1,"masked1")
plt.show()

# input('Press ENTER to exit')

# print(doa_kn.data)
directivity_stft = psa.compute_directivity(stft)
psa.plot_diffuseness(diffuseness_stft, title='diffuseness')

## Apply diffuseness mask to separate background and foreground

background_stft = stft.apply_mask(diffuseness_stft)
foreground_stft = stft.apply_mask(directivity_stft)

psa.plot_magnitude_spectrogram(background_stft,
                               title='background magnitude spectrogram')
psa.plot_magnitude_spectrogram(foreground_stft,
                               title='foreground magnitude spectrogram')

## Find DOA on the foreground

doa = psa.compute_DOA(stft)
psa.plot_doa(doa, title='doa')

doa_foreground = psa.compute_DOA(foreground_stft)
psa.plot_doa(doa_foreground, title='doa_foreground')

doa_background = psa.compute_DOA(background_stft)
psa.plot_doa(doa_background, title='doa_background')

## Just get the most directive values

directivity_mask = directivity_stft.compute_mask(th=0.95)
psa.plot_mask(directivity_mask, title='directivity mask')

masked_doa_foreground = doa_foreground.apply_mask(directivity_mask)
psa.plot_doa(masked_doa_foreground, title='masked_doa_foreground')
def compute_peak_statistics(ir,
                            sample_rate,
                            ambisonics_ordering,
                            ambisonics_normalization,
                            plot=False,
                            plot_title = ''):


    ## Signal
    signal = psa.Signal(ir, int(sample_rate), ambisonics_ordering, ambisonics_normalization)
    if plot:
        psa.plot_signal(signal,title=plot_title+'IR')

    stft = psa.Stft.fromSignal(signal,
                               window_size=analysis_window_size,
                               window_overlap=window_overlap,
                               nfft=fft_size,
                               )
    stft = stft.limit_bands(fmin=fmin, fmax=fmax)

    if plot:
        psa.plot_magnitude_spectrogram(stft,title=plot_title+'IR Magnitude Spectrogram, w='+str(analysis_window_size))

    ### Energy Density
    energy_density_t = psa.compute_energy_density(signal)
    if plot:
        psa.plot_signal(energy_density_t,'Energy Density', y_scale='log')

    # # Smoothed signal
    # L = gaussian_window_length
    # smooth_window = scipy.signal.general_gaussian(L, p=gaussian_window_shape, sig=gaussian_window_std)
    # smoothed_energy_density_t = scipy.signal.fftconvolve(smooth_window, energy_density_t.data[0, :])
    # smoothed_energy_density_t = (np.average(energy_density_t.data[0, :]) / np.average(smoothed_energy_density_t)) * smoothed_energy_density_t
    # smoothed_energy_density_t = np.roll(smoothed_energy_density_t, -((L - 1) / 2))
    # smoothed_energy_density_t = smoothed_energy_density_t[:-(L - 1)]  # same length
    #
    # ### Peak peaking
    #
    # # WAVELET
    # cwt_widths = np.arange(0.5*L,1.5*L) # Find peaks of shape among 2 gaussian window lengths
    # smoothed_peaks = scipy.signal.find_peaks_cwt(smoothed_energy_density_t, widths=cwt_widths)

    # # Fine sample correction of peaks: find local maxima over a gaussian window length
    # corrected_peaks = copy.deepcopy(smoothed_peaks)
    # for peak_idx,peak in enumerate(smoothed_peaks):
    #     local_energy = smoothed_energy_density_t[peak - (L / 2):peak + (L / 2)]
    #     corrected_peaks[peak_idx] = np.argmax(local_energy) + peak - (L / 2)
    #
    # if plot:
    #     plt.figure()
    #     plt.suptitle('Smoothed Energy Density & peaks')
    #     ax = plt.subplot(111)
    #     ax.semilogy(energy_density_t.data[0,:])
    #     ax.semilogy(smoothed_energy_density_t)
    #
    #     # plot peak estimates
    #     for peak in corrected_peaks:
    #         plt.axvline(x=peak, color='g')
    #
    #     # plot time frames
    #     for x in np.arange(0,processing_window_samples,analysis_window_size):
    #         plt.axvline(x=x, color='r', alpha=0.3)
    #
    #     plt.grid()
    #
    # peak_time_bins = []
    # for peak in corrected_peaks:
    #     peak_time_bins.append(find_maximal_time_bin(peak, stft, overlap_factor))


    ## Raw Estimates
    doa = psa.compute_DOA(stft)
    if plot:
        psa.plot_doa(doa,title=plot_title+'DoA estimates, w='+str(analysis_window_size))

    # diffuseness = psa.compute_diffuseness(stft)
    # if plot:
    #     psa.plot_diffuseness(diffuseness,title=plot_title+'Diffuseness, w='+str(analysis_window_size))


    neighborhood_size = 3
    ## DOA variance
    doa_var = copy.deepcopy(doa)
    for n in range(doa.get_num_time_bins()):
        for k in range(doa.get_num_frequency_bins()):
            local_var_azi = 0
            local_var_ele = 0
            local_azi = []
            local_ele = []
            r = int(np.floor(neighborhood_size/2)) # neighborhood radius
            for x in np.arange(n - r, n + r + 1):
                for y in np.arange(k - r, k + r + 1):
                    if x < 0:
                        continue
                    elif x >= doa.get_num_time_bins():
                        continue
                    if y < 0:
                        continue
                    elif y >= doa.get_num_frequency_bins():
                        continue
                    local_azi.append(doa.data[0,y,x])
                    local_ele.append(doa.data[1,y,x])
                    # local_var_azi += np.std(doa.data[0,y,x])
                    # local_var_ele += np.std(doa.data[1,y,x])
            local_var_azi = scipy.stats.circvar(np.array(local_azi))
            local_var_ele = np.var(np.array(local_ele))
            doa_var.data[0,k,n] = local_var_azi
            doa_var.data[1,k,n] = local_var_ele

    ## DOA VAR salience

    neighborhood_size = round_up_to_odd(doa_var.get_num_frequency_bins())
    doa_var_salience = threshold_local(doa_var.data[0,:],block_size=neighborhood_size)
    doa_var_max_salience_mask = copy.deepcopy(doa_var)
    doa_var_min_salience_mask = copy.deepcopy(doa_var)
    for k in range(doa_var.get_num_frequency_bins()):
        for n in range(doa_var.get_num_time_bins()):

            if doa_var.data[0, k, n] > doa_var_salience[k, n]:
                doa_var_max_salience_mask.data[:, k, n] = 1.
            else:
                doa_var_max_salience_mask.data[:, k, n] = np.nan

            if doa_var.data[0, k, n] < doa_var_salience[k, n]:
                doa_var_min_salience_mask.data[:, k, n] = 1.
            else:
                doa_var_min_salience_mask.data[:, k, n] = np.nan


    # MINIMUM VARIANCE DOA
    masked_doa = doa.apply_mask(doa_var_min_salience_mask)
    if plot:
        psa.plot_doa(masked_doa,
                     title=plot_title + 'DOA - Minimum variance Salience Masked, w=' + str(analysis_window_size) + ' N: ' + str(
                         neighborhood_size))

    masked_doa = doa.apply_mask(doa_var_max_salience_mask)
    # if plot:
    #     psa.plot_doa(masked_doa,
    #                  title=plot_title + 'DOA - Maximim variance Salience Masked, w=' + str(analysis_window_size) + ' N: ' + str(
    #                      neighborhood_size))


    # if plot:
        # plt.figure()
        # plt.suptitle('DOA VAR')
        # plt.subplot(211)
        # plt.pcolormesh(doa_var.data[0,:,:])
        # plt.subplot(212)
        # plt.pcolormesh(doa_var.data[1,:,:])
        #
        # psa.plot_mask(doa_var_max_salience_mask,title='MAX SALIENCE')
        # psa.plot_mask(doa_var_min_salience_mask,title='MIN SALIENCE')







    ## Energy density
    energy_density_tf = psa.compute_energy_density(stft)
    # if plot:
    #     psa.plot_magnitude_spectrogram(energy_density_tf,title='Energy Density Spectrogram, w='+str(analysis_window_size))


    # Energy density salience

    neighborhood_size = round_up_to_odd(energy_density_tf.get_num_frequency_bins())
    energy_density_salience = threshold_local(energy_density_tf.data[0,:],block_size=neighborhood_size)
    energy_density_salience_mask = copy.deepcopy(energy_density_tf)
    for k in range(energy_density_tf.get_num_frequency_bins()):
        for n in range(energy_density_tf.get_num_time_bins()):
            if energy_density_tf.data[0, k, n] > energy_density_salience[k, n]:
                energy_density_salience_mask.data[:, k, n] = 1.
            else:
                energy_density_salience_mask.data[:, k, n] = np.nan

    # if plot:
    #     fig = plt.figure()
    #     fig.suptitle('energy salience, w=' + str(analysis_window_size))
    #
    #     x = np.arange(np.shape(energy_density_salience)[0])
    #     y = np.arange(np.shape(energy_density_salience)[1])
    #     plt.pcolormesh(y, x, energy_density_salience, norm=LogNorm())
    #     plt.ylabel('Frequency [Hz]')
    #     plt.xlabel('Time [sec]')
    #     plt.colorbar()

    # if plot:
    #     psa.plot_mask(energy_density_salience_mask, title='Energy Salience Mask'+str(analysis_window_size))

    masked_energy = energy_density_tf.apply_mask(energy_density_salience_mask)
    # if plot:
    #     psa.plot_magnitude_spectrogram(masked_energy, title=plot_title+'Energy - Energy Salience Masked, w='+str(analysis_window_size)+' N: '+str(neighborhood_size))

    masked_doa = doa.apply_mask(energy_density_salience_mask)
    if plot:
        psa.plot_doa(masked_doa, title=plot_title+'DOA - Energy Salience Masked, w='+str(analysis_window_size)+' N: '+str(neighborhood_size))

    masked_doa = doa.apply_mask(energy_density_salience_mask).apply_mask(doa_var_min_salience_mask)
    if plot:
        psa.plot_doa(masked_doa, title=plot_title+'DOA - VAR MIN,  Energy Salience Masked, w='+str(analysis_window_size)+' N: '+str(neighborhood_size))


    # masked_diffuseness = diffuseness.apply_mask(energy_density_salience_mask)
    # if plot:
    #     psa.plot_diffuseness(masked_diffuseness, title=plot_title+'Diffuseness - Energy Salience Masked, w='+str(analysis_window_size)+' N: '+str(neighborhood_size))


    # # Diffuseness density salience
    #
    # neighborhood_size = round_up_to_odd(diffuseness.get_num_frequency_bins())
    # diffuseness_salience = threshold_local(diffuseness.data[0,:],block_size=neighborhood_size)
    # diffuseness_salience_mask = copy.deepcopy(diffuseness)
    # for k in range(diffuseness.get_num_frequency_bins()):
    #     for n in range(diffuseness.get_num_time_bins()):
    #         if diffuseness.data[0, k, n] < diffuseness_salience[k, n]:
    #             diffuseness_salience_mask.data[:, k, n] = 1.
    #         else:
    #             diffuseness_salience_mask.data[:, k, n] = np.nan
    #
    # masked_energy = energy_density_tf.apply_mask(diffuseness_salience_mask)
    # if plot:
    #     psa.plot_magnitude_spectrogram(masked_energy, title=plot_title + 'Energy - Diffuseness Salience Masked, w=' + str(
    #         analysis_window_size) + ' N: ' + str(neighborhood_size))
    #
    # masked_doa = doa.apply_mask(diffuseness_salience_mask)
    # if plot:
    #     psa.plot_doa(masked_doa, title=plot_title + 'DOA - Diffuseness Salience Masked, w=' + str(
    #         analysis_window_size) + ' N: ' + str(neighborhood_size))
    #
    # masked_diffuseness = diffuseness.apply_mask(diffuseness_salience_mask)
    # if plot:
    #     psa.plot_diffuseness(masked_diffuseness, title=plot_title + 'Diffuseness - Diffuseness Salience Masked, w=' + str(
    #         analysis_window_size) + ' N: ' + str(neighborhood_size))

    # #
    # if plot:
    #     psa.plot_mask(diffuseness_salience_mask, title='Diffuseness Salience Mask'+str(neighborhood_size))
    # #
    # masked_dif = diffuseness.apply_mask(diffuseness_salience_mask)
    # if plot:
    #     psa.plot_diffuseness(masked_dif, title='Diffuseness - Salience Masked'+str(neighborhood_size))
    #
    # energy_diffuseness_mask = energy_density_salience_mask.apply_mask(diffuseness_salience_mask)
    #
    # masked_doa = masked_doa.apply_mask(diffuseness_salience_mask)
    # if plot:
    #     psa.plot_doa(masked_doa, title='DOA - Salience Masked - Diffuseness Masked, w='+str(analysis_window_size))
    #
    # fig = plt.figure()
    # fig.suptitle("diffuseness salience, block:"+str(neighborhood_size), fontsize=16)
    # x = np.arange(np.shape(diffuseness_salience)[0])
    # y = np.arange(np.shape(diffuseness_salience)[1])
    # plt.pcolormesh(y,x, diffuseness_salience, cmap='plasma_r',norm=LogNorm())
    # plt.ylabel('Frequency [Hz]')
    # plt.xlabel('Time [sec]')
    # plt.colorbar()



    # diffuseness_energy_mask = diffuseness_mask.apply_mask(energy_density_mask)
    # if plot:
    #     psa.plot_mask(diffuseness_energy_mask, title='Diffuseness + Energy Density Mask')
    #
    # masked_diffuseness = diffuseness.apply_mask(diffuseness_mask)
    # if plot:
    #     psa.plot_diffuseness(masked_diffuseness, title='Diffuseness, diffuseness mask')
    #
    # masked_diffuseness = masked_diffuseness.apply_mask(energy_density_mask)
    # if plot:
    #     psa.plot_diffuseness(masked_diffuseness, title='Diffuseness, energy density maskm diffuseness mask')
    #
    # masked_doa = masked_doa.apply_mask(diffuseness_mask)
    # if plot:
    #     psa.plot_doa(masked_doa,title='DoA estimates, energy density mask, diffuseness mask')


    ### Find horizontal-contiguous bins on doa estimates
    time_bins_with_energy = []
    for n in range(energy_density_salience_mask.get_num_time_bins()):
        if not np.all(np.isnan(energy_density_salience_mask.data[0,:,n])):
            time_bins_with_energy.append(n)

    time_region_starts = []
    time_region_ends = []
    for idx, b in enumerate(time_bins_with_energy):
        if time_bins_with_energy[idx] - time_bins_with_energy[idx - 1] != 1:
            time_region_starts.append(time_bins_with_energy[idx])
            time_region_ends.append(time_bins_with_energy[idx - 1]+1)

    time_region_starts.sort()
    time_region_ends.sort()
    assert len(time_region_starts) == len(time_region_ends)


    # Compute local doa estimates on contiguous time regions
    peak_stats = []
    for idx in range(len(time_region_starts)):
        n_range = range(time_region_starts[idx],time_region_ends[idx])

        local_azi = []
        local_ele = []

        index_of_bins_estimated = []
        for n in n_range:
            # Filter nans
            for k in np.arange(energy_density_salience_mask.get_num_frequency_bins()):

                if not np.isnan(energy_density_salience_mask.data[0, k, n]):
                    local_azi.append(masked_doa.data[0, k, n])
                    local_ele.append(masked_doa.data[1, k, n])
                    index_of_bins_estimated.append(n)

        local_azi = np.asarray(local_azi)
        local_ele = np.asarray(local_ele)
        # local_dif = np.asarray(local_dif)

        local_azi_mean = scipy.stats.circmean(local_azi, high=np.pi, low=-np.pi)
        local_azi_std = scipy.stats.circstd(local_azi, high=np.pi, low=-np.pi)
        local_ele_mean = np.mean(local_ele)
        local_ele_std = np.std(local_ele)

        if plot:
            fig = plt.figure()
            ax = fig.add_subplot(111)
            plt.suptitle(plot_title+'FREQ BIN '+str(idx)+' - bins '+str(n_range))
            # cmap = plt.cm.get_cmap("copper")
            plt.grid()
            plt.xlim(-np.pi,np.pi)
            plt.ylim(-np.pi/2,np.pi/2)

            plt.scatter(local_azi, local_ele, marker='o',)
            plt.scatter(local_azi_mean, local_ele_mean, c='red', s=20, marker='+')
            ax.add_patch(Ellipse(xy=(local_azi_mean,local_ele_mean), width=local_azi_std,height=local_ele_std, alpha=0.5))
            ax.add_patch(Ellipse(xy=(local_azi_mean,local_ele_mean), width=3*local_azi_std,height=3*local_ele_std, alpha=0.1))


        mean_location = np.mean(index_of_bins_estimated)
        time_resolution_ms = float(processing_window_ms) / masked_doa.get_num_time_bins()
        estimated_location_ms = mean_location * time_resolution_ms

        peak_stats.append(
            [estimated_location_ms, [local_azi_mean, local_azi_std], [local_ele_mean, local_ele_std]])

    ### Return peak stats
    return peak_stats