Module libquantum.spectra
This module calculates spectra: STFT, FFT, CQT
Expand source code
"""
This module calculates spectra: STFT, FFT, CQT
"""
import numpy as np
import scipy.signal as signal
import librosa
from libquantum import atoms, scales, utils
from libquantum.scales import EPSILON
from typing import Union, Tuple
def quantum_gauss_stdev(points_number: int) -> float:
"""
Calculate Gauss Standard Deviation based on the input number of points
:param points_number: number of points
:return: float with standard deviation
"""
gauss_stdev = 0.5*points_number/np.pi
return gauss_stdev
def q_gauss(points_number: int) -> np.ndarray:
"""
Calculate Gauss envelope
:param points_number: number of points
:return: numpy array with envelope values
"""
gauss_stdev = quantum_gauss_stdev(points_number)
gauss_amp = np.pi**(-0.25)
gauss_envelope = gauss_amp*signal.get_window(('gaussian', gauss_stdev), points_number)
return gauss_envelope
def cqt_scaling(band_order_Nth: float,
scale_frequency_center_hz: float,
frequency_sample_rate_hz: float,
tfr_shape: tuple,
dictionary_type: str = "norm") -> Union[np.ndarray, float]:
"""
Calculate scale for CQT
:param band_order_Nth: Nth order of constant Q bands
:param scale_frequency_center_hz: center frequency fc in Hz
:param frequency_sample_rate_hz: sample rate of frequency in Hz
:param tfr_shape: shape of TFR, Tuple
:param dictionary_type: "tone" or "norm". Default is 'norm'
:return: numpy array with scale values if dictionary_type
"""
atom_scales = atoms.chirp_scale_from_order(band_order_Nth,
scale_frequency_center_hz,
frequency_sample_rate_hz)
if dictionary_type == "tone":
# The sqrt(2) factor reconciled the rms tone amplitude.
# That way a 15 bit input amplitude returns 15 bit log2 Sxx
atom_amp = np.sqrt(2)*(4*np.pi*atom_scales**2)**(-0.25) # See Eq. A15 of Garces 2020
cqt_multipier = utils.just_tile(atom_amp, tfr_shape)
else:
cqt_multipier = 1.
return cqt_multipier
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 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 stft_reassign_from_sig(sig_wf: np.ndarray,
frequency_sample_rate_hz: float,
band_order_Nth: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray,
np.ndarray]:
"""
Librosa STFT is complex FFT grid, not power
Reassigned frequencies are not the same as the standard mesh frequencies
: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: six numpy ndarrays with STFT, STFT_bits, time_stft_s, frequency_stft_hz, time_stft_rsg_s,
frequency_stft_rsg_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('Reassigned 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
# Reassigned frequencies require a 'best fit' solution.
frequency_stft_rsg_hz, time_stft_rsg_s, STFT_mag = \
librosa.reassigned_spectrogram(sig_wf, sr=frequency_sample_rate_hz,
n_fft=stft_points_per_seg,
hop_length=stft_points_hop, win_length=None,
window='hann', center=False, pad_mode='reflect')
# Must be scaled to match scipy psd
STFT_mag *= STFT_Scaling
STFT_bits = utils.log2epsilon(STFT_mag)
# Standard mesh times and frequencies for plotting - nice to have both
time_stft_s = librosa.times_like(STFT_mag, 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)
# Reassigned frequencies are not the same as the standard mesh frequencies
return STFT_mag, STFT_bits, time_stft_s, frequency_stft_hz, time_stft_rsg_s, frequency_stft_rsg_hz
# GENERAL FFT TOOLS
def fft_real_dB(sig: np.ndarray,
sample_interval_s: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""
FFT, real frequencies only, magnitude in dB
: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_dB,
fft_spectral_phase_radians
"""
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_dB = 10.*np.log10(2.*(np.abs(fft_sig_pos))**2. + EPSILON)
fft_spectral_phase_radians = np.angle(fft_sig_pos)
return fft_frequency_pos, fft_sig_pos, fft_spectral_power_pos_dB, 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 ifft_real(fft_sig_pos) -> np.ndarray:
"""
Calculate the inverse of the one-dimensional discrete Fourier Transform for real input.
:param fft_sig_pos: input array
:return: the truncated or zero-padded input, transformed along the axis
"""
ifft_sig = np.fft.irfft(fft_sig_pos).real
fft_points = len(ifft_sig)
ifft_sig *= fft_points
return ifft_sig
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
# Inverse Fourier Transform FFT of real function
def ifft_complex(fft_sig_complex) -> np.ndarray:
"""
Compute the one-dimensional inverse discrete Fourier Transform.
:param fft_sig_complex: input array, can be complex.
:return: the truncated or zero-padded input, transformed along the axis
"""
ifft_sig = np.fft.ifft(fft_sig_complex)
fft_points = len(ifft_sig)
ifft_sig *= fft_points
return ifft_sig
# Time shifted the FFT before inversion
def fft_time_shift(fft_sig: np.ndarray,
fft_frequency: np.ndarray,
time_lead: Union[int, float]) -> np.ndarray:
"""
Time shift an FFT. Frequency and the time shift time_lead must have consistent units and be within window
:param fft_sig: FFT signal
:param fft_frequency: FFT frequencies
:param time_lead: time shift
:return: numpy ndarray with time shifted FFT signal
"""
# frequency and the time shift time_lead must have consistent units and be within window
fft_phase_time_shift = np.exp(-1j*2*np.pi*fft_frequency*time_lead)
fft_sig *= fft_phase_time_shift
return fft_sig
# Compute Welch from spectrogram
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_welch_snr_power(f_center: np.ndarray,
Sxx: np.ndarray,
Sxx2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
"""
Calculate SNR power from specogram
:param f_center: array of sample frequencies.
:param Sxx: numpy array with spectogram
:param Sxx2: numpy array with spectogram 2. Must have the same length as Sxx
:return: numpy ndarray with snr frequency, numpy ndarray with snr power
"""
# Estimate Welch periodogram by adding Sxx and dividing by the number of windows
# Removes zero frequency
Welch_Sxx = np.average(Sxx[1:], axis=1)
Welch_Sxx /= np.max(Sxx[1:])
Welch_Sxx2 = np.average(Sxx2[1:], axis=1)
Welch_Sxx2 /= np.max(Sxx2[1:])
snr_power = Welch_Sxx2/Welch_Sxx
snr_frequency = f_center[1:]
return snr_frequency, snr_power
Functions
def cqt_from_sig(sig_wf: numpy.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float, cqt_window: str = 'hann', dictionary_type: str = 'norm') ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.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
Expand source code
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 cqt_scaling(band_order_Nth: float, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, tfr_shape: tuple, dictionary_type: str = 'norm') ‑> Union[numpy.ndarray, float]
-
Calculate scale for CQT
:param band_order_Nth: Nth order of constant Q bands :param scale_frequency_center_hz: center frequency fc in Hz :param frequency_sample_rate_hz: sample rate of frequency in Hz :param tfr_shape: shape of TFR, Tuple :param dictionary_type: "tone" or "norm". Default is 'norm' :return: numpy array with scale values if dictionary_type
Expand source code
def cqt_scaling(band_order_Nth: float, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, tfr_shape: tuple, dictionary_type: str = "norm") -> Union[np.ndarray, float]: """ Calculate scale for CQT :param band_order_Nth: Nth order of constant Q bands :param scale_frequency_center_hz: center frequency fc in Hz :param frequency_sample_rate_hz: sample rate of frequency in Hz :param tfr_shape: shape of TFR, Tuple :param dictionary_type: "tone" or "norm". Default is 'norm' :return: numpy array with scale values if dictionary_type """ atom_scales = atoms.chirp_scale_from_order(band_order_Nth, scale_frequency_center_hz, frequency_sample_rate_hz) if dictionary_type == "tone": # The sqrt(2) factor reconciled the rms tone amplitude. # That way a 15 bit input amplitude returns 15 bit log2 Sxx atom_amp = np.sqrt(2)*(4*np.pi*atom_scales**2)**(-0.25) # See Eq. A15 of Garces 2020 cqt_multipier = utils.just_tile(atom_amp, tfr_shape) else: cqt_multipier = 1. return cqt_multipier
def fft_complex_bits(sig: numpy.ndarray, sample_interval_s: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.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
Expand source code
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: numpy.ndarray, sample_interval_s: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.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
Expand source code
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 fft_real_dB(sig: numpy.ndarray, sample_interval_s: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]
-
FFT, real frequencies only, magnitude in dB
: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_dB, fft_spectral_phase_radians
Expand source code
def fft_real_dB(sig: np.ndarray, sample_interval_s: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ FFT, real frequencies only, magnitude in dB :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_dB, fft_spectral_phase_radians """ 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_dB = 10.*np.log10(2.*(np.abs(fft_sig_pos))**2. + EPSILON) fft_spectral_phase_radians = np.angle(fft_sig_pos) return fft_frequency_pos, fft_sig_pos, fft_spectral_power_pos_dB, fft_spectral_phase_radians
def fft_time_shift(fft_sig: numpy.ndarray, fft_frequency: numpy.ndarray, time_lead: Union[int, float]) ‑> numpy.ndarray
-
Time shift an FFT. Frequency and the time shift time_lead must have consistent units and be within window
:param fft_sig: FFT signal :param fft_frequency: FFT frequencies :param time_lead: time shift :return: numpy ndarray with time shifted FFT signal
Expand source code
def fft_time_shift(fft_sig: np.ndarray, fft_frequency: np.ndarray, time_lead: Union[int, float]) -> np.ndarray: """ Time shift an FFT. Frequency and the time shift time_lead must have consistent units and be within window :param fft_sig: FFT signal :param fft_frequency: FFT frequencies :param time_lead: time shift :return: numpy ndarray with time shifted FFT signal """ # frequency and the time shift time_lead must have consistent units and be within window fft_phase_time_shift = np.exp(-1j*2*np.pi*fft_frequency*time_lead) fft_sig *= fft_phase_time_shift return fft_sig
def fft_welch_from_Sxx_bits(f_center: numpy.ndarray, Sxx: numpy.ndarray) ‑> Tuple[numpy.ndarray, numpy.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
Expand source code
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_welch_snr_power(f_center: numpy.ndarray, Sxx: numpy.ndarray, Sxx2: numpy.ndarray) ‑> Tuple[numpy.ndarray, numpy.ndarray]
-
Calculate SNR power from specogram
:param f_center: array of sample frequencies. :param Sxx: numpy array with spectogram :param Sxx2: numpy array with spectogram 2. Must have the same length as Sxx :return: numpy ndarray with snr frequency, numpy ndarray with snr power
Expand source code
def fft_welch_snr_power(f_center: np.ndarray, Sxx: np.ndarray, Sxx2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ Calculate SNR power from specogram :param f_center: array of sample frequencies. :param Sxx: numpy array with spectogram :param Sxx2: numpy array with spectogram 2. Must have the same length as Sxx :return: numpy ndarray with snr frequency, numpy ndarray with snr power """ # Estimate Welch periodogram by adding Sxx and dividing by the number of windows # Removes zero frequency Welch_Sxx = np.average(Sxx[1:], axis=1) Welch_Sxx /= np.max(Sxx[1:]) Welch_Sxx2 = np.average(Sxx2[1:], axis=1) Welch_Sxx2 /= np.max(Sxx2[1:]) snr_power = Welch_Sxx2/Welch_Sxx snr_frequency = f_center[1:] return snr_frequency, snr_power
def ifft_complex(fft_sig_complex) ‑> numpy.ndarray
-
Compute the one-dimensional inverse discrete Fourier Transform.
:param fft_sig_complex: input array, can be complex. :return: the truncated or zero-padded input, transformed along the axis
Expand source code
def ifft_complex(fft_sig_complex) -> np.ndarray: """ Compute the one-dimensional inverse discrete Fourier Transform. :param fft_sig_complex: input array, can be complex. :return: the truncated or zero-padded input, transformed along the axis """ ifft_sig = np.fft.ifft(fft_sig_complex) fft_points = len(ifft_sig) ifft_sig *= fft_points return ifft_sig
def ifft_real(fft_sig_pos) ‑> numpy.ndarray
-
Calculate the inverse of the one-dimensional discrete Fourier Transform for real input.
:param fft_sig_pos: input array :return: the truncated or zero-padded input, transformed along the axis
Expand source code
def ifft_real(fft_sig_pos) -> np.ndarray: """ Calculate the inverse of the one-dimensional discrete Fourier Transform for real input. :param fft_sig_pos: input array :return: the truncated or zero-padded input, transformed along the axis """ ifft_sig = np.fft.irfft(fft_sig_pos).real fft_points = len(ifft_sig) ifft_sig *= fft_points return ifft_sig
def q_gauss(points_number: int) ‑> numpy.ndarray
-
Calculate Gauss envelope
:param points_number: number of points :return: numpy array with envelope values
Expand source code
def q_gauss(points_number: int) -> np.ndarray: """ Calculate Gauss envelope :param points_number: number of points :return: numpy array with envelope values """ gauss_stdev = quantum_gauss_stdev(points_number) gauss_amp = np.pi**(-0.25) gauss_envelope = gauss_amp*signal.get_window(('gaussian', gauss_stdev), points_number) return gauss_envelope
def quantum_gauss_stdev(points_number: int) ‑> float
-
Calculate Gauss Standard Deviation based on the input number of points
:param points_number: number of points :return: float with standard deviation
Expand source code
def quantum_gauss_stdev(points_number: int) -> float: """ Calculate Gauss Standard Deviation based on the input number of points :param points_number: number of points :return: float with standard deviation """ gauss_stdev = 0.5*points_number/np.pi return gauss_stdev
def stft_from_sig(sig_wf: numpy.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.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
Expand source code
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 stft_reassign_from_sig(sig_wf: numpy.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float) ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]
-
Librosa STFT is complex FFT grid, not power Reassigned frequencies are not the same as the standard mesh frequencies
: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: six numpy ndarrays with STFT, STFT_bits, time_stft_s, frequency_stft_hz, time_stft_rsg_s, frequency_stft_rsg_hz
Expand source code
def stft_reassign_from_sig(sig_wf: np.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Librosa STFT is complex FFT grid, not power Reassigned frequencies are not the same as the standard mesh frequencies :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: six numpy ndarrays with STFT, STFT_bits, time_stft_s, frequency_stft_hz, time_stft_rsg_s, frequency_stft_rsg_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('Reassigned 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 # Reassigned frequencies require a 'best fit' solution. frequency_stft_rsg_hz, time_stft_rsg_s, STFT_mag = \ librosa.reassigned_spectrogram(sig_wf, sr=frequency_sample_rate_hz, n_fft=stft_points_per_seg, hop_length=stft_points_hop, win_length=None, window='hann', center=False, pad_mode='reflect') # Must be scaled to match scipy psd STFT_mag *= STFT_Scaling STFT_bits = utils.log2epsilon(STFT_mag) # Standard mesh times and frequencies for plotting - nice to have both time_stft_s = librosa.times_like(STFT_mag, 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) # Reassigned frequencies are not the same as the standard mesh frequencies return STFT_mag, STFT_bits, time_stft_s, frequency_stft_hz, time_stft_rsg_s, frequency_stft_rsg_hz