def test_dpss_windows(): "Are the eigenvalues representing spectral concentration near unity" # these values from Percival and Walden 1993 _, l = tsa.dpss_windows(31, 6, 4) unos = np.ones(4) npt.assert_array_almost_equal(l, unos) _, l = tsa.dpss_windows(31, 7, 4) npt.assert_array_almost_equal(l, unos) _, l = tsa.dpss_windows(31, 8, 4) npt.assert_array_almost_equal(l, unos) _, l = tsa.dpss_windows(31, 8, 4.2) npt.assert_array_almost_equal(l, unos)
def taper_segments(X, NW=3): """Apply taper functions to signal over all chunks Parameters ========== X: np.ndarray shape (N_CHUNKS, N_CHANNELS, N_SAMPLES) dtype float - Chunked input signal NW: int default 3 - Time bandwith parameter (copied from matlab function), the higher the bandwidth, the more tapers used. Returns ======= output: np.ndarray shape (N_CHUNKS, N_CHANNELS, N_TAPERS, N_SAMPLES) dtype float - Signal transformed by applying tapers. Uses N_TAPERS = 2 * NW - 1 """ _, _, window_size = X.shape n_tapers = 2 * NW - 1 # Get the taper functions as a matrix tapers, _ = ntalg.dpss_windows(window_size, NW, n_tapers) # Apply the dpss functions elementwise to the input signal return X[:, :, np.newaxis, :] * tapers
def test_dpss_windows(): """ Test a couple of funky corner cases of DPSS_windows """ N = 1024 NW = 0 # Setting NW to 0 triggers the weird corner case in which some of # the symmetric tapers have a negative average Kmax = 7 # But that's corrected by the algorithm: d, w = tsa.dpss_windows(1024, 0, 7) for this_d in d[0::2]: npt.assert_equal(this_d.sum(axis=-1) < 0, False) # Make sure we interpolate to the proper number of points d, w = tsa.dpss_windows(245411, 4, 8, 1000) npt.assert_equal(d.shape[-1], 245411)
def test_dpss_properties(): """ Test conventions of Slepian eigenvectors """ N = 2000 NW = 200 d, lam = tsa.dpss_windows(N, NW, 2*NW-2) # 2NW-2 lamdas should be all > 0.9 npt.assert_( (lam > 0.9).all(), 'Eigenvectors show poor spectral concentration' ) # test orthonomality err = np.linalg.norm(d.dot(d.T) - np.eye(2*NW-2), ord='fro') npt.assert_(err**2 < 1e-16, 'Eigenvectors not numerically orthonormal') # test positivity of even functions npt.assert_( (d[::2].sum(axis=1) > 0).all(), 'Even Slepian sequences should have positive DC' ) # test positive initial slope of odd functions # (this tests the sign of a linear slope) pk = np.argmax(np.abs(d[1::2, :N//2]), axis=1) t = True for p, f in zip(pk, d[1::2]): t = t and np.sum( np.arange(1,p+1) * f[:p] ) >= 0 npt.assert_(t, 'Odd Slepians should begin positive-going')
def estimate(self, signal, sample_rate, start_time, end_time, debug=False): slen = len(signal) #compute DPSS tapers for signals NW = max(1, int((slen / sample_rate) * self.bandwidth)) K = 2 * NW - 1 tapers, eigs = ntalg.dpss_windows(slen, NW, K) ntapers = len(tapers) if debug: print( '[MultiTaperSpectrumEstimator.estimate] slen=%d, NW=%d, K=%d, bandwidth=%0.1f, ntapers: %d' % (slen, NW, K, self.bandwidth, ntapers)) #compute a set of tapered signals s_tap = tapers * signal #compute the FFT of each tapered signal s_fft = fft(s_tap, axis=1) #throw away negative frequencies of the spectrum cspec_freq = fftfreq(slen, d=1.0 / sample_rate) nz = cspec_freq >= 0.0 s_fft = s_fft[:, nz] flen = nz.sum() cspec_freq = cspec_freq[nz] #print '(1)cspec_freq.shape=',cspec_freq.shape #print '(1)s_fft.shape=',s_fft.shape #determine the weights used to combine the tapered signals if self.adaptive and ntapers > 1: #compute the adaptive weights weights, weights_dof = ntutils.adaptive_weights( s_fft, eigs, sides='twosided', max_iter=self.max_adaptive_iter) else: weights = np.ones([ntapers, flen]) / float(ntapers) #print '(1)weights.shape=',weights.shape def make_spectrum(signal, signal_weights): denom = (signal_weights**2).sum(axis=0) return (np.abs(signal * signal_weights)**2).sum(axis=0) / denom if self.jackknife: #do leave-one-out cross validation to estimate the complex mean and standard deviation of the spectrum cspec_mean = np.zeros([flen], dtype='complex') for k in range(ntapers): index = range(ntapers) del index[k] #compute an estimate of the spectrum using all but the kth weight cspec_est = make_spectrum(s_fft[index, :], weights[index, :]) cspec_diff = cspec_est - cspec_mean #do an online update of the mean spectrum cspec_mean += cspec_diff / (k + 1) else: #compute the average complex spectrum weighted across tapers cspec_mean = make_spectrum(s_fft, weights) return cspec_freq, cspec_mean.squeeze()
def estimate(self, signal, sample_rate, start_time, end_time, debug=False): slen = len(signal) #compute DPSS tapers for signals NW = max(1, int((slen / sample_rate)*self.bandwidth)) K = 2*NW - 1 tapers, eigs = ntalg.dpss_windows(slen, NW, K) ntapers = len(tapers) if debug: print '[MultiTaperSpectrumEstimator.estimate] slen=%d, NW=%d, K=%d, bandwidth=%0.1f, ntapers: %d' % (slen, NW, K, self.bandwidth, ntapers) #compute a set of tapered signals s_tap = tapers * signal #compute the FFT of each tapered signal s_fft = fft(s_tap, axis=1) #throw away negative frequencies of the spectrum cspec_freq = fftfreq(slen, d=1.0/sample_rate) nz = cspec_freq >= 0.0 s_fft = s_fft[:, nz] flen = nz.sum() cspec_freq = cspec_freq[nz] #print '(1)cspec_freq.shape=',cspec_freq.shape #print '(1)s_fft.shape=',s_fft.shape #determine the weights used to combine the tapered signals if self.adaptive and ntapers > 1: #compute the adaptive weights weights,weights_dof = ntutils.adaptive_weights(s_fft, eigs, sides='twosided', max_iter=self.max_adaptive_iter) else: weights = np.ones([ntapers, flen]) / float(ntapers) #print '(1)weights.shape=',weights.shape def make_spectrum(signal, signal_weights): denom = (signal_weights**2).sum(axis=0) return (np.abs(signal * signal_weights)**2).sum(axis=0) / denom if self.jackknife: #do leave-one-out cross validation to estimate the complex mean and standard deviation of the spectrum cspec_mean = np.zeros([flen], dtype='complex') for k in range(ntapers): index = range(ntapers) del index[k] #compute an estimate of the spectrum using all but the kth weight cspec_est = make_spectrum(s_fft[index, :], weights[index, :]) cspec_diff = cspec_est - cspec_mean #do an online update of the mean spectrum cspec_mean += cspec_diff / (k+1) else: #compute the average complex spectrum weighted across tapers cspec_mean = make_spectrum(s_fft, weights) return cspec_freq,cspec_mean.squeeze()
def test_long_dpss_win(): """ Test that very long dpss windows can be generated (using interpolation)""" # This one is generated using interpolation: a1,e = tsa.dpss_windows(166800, 4, 8, interp_from=4096) # This one is calculated: a2,e = tsa.dpss_windows(166800, 4, 8) # They should be very similar: npt.assert_almost_equal(a1, a2, decimal=5) # They should both be very similar to the same one calculated in matlab # (using 'a = dpss(166800, 4, 8)'). test_dir_path = os.path.join(nitime.__path__[0], 'tests') matlab_long_dpss = np.load(os.path.join(test_dir_path, 'long_dpss_matlab.npy')) # We only have the first window to compare against: # Both for the interpolated case: npt.assert_almost_equal(a1[0], matlab_long_dpss, decimal=5) # As well as the calculated case: npt.assert_almost_equal(a1[0], matlab_long_dpss, decimal=5)
def test_dpss_windows(): """ Test a funky corner case of DPSS_windows """ N = 1024 NW = 0 # Setting NW to 0 triggers the weird corner case in which some of # the symmetric tapers have a negative average Kmax = 7 # But that's corrected by the algorithm: d,w=tsa.dpss_windows(1024, 0, 7) for this_d in d[0::2]: npt.assert_equal(this_d.sum(axis=-1)< 0, False)
def test_dpss_windows(): """ Test a funky corner case of DPSS_windows """ N = 1024 NW = 0 # Setting NW to 0 triggers the weird corner case in which some of # the symmetric tapers have a negative average Kmax = 7 # But that's corrected by the algorithm: d, w = tsa.dpss_windows(1024, 0, 7) for this_d in d[0::2]: npt.assert_equal(this_d.sum(axis=-1) < 0, False)
def test_long_dpss_win(): """ Test that very long dpss windows can be generated (using interpolation)""" # This one is generated using interpolation: a1, e = tsa.dpss_windows(166800, 4, 8, interp_from=4096) # This one is calculated: a2, e = tsa.dpss_windows(166800, 4, 8) # They should be very similar: npt.assert_almost_equal(a1, a2, decimal=5) # They should both be very similar to the same one calculated in matlab # (using 'a = dpss(166800, 4, 8)'). test_dir_path = os.path.join(nitime.__path__[0], 'tests') matlab_long_dpss = np.load( os.path.join(test_dir_path, 'long_dpss_matlab.npy')) # We only have the first window to compare against: # Both for the interpolated case: npt.assert_almost_equal(a1[0], matlab_long_dpss, decimal=5) # As well as the calculated case: npt.assert_almost_equal(a1[0], matlab_long_dpss, decimal=5)
def test_dpss_matlab(): """Do the dpss windows resemble the equivalent matlab result The variable b is read in from a text file generated by issuing: dpss(100,2) in matlab """ a, _ = tsa.dpss_windows(100, 2, 4) b = np.loadtxt(os.path.join(test_dir_path, 'dpss_matlab.txt')) npt.assert_almost_equal(a, b.T)
def test_mtm_cross_spectrum(): """ Test the multi-taper cross-spectral estimation. Based on the example in doc/examples/multi_taper_coh.py """ NW = 4 K = 2 * NW - 1 N = 2 ** 10 n_reps = 10 n_freqs = N tapers, eigs = tsa.dpss_windows(N, NW, 2 * NW - 1) est_psd = [] for k in xrange(n_reps): data,nz,alpha = utils.ar_generator(N=N) fgrid, hz = tsa.freq_response(1.0, a=np.r_[1, -alpha], n_freqs=n_freqs) # 'one-sided', so multiply by 2: psd = 2 * (hz * hz.conj()).real tdata = tapers * data tspectra = np.fft.fft(tdata) L = N / 2 + 1 sides = 'onesided' w, _ = utils.adaptive_weights(tspectra, eigs, sides=sides) sxx = tsa.mtm_cross_spectrum(tspectra, tspectra, w, sides=sides) est_psd.append(sxx) fxx = np.mean(est_psd, 0) psd_ratio = np.mean(fxx / psd) # This is a rather lenient test, making sure that the average ratio is 1 to # within an order of magnitude. That is, that they are equal on average: npt.assert_array_almost_equal(psd_ratio, 1, decimal=1) # Test raising of error in case the inputs don't make sense: npt.assert_raises(ValueError, tsa.mtm_cross_spectrum, tspectra,np.r_[tspectra, tspectra], (w, w))
def test_mtm_cross_spectrum(): """ Test the multi-taper cross-spectral estimation. Based on the example in doc/examples/multi_taper_coh.py """ NW = 4 K = 2 * NW - 1 N = 2**10 n_reps = 10 n_freqs = N tapers, eigs = tsa.dpss_windows(N, NW, 2 * NW - 1) est_psd = [] for k in range(n_reps): data, nz, alpha = utils.ar_generator(N=N) fgrid, hz = tsa.freq_response(1.0, a=np.r_[1, -alpha], n_freqs=n_freqs) # 'one-sided', so multiply by 2: psd = 2 * (hz * hz.conj()).real tdata = tapers * data tspectra = fftpack.fft(tdata) L = N / 2 + 1 sides = 'onesided' w, _ = utils.adaptive_weights(tspectra, eigs, sides=sides) sxx = tsa.mtm_cross_spectrum(tspectra, tspectra, w, sides=sides) est_psd.append(sxx) fxx = np.mean(est_psd, 0) psd_ratio = np.mean(fxx / psd) # This is a rather lenient test, making sure that the average ratio is 1 to # within an order of magnitude. That is, that they are equal on average: npt.assert_array_almost_equal(psd_ratio, 1, decimal=1) # Test raising of error in case the inputs don't make sense: npt.assert_raises(ValueError, tsa.mtm_cross_spectrum, tspectra, np.r_[tspectra, tspectra], (w, w))
def test_dpss_properties(): """ Test conventions of Slepian eigenvectors """ N = 2000 NW = 200 d, lam = tsa.dpss_windows(N, NW, 2 * NW - 2) # 2NW-2 lamdas should be all > 0.9 nt.assert_true((lam > 0.9).all(), 'Eigenvectors show poor spectral concentration') # test orthonomality err = np.linalg.norm(d.dot(d.T) - np.eye(2 * NW - 2), ord='fro') nt.assert_true(err**2 < 1e-16, 'Eigenvectors not numerically orthonormal') # test positivity of even functions nt.assert_true((d[::2].sum(axis=1) > 0).all(), 'Even Slepian sequences should have positive DC') # test positive initial slope of odd functions # (this tests the sign of a linear slope) pk = np.argmax(np.abs(d[1::2, :N / 2]), axis=1) t = True for p, f in zip(pk, d[1::2]): t = t and np.sum(np.arange(1, p + 1) * f[:p]) >= 0 nt.assert_true(t, 'Odd Slepians should begin positive-going')
def mtspecraw(x, params, verbose=None, bootstrapMode=False): """Multitaper Spectrum (of raw signal) Parameters ---------- x - Numpy array Input data numpy array (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- Normal mode: Tuple (mtspecraw, f) mtspecraw - multitapered spectrum f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: mtspecraw - Multitapered spectrum (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Raw Spectrum Estimation') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 nchans = x.shape[0] ntrials = x.shape[trialdim] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 nchans = 1 ntrials = x.shape[trialdim] logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the results Sraw = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) Sraw[k, :, :] = (abs(xw)**2).mean(axis=trialdim) # Average over tapers and squeeze to pretty shapes Sraw = Sraw.mean(axis=0) Sraw = Sraw[:, fInd].squeeze() if bootstrapMode: out = {} out['mtspecraw'] = Sraw out['f'] = f return out else: return (Sraw, f)
def mtpspec(x, params, verbose=None, bootstrapMode=False): """Multitaper Pairwise Power Spectral estimate Parameters ---------- x - Numpy Array Input data numpy array (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['Npairs'] - Number of pairs for pairwise analysis verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (pspec, f): pspec - Multitapered Pairwise Power estimate (channel x frequency) f - Frequency vector matching plv In bootstrap mode: Dictionary with following keys: pspec - Multitapered Pairwise Power estimate (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Pairwise Power Estimate') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result pspec = np.zeros((ntaps, nchans, nfft)) for ch in np.arange(0, nchans): for k, tap in enumerate(w): logger.debug('Running Channel # %d, taper #%d', ch, k) xw = sci.fft(tap * x, n=nfft, axis=timedim) npairs = params['Npairs'] trial_pairs = np.random.randint(0, ntrials, (npairs, 2)) # For unbiasedness, pairs should be made of independent trials! trial_pairs = trial_pairs[np.not_equal(trial_pairs[:, 0], trial_pairs[:, 1])] if(nchans == 1): xw_1 = xw[trial_pairs[:, 0]] xw_2 = xw[trial_pairs[:, 1]] pspec[k, ch, :] = np.real((xw_1*xw_2.conj()).mean(axis=0)) else: xw_1 = xw[ch, trial_pairs[:, 0], :] xw_2 = xw[ch, trial_pairs[:, 1], :] pspec[k, ch, :] = np.real((xw_1*xw_2.conj()).mean(axis=0)) pspec = pspec.mean(axis=0) pspec = pspec[:, fInd].squeeze() if bootstrapMode: out = {} out['pspec'] = pspec out['f'] = f return out else: return (pspec, f)
def mtspec(x, params, verbose=None, bootstrapMode=False): """Multitaper Spectrum and SNR estimate Parameters ---------- x - NumPy Array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['noisefloortype'] - (optional) 1: random phase, 0 (default): flip-phase on half the trials verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: (S, N ,f): Tuple S - Multitapered spectrum (channel x frequency) N - Noise floor estimate f - Frequency vector matching S and N In bootstrap mode: Dictionary with the following keys: mtspec - Multitapered spectrum (channel x frequency) mtspec_* - Noise floor estimate, where * is 'randomPhase' if params['noisefloortype'] == 1, and 'noiseFloorViaPhaseFlip' otherwise f - Frequency vector matching plv """ logger.info('Running Multitaper Spectrum and Noise-floor Estimation') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) S = np.zeros((ntaps, nchans, nfft)) N = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) S[k, :, :] = abs(xw.mean(axis=trialdim)) if ('noisefloortype' in params) and (params['noisefloortype'] == 1): randph = sci.rand(nchans, ntrials, nfft) * 2 * sci.pi N[k, :, :] = abs((xw*sci.exp(1j*randph)).mean(axis=trialdim)) noiseTag = 'noiseFloorViaRandomPhase' logger.info('using random phase for noise floor estimate') else: randsign = np.ones((nchans, ntrials, nfft)) # reflects fix to bootstrapmode parameter if bootstrapMode and 'bootstrapTrialsSelected' in params: flipTheseTrials = np.where( (params['bootstrapTrialsSelected'] % 2) == 0) else: flipTheseTrials = np.arange(0, ntrials, 2) randsign[:, flipTheseTrials, :] = -1 N[k, :, :] = abs((xw*(randsign.squeeze())).mean(axis=trialdim)) noiseTag = 'noiseFloorViaPhaseFlip' logger.info('flipping phase of half of the trials ' + 'for noise floor estimate') # Average over tapers and squeeze to pretty shapes S = S.mean(axis=0) N = N.mean(axis=0) S = S[:, fInd].squeeze() N = N[:, fInd].squeeze() if bootstrapMode: out = {} out['mtspec'] = S out['mtspec_' + noiseTag] = N out['f'] = f return out else: return (S, N, f)
def mtcspec(x, params, verbose=None, bootstrapMode=False): """Multitaper complex PCA and power spectral estimate Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - 1 for ITC, 0 for PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (cspec, f): cspec - Multitapered PLV estimate using cPCA f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: cspec - Multitapered PLV estimate using cPCA f - Frequency vector matching plv """ logger.info('Running Multitaper Complex PCA based power estimation!') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result cspec = np.zeros((ntaps, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) C = (xw.mean(axis=trialdim)).squeeze() for fi in np.arange(0, nfft): Csd = np.outer(C[:, fi], C[:, fi].conj()) vals = linalg.eigh(Csd, eigvals_only=True) cspec[k, fi] = vals[-1] / nchans # Average over tapers and squeeze to pretty shapes cspec = (cspec.mean(axis=0)).squeeze() cspec = cspec[fInd] if bootstrapMode: out = {} out['mtcspec'] = cspec out['f'] = f return out else: return (cspec, f)
def mtplv(x, params, verbose=None, bootstrapMode=False): """Multitaper Phase-Locking Value Parameters ---------- x - NumPy Array Input Data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - 1 for ITC, 0 for PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: (plvtap, f): Tuple plvtap - Multitapered phase-locking estimate (channel x frequency) In bootstrap mode: Dictionary with the following keys: mtplv - Multitapered phase-locking estimate (channel x frequency)i f - Frequency vector matching plv """ logger.info('Running Multitaper PLV Estimation') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 nchans = x.shape[0] ntrials = x.shape[trialdim] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry, The data should be a 2 or 3 dimensional array') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result plvtap = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) if(params['itc'] == 0): plvtap[k, :, :] = abs((xw/abs(xw)).mean(axis=trialdim))**2 else: plvtap[k, :, :] = ((abs(xw.mean(axis=trialdim))**2) / ((abs(xw) ** 2).mean(axis=trialdim))) plvtap = plvtap.mean(axis=0) plvtap = plvtap[:, fInd].squeeze() if bootstrapMode: out = {} out['mtplv'] = plvtap out['f'] = f else: return (plvtap, f) return out
""" We start by performing the detailed analysis, but note that a significant short-cut is presented below, so if you just want to know how to do this (without needing to understand the details), skip on down. We start by defining how many tapers will be used and calculate the values of the tapers and the associated eigenvalues of each taper: """ NW = 4 K = 2 * NW - 1 tapers, eigs = alg.dpss_windows(n_samples, NW, K) """ We multiply the data by the tapers and derive the fourier transform and the magnitude of the squared spectra (the power) for each tapered time-series: """ tdata = tapers[None, :, :] * pdata[:, None, :] tspectra = np.fft.fft(tdata) ## mag_sqr_spectra = np.abs(tspectra) ## np.power(mag_sqr_spectra, 2, mag_sqr_spectra)
def FKCoherence(self, st, inv, DT, linf, lsup, slim, win_len, sinc, method): def find_nearest(array, value): idx, val = min(enumerate(array), key=lambda x: abs(x[1] - value)) return idx, val sides = 'onesided' pi = math.pi smax = slim smin = -1 * smax Sx = np.arange(smin, smax, sinc)[np.newaxis] Sy = np.arange(smin, smax, sinc)[np.newaxis] nx = ny = len(Sx[0]) Sy = np.fliplr(Sy) #####Convert start from Greogorian to actual date############### Time = DT Time = Time - int(Time) d = date.fromordinal(int(DT)) date1 = d.isoformat() H = (Time * 24) H1 = int(H) # Horas minutes = (H - int(H)) * 60 minutes1 = int(minutes) seconds = (minutes - int(minutes)) * 60 H1 = str(H1).zfill(2) minutes1 = str(minutes1).zfill(2) seconds = "%.2f" % seconds seconds = str(seconds).zfill(2) DATE = date1 + "T" + str(H1) + minutes1 + seconds t1 = UTCDateTime(DATE) ########End conversion############################### st.trim(starttime=t1, endtime=t1 + win_len) st.sort() n = len(st) for i in range(n): coords = inv.get_coordinates(st[i].id) st[i].stats.coordinates = AttribDict({ 'latitude': coords['latitude'], 'elevation': coords['elevation'], 'longitude': coords['longitude'] }) coord = get_geometry(st, coordsys='lonlat', return_center=True) tr = st[0] win = len(tr.data) if (win % 2) == 0: nfft = win / 2 + 1 else: nfft = (win + 1) / 2 nr = st.count() # number of stations delta = st[0].stats.delta fs = 1 / delta fn = fs / 2 freq = np.arange(0, fn, fn / nfft) value1, freq1 = find_nearest(freq, linf) value2, freq2 = find_nearest(freq, lsup) df = value2 - value1 m = np.zeros((win, nr)) WW = np.hamming(int(win)) WW = np.transpose(WW) for i in range(nr): tr = st[i] if method == "FK": m[:, i] = (tr.data - np.mean(tr.data)) * WW else: m[:, i] = (tr.data - np.mean(tr.data)) pdata = np.transpose(m) #####Coherence###### NW = 2 # the time-bandwidth product##Buena seleccion de 2-3 K = 2 * NW - 1 tapers, eigs = alg.dpss_windows(win, NW, K) tdata = tapers[None, :, :] * pdata[:, None, :] tspectra = fftpack.fft(tdata) w = np.empty((nr, int(K), int(nfft))) for i in range(nr): w[i], _ = utils.adaptive_weights(tspectra[i], eigs, sides=sides) nseq = nr L = int(nfft) #csd_mat = np.zeros((nseq, nseq, L), 'D') #psd_mat = np.zeros((2, nseq, nseq, L), 'd') coh_mat = np.zeros((nseq, nseq, L), 'd') #coh_var = np.zeros_like(coh_mat) Cx = np.ones((nr, nr, df), dtype=np.complex128) if method == "MTP.COHERENCE": for i in range(nr): for j in range(nr): sxy = alg.mtm_cross_spectrum(tspectra[i], (tspectra[j]), (w[i], w[j]), sides='onesided') sxx = alg.mtm_cross_spectrum(tspectra[i], tspectra[i], w[i], sides='onesided') syy = alg.mtm_cross_spectrum(tspectra[j], tspectra[j], w[j], sides='onesided') s = sxy / np.sqrt((sxx * syy)) cxcohe = s[value1:value2] Cx[i, j, :] = cxcohe # Calculates Conventional FK-power if method == "FK": for i in range(nr): for j in range(nr): A = np.fft.rfft(m[:, i]) B = np.fft.rfft(m[:, j]) #Relative Power den = np.absolute(A) * np.absolute(np.conjugate(B)) out = (A * np.conjugate(B)) / den cxcohe = out[value1:value2] Cx[i, j, :] = cxcohe r = np.zeros((nr, 2), dtype=np.complex128) S = np.zeros((1, 2), dtype=np.complex128) Pow = np.zeros((len(Sx[0]), len(Sy[0]), df)) for n in range(nr): r[n, :] = coord[n][0:2] freq = freq[value1:value2] for i in range(ny): for j in range(nx): S[0, 0] = Sx[0][j] S[0, 1] = Sy[0][i] k = (S * r) K = np.sum(k, axis=1) n = 0 for f in freq: A = np.exp(-1j * 2 * pi * f * K) B = np.conjugate(np.transpose(A)) D = np.matmul(B, Cx[:, :, n]) / nr P = np.matmul(D, A) / nr Pow[i, j, n] = np.abs(P) n = n + 1 Pow = np.mean(Pow, axis=2) #Pow = Pow / len(freq) Pow = np.fliplr(Pow) x = y = np.linspace(smin, smax, nx) nn = len(x) maximum_power = np.where(Pow == np.amax(Pow)) Sxpow = (maximum_power[1] - nn / 2) * sinc Sypow = (maximum_power[0] - nn / 2) * sinc return Pow, Sxpow, Sypow, coord
def mtcpca_timeDomain(x, params, verbose=None, bootstrapMode=False): """Multitaper complex PCA and regular time-domain PCA and return time domain waveforms. Note of caution --------------- The cPCA method is not really suited to extract fast transient features of the time domain waveform. This is because, the frequency domain representation of any signal (when you think of it as random process) is interpretable only when the signal is stationary, i.e., in steady-state. Practically speaking, the process of transforming short epochs to the frequency domain necessarily involves smoothing in frequency. This leakage is minimized by tapering the original signal using DPSS windows, also known as Slepian sequences. The effect of this tapering would be present when going back to the time domain. Note that only a single taper is used here as combining tapers with different symmetries in the time- domain leads to funny cancellations. Also, for transient features, simple time-domain PCA is likely to perform better as the cPCA smoothes out transient features. Thus both regular time-domain PCA and cPCA outputs are returned. Note that for sign of the output is indeterminate (you may need to flip the output to match the polarity of signal channel responses) Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - Dictionary of parameter settings params['Fs'] - sampling rate verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (y_cpc, y_pc): 'y_cpc' - Multitapered cPCA estimate of time-domain waveform 'y_pc' - Regular time-domain PCA In bootstrap mode: Dictionary with the following keys: 'y_cpc' - Multitapered cPCA estimate of time-domain waveform 'y_pc' - Regular time-domain PCA """ logger.info('Running Multitaper Complex PCA to extract time waveform!') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) w, conc = dpss_windows(x.shape[timedim], 1, 1) w = w.squeeze() / w.max() cpc_freq = np.zeros(nfft, dtype=np.complex) cspec = np.zeros(nfft) xw = sci.fft(w * x, n=nfft, axis=timedim) C = (xw.mean(axis=trialdim)).squeeze() Cnorm = C / ((abs(xw).mean(axis=trialdim)).squeeze()) for fi in np.arange(0, nfft): Csd = np.outer(Cnorm[:, fi], Cnorm[:, fi].conj()) vals, vecs = linalg.eigh(Csd, eigvals_only=False) cspec[fi] = vals[-1] cwts = vecs[:, -1] / (np.abs(vecs[:, -1]).sum()) cpc_freq[fi] = (cwts.conjugate() * C[:, fi]).sum() # Filter through spectrum, do ifft. cscale = cspec**0.5 cscale = cscale / cscale.max() # Maxgain of filter = 1 y_cpc = sci.ifft(cpc_freq * cscale)[:x.shape[timedim]] # Do time domain PCA x_ave = x.mean(axis=trialdim) C_td = np.cov(x_ave) vals, vecs = linalg.eigh(C_td, eigvals_only=False) y_pc = np.dot(vecs[:, -1].T, x_ave) / (vecs[:, -1].sum()) if bootstrapMode: out = {} out['y_cpc'] = y_cpc out['y_pc'] = y_pc return out else: return (y_cpc, y_pc)
def mtcspec(x, params, verbose=None, bootstrapMode=False): """Multitaper complex PCA and power spectral estimate Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - 1 for ITC, 0 for PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (cspec, f): cspec - Multitapered PLV estimate using cPCA f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: cspec - Multitapered PLV estimate using cPCA f - Frequency vector matching plv """ logger.info('Running Multitaper Complex PCA based power estimation!') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result cspec = np.zeros((ntaps, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) C = (xw.mean(axis=trialdim)).squeeze() for fi in np.arange(0, nfft): Csd = np.outer(C[:, fi], C[:, fi].conj()) vals = linalg.eigh(Csd, eigvals_only=True) cspec[k, fi] = vals[-1] / nchans # Average over tapers and squeeze to pretty shapes cspec = (cspec.mean(axis=0)).squeeze() cspec = cspec[fInd] if bootstrapMode: out = {} out['mtcspec'] = cspec out['f'] = f return out else: return (cspec, f)
def mtphase(x, params, verbose=None, bootstrapMode=False): """Multitaper phase estimation Parameters ---------- x - NumPy Array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns: ------- In normal mode: (Ph, f): Tuple Ph - Multitapered phase spectrum (channel x frequency) f - Frequency vector matching S and N In bootstrap mode: Dictionary with the following keys: Ph - Multitapered phase spectrum (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Spectrum and Noise-floor Estimation') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) Ph = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) Ph[k, :, :] = np.angle(xw.mean(axis=trialdim)) # Average over tapers and squeeze to pretty shapes Ph = Ph[:, :, fInd].mean(axis=0).squeeze() if bootstrapMode: out = {} out['mtphase'] = Ph out['f'] = f return out else: return (Ph, f)
def mtspec(x, params, verbose=None, bootstrapMode=False): """Multitaper Spectrum and SNR estimate Parameters ---------- x - NumPy Array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['noisefloortype'] - (optional) 1: random phase, 0 (default): flip-phase on half the trials verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: (S, N ,f): Tuple S - Multitapered spectrum (channel x frequency) N - Noise floor estimate f - Frequency vector matching S and N In bootstrap mode: Dictionary with the following keys: mtspec - Multitapered spectrum (channel x frequency) mtspec_* - Noise floor estimate, where * is 'randomPhase' if params['noisefloortype'] == 1, and 'noiseFloorViaPhaseFlip' otherwise f - Frequency vector matching plv """ logger.info('Running Multitaper Spectrum and Noise-floor Estimation') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) S = np.zeros((ntaps, nchans, nfft)) N = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) S[k, :, :] = abs(xw.mean(axis=trialdim)) if ('noisefloortype' in params) and (params['noisefloortype'] == 1): randph = sci.rand(nchans, ntrials, nfft) * 2 * sci.pi N[k, :, :] = abs((xw * sci.exp(1j * randph)).mean(axis=trialdim)) noiseTag = 'noiseFloorViaRandomPhase' logger.info('using random phase for noise floor estimate') else: randsign = np.ones((nchans, ntrials, nfft)) # reflects fix to bootstrapmode parameter if bootstrapMode and 'bootstrapTrialsSelected' in params: flipTheseTrials = np.where((params['bootstrapTrialsSelected'] % 2) == 0) else: flipTheseTrials = np.arange(0, ntrials, 2) randsign[:, flipTheseTrials, :] = -1 N[k, :, :] = abs((xw * (randsign.squeeze())).mean(axis=trialdim)) noiseTag = 'noiseFloorViaPhaseFlip' logger.info('flipping phase of half of the trials ' + 'for noise floor estimate') # Average over tapers and squeeze to pretty shapes S = S.mean(axis=0) N = N.mean(axis=0) S = S[:, fInd].squeeze() N = N[:, fInd].squeeze() if bootstrapMode: out = {} out['mtspec'] = S out['mtspec_' + noiseTag] = N out['f'] = f return out else: return (S, N, f)
def _mtcpca_complete(x, params, verbose=None, bootstrapMode=False): """ Internal convenience function to obtain plv and spectrum with cpca and multitaper. Equivalent to calling: spectral.mtcpca(data, params, ...) spectral.mtcspec(data, params, ...) With the exception that this function returns a dictionary for S + N, each of which have keys "plv_*" and "spectrum_*", where * is "normalPhase" or "noiseFloorViaPhaseFlip". Gets power spectra and plv on the same set of data using multitaper and complex PCA. Returns a noise floor esimate of each by running the same computations on the original data, as well as the original data with the phase of half of the trials flipped. For a large number of trials, the spectra of the data and the half-trials-phase-flipped data should be similar, while the PLV values for the half-trials-flipped data should be hovering near the PLV value of off-frequency components in the original data. Primarily useful when debugging, bootstrapping, or when using scripts that for some reason randomizes data in between calls to mtcpca and mtcspec. Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - dictionary. Must contain the following fields: params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - If True, normalize after mean like ITC instead of PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (S, N, f) Where S and N are data for signal and for noise floor, respectively, each as a dictionary with the following keys: mtcpcaSpectrum - Multitapered power spectral estimate using cPCA mtcpcaPLV- Multitapered PLV using cPCA f - frequency vector In bootstrap mode: dictionary with keys: mtcpcaSpectrum_* - Multitapered power spectral estimate using cPCA mtcpcaPLV_*- Multitapered PLV using cPCA f - frequency vector where * in the above is the type of noise floor """ out = {} logger.info('Running Multitaper Complex PCA based ' + 'plv and power estimation.') x = x.squeeze() if len(x.shape) == 3: timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) plv = np.zeros((ntaps, len(f))) cspec = np.zeros((ntaps, len(f))) phaseTypes = list(['normalPhase', 'noiseFloorViaPhaseFlip']) for thisType in phaseTypes: if thisType == 'noiseFloorViaPhaseFlip': # flip the phase of every other trial phaseFlipper = np.ones(x.shape) # important change: when noise floor is computed, it will # only select trials that were originally labeled as even-numbered # this way, bootstrapped noise floors are where they would be # expected to be rather than artificially low if bootstrapMode and 'bootstrapTrialsSelected' in params: flipTheseTrials = np.where((params['bootstrapTrialsSelected'] % 2) == 0) else: flipTheseTrials = np.arange(0, x.shape[1], 2) phaseFlipper[:, flipTheseTrials, :] = -1.0 elif thisType == 'normalPhase': phaseFlipper = 1.0 useData = x * phaseFlipper for k, tap in enumerate(w): logger.info(thisType + 'Doing Taper #%d', k) xw = sci.fft((tap * useData), n=nfft, axis=timedim) # no point keeping everything if fpass was already set xw = xw[:, :, fInd] C = xw.mean(axis=trialdim).squeeze() if params['itc']: plvC = (xw.mean(axis=trialdim) / (abs(xw).mean(axis=trialdim))).squeeze() else: plvC = (xw / abs(xw)).mean(axis=trialdim).squeeze() for fi in np.arange(0, len(f)): powerCsd = np.outer(C[:, fi], C[:, fi].conj()) powerEigenvals = linalg.eigh(powerCsd, eigvals_only=True) cspec[k, fi] = powerEigenvals[-1] / nchans plvCsd = np.outer(plvC[:, fi], plvC[:, fi].conj()) plvEigenvals = linalg.eigh(plvCsd, eigvals_only=True) plv[k, fi] = plvEigenvals[-1] / nchans # Avage over tapers and squeeze to pretty shapes mtcpcaSpectrum = (cspec.mean(axis=0)).squeeze() mtcpcaPhaseLockingValue = (plv.mean(axis=0)).squeeze() if mtcpcaSpectrum.shape != mtcpcaPhaseLockingValue.shape: logger.error('internal error: shape mismatch between PLV ' + ' and magnitude result arrays') out['mtcpcaSpectrum_' + thisType] = mtcpcaSpectrum out['mtcpcaPLV_' + thisType] = mtcpcaPhaseLockingValue if bootstrapMode: out['f'] = f return out else: S = {} S['spectrum'] = ['mtcpcaSpectrum_normalPhase'] S['plv'] = ['mtcpcaPLV_normalPhase'] N = {} N['spectrum'] = out['mtcpcaSpectrum_noiseFloorViaPhaseFlip'] N['plv'] = out['mtcpcaPLV_noiseFloorViaPhaseFlip'] return (S, N, f)
def mtpspec(x, params, verbose=None, bootstrapMode=False): """Multitaper Pairwise Power Spectral estimate Parameters ---------- x - Numpy Array Input data numpy array (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['Npairs'] - Number of pairs for pairwise analysis verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (pspec, f): pspec - Multitapered Pairwise Power estimate (channel x frequency) f - Frequency vector matching plv In bootstrap mode: Dictionary with following keys: pspec - Multitapered Pairwise Power estimate (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Pairwise Power Estimate') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result pspec = np.zeros((ntaps, nchans, nfft)) for ch in np.arange(0, nchans): for k, tap in enumerate(w): logger.debug('Running Channel # %d, taper #%d', ch, k) xw = sci.fft(tap * x, n=nfft, axis=timedim) npairs = params['Npairs'] trial_pairs = np.random.randint(0, ntrials, (npairs, 2)) # For unbiasedness, pairs should be made of independent trials! trial_pairs = trial_pairs[np.not_equal(trial_pairs[:, 0], trial_pairs[:, 1])] if (nchans == 1): xw_1 = xw[trial_pairs[:, 0]] xw_2 = xw[trial_pairs[:, 1]] pspec[k, ch, :] = np.real((xw_1 * xw_2.conj()).mean(axis=0)) else: xw_1 = xw[ch, trial_pairs[:, 0], :] xw_2 = xw[ch, trial_pairs[:, 1], :] pspec[k, ch, :] = np.real((xw_1 * xw_2.conj()).mean(axis=0)) pspec = pspec.mean(axis=0) pspec = pspec[:, fInd].squeeze() if bootstrapMode: out = {} out['pspec'] = pspec out['f'] = f return out else: return (pspec, f)
def eigs(self): return tsa.dpss_windows(self.input.shape[-1], self.NW, 2 * self.NW - 1)[1]
def mtspecraw(x, params, verbose=None, bootstrapMode=False): """Multitaper Spectrum (of raw signal) Parameters ---------- x - Numpy array Input data numpy array (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- Normal mode: Tuple (mtspecraw, f) mtspecraw - multitapered spectrum f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: mtspecraw - Multitapered spectrum (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Raw Spectrum Estimation') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 nchans = x.shape[0] ntrials = x.shape[trialdim] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 nchans = 1 ntrials = x.shape[trialdim] logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the results Sraw = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) Sraw[k, :, :] = (abs(xw)**2).mean(axis=trialdim) # Average over tapers and squeeze to pretty shapes Sraw = Sraw.mean(axis=0) Sraw = Sraw[:, fInd].squeeze() if bootstrapMode: out = {} out['mtspecraw'] = Sraw out['f'] = f return out else: return (Sraw, f)
def compute_coherence_original(s1, s2, sample_rate, bandwidth, jackknife=False, tanh_transform=False): """ An implementation of computing the coherence. Don't use this. """ minlen = min(len(s1), len(s2)) if s1.shape != s2.shape: s1 = s1[:minlen] s2 = s2[:minlen] window_length = len(s1) / sample_rate window_length_bins = int(window_length * sample_rate) #compute DPSS tapers for signals NW = int(window_length * bandwidth) K = 2 * NW - 1 print 'compute_coherence: NW=%d, K=%d' % (NW, K) tapers, eigs = ntalg.dpss_windows(window_length_bins, NW, K) njn = len(eigs) jn_indices = [range(njn)] #compute jackknife indices if jackknife: jn_indices = list() for i in range(len(eigs)): jn = range(len(eigs)) jn.remove(i) jn_indices.append(jn) #taper the signals s1_tap = tapers * s1 s2_tap = tapers * s2 #compute fft of tapered signals s1_fft = fftpack.fft(s1_tap, axis=1) s2_fft = fftpack.fft(s2_tap, axis=1) #compute adaptive weights for each taper w1, nu1 = ntutils.adaptive_weights(s1_fft, eigs, sides='onesided') w2, nu2 = ntutils.adaptive_weights(s2_fft, eigs, sides='onesided') coherence_estimates = list() for jn in jn_indices: #compute cross spectral density sxy = ntalg.mtm_cross_spectrum(s1_fft[jn, :], s2_fft[jn, :], (w1[jn], w2[jn]), sides='onesided') #compute individual power spectrums sxx = ntalg.mtm_cross_spectrum(s1_fft[jn, :], s1_fft[jn, :], w1[jn], sides='onesided') syy = ntalg.mtm_cross_spectrum(s2_fft[jn, :], s2_fft[jn, :], w2[jn], sides='onesided') #compute coherence coherence = np.abs(sxy)**2 / (sxx * syy) coherence_estimates.append(coherence) #compute variance coherence_estimates = np.array(coherence_estimates) coherence_variance = np.zeros([coherence_estimates.shape[1]]) coherence_mean = coherence_estimates[0] if jackknife: coherence_mean = coherence_estimates.mean(axis=0) #mean subtract and square cv = np.sum((coherence_estimates - coherence_mean)**2, axis=0) coherence_variance[:] = (1.0 - 1.0 / njn) * cv #compute frequencies sampint = 1.0 / sample_rate L = minlen / 2 + 1 freq = np.linspace(0, 1 / (2 * sampint), L) #compute upper and lower bounds cmean = coherence_mean coherence_lower = cmean - 2 * np.sqrt(coherence_variance) coherence_upper = cmean + 2 * np.sqrt(coherence_variance) cdata = CoherenceData() cdata.coherence = coherence_mean cdata.coherence_lower = coherence_lower cdata.coherence_upper = coherence_upper cdata.frequency = freq cdata.sample_rate = sample_rate return cdata
def mtppc(x, params, verbose=None, bootstrapMode=False): """Multitaper Pairwise Phase Consistency Parameters ---------- x - Numpy array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['Npairs'] - Number of pairs for PPC analysis params['itc'] - If True, normalize after mean like ITC instead of PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (ppc, f): ppc - Multitapered PPC estimate (channel x frequency) f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: mtppc - Multitapered PPC estimate (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Pairwise Phase Consistency Estimate') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the result ppc = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) npairs = params['nPairs'] trial_pairs = np.random.randint(0, ntrials, (npairs, 2)) if(nchans == 1): if(not params['itc']): xw_1 = xw[trial_pairs[:, 0], :]/abs(xw[trial_pairs[:, 0], :]) xw_2 = xw[trial_pairs[:, 1], :]/abs(xw[trial_pairs[:, 1], :]) ppc[k, :, :] = np.real((xw_1*xw_2.conj()).mean(axis=trialdim)) else: xw_1 = xw[trial_pairs[:, 0]] xw_2 = xw[trial_pairs[:, 1]] ppc_unnorm = np.real((xw_1 * xw_2.conj()).mean(axis=trialdim)) ppc[k, :, :] = (ppc_unnorm / (abs(xw_1).mean(trialdim) * abs(xw_2).mean(trialdim))) else: if(not params['itc']): xw_1 = (xw[:, trial_pairs[:, 0], :] / abs(xw[:, trial_pairs[:, 0], :])) xw_2 = (xw[:, trial_pairs[:, 1], :] / abs(xw[:, trial_pairs[:, 1], :])) ppc[k, :, :] = np.real((xw_1*xw_2.conj()). mean(axis=trialdim)) else: xw_1 = xw[:, trial_pairs[:, 0], :] xw_2 = xw[:, trial_pairs[:, 1], :] ppc_unnorm = np.real((xw_1 * xw_2.conj()).mean(axis=trialdim)) ppc[k, :, :] = (ppc_unnorm / (abs(xw_1).mean(trialdim) * abs(xw_2).mean(trialdim))) ppc = ppc.mean(axis=0) ppc = ppc[:, fInd].squeeze() if bootstrapMode: out = {} out['mtppc'] = ppc out['f'] = f return out else: return (ppc, f)
""" (b, a) = signal.butter(3, W, btype='lowpass') slp = signal.lfilter(b, a, s) """ Modulate both signals away from baseband. """ s_mod = s * np.cos(2 * np.pi * np.arange(N) * float(200) / N) slp_mod = slp * np.cos(2 * np.pi * np.arange(N) * float(200) / N) fm = int(np.round(float(200) * nfft / N)) """ Create Slepians with the desired bandpass resolution (2W). """ (dpss, eigs) = nt_alg.dpss_windows(N, NW, 2 * NW) keep = eigs > 0.9 dpss = dpss[keep] eigs = eigs[keep] """ Test 1 ------ We'll compare multitaper baseband power estimation with regular Hilbert transform method under actual narrowband conditions. """ # MT method xk = nt_alg.tapered_spectra(slp_mod, dpss, NFFT=nfft) mtm_bband = np.sum(2 * (xk[:, fm] * np.sqrt(eigs))[:, None] * dpss, axis=0)
def multitaper_cross_spectral_estimates(traces, delta, NW, compute_confidence_intervals=True, confidence_interval=0.95): # Define the number of tapers, their values and associated eigenvalues: npts = len(traces[0]) K = 2 * NW - 1 tapers, eigs = alg.dpss_windows(npts, NW, K) # Multiply the data by the tapers, calculate the Fourier transform # We multiply the data by the tapers and derive the fourier transform and the # magnitude of the squared spectra (the power) for each tapered time-series: tdata = tapers[None, :, :] * traces[:, None, :] tspectra = fftpack.fft(tdata) # The coherency for real sequences is symmetric so only half # the spectrum if required L = npts // 2 + 1 if L < npts: freqs = np.linspace(0, 1. / (2. * delta), L) else: freqs = np.linspace(0, 1. / delta, L, endpoint=False) # Estimate adaptive weighting of the tapers, based on the data # (see Thomsen, 2007; 10.1109/MSP.2007.4286561) w = np.empty((2, K, L)) for i in range(2): w[i], _ = utils.adaptive_weights(tspectra[i], eigs, sides='onesided') # Calculate the multi-tapered cross spectrum # and the PSDs for the two time-series: sxy = alg.mtm_cross_spectrum(tspectra[0], tspectra[1], (w[0], w[1]), sides='onesided') sxx = alg.mtm_cross_spectrum(tspectra[0], tspectra[0], w[0], sides='onesided') syy = alg.mtm_cross_spectrum(tspectra[1], tspectra[1], w[1], sides='onesided') Z = sxy / syy spectral_estimates = {} spectral_estimates['frequencies'] = freqs spectral_estimates['magnitude_squared_coherence'] = np.abs(sxy)**2 / (sxx * syy) spectral_estimates['transfer_function'] = Z # Transfer function spectral_estimates['admittance'] = np.real(Z) spectral_estimates['gain'] = np.absolute(Z) spectral_estimates['phase'] = np.angle(Z, deg=True) # Estimate confidence intervals if compute_confidence_intervals: spectral_estimates['confidence_bounds'] = {} c_bnds = [ 0.5 - confidence_interval / 2., 0.5 + confidence_interval / 2. ] variances = jackknifed_variances(tspectra[0], tspectra[1], eigs, adaptive=True) spectral_estimates['confidence_bounds']['admittance'] = [ spectral_estimates['admittance'] + dist.t.ppf(c_bnds[0], K - 1) * np.sqrt(variances['admittance']), spectral_estimates['admittance'] + dist.t.ppf(c_bnds[1], K - 1) * np.sqrt(variances['admittance']) ] spectral_estimates['confidence_bounds']['gain'] = [ spectral_estimates['gain'] + dist.t.ppf(c_bnds[0], K - 1) * np.sqrt(variances['gain']), spectral_estimates['gain'] + dist.t.ppf(c_bnds[1], K - 1) * np.sqrt(variances['gain']) ] spectral_estimates['confidence_bounds']['phase'] = [ spectral_estimates['phase'] + dist.t.ppf(c_bnds[0], K - 1) * np.sqrt(variances['phase']), spectral_estimates['phase'] + dist.t.ppf(c_bnds[1], K - 1) * np.sqrt(variances['phase']) ] spectral_estimates['confidence_bounds'][ 'magnitude_squared_coherence'] = [ spectral_estimates['magnitude_squared_coherence'] + dist.t.ppf(c_bnds[0], K - 1) * np.sqrt(variances['magnitude_squared_coherence']), spectral_estimates['magnitude_squared_coherence'] + dist.t.ppf(c_bnds[1], K - 1) * np.sqrt(variances['magnitude_squared_coherence']) ] return spectral_estimates
def mtcpca_timeDomain(x, params, verbose=None, bootstrapMode=False): """Multitaper complex PCA and regular time-domain PCA and return time domain waveforms. Note of caution --------------- The cPCA method is not really suited to extract fast transient features of the time domain waveform. This is because, the frequency domain representation of any signal (when you think of it as random process) is interpretable only when the signal is stationary, i.e., in steady-state. Practically speaking, the process of transforming short epochs to the frequency domain necessarily involves smoothing in frequency. This leakage is minimized by tapering the original signal using DPSS windows, also known as Slepian sequences. The effect of this tapering would be present when going back to the time domain. Note that only a single taper is used here as combining tapers with different symmetries in the time- domain leads to funny cancellations. Also, for transient features, simple time-domain PCA is likely to perform better as the cPCA smoothes out transient features. Thus both regular time-domain PCA and cPCA outputs are returned. Note that for sign of the output is indeterminate (you may need to flip the output to match the polarity of signal channel responses) Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - Dictionary of parameter settings params['Fs'] - sampling rate verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (y_cpc, y_pc): 'y_cpc' - Multitapered cPCA estimate of time-domain waveform 'y_pc' - Regular time-domain PCA In bootstrap mode: Dictionary with the following keys: 'y_cpc' - Multitapered cPCA estimate of time-domain waveform 'y_pc' - Regular time-domain PCA """ logger.info('Running Multitaper Complex PCA to extract time waveform!') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) w, conc = dpss_windows(x.shape[timedim], 1, 1) w = w.squeeze() / w.max() cpc_freq = np.zeros(nfft, dtype=np.complex) cspec = np.zeros(nfft) xw = sci.fft(w * x, n=nfft, axis=timedim) C = (xw.mean(axis=trialdim)).squeeze() Cnorm = C / ((abs(xw).mean(axis=trialdim)).squeeze()) for fi in np.arange(0, nfft): Csd = np.outer(Cnorm[:, fi], Cnorm[:, fi].conj()) vals, vecs = linalg.eigh(Csd, eigvals_only=False) cspec[fi] = vals[-1] cwts = vecs[:, -1] / (np.abs(vecs[:, -1]).sum()) cpc_freq[fi] = (cwts.conjugate() * C[:, fi]).sum() # Filter through spectrum, do ifft. cscale = cspec ** 0.5 cscale = cscale / cscale.max() # Maxgain of filter = 1 y_cpc = sci.ifft(cpc_freq * cscale)[:x.shape[timedim]] # Do time domain PCA x_ave = x.mean(axis=trialdim) C_td = np.cov(x_ave) vals, vecs = linalg.eigh(C_td, eigvals_only=False) y_pc = np.dot(vecs[:, -1].T, x_ave) / (vecs[:, -1].sum()) if bootstrapMode: out = {} out['y_cpc'] = y_cpc out['y_pc'] = y_pc return out else: return (y_cpc, y_pc)
def compute_coherence_original(s1, s2, sample_rate, bandwidth, jackknife=False, tanh_transform=False): """ An implementation of computing the coherence. Don't use this. """ minlen = min(len(s1), len(s2)) if s1.shape != s2.shape: s1 = s1[:minlen] s2 = s2[:minlen] window_length = len(s1) / sample_rate window_length_bins = int(window_length * sample_rate) #compute DPSS tapers for signals NW = int(window_length*bandwidth) K = 2*NW - 1 print 'compute_coherence: NW=%d, K=%d' % (NW, K) tapers,eigs = ntalg.dpss_windows(window_length_bins, NW, K) njn = len(eigs) jn_indices = [range(njn)] #compute jackknife indices if jackknife: jn_indices = list() for i in range(len(eigs)): jn = range(len(eigs)) jn.remove(i) jn_indices.append(jn) #taper the signals s1_tap = tapers * s1 s2_tap = tapers * s2 #compute fft of tapered signals s1_fft = fftpack.fft(s1_tap, axis=1) s2_fft = fftpack.fft(s2_tap, axis=1) #compute adaptive weights for each taper w1,nu1 = ntutils.adaptive_weights(s1_fft, eigs, sides='onesided') w2,nu2 = ntutils.adaptive_weights(s2_fft, eigs, sides='onesided') coherence_estimates = list() for jn in jn_indices: #compute cross spectral density sxy = ntalg.mtm_cross_spectrum(s1_fft[jn, :], s2_fft[jn, :], (w1[jn], w2[jn]), sides='onesided') #compute individual power spectrums sxx = ntalg.mtm_cross_spectrum(s1_fft[jn, :], s1_fft[jn, :], w1[jn], sides='onesided') syy = ntalg.mtm_cross_spectrum(s2_fft[jn, :], s2_fft[jn, :], w2[jn], sides='onesided') #compute coherence coherence = np.abs(sxy)**2 / (sxx * syy) coherence_estimates.append(coherence) #compute variance coherence_estimates = np.array(coherence_estimates) coherence_variance = np.zeros([coherence_estimates.shape[1]]) coherence_mean = coherence_estimates[0] if jackknife: coherence_mean = coherence_estimates.mean(axis=0) #mean subtract and square cv = np.sum((coherence_estimates - coherence_mean)**2, axis=0) coherence_variance[:] = (1.0 - 1.0/njn) * cv #compute frequencies sampint = 1.0 / sample_rate L = minlen / 2 + 1 freq = np.linspace(0, 1 / (2 * sampint), L) #compute upper and lower bounds cmean = coherence_mean coherence_lower = cmean - 2*np.sqrt(coherence_variance) coherence_upper = cmean + 2*np.sqrt(coherence_variance) cdata = CoherenceData() cdata.coherence = coherence_mean cdata.coherence_lower = coherence_lower cdata.coherence_upper = coherence_upper cdata.frequency = freq cdata.sample_rate = sample_rate return cdata
def mtphase(x, params, verbose=None, bootstrapMode=False): """Multitaper phase estimation Parameters ---------- x - NumPy Array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns: ------- In normal mode: (Ph, f): Tuple Ph - Multitapered phase spectrum (channel x frequency) f - Frequency vector matching S and N In bootstrap mode: Dictionary with the following keys: Ph - Multitapered phase spectrum (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Spectrum and Noise-floor Estimation') x = x.squeeze() if(len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif(len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) Ph = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) Ph[k, :, :] = np.angle(xw.mean(axis=trialdim)) # Average over tapers and squeeze to pretty shapes Ph = Ph[:, :, fInd].mean(axis=0).squeeze() if bootstrapMode: out = {} out['mtphase'] = Ph out['f'] = f return out else: return (Ph, f)
def compute_mtcoherence(s1, s2, sample_rate, window_size, bandwidth=15.0, chunk_len_percentage_tolerance=0.30, frequency_cutoff=None, tanh_transform=False, debug=False): """ Computing the multi-taper coherence between signals s1 and s2. To do so, the signals are broken up into segments of length specified by window_size. Then the multi-taper coherence is computed between each segment. The mean coherence is computed across segments, and an estimate of the coherence variance is computed across segments. sample_rate: the sample rate in Hz of s1 and s2 window_size: size of the segments in seconds bandwidth: related to the # of tapers used to compute the spectral density. The higher the bandwidth, the more tapers. chunk_len_percentage_tolerance: If there are leftover segments whose lengths are less than window_size, use them if they comprise at least the fraction of window_size specified by chunk_len_percentage_tolerance frequency_cutoff: the frequency at which to cut off the coherence when computing the normal mutual information tanh_transform: whether to transform the coherences when computing the upper and lower bounds, supposedly improves the estimate of variance. """ minlen = min(len(s1), len(s2)) if s1.shape != s2.shape: s1 = s1[:minlen] s2 = s2[:minlen] sample_length_bins = min(len(s1), int(window_size * sample_rate)) #compute DPSS tapers for signals NW = int(window_size*bandwidth) K = 2*NW - 1 #print 'compute_coherence: NW=%d, K=%d' % (NW, K) tapers,eigs = ntalg.dpss_windows(sample_length_bins, NW, K) if debug: print '[compute_coherence] bandwidth=%0.1f, # of tapers: %d' % (bandwidth, len(eigs)) #break signal into chunks and estimate coherence for each chunk nchunks = int(np.floor(len(s1) / float(sample_length_bins))) nleft = len(s1) % sample_length_bins if nleft > 0: nchunks += 1 #print 'sample_length_bins=%d, # of chunks:%d, # samples in last chunk: %d' % (sample_length_bins, nchunks, nleft) coherence_estimates = list() for k in range(nchunks): s = k*sample_length_bins e = min(len(s1), s + sample_length_bins) chunk_len = e - s chunk_percentage = chunk_len / float(sample_length_bins) if chunk_percentage < chunk_len_percentage_tolerance: #don't compute coherence for a chunk whose length is less than a certain percentage of sample_length_bins continue s1_chunk = np.zeros([sample_length_bins]) s2_chunk = np.zeros([sample_length_bins]) s1_chunk[:chunk_len] = s1[s:e] s2_chunk[:chunk_len] = s2[s:e] #taper the signals s1_tap = tapers * s1_chunk s2_tap = tapers * s2_chunk #compute fft of tapered signals s1_fft = fftpack.fft(s1_tap, axis=1) s2_fft = fftpack.fft(s2_tap, axis=1) #compute adaptive weights for each taper w1,nu1 = ntutils.adaptive_weights(s1_fft, eigs, sides='onesided') w2,nu2 = ntutils.adaptive_weights(s2_fft, eigs, sides='onesided') #compute cross spectral density sxy = ntalg.mtm_cross_spectrum(s1_fft, s2_fft, (w1, w2), sides='onesided') #compute individual power spectrums sxx = ntalg.mtm_cross_spectrum(s1_fft, s1_fft, w1, sides='onesided') syy = ntalg.mtm_cross_spectrum(s2_fft, s2_fft, w2, sides='onesided') #compute coherence coherence = np.abs(sxy)**2 / (sxx * syy) coherence_estimates.append(coherence) #compute variance coherence_estimates = np.array(coherence_estimates) if tanh_transform: coherence_estimates = np.arctanh(coherence_estimates) coherence_variance = np.zeros([coherence_estimates.shape[1]]) coherence_mean = coherence_estimates.mean(axis=0) #mean subtract and square cv = np.sum((coherence_estimates - coherence_mean)**2, axis=0) coherence_variance[:] = (1.0 - 1.0/nchunks) * cv if tanh_transform: coherence_variance = np.tanh(coherence_variance) coherence_mean = np.tanh(coherence_mean) #compute frequencies sampint = 1.0 / sample_rate L = sample_length_bins / 2 + 1 freq = np.linspace(0, 1 / (2 * sampint), L) #compute upper and lower bounds coherence_lower = coherence_mean - 2*np.sqrt(coherence_variance) coherence_upper = coherence_mean + 2*np.sqrt(coherence_variance) cdata = CoherenceData(frequency_cutoff=frequency_cutoff) cdata.coherence = coherence_mean cdata.coherence_lower = coherence_lower cdata.coherence_upper = coherence_upper cdata.frequency = freq cdata.sample_rate = sample_rate return cdata
def _mtcpca_complete(x, params, verbose=None, bootstrapMode=False): """ Internal convenience function to obtain plv and spectrum with cpca and multitaper. Equivalent to calling: spectral.mtcpca(data, params, ...) spectral.mtcspec(data, params, ...) With the exception that this function returns a dictionary for S + N, each of which have keys "plv_*" and "spectrum_*", where * is "normalPhase" or "noiseFloorViaPhaseFlip". Gets power spectra and plv on the same set of data using multitaper and complex PCA. Returns a noise floor esimate of each by running the same computations on the original data, as well as the original data with the phase of half of the trials flipped. For a large number of trials, the spectra of the data and the half-trials-phase-flipped data should be similar, while the PLV values for the half-trials-flipped data should be hovering near the PLV value of off-frequency components in the original data. Primarily useful when debugging, bootstrapping, or when using scripts that for some reason randomizes data in between calls to mtcpca and mtcspec. Parameters ---------- x - NumPy Array Input data (channel x trial x time) params - dictionary. Must contain the following fields: params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - If True, normalize after mean like ITC instead of PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (S, N, f) Where S and N are data for signal and for noise floor, respectively, each as a dictionary with the following keys: mtcpcaSpectrum - Multitapered power spectral estimate using cPCA mtcpcaPLV- Multitapered PLV using cPCA f - frequency vector In bootstrap mode: dictionary with keys: mtcpcaSpectrum_* - Multitapered power spectral estimate using cPCA mtcpcaPLV_*- Multitapered PLV using cPCA f - frequency vector where * in the above is the type of noise floor """ out = {} logger.info('Running Multitaper Complex PCA based ' + 'plv and power estimation.') x = x.squeeze() if len(x.shape) == 3: timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) else: logger.error('Sorry! The data should be a 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) plv = np.zeros((ntaps, len(f))) cspec = np.zeros((ntaps, len(f))) phaseTypes = list(['normalPhase', 'noiseFloorViaPhaseFlip']) for thisType in phaseTypes: if thisType == 'noiseFloorViaPhaseFlip': # flip the phase of every other trial phaseFlipper = np.ones(x.shape) # important change: when noise floor is computed, it will # only select trials that were originally labeled as even-numbered # this way, bootstrapped noise floors are where they would be # expected to be rather than artificially low if bootstrapMode and 'bootstrapTrialsSelected' in params: flipTheseTrials = np.where( (params['bootstrapTrialsSelected'] % 2) == 0) else: flipTheseTrials = np.arange(0, x.shape[1], 2) phaseFlipper[:, flipTheseTrials, :] = -1.0 elif thisType == 'normalPhase': phaseFlipper = 1.0 useData = x * phaseFlipper for k, tap in enumerate(w): logger.info(thisType + 'Doing Taper #%d', k) xw = sci.fft((tap * useData), n=nfft, axis=timedim) # no point keeping everything if fpass was already set xw = xw[:, :, fInd] C = xw.mean(axis=trialdim).squeeze() if params['itc']: plvC = (xw.mean(axis=trialdim) / (abs(xw).mean(axis=trialdim))).squeeze() else: plvC = (xw / abs(xw)).mean(axis=trialdim).squeeze() for fi in np.arange(0, len(f)): powerCsd = np.outer(C[:, fi], C[:, fi].conj()) powerEigenvals = linalg.eigh(powerCsd, eigvals_only=True) cspec[k, fi] = powerEigenvals[-1] / nchans plvCsd = np.outer(plvC[:, fi], plvC[:, fi].conj()) plvEigenvals = linalg.eigh(plvCsd, eigvals_only=True) plv[k, fi] = plvEigenvals[-1] / nchans # Avage over tapers and squeeze to pretty shapes mtcpcaSpectrum = (cspec.mean(axis=0)).squeeze() mtcpcaPhaseLockingValue = (plv.mean(axis=0)).squeeze() if mtcpcaSpectrum.shape != mtcpcaPhaseLockingValue.shape: logger.error('internal error: shape mismatch between PLV ' + ' and magnitude result arrays') out['mtcpcaSpectrum_' + thisType] = mtcpcaSpectrum out['mtcpcaPLV_' + thisType] = mtcpcaPhaseLockingValue if bootstrapMode: out['f'] = f return out else: S = {} S['spectrum'] = ['mtcpcaSpectrum_normalPhase'] S['plv'] = ['mtcpcaPLV_normalPhase'] N = {} N['spectrum'] = out['mtcpcaSpectrum_noiseFloorViaPhaseFlip'] N['plv'] = out['mtcpcaPLV_noiseFloorViaPhaseFlip'] return (S, N, f)
def __getitem__(self, k): return self.pop(k, dpss_windows(*k))
pdata = utils.percent_change(data) """ We start by performing the detailed analysis, but note that a significant short-cut is presented below, so if you just want to know how to do this (without needing to understand the details), skip on down. We start by defining how many tapers will be used and calculate the values of the tapers and the associated eigenvalues of each taper: """ NW = 4 K = 2 * NW - 1 tapers, eigs = alg.dpss_windows(n_samples, NW, K) """ We multiply the data by the tapers and derive the fourier transform and the magnitude of the squared spectra (the power) for each tapered time-series: """ tdata = tapers[None, :, :] * pdata[:, None, :] tspectra = fftpack.fft(tdata) ## mag_sqr_spectra = np.abs(tspectra) ## np.power(mag_sqr_spectra, 2, mag_sqr_spectra) """ Coherence for real sequences is symmetric, so we calculate this for only half the spectrum (the other half is equal):
(b, a) = signal.butter(3, W, btype='lowpass') slp = signal.lfilter(b, a, s) """ Modulate both signals away from baseband. """ s_mod = s * np.cos(2*np.pi*np.arange(N) * float(200) / N) slp_mod = slp * np.cos(2*np.pi*np.arange(N) * float(200) / N) fm = int( np.round(float(200) * nfft / N) ) """ Create Slepians with the desired bandpass resolution (2W). """ (dpss, eigs) = nt_alg.dpss_windows(N, NW, 2*NW) keep = eigs > 0.9 dpss = dpss[keep]; eigs = eigs[keep] """ Test 1 ------ We'll compare multitaper baseband power estimation with regular Hilbert transform method under actual narrowband conditions. """ # MT method xk = nt_alg.tapered_spectra(slp_mod, dpss, NFFT=nfft) mtm_bband = np.sum( 2 * (xk[:,fm] * np.sqrt(eigs))[:,None] * dpss, axis=0 )
def extract_features(EEG_segs, channel_names, combined_channel_names, Fs, NW, total_freq_range, sub_window_time, sub_window_step, seg_start_ids, return_feature_names=False, n_jobs=-1, verbose=True): """Extract features from EEG segments. Arguments: EEG_segs -- a list of EEG segments in numpy.ndarray type, size=(sample_point, channel_num) channel_names -- a list of channel names for each column of EEG_segs ##combined_channel_names -- a list of combined column_channels_names, for example from 'F3M2' and 'F4M1' to 'F' Fs -- sampling frequency in Hz Keyword arguments: process_num -- default None, number of parallel processes, if None, equals to 4x #CPU. Outputs: features from each segment in numpy.ndarray type, size=(seg_num, feature_num) a list of names of each feature psd estimation, size=(window_num, freq_point_num, channel_num), or a list of them for each band frequencies, size=(freq_point_num,), or a list of them for each band """ #if type(EEG_segs)!=list: # raise TypeError('EEG segments should be list of numpy.ndarray, with size=(sample_point, channel_num).') seg_num, channel_num, window_size = EEG_segs.shape if seg_num <= 0: return [] sub_window_size = int(round(sub_window_time*Fs)) sub_step_size = int(round(sub_window_step*Fs)) dpss, eigvals = tsa.dpss_windows(sub_window_size,NW,2*NW) nfft = max(1<<(sub_window_size-1).bit_length(), sub_window_size) freq = np.arange(0, Fs, Fs*1.0/nfft)[:nfft//2+1] total_freq_id = np.where(np.logical_and(freq>=total_freq_range[0], freq<total_freq_range[1]))[0] old_threshold = np.get_printoptions()['threshold'] np.set_printoptions(threshold=sys.maxsize)#np.nan) features = Parallel(n_jobs=n_jobs,verbose=verbose,backend='multiprocessing')(delayed(compute_features_each_seg)(EEG_segs[segi], window_size, channel_num, band_num, NW, Fs, freq, band_freq, total_freq_range, total_freq_id, sub_window_size, sub_step_size, dpss=dpss, eigvals=eigvals) for segi in range(seg_num)) np.set_printoptions(threshold=old_threshold) if return_feature_names: feature_names = ['mean_gradient_%s'%chn for chn in channel_names] feature_names += ['kurtosis_%s'%chn for chn in channel_names] feature_names += ['sample_entropy_%s'%chn for chn in channel_names] for ffn in ['max','min','mean','std','kurtosis']:#,'skewness' for bn in band_names: if ffn=='kurtosis' or bn!='sigma': # no need for sigma band feature_names += ['%s_bandpower_%s_%s'%(bn,ffn,chn) for chn in combined_channel_names] power_ratios = ['delta/theta','delta/alpha','theta/alpha'] for pr in power_ratios: feature_names += ['%s_max_%s'%(pr,chn) for chn in combined_channel_names] feature_names += ['%s_min_%s'%(pr,chn) for chn in combined_channel_names] feature_names += ['%s_mean_%s'%(pr,chn) for chn in combined_channel_names] feature_names += ['%s_std_%s'%(pr,chn) for chn in combined_channel_names] # features.shape = (#epoch, 102) if return_feature_names: return np.array(features), feature_names#, pxx_mts, freqs else: return np.array(features)#, pxx_mts, freqs
def __vespa_az(self, st): def find_nearest(array, value): idx, val = min(enumerate(array), key=lambda x: abs(x[1] - value)) return idx, val sides = 'onesided' pi = math.pi st.sort() n = len(st) for i in range(n): coords = self.inv.get_coordinates(st[i].id) st[i].stats.coordinates = AttribDict({ 'latitude': coords['latitude'], 'elevation': coords['elevation'], 'longitude': coords['longitude'] }) coord = get_geometry(st, coordsys='lonlat', return_center=True) tr = st[0] win = len(tr.data) if (win % 2) == 0: nfft = win / 2 + 1 else: nfft = (win + 1) / 2 nr = st.count() # number of stations delta = st[0].stats.delta fs = 1 / delta fn = fs / 2 freq = np.arange(0, fn, fn / nfft) value1, freq1 = find_nearest(freq, self.linf) value2, freq2 = find_nearest(freq, self.lsup) df = value2 - value1 m = np.zeros((win, nr)) WW = np.hamming(int(win)) WW = np.transpose(WW) for i in range(nr): tr = st[i] if self.method == "FK": m[:, i] = (tr.data - np.mean(tr.data)) * WW else: m[:, i] = (tr.data - np.mean(tr.data)) pdata = np.transpose(m) #####Coherence###### NW = 2 # the time-bandwidth product##Buena seleccion de 2-3 K = 2 * NW - 1 tapers, eigs = alg.dpss_windows(win, NW, K) tdata = tapers[None, :, :] * pdata[:, None, :] tspectra = fftpack.fft(tdata) w = np.empty((nr, int(K), int(nfft))) for i in range(nr): w[i], _ = utils.adaptive_weights(tspectra[i], eigs, sides=sides) Cx = np.ones((nr, nr, df), dtype=np.complex128) if self.method == "MTP.COHERENCE": for i in range(nr): for j in range(nr): sxy = alg.mtm_cross_spectrum(tspectra[i], (tspectra[j]), (w[i], w[j]), sides='onesided') sxx = alg.mtm_cross_spectrum(tspectra[i], tspectra[i], w[i], sides='onesided') syy = alg.mtm_cross_spectrum(tspectra[j], tspectra[j], w[j], sides='onesided') s = sxy / np.sqrt((sxx * syy)) cxcohe = s[value1:value2] Cx[i, j, :] = cxcohe ####Calculates Conventional FK-power ##without normalization if self.method == "FK": for i in range(nr): for j in range(nr): A = np.fft.rfft(m[:, i]) B = np.fft.rfft(m[:, j]) #Power #out = A * np.conjugate(B) #Relative Power den = np.absolute(A) * np.absolute(np.conjugate(B)) out = (A * np.conjugate(B)) / den cxcohe = out[value1:value2] Cx[i, j, :] = cxcohe r = np.zeros((nr, 2)) S = np.zeros((1, 2)) Pow = np.zeros((360, df)) for n in range(nr): r[n, :] = coord[n][0:2] freq = freq[value1:value2] rad = np.pi / 180 slow_range = np.linspace(0, self.slow, 360) for j in range(360): ang = self.azimuth2mathangle(self.baz) S[0, 0] = slow_range[j] * np.cos(rad * ang) S[0, 1] = slow_range[j] * np.sin(rad * ang) k = (S * r) K = np.sum(k, axis=1) n = 0 for f in freq: A = np.exp(-1j * 2 * pi * f * K) B = np.conjugate(np.transpose(A)) D = np.matmul(B, Cx[:, :, n]) / nr P = np.matmul(D, A) / nr Pow[j, n] = np.abs(P) n = n + 1 Pow = np.mean(Pow, axis=1) return Pow
def mtplv(x, params, verbose=None, bootstrapMode=False): """Multitaper Phase-Locking Value Parameters ---------- x - NumPy Array Input Data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['itc'] - 1 for ITC, 0 for PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: (plvtap, f): Tuple plvtap - Multitapered phase-locking estimate (channel x frequency) In bootstrap mode: Dictionary with the following keys: mtplv - Multitapered phase-locking estimate (channel x frequency)i f - Frequency vector matching plv """ logger.info('Running Multitaper PLV Estimation') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 nchans = x.shape[0] ntrials = x.shape[trialdim] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry, The data should be a 2 or 3 dimensional array') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the PLV result plvtap = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) if (params['itc'] == 0): plvtap[k, :, :] = abs((xw / abs(xw)).mean(axis=trialdim))**2 else: plvtap[k, :, :] = ((abs(xw.mean(axis=trialdim))**2) / ((abs(xw)**2).mean(axis=trialdim))) plvtap = plvtap.mean(axis=0) plvtap = plvtap[:, fInd].squeeze() if bootstrapMode: out = {} out['mtplv'] = plvtap out['f'] = f else: return (plvtap, f) return out
def compute_mtcoherence(s1, s2, sample_rate, window_size, bandwidth=15.0, chunk_len_percentage_tolerance=0.30, frequency_cutoff=None, tanh_transform=False, debug=False): """ Computing the multi-taper coherence between signals s1 and s2. To do so, the signals are broken up into segments of length specified by window_size. Then the multi-taper coherence is computed between each segment. The mean coherence is computed across segments, and an estimate of the coherence variance is computed across segments. sample_rate: the sample rate in Hz of s1 and s2 window_size: size of the segments in seconds bandwidth: related to the # of tapers used to compute the spectral density. The higher the bandwidth, the more tapers. chunk_len_percentage_tolerance: If there are leftover segments whose lengths are less than window_size, use them if they comprise at least the fraction of window_size specified by chunk_len_percentage_tolerance frequency_cutoff: the frequency at which to cut off the coherence when computing the normal mutual information tanh_transform: whether to transform the coherences when computing the upper and lower bounds, supposedly improves the estimate of variance. """ minlen = min(len(s1), len(s2)) if s1.shape != s2.shape: s1 = s1[:minlen] s2 = s2[:minlen] sample_length_bins = min(len(s1), int(window_size * sample_rate)) #compute DPSS tapers for signals NW = int(window_size * bandwidth) K = 2 * NW - 1 #print 'compute_coherence: NW=%d, K=%d' % (NW, K) tapers, eigs = ntalg.dpss_windows(sample_length_bins, NW, K) if debug: print '[compute_coherence] bandwidth=%0.1f, # of tapers: %d' % ( bandwidth, len(eigs)) #break signal into chunks and estimate coherence for each chunk nchunks = int(np.floor(len(s1) / float(sample_length_bins))) nleft = len(s1) % sample_length_bins if nleft > 0: nchunks += 1 #print 'sample_length_bins=%d, # of chunks:%d, # samples in last chunk: %d' % (sample_length_bins, nchunks, nleft) coherence_estimates = list() for k in range(nchunks): s = k * sample_length_bins e = min(len(s1), s + sample_length_bins) chunk_len = e - s chunk_percentage = chunk_len / float(sample_length_bins) if chunk_percentage < chunk_len_percentage_tolerance: #don't compute coherence for a chunk whose length is less than a certain percentage of sample_length_bins continue s1_chunk = np.zeros([sample_length_bins]) s2_chunk = np.zeros([sample_length_bins]) s1_chunk[:chunk_len] = s1[s:e] s2_chunk[:chunk_len] = s2[s:e] #taper the signals s1_tap = tapers * s1_chunk s2_tap = tapers * s2_chunk #compute fft of tapered signals s1_fft = fftpack.fft(s1_tap, axis=1) s2_fft = fftpack.fft(s2_tap, axis=1) #compute adaptive weights for each taper w1, nu1 = ntutils.adaptive_weights(s1_fft, eigs, sides='onesided') w2, nu2 = ntutils.adaptive_weights(s2_fft, eigs, sides='onesided') #compute cross spectral density sxy = ntalg.mtm_cross_spectrum(s1_fft, s2_fft, (w1, w2), sides='onesided') #compute individual power spectrums sxx = ntalg.mtm_cross_spectrum(s1_fft, s1_fft, w1, sides='onesided') syy = ntalg.mtm_cross_spectrum(s2_fft, s2_fft, w2, sides='onesided') #compute coherence coherence = np.abs(sxy)**2 / (sxx * syy) coherence_estimates.append(coherence) #compute variance coherence_estimates = np.array(coherence_estimates) if tanh_transform: coherence_estimates = np.arctanh(coherence_estimates) coherence_variance = np.zeros([coherence_estimates.shape[1]]) coherence_mean = coherence_estimates.mean(axis=0) #mean subtract and square cv = np.sum((coherence_estimates - coherence_mean)**2, axis=0) coherence_variance[:] = (1.0 - 1.0 / nchunks) * cv if tanh_transform: coherence_variance = np.tanh(coherence_variance) coherence_mean = np.tanh(coherence_mean) #compute frequencies sampint = 1.0 / sample_rate L = sample_length_bins / 2 + 1 freq = np.linspace(0, 1 / (2 * sampint), L) #compute upper and lower bounds coherence_lower = coherence_mean - 2 * np.sqrt(coherence_variance) coherence_upper = coherence_mean + 2 * np.sqrt(coherence_variance) cdata = CoherenceData(frequency_cutoff=frequency_cutoff) cdata.coherence = coherence_mean cdata.coherence_lower = coherence_lower cdata.coherence_upper = coherence_upper cdata.frequency = freq cdata.sample_rate = sample_rate return cdata
def mtppc(x, params, verbose=None, bootstrapMode=False): """Multitaper Pairwise Phase Consistency Parameters ---------- x - Numpy array Input data (channel x trial x time) or (trials x time) params - Dictionary of parameter settings params['Fs'] - sampling rate params['tapers'] - [TW, Number of tapers] params['fpass'] - Freqency range of interest, e.g. [5, 1000] params['Npairs'] - Number of pairs for PPC analysis params['itc'] - If True, normalize after mean like ITC instead of PLV verbose : bool, str, int, or None The verbosity of messages to print. If a str, it can be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. Returns ------- In normal mode: Tuple (ppc, f): ppc - Multitapered PPC estimate (channel x frequency) f - Frequency vector matching plv In bootstrap mode: Dictionary with the following keys: mtppc - Multitapered PPC estimate (channel x frequency) f - Frequency vector matching plv """ logger.info('Running Multitaper Pairwise Phase Consistency Estimate') x = x.squeeze() if (len(x.shape) == 3): timedim = 2 trialdim = 1 ntrials = x.shape[trialdim] nchans = x.shape[0] logger.info('The data is of format %d channels x %d trials x time', nchans, ntrials) elif (len(x.shape) == 2): timedim = 1 trialdim = 0 ntrials = x.shape[trialdim] nchans = 1 logger.info('The data is of format %d trials x time (single channel)', ntrials) else: logger.error('Sorry! The data should be a 2 or 3 dimensional array!') # Calculate the tapers nfft, f, fInd = _get_freq_stuff(x, params, timedim) ntaps = params['tapers'][1] TW = params['tapers'][0] w, conc = dpss_windows(x.shape[timedim], TW, ntaps) # Make space for the result ppc = np.zeros((ntaps, nchans, nfft)) for k, tap in enumerate(w): logger.info('Doing Taper #%d', k) xw = sci.fft(tap * x, n=nfft, axis=timedim) npairs = params['nPairs'] trial_pairs = np.random.randint(0, ntrials, (npairs, 2)) if (nchans == 1): if (not params['itc']): xw_1 = xw[trial_pairs[:, 0], :] / abs(xw[trial_pairs[:, 0], :]) xw_2 = xw[trial_pairs[:, 1], :] / abs(xw[trial_pairs[:, 1], :]) ppc[k, :, :] = np.real( (xw_1 * xw_2.conj()).mean(axis=trialdim)) else: xw_1 = xw[trial_pairs[:, 0]] xw_2 = xw[trial_pairs[:, 1]] ppc_unnorm = np.real((xw_1 * xw_2.conj()).mean(axis=trialdim)) ppc[k, :, :] = ( ppc_unnorm / (abs(xw_1).mean(trialdim) * abs(xw_2).mean(trialdim))) else: if (not params['itc']): xw_1 = (xw[:, trial_pairs[:, 0], :] / abs(xw[:, trial_pairs[:, 0], :])) xw_2 = (xw[:, trial_pairs[:, 1], :] / abs(xw[:, trial_pairs[:, 1], :])) ppc[k, :, :] = np.real( (xw_1 * xw_2.conj()).mean(axis=trialdim)) else: xw_1 = xw[:, trial_pairs[:, 0], :] xw_2 = xw[:, trial_pairs[:, 1], :] ppc_unnorm = np.real((xw_1 * xw_2.conj()).mean(axis=trialdim)) ppc[k, :, :] = ( ppc_unnorm / (abs(xw_1).mean(trialdim) * abs(xw_2).mean(trialdim))) ppc = ppc.mean(axis=0) ppc = ppc[:, fInd].squeeze() if bootstrapMode: out = {} out['mtppc'] = ppc out['f'] = f return out else: return (ppc, f)
fig.savefig('windowing_functions.png') #%% Slepian tapers plt.close('all') Fs = 1000 params = (4000/Fs,Fs/500,15) talims = np.array([-2500,2500])/Fs falims = np.array([-2/500,2/500])*Fs N = int(params[0] * Fs) NW = params[0]*params[1] K = params[2] dpss, eigvals = tsa.dpss_windows(N, NW, K) ta = np.linspace(-N/2/Fs,N/2/Fs,N) dpss_f = np.fft.fft(dpss, n=N*pad) fa = np.fft.fftfreq(N*pad,1/Fs) dpss_S = dpss_f*dpss_f.conj() order = np.argsort(fa) fa = fa[order] dpss_S = dpss_S[:,order] for nplot in [1,5]: fig,axs = plt.subplots(nrows=1,ncols=2,figsize=[7,2.5]) axs[0].plot(ta,dpss[:nplot,:].T) axs[0].set_xlim(talims)