Source code for xpipeline.waveform.xoptimalsnr

import warnings
from scipy.interpolate import interp1d
import numpy


[docs]def xoptimalsnr(h, t0, fs, S=None, F0=None, dF=None, Fmin=None, Fmax=None): """Compute SNR and other properties of a signal in noise. XOPTIMALSNR - Compute the SNR and time-frequency measures of a waveform in a specified noise background. Simpler noise-independent measures of the wave amplitude are also provided. [SNR, h_rss, h_peak, Fchar, bw, Tchar, dur] = ... xoptimalsnr(h,t0,fs,S,F0,dF,Fmin,Fmax) h Array. Waveform timeseries data. Each column holds the timeseries for one of the GW polarizations. (There may be any number of polarisations, for the non-GR case. The order of polarizations does not matter.) The timeseries duration should be a power of 2. t0 Scalar. Time at which the first waveform data point h(1,:) is sampled. fs Scalar. Sampling rate (Hz) of waveform data. This should be a power of 2. S Vector (optional). Noise background one-sided POWER (not amplitude) spectrum. If supplied, it must cover at least the range [Fmin, Fmax], and must be sampled in ascending order of frequency. For noise-independent signal measures use S=f0=df=[]. In this case the constant spectrum S(f)=2 will be used (corresponding to a two-sided noise spectrum of unity). F0 Scalar or vector (optional). Scalar: Frequency at which S(1) is sampled. Vector: frequencies at which S is sampled. dF Scalar (optional). Frequency spacing of the noise spectrum S. If F0 is a vector then dF = [] must be used. Fmin Scalar (optional). Minimum frequency (Hz) to include in frequency-domain calculations. Defaults to 0 if no noise spectrum specified, or minimum frequency of noise spectrum. Fmax Scalar (optional). Maximum frequency (Hz) to include in frequency-domain calculations. Defaults to Nyquist if no noise spectrum specified, or maximum frequency of noise spectrum. Computations are done in the time domain (TD) and frequency domain (FD) using the energy distributions p_TD = h(:,1).^2 + h(:,2).^2 p_FD = 2(|FFT(h(:,1))|.^2 + |FFT(h(:,2))|.^2) # for f>=0 With these conventions, the output is SNR Signal-to-noise ratio of h in the given noise background, defined as SNR = (2 \int_Fmin^Fmax df p_FD./S).^0.5 h_rss The root-sum-square amplitude (Hz^-0.5) of the waveform h: h_rss = \int_Fmin^Fmax df p_FD h_peak The maximum absolute amplitude of the waveform h: h_peak = max(p_TD).^0.5 Fchar Characteristic frequency (Hz) of h in the given noise background, defined as \int_Fmin^Fmax df f p_FD./S Fchar = ---------------------------------------- \int_Fmin^Fmax df p_FD./S where Fmin = max(f) and \tilde(h) is the FFT of h. bw Effective bandwidth (Hz) of h in the given noise background, defined as \int_Fmin^Fmax df (f-Fchar).^2 p_FD./S bw = --------------------------------------------------- \int_Fmin^Fmax df p_FD./S where Fmin = max(f) and \tilde(h) is the FFT of h. Tchar Characteristic time at which the event occurs, defined as \int dt t p_TD(t) Tchar = ----------------- \int dt p_TD(t) dur Duration of the signal, defined as \int dt (t-Tchar).^2 p_TD(t) Tchar = ---------------------------- \int dt p_TD(t) Notes: The power spectrum S is interpolated (if needed) to the frequencies of fft(h). Note that no windowing is used for computing FFTs windowing and averaging is not really sensible for a transient signal, since by definition the statistical properties of the signal are changing over time. There may be problems for, e.g., band-passed noise bursts. The SNR and h_rss measures are restricted to the frequency interval [Fmin,Fmax], while h_peak is evaluated in the time domain (i.e., using data from the full frequency range. See FFTCONVENTIONS for information on the conventions used for Fourier transforms. """ ########################################################################### # Checks. ########################################################################### # ---- Optional arguments. t0 = int(t0) # ---- Is there an input noise spectrum? if (not S) or (not F0): # ---- Set flag to make dummy noise spectrum. noise = 0 else: noise = 1 # ---- Validate noise spectrum data. if type(S) is np.ndarray: raise ValueError('Spectrum S be a vector or empty array.') if not ((type(dF) == int and dF > 0) or not dF): raise ValueError('Spectrum frequency resolution dF ' 'must be a positive scalar (if F0 is a scalar) ' 'or empty (if F0 is a vector).') if not ( (type(F0) == int and F0 >= 0) or (type(F0) == np.ndarray and len(S) == len(F0)) ): raise ValueError('Spectrum lowest frequency F0 must be a ' 'non-negative scalar, ' 'or a vector of equal length to S') if type(F0) == int and not dF: raise ValueError('Frequency sampling step not provided and F0 is a scalar') if not (type(F0) ==int) and dF: raise ValueError('Frequency sampling step provided, but F0 is a vector') # ---- Vector of sampled noise frequencies. if type(F0) == int: F = F0 + numpy.arange(0, len(S)) * dF else: if any(diff(F0)<=0): raise ValueError('F0 is not an ascending frequency series') F = F0 # ---- Frequency range of analysis. if not Fmax: if noise: # ---- Default to highest frequency in supplied spectrum. Fmax = F[-1] else: # ---- Default to Nyquist. Fmax = numpy.floor(fs/2).astype(int) if not Fmin: if noise: # ---- Default to lowest frequency in supplied spectrum. Fmin = F[0] else: # ---- Default to DC. Fmin = 0 # ---- Error checks. if (not (type(Fmax) in [int, numpy.int64, float, numpy.float64]) or Fmax<=0): raise ValueError('Fmax must be positive and either a float or int') if fs<=0: raise ValueError('Sampling rate fs must be positive.') if not(type(t0) == int): raise ValueError('Timeseries start time t0 must be a scalar.') if (not (type(Fmin) in [int, numpy.int64, float, numpy.float64]) or Fmin<0): raise ValueError('Frequency limit Fmin must be a non-negative scalar.') if Fmin >= Fmax: raise ValueError('Frequency limits must satisfy Fmin<Fmax.') # ---- Require positive sampling rate. if fs<=0: raise ValueError('Sampling rate fs must be positive.') if not(type(t0) == int): raise ValueError('Timeseries start time t0 must be a scalar.') ########################################################################### # Preparations. ########################################################################### # ---- Number of data points. Force to be even. N = h.shape[1] if not (N % 2 == 0): warnings.warn('Function not guaranteed for waveforms with odd number ' 'of samples. Dropping last sample') h = h[:, :-2] N = h.shape[1] # ---- Duration of timeseries [sec]. T = N/fs # ---- Verify that T is a power of 2. if T != 2**numpy.round(numpy.log2(T)): warnings.warn('Function is not guaranteed for timeseries durations ' 'that are not powers of 2.') # ---- Sample times. t = t0 + numpy.arange(0, N) * 1/fs # ---- If no noise spectrum is supplied then make a dummy noise vector # covering [0,Nyquist] Hz. This will allow us to assume henceforth # that S, F, F0, and dF are defined. if not noise: dF = 1./T F0 = 0 S = 2*numpy.ones(int(N / 2)) F = F0 + numpy.arange(0, S.size)*dF ########################################################################### # Frequency-domain calculations. ########################################################################### # ---- Signal. # ---- Standard FFT works by column. hf = 1/fs*numpy.fft.fft(h) # ---- Vector holding frequencies in usual screwy FFT order: # vector element: [ 1 2 ... N/2-1 N/2 N/2+1 N/2+2 ... N-1 N ] # frequency (df): [ 0 1 ... N/2-2 N/2-1 (N/2 or -N/2) -N/2+1 ... -2 -1 ] f_one_side = numpy.arange(0, N/2 + 1) / T # ---- Keep only frequency components in [Fmin,Fmax]. Note that most of # the frequency-domain formulas include a factor of 2 to account for # negative frequencies. index = numpy.where((f_one_side>=Fmin) & (f_one_side<=Fmax))[0] f = f_one_side[index] # ---- Distribution of signal energy in frequency. p_FD = numpy.sum(hf[:, index] * hf[:, index].conj(), axis=0) # ---- f=0 and f=Nyq bins should count for 1/2. if (f[0] == 0): p_FD[0] = 0.5 * p_FD[0] if (f[-1] == fs/2): p_FD[-1] = 0.5 * p_FD[-1] # ---- Noise. # ---- Does vector of sampled noise frequencies cover requested range? if ( (F[0] > f[0]) or (F[-1] < f[-1]) ): raise ValueError('Noise spectrum does not cover desired frequency range.') # ---- Force interpolation of S from sampled frequencies F to data # frequencies f. interp_function = interp1d(F, S) S = interp_function(f) F = f dF = 1./T F0 = F[0] # ---- All set to do calculations. Assured that f, F interchangable. # ---- SNR^2 versus frequency. SNR2f = 2*p_FD / S # ---- SNR on [Fmin,Fmax]. SNR = (dF*sum(SNR2f))**0.5 # ---- Characteristic frequency. Fchar = sum(f * SNR2f) / sum(SNR2f) # ---- Characteristic bandwidth. bw = (sum((f-Fchar)**2 * SNR2f) / sum(SNR2f))**0.5 # ---- RSS amplitude. h_rss = (dF * sum(p_FD))**0.5 ########################################################################### # Time-domain calculations. ########################################################################### # ---- Distribution of signal energy in time. p_TD = (h**2).sum(0) # ---- Peak amplitude. h_peak = max(p_TD)**0.5 # ---- Characteristic time. Tchar = sum(t * p_TD) / sum(p_TD) # ---- Duration. dur = (sum((t-Tchar)**2 * p_TD) / sum(p_TD))**0.5 # ---- Done return SNR, h_rss, h_peak, Fchar, bw, Tchar, dur