def ref_crosscov(x, y, all_lags=True): "Computes sxy[k] = E{x[n]*y[n+k]}" x = utils.remove_bias(x, 0) y = utils.remove_bias(y, 0) lx, ly = len(x), len(y) pad_len = lx + ly - 1 sxy = np.correlate(x, y, mode='full') / lx if all_lags: return sxy c_idx = pad_len / 2 return sxy[c_idx:]
def test_debias(): x = np.arange(64).reshape(4, 4, 4) x0 = utils.remove_bias(x, axis=1) npt.assert_equal((x0.mean(axis=1) == 0).all(), True)
def multi_taper_csd(s, Fs=2 * np.pi, BW=None, low_bias=True, adaptive=False, sides='default'): """Returns an estimate of the Cross Spectral Density (CSD) function between all (N choose 2) pairs of timeseries in s, using the multitaper method. If the NW product, or the BW and Fs in Hz are not specified by the user, a bandwidth of 4 times the fundamental frequency, corresponding to NW = 4 will be used. Parameters ---------- s : ndarray An array of sampled random processes, where the time axis is assumed to be on the last axis. If ndim > 2, the number of time series to compare will still be taken as prod(s.shape[:-1]) Fs: float, Sampling rate of the signal BW: float, The bandwidth of the windowing function will determine the number tapers to use. This parameters represents trade-off between frequency resolution (lower main lobe BW for the taper) and variance reduction (higher BW and number of averaged estimates). adaptive : {True, False} Use adaptive weighting to combine spectra low_bias : {True, False} Rather than use 2NW tapers, only use the tapers that have better than 90% spectral concentration within the bandwidth (still using a maximum of 2NW tapers) sides : str (optional) [ 'default' | 'onesided' | 'twosided' ] This determines which sides of the spectrum to return. For complex-valued inputs, the default is two-sided, for real-valued inputs, default is one-sided Indicates whether to return a one-sided or two-sided Returns ------- (freqs, csd_est) : ndarrays The estimatated CSD and the frequency points vector. The CSD{i,j}(f) are returned in a square "matrix" of vectors holding Sij(f). For an input array of (M,N), the output is (M,M,N) """ # have last axis be time series for now N = s.shape[-1] rest_of = s.shape[:-1] M = int(np.product(rest_of)) s = s.reshape(M, N) # de-mean this sucker s = utils.remove_bias(s, axis=-1) #Get the number of tapers from the sampling rate and the bandwidth: if BW is not None: NW = BW / (2 * Fs) * N else: NW = 4 Kmax = int(2 * NW) dpss, eigvals = dpss_windows(N, NW, Kmax) if low_bias: keepers = (eigvals > 0.9) dpss = dpss[keepers] eigvals = eigvals[keepers] Kmax = len(dpss) # if the time series is a complex vector, a one sided PSD is invalid: if (sides == 'default' and np.iscomplexobj(s)) or sides == 'twosided': sides = 'twosided' elif sides in ('default', 'onesided'): sides = 'onesided' sig_sl = [slice(None)] * len(s.shape) sig_sl.insert(len(s.shape) - 1, np.newaxis) # tapered.shape is (M, Kmax, N) tapered = s[sig_sl] * dpss # compute the y_{i,k}(f) tapered_spectra = fftpack.fft(tapered) # compute the cross-spectral density functions last_freq = N / 2 + 1 if sides == 'onesided' else N if adaptive: w = np.empty(tapered_spectra.shape[:-1] + (last_freq,)) nu = np.empty((M, last_freq)) for i in xrange(M): w[i], nu[i] = utils.adaptive_weights( tapered_spectra[i], eigvals, sides=sides ) else: weights = np.sqrt(eigvals).reshape(Kmax, 1) csdfs = np.empty((M, M, last_freq), 'D') for i in xrange(M): if adaptive: wi = w[i] else: wi = weights for j in xrange(i + 1): if adaptive: wj = w[j] else: wj = weights ti = tapered_spectra[i] tj = tapered_spectra[j] csdfs[i, j] = mtm_cross_spectrum(ti, tj, (wi, wj), sides=sides) upper_idc = triu_indices(M, k=1) lower_idc = tril_indices(M, k=-1) csdfs[upper_idc] = csdfs[lower_idc].conj() if sides == 'onesided': freqs = np.linspace(0, Fs / 2, N / 2 + 1) else: freqs = np.linspace(0, Fs, N, endpoint=False) return freqs, csdfs
def multi_taper_psd(s, Fs=2 * np.pi, BW=None, adaptive=False, jackknife=True, low_bias=True, sides='default', NFFT=None): """Returns an estimate of the PSD function of s using the multitaper method. If the NW product, or the BW and Fs in Hz are not specified by the user, a bandwidth of 4 times the fundamental frequency, corresponding to NW = 4 will be used. Parameters ---------- s : ndarray An array of sampled random processes, where the time axis is assumed to be on the last axis Fs: float Sampling rate of the signal BW: float The bandwidth of the windowing function will determine the number tapers to use. This parameters represents trade-off between frequency resolution (lower main lobe BW for the taper) and variance reduction (higher BW and number of averaged estimates). adaptive : {True/False} Use an adaptive weighting routine to combine the PSD estimates of different tapers. jackknife : {True/False} Use the jackknife method to make an estimate of the PSD variance at each point. low_bias : {True/False} Rather than use 2NW tapers, only use the tapers that have better than 90% spectral concentration within the bandwidth (still using a maximum of 2NW tapers) sides : str (optional) [ 'default' | 'onesided' | 'twosided' ] This determines which sides of the spectrum to return. For complex-valued inputs, the default is two-sided, for real-valued inputs, default is one-sided Indicates whether to return a one-sided or two-sided Returns ------- (freqs, psd_est, var_or_nu) : ndarrays The first two arrays are the frequency points vector and the estimatated PSD. The last returned array differs depending on whether the jackknife was used. It is either * The jackknife estimated variance of the log-psd, OR * The degrees of freedom in a chi2 model of how the estimated PSD is distributed about the true log-PSD (this is either 2*floor(2*NW), or calculated from adaptive weights) """ # have last axis be time series for now N = s.shape[-1] if not NFFT else NFFT rest_of_dims = s.shape[:-1] s = s.reshape(int(np.product(rest_of_dims)), N) # de-mean this sucker s = utils.remove_bias(s, axis=-1) # Get the number of tapers from the sampling rate and the bandwidth: if BW is not None: NW = BW / (2 * Fs) * N else: NW = 4 Kmax = int(2 * NW) dpss, eigs = dpss_windows(N, NW, Kmax) if low_bias: keepers = (eigs > 0.9) dpss = dpss[keepers] eigs = eigs[keepers] Kmax = len(dpss) # if the time series is a complex vector, a one sided PSD is invalid: if (sides == 'default' and np.iscomplexobj(s)) or sides == 'twosided': sides = 'twosided' elif sides in ('default', 'onesided'): sides = 'onesided' sig_sl = [slice(None)] * len(s.shape) sig_sl.insert(-1, np.newaxis) # tapered.shape is (..., Kmax, N) tapered = s[sig_sl] * dpss # Find the direct spectral estimators S_k(f) for k tapered signals.. # don't normalize the periodograms by 1/N as normal.. since the taper # windows are orthonormal, they effectively scale the signal by 1/N # XXX: scipy fft is faster tapered_spectra = fftpack.fft(tapered) last_freq = N / 2 + 1 if sides == 'onesided' else N # degrees of freedom at each timeseries, at each freq nu = np.empty((s.shape[0], last_freq)) if adaptive: weights = np.empty(tapered_spectra.shape[:-1] + (last_freq,)) for i in xrange(s.shape[0]): weights[i], nu[i] = utils.adaptive_weights( tapered_spectra[i], eigs, sides=sides ) else: # let the weights simply be the square-root of the eigenvalues. # repeat these values across all n_chan channels of data n_chan = tapered.shape[0] weights = np.tile(np.sqrt(eigs), n_chan).reshape(n_chan, Kmax, 1) nu.fill(2 * Kmax) if jackknife: jk_var = np.empty_like(nu) for i in xrange(s.shape[0]): jk_var[i] = utils.jackknifed_sdf_variance( tapered_spectra[i], eigs, sides=sides, adaptive=adaptive ) # Compute the unbiased spectral estimator for S(f) as the sum of # the S_k(f) weighted by the function w_k(f)**2, all divided by the # sum of the w_k(f)**2 over k # 1st, roll the tapers axis forward tapered_spectra = np.rollaxis(tapered_spectra, 1, start=0) weights = np.rollaxis(weights, 1, start=0) sdf_est = mtm_cross_spectrum( tapered_spectra, tapered_spectra, weights, sides=sides ) if sides == 'onesided': freqs = np.linspace(0, Fs / 2, N / 2 + 1) else: freqs = np.linspace(0, Fs, N, endpoint=False) out_shape = rest_of_dims + (len(freqs),) sdf_est.shape = out_shape # XXX: always return nu and jk_var if jackknife: jk_var.shape = out_shape return freqs, sdf_est, jk_var else: nu.shape = out_shape return freqs, sdf_est, nu
def tapered_spectra(s, tapers, NFFT=None, low_bias=True): """ Compute the tapered spectra of the rows of s. Parameters ---------- s : ndarray, (n_arr, n_pts) An array whose rows are timeseries. tapers : ndarray or container Either the precomputed DPSS tapers, or the pair of parameters (NW, K) needed to compute K tapers of length n_pts. NFFT : int Number of FFT bins to compute low_bias : Boolean If compute DPSS, automatically select tapers corresponding to > 90% energy concentration. Returns ------- t_spectra : ndarray, shaped (n_arr, K, NFFT) The FFT of the tapered sequences in s. First dimension is squeezed out if n_arr is 1. eigvals : ndarray The eigenvalues are also returned if DPSS are calculated here. """ N = s.shape[-1] # XXX: don't allow NFFT < N -- not every implementation is so restrictive! if NFFT is None or NFFT < N: NFFT = N rest_of_dims = s.shape[:-1] M = int(np.product(rest_of_dims)) s = s.reshape(int(np.product(rest_of_dims)), N) # de-mean this sucker s = utils.remove_bias(s, axis=-1) if not isinstance(tapers, np.ndarray): # then tapers is (NW, K) args = (N,) + tuple(tapers) dpss, eigvals = dpss_windows(*args) if low_bias: keepers = (eigvals > 0.9) dpss = dpss[keepers] eigvals = eigvals[keepers] tapers = dpss else: eigvals = None K = tapers.shape[0] sig_sl = [slice(None)] * len(s.shape) sig_sl.insert(len(s.shape) - 1, np.newaxis) # tapered.shape is (M, Kmax, N) tapered = s[sig_sl] * tapers # compute the y_{i,k}(f) -- full FFT takes ~1.5x longer, but unpacking # results of real-valued FFT eats up memory t_spectra = fftpack.fft(tapered, n=NFFT, axis=-1) t_spectra.shape = rest_of_dims + (K, NFFT) if eigvals is None: return t_spectra return t_spectra, eigvals
def tapered_spectra(s, tapers, NFFT=None, low_bias=True): """ Compute the tapered spectra of the rows of s. Parameters ---------- s : ndarray, (n_arr, n_pts) An array whose rows are timeseries. tapers : ndarray or container Either the precomputed DPSS tapers, or the pair of parameters (NW, K) needed to compute K tapers of length n_pts. NFFT : int Number of FFT bins to compute low_bias : Boolean If compute DPSS, automatically select tapers corresponding to > 90% energy concentration. Returns ------- t_spectra : ndarray, shaped (n_arr, K, NFFT) The FFT of the tapered sequences in s. First dimension is squeezed out if n_arr is 1. eigvals : ndarray The eigenvalues are also returned if DPSS are calculated here. """ N = s.shape[-1] # XXX: don't allow NFFT < N -- not every implementation is so restrictive! if NFFT is None or NFFT < N: NFFT = N rest_of_dims = s.shape[:-1] M = int(np.product(rest_of_dims)) s = s.reshape(int(np.product(rest_of_dims)), N) # de-mean this sucker s = utils.remove_bias(s, axis=-1) if not isinstance(tapers, np.ndarray): # then tapers is (NW, K) args = (N, ) + tuple(tapers) dpss, eigvals = dpss_windows(*args) if low_bias: keepers = (eigvals > 0.9) dpss = dpss[keepers] eigvals = eigvals[keepers] tapers = dpss else: eigvals = None K = tapers.shape[0] sig_sl = [slice(None)] * len(s.shape) sig_sl.insert(len(s.shape) - 1, np.newaxis) # tapered.shape is (M, Kmax, N) tapered = s[sig_sl] * tapers # compute the y_{i,k}(f) -- full FFT takes ~1.5x longer, but unpacking # results of real-valued FFT eats up memory t_spectra = fftpack.fft(tapered, n=NFFT, axis=-1) t_spectra.shape = rest_of_dims + (K, NFFT) if eigvals is None: return t_spectra return t_spectra, eigvals
def test_debias(): x = np.arange(64).reshape(4,4,4) x0 = utils.remove_bias(x, axis=1) assert (x0.mean(axis=1)==0).all(), \ 'did not remove the bias from axis 1'