def snr_ref_max_profile( tfr_coeff_complex: np.ndarray, energy_mean: float, snr_max: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Computes the snr energy, snr bits and snr entropy from frequency-dependent noise model/profile mean energy and max snr per band :param tfr_coeff_complex: Complex coefficients for time-frequency representation. Can be real. :param energy_mean: baseline frequency-dependent mean energy. :param snr_max: baseline max frequency-dependent linear snr :return: three np.ndarrays with snr energy, snr bits and snr entropy respectively """ # Evaluate Log energy entropy (LEE) = log(p) and Shannon Entropy (SE) = -p*log(p) # Assumes linear spectral coefficien ts (not power), takes the square energy = np.abs(tfr_coeff_complex)**2 snr_lin = energy / energy_mean # Surprisal = log(p) snr_bits = 0.5 * utils.log2epsilon(snr_lin + EPSILON) # SNR entropy per pixel snr_entropy = snr_lin * snr_bits # Scale by max snr_entropy /= snr_max return snr_lin, snr_bits, snr_entropy
def stft_from_sig( sig_wf: np.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Librosa STFT is complex FFT grid, not power :param sig_wf: array with input signal :param frequency_sample_rate_hz: sample rate of frequency in Hz :param band_order_Nth: Nth order of constant Q bands :return: four numpy ndarrays with STFT, STFT_bits, time_stft_s, frequency_stft_hz """ sig_duration_s = len(sig_wf) / frequency_sample_rate_hz _, min_frequency_hz = scales.from_duration(band_order_Nth, sig_duration_s) order_Nth, cycles_M, quality_Q, \ frequency_center, frequency_start, frequency_end = \ scales.frequency_bands_g2f1(scale_order_input=band_order_Nth, frequency_low_input=min_frequency_hz, frequency_sample_rate_input=frequency_sample_rate_hz) # Choose the spectral resolution as the key parameter frequency_resolution_min_hz = np.min(frequency_end - frequency_start) frequency_resolution_max_hz = np.max(frequency_end - frequency_start) frequency_resolution_hz_geo = np.sqrt(frequency_resolution_min_hz * frequency_resolution_max_hz) stft_time_duration_s = 1 / frequency_resolution_hz_geo stft_points_per_seg = int(frequency_sample_rate_hz * stft_time_duration_s) # From CQT stft_points_hop, _, _, _, _ = \ scales.cqt_frequency_bands_g2f1(band_order_Nth, min_frequency_hz, frequency_sample_rate_hz, is_power_2=False) print('STFT Duration, NFFT, HOP:', len(sig_wf), stft_points_per_seg, stft_points_hop) STFT_Scaling = 2 * np.sqrt(np.pi) / stft_points_per_seg STFT = librosa.core.stft(sig_wf, n_fft=stft_points_per_seg, hop_length=stft_points_hop, win_length=None, window='hann', center=True, pad_mode='reflect') # Must be scaled to match scipy psd STFT *= STFT_Scaling STFT_bits = utils.log2epsilon(STFT) time_stft_s = librosa.times_like(STFT, sr=frequency_sample_rate_hz, hop_length=stft_points_hop) frequency_stft_hz = librosa.core.fft_frequencies( sr=frequency_sample_rate_hz, n_fft=stft_points_per_seg) return STFT, STFT_bits, time_stft_s, frequency_stft_hz
def fft_welch_from_Sxx_bits(f_center: np.ndarray, Sxx: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ Compute Welch periodogram from spectrogram :param f_center: array of sample frequencies. :param Sxx: numpy array with spectogram :return: numpy array with frequencies, numpy array with Welch periodogram """ # Estimate Welch periodogram by adding Sxx and dividing by the number of windows # Removes zero frequency Welch_Sxx = np.average(Sxx, axis=1) Welch_Sxx_bits = 0.5 * utils.log2epsilon(Welch_Sxx[1:]) f_center_nozero = f_center[1:] return f_center_nozero, Welch_Sxx_bits
def fft_complex_bits( sig: np.ndarray, sample_interval_s: float ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Compute the one-dimensional discrete Fourier Transform in bits :param sig: array with input signal :param sample_interval_s: sample interval in seconds :return: four numpy arrays with fft_frequency, fft_sig, fft_spectral_bits, fft_spectral_phase_radians """ # FFT for sigetic, by the book fft_points = len(sig) fft_sig = np.fft.fft(sig) # returns correct RMS power level fft_sig /= fft_points fft_frequency = np.fft.fftfreq(fft_points, d=sample_interval_s) fft_spectral_bits = utils.log2epsilon(fft_sig) fft_spectral_phase_radians = np.angle(fft_sig) return fft_frequency, fft_sig, fft_spectral_bits, fft_spectral_phase_radians
def fft_real_bits( sig: np.ndarray, sample_interval_s: float ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ FFT, real frequencies only, magnitude in bits :param sig: array with input signal :param sample_interval_s: sample interval in seconds :return: four numpy ndarrays with fft_frequency_pos, fft_sig_pos, fft_spectral_power_pos_bits, fft_spectral_phase_radians """ # FFT for sigetic, by the book fft_points = len(sig) fft_sig_pos = np.fft.rfft(sig) # returns correct RMS power level sqrt(2) -> 1 fft_sig_pos /= fft_points fft_frequency_pos = np.fft.rfftfreq(fft_points, d=sample_interval_s) fft_spectral_power_pos_bits = utils.log2epsilon(2. * np.abs(fft_sig_pos)) fft_spectral_phase_radians = np.angle(fft_sig_pos) return fft_frequency_pos, fft_sig_pos, fft_spectral_power_pos_bits, fft_spectral_phase_radians
def snr_mean_max( tfr_coeff_complex: np.ndarray ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Computes the snr lin energy, snr in bits, and snr entropy defined in Garces (2020) :param tfr_coeff_complex: Complex coefficients for time-frequency representation. Can be real. :return: three np.ndarrays with snr lin energy, snr in bits, and snr entropy respectively """ # Evaluate Log energy entropy (LEE) = log(p) and Shannon Entropy (SE) = -p*log(p) # Assumes linear spectral coefficien ts (not power), takes the square energy = np.abs(tfr_coeff_complex)**2 energy_mean = np.mean(energy) snr_lin = energy / energy_mean # Surprisal = log(p) snr_bits = 0.5 * utils.log2epsilon(snr_lin + EPSILON) # SNR entropy per pixel snr_entropy = snr_lin * snr_bits # Scale by max snr_entropy /= np.max(snr_lin) return snr_lin, snr_bits, snr_entropy
def cqt_from_sig( sig_wf: np.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float, cqt_window: str = 'hann', dictionary_type: str = "norm" ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Compute the constant-Q transform of a signal. :param sig_wf: array with input signal :param frequency_sample_rate_hz: sample rate of frequency in Hz :param band_order_Nth: Nth order of constant Q bands :param cqt_window: string, "cqt_gauss" or librosa window specification for the basis filter. Default is 'hann' :param dictionary_type: "tone" or "norm". Default is 'norm' :return: four numpy ndarrays with CQT, CQT_bits, time_cqt_s, frequency_cqt_hz """ sig_duration_s = len(sig_wf) / frequency_sample_rate_hz min_scale_s, min_frequency_hz = scales.from_duration( band_order_Nth, sig_duration_s) # Match default cwt cqt_points_hop_min, frequency_hz_center_min, scale_number_bins, order_Nth, cqt_points_per_seg_max = \ scales.cqt_frequency_bands_g2f1(band_order_Nth, min_frequency_hz, frequency_sample_rate_hz, is_power_2=False) print('CQT Duration, NFFT, HOP:', len(sig_wf), cqt_points_per_seg_max, cqt_points_hop_min) int_order_N = int(band_order_Nth) # CQT is not power if cqt_window == "cqt_gauss": CQT = librosa.core.cqt(sig_wf, sr=frequency_sample_rate_hz, hop_length=cqt_points_hop_min, fmin=frequency_hz_center_min, n_bins=scale_number_bins, bins_per_octave=int_order_N, tuning=0.0, filter_scale=1, norm=1, sparsity=0.0, window=q_gauss, scale=True, pad_mode='reflect') else: CQT = librosa.core.cqt(sig_wf, sr=frequency_sample_rate_hz, hop_length=cqt_points_hop_min, fmin=frequency_hz_center_min, n_bins=scale_number_bins, bins_per_octave=int_order_N, tuning=0.0, filter_scale=1, norm=1, sparsity=0.0, window=cqt_window, scale=True, pad_mode='reflect') time_cqt_s = librosa.times_like(CQT, sr=frequency_sample_rate_hz, hop_length=cqt_points_hop_min) frequency_cqt_hz = librosa.core.cqt_frequencies( scale_number_bins, frequency_hz_center_min, bins_per_octave=int_order_N, tuning=0.0) cqt_multiplier = cqt_scaling(band_order_Nth, frequency_cqt_hz, frequency_sample_rate_hz, CQT.shape, dictionary_type) CQT *= cqt_multiplier CQT_bits = utils.log2epsilon(CQT) return CQT, CQT_bits, time_cqt_s, frequency_cqt_hz
def cwt_chirp_complex( band_order_Nth: float, sig_wf: np.ndarray, frequency_low_hz: float, frequency_sample_rate_hz: float, frequency_high_hz: float = scales.Slice.F0, cwt_type: str = "fft", index_shift: float = 0, frequency_ref: float = scales.Slice.F1, scale_base: float = scales.Slice.G2, dictionary_type: str = "norm" ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Calculate CWT for chirp :param band_order_Nth: Nth order of constant Q bands :param sig_wf: array with input signal :param frequency_low_hz: lowest frequency in Hz :param frequency_sample_rate_hz: sample rate in Hz :param frequency_high_hz: highest frequency in Hz :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft" Address ghost folding in "fft", compared to "conv" :param index_shift: index of shift. Default is 0.0 :param frequency_ref: reference frequency in Hz. Default is F1 :param scale_base: G2 or G3. Default is G2 :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm" :return: cwt, cwt_bits, time_s, frequency_cwt_hz """ wavelet_points = len(sig_wf) time_s = np.arange(wavelet_points) / frequency_sample_rate_hz if cwt_type == "morlet2": index_shift = 0 # Planck frequency is absolute upper limit if frequency_high_hz > frequency_sample_rate_hz / 2.: frequency_high_hz = frequency_sample_rate_hz / 2. order_Nth, cycles_M, quality_Q, _,\ frequency_cwt_hz_flipped, frequency_start_flipped, frequency_end_flipped = \ chirp_frequency_bands(scale_order_input=band_order_Nth, frequency_low_input=frequency_low_hz, frequency_sample_rate_input=frequency_sample_rate_hz, frequency_high_input=frequency_high_hz, index_shift=index_shift, frequency_ref=frequency_ref, scale_base=scale_base) scale_points = len(frequency_cwt_hz_flipped) if cwt_type == "morlet2": scale_atom = chirp_scale(cycles_M, frequency_cwt_hz_flipped, frequency_sample_rate_hz) cwt_flipped = signal.cwt(data=sig_wf, wavelet=signal.morlet2, widths=scale_atom, w=cycles_M, dtype=np.complex128) elif cwt_type == "fft": sig_fft = np.fft.fft(sig_wf) cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128) for ii in range(scale_points): atom, _ = chirp_centered_4cwt( band_order_Nth=order_Nth, sig_or_time=sig_wf, scale_frequency_center_hz=frequency_cwt_hz_flipped[ii], frequency_sample_rate_hz=frequency_sample_rate_hz, index_shift=index_shift, scale_base=scale_base, dictionary_type=dictionary_type) atom_fft = np.fft.fft(atom) cwt_raw = np.fft.ifft(sig_fft * np.conj(atom_fft)) cwt_flipped[ii, :] = np.append(cwt_raw[wavelet_points // 2:], cwt_raw[0:wavelet_points // 2]) elif cwt_type == "conv": cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128) for ii in range(scale_points): atom, _ = chirp_centered_4cwt( band_order_Nth=order_Nth, sig_or_time=sig_wf, scale_frequency_center_hz=frequency_cwt_hz_flipped[ii], frequency_sample_rate_hz=frequency_sample_rate_hz, index_shift=index_shift, scale_base=scale_base, dictionary_type=dictionary_type) cwt_flipped[ii, :] = signal.convolve(sig_wf, np.conj(atom)[::-1], mode='same') else: print("Incorrect cwt_type specification in cwt_chirp_complex") # Time scales are increasing, which is the opposite of what is expected for the frequency. Flip. frequency_cwt_hz = np.flip(frequency_cwt_hz_flipped) cwt = np.flipud(cwt_flipped) cwt_bits = utils.log2epsilon(cwt) return cwt, cwt_bits, time_s, frequency_cwt_hz
freq_target, freq_low, freq_high, band_order, log_scale_base, override ] print(freq_params) w_target = 2 * np.pi * freq_target decay_constant = 1 / (2 * w_target**2) wwz = libwwz.wwt(magnitudes=mic_sig, timestamps=mic_sig_epoch_s - mic_sig_epoch_s[0], time_divisions=len(mic_cqt_time_s), freq_params=freq_params, decay_constant=decay_constant, method='octave') mic_wwz = wwz[3].T mic_wwz_bits = utils.log2epsilon(mic_wwz) mic_wwz_snr, mic_wwz_snr_bits, mic_wwz_snr_entropy = entropy.snr_mean_max( tfr_coeff_complex=mic_wwz) pltq.plot_wf_mesh_mesh_vert(redvox_id=station_id_str, wf_panel_2_sig=mic_sig, wf_panel_2_time=mic_sig_epoch_s, mesh_time=mic_cqt_time_s, mesh_frequency=mic_cqt_frequency_hz, mesh_panel_1_trf=mic_wwz_bits, mesh_panel_1_colormap_scaling="range", mesh_panel_0_tfr=mic_wwz_snr_entropy, wf_panel_2_units="Norm", mesh_panel_1_cbar_units="bits", mesh_panel_0_cbar_units="eSNR bits", figure_title="WWZ for " + EVENT_NAME, frequency_hz_ymin=fmin,