def test_extremes(self): # Test extremes of alpha lam = windows.dpss(31, 6, 4, return_ratios=True)[1] assert_array_almost_equal(lam, 1.) lam = windows.dpss(31, 7, 4, return_ratios=True)[1] assert_array_almost_equal(lam, 1.) lam = windows.dpss(31, 8, 4, return_ratios=True)[1] assert_array_almost_equal(lam, 1.)
def dpss_windows(N, NW, K): """ Convenience wrapper of scipy's DPSS window method that always returns K orthonormal tapers and eigenvalues. Parameters ---------- N: int Sequence length NW: float Sequence time-frequency product (half integers). K: int Number of DPSS to calculate (typically <= 2 * NW) Returns ------- dpss: ndarray (K, N) orthonormal tapers eigs: ndarray K eigenvalues (bandpass concentration ratios) """ vecs, eigs = dpss(N, NW, Kmax=int(K), sym=False, norm=2, return_ratios=True) return vecs, eigs
def slepian_window(t, args): """ Computes the value of a Slepian modulation shape at a given time. Parameters ---------- t: time point at which to compute the value of the modulation shape args: dictionnary containing the arguments used by the shape computations. args must contain the keys: 't_end' : end time of the desired shape (in s) 'rabi_freq': Rabi frequency of the pulse (in Hz.rad) 'alpha' : value of the shape's free parameter 'nb_qubits': number of qubits in the array """ from scipy.signal.windows import dpss t_end = args['t_end'] alpha = args['alpha'] nb_qubits = args['nb_qubits'] omega0_max = 2 * np.pi * ( 10.0e9 + (nb_qubits - 1) * 0.1e9 ) # Valid only when the lowest frequency is 1.0 GHz #TODO: Add smarter way to recover the proper frequency to consider here nb_time_pts = np.ceil(t_end / (1. / (omega0_max) / 100)).astype(int) res_full = dpss(nb_time_pts, alpha) res = res_full[(nb_time_pts * t / t_end).astype(int)] return res
def dpsschk(tapers, N, Fs): """ check tapers """ tapers, eigs = dpss(N, NW = tapers[0], Kmax=tapers[1], sym=False, return_ratios = True) tapers = tapers * np.sqrt(Fs) tapers = tapers.T return tapers
def _get_window(self, win_size: int): """Get the window to apply to the data""" from scipy.signal import get_window from scipy.signal.windows import dpss if self.win_fnc == "dpss": return dpss(win_size, 5) return get_window(self.win_fnc, win_size)
def pmtm(data, NW=4, Fs=None, NFFT=None): ''' Compute the power spectrum via Multitapering. If the number of tapers == 1, it is a stft (short-time fourier transform) Parameters ---------- data : TYPE Input data vector. Fs : TYPE The sampling frequency. tapers : TYPE Matrix containing the discrete prolate spheroidal sequences (dpss). NFFT : TYPE Number of frequency points to evaluate the PSD at. Returns ------- Sk : TYPE Power spectrum computed via MTM. ''' # Number of channels if data.ndim == 1: data = np.expand_dims(data, axis=1) else: data = transpose(data, 'column') # Data length N = data.shape[0] channels = data.shape[1] if Fs == None: Fs = 2 * np.pi # set the NFFT if NFFT == None: NFFT = max(256, 2**nextpow2(N)) w = pmtm_params(Fs, NFFT) # Compute tapers tapers, concentration = dpss(N, NW, Kmax=2 * NW - 1, return_ratios=True) tapers = transpose(tapers, 'column') Sk = np.empty((NFFT, channels)) Sk[:] = np.NaN for channel in range(channels): # Compute the FFT Sk_complex = np.fft.fft( np.multiply(tapers.transpose(), data[:, channel]), NFFT) # Compute the whole power spectrum [Power] Sk[:, channel] = np.mean(abs(Sk_complex)**2, axis=0) return Sk_complex, Sk, w, NFFT
def test_unity(self): # Test unity value handling (gh-2221) for M in range(1, 21): # corrected w/approximation (default) win = windows.dpss(M, M / 2.1) expected = M % 2 # one for odd, none for even assert_equal(np.isclose(win, 1.).sum(), expected, err_msg='%s' % (win,)) # corrected w/subsample delay (slower) win_sub = windows.dpss(M, M / 2.1, norm='subsample') if M > 2: # @M=2 the subsample doesn't do anything assert_equal(np.isclose(win_sub, 1.).sum(), expected, err_msg='%s' % (win_sub,)) assert_allclose(win, win_sub, rtol=0.03) # within 3% # not the same, l2-norm win_2 = windows.dpss(M, M / 2.1, norm=2) expected = 1 if M == 1 else 0 assert_equal(np.isclose(win_2, 1.).sum(), expected, err_msg='%s' % (win_2,))
def get_tapers(N, bandwidth, *, fs=1, min_lambda=0.95, n_tapers=None): """ Compute tapers and associated energy concentrations for the Thomson multitaper method Parameters ---------- N : int Length of taper bandwidth : float Bandwidth of taper, in Hz fs : float, optional Sampling rate, in Hz. Default is 1 Hz. min_lambda : float, optional Minimum energy concentration that each taper must satisfy. Default is 0.95. n_tapers : int, optional Number of tapers to compute Default is to use all tapers that satisfied 'min_lambda'. Returns ------- tapers : np.ndarray, with shape (n_tapers, N) lambdas : np.ndarray, with shape (n_tapers, ) Energy concentrations for each taper """ NW = bandwidth * N / fs K = int(np.ceil(2*NW)) - 1 if K < 1: raise ValueError( f"Not enough tapers, with 'NW' of {NW}. Increase the bandwidth or " "use more data points") tapers, lambdas = dpss(N, NW, Kmax=K, norm=2, return_ratios=True) mask = lambdas > min_lambda if not np.sum(mask) > 0: raise ValueError( "None of the tapers satisfied the minimum energy concentration" f" criteria of {min_lambda}") tapers = tapers[mask] lambdas = lambdas[mask] if n_tapers is not None: if n_tapers > tapers.shape[0]: raise ValueError( f"'n_tapers' of {n_tapers} is greater than the {tapers.shape[0]}" f" that satisfied the minimum energy concentration criteria of {min_lambda}") tapers = tapers[:n_tapers] lambdas = lambdas[:n_tapers] return tapers, lambdas
def filtered_shape(end_time, shape, args): """ Returns an array containing the value of the filtered shape function over its whole duration. Parameters ---------- end_time: total duration of the pulse (in ns) shape : string specifying the name of th shape args : tuple containing the parameters of the shapes. For more generality, values for the possible free parameters of the functions are also expected. In case the desired shape does not have free parameters, any dummy value can be provided. The tuple is organised as follows: c (optimsed blackman shape parameter), alpha (0th order Kaiser shape parameter), gamma (1st order Kaiser shape parameter), a (gaussian and gaussian-like shape parameter), rabi_frequency, rotation_angle. Returns ------- """ from qubit_utilities import prepare_filter c, alpha, gamma, a, rabi_frequency, rotation_angle, omega, omega0_max, nb_time_pts = args # args = (c, alpha, gamma, rabi_frequency, rotation_angle) if shape == 'kaiser0': times = np.linspace(0, end_time, nb_time_pts) else: times = np.linspace(-end_time / 2, end_time / 2, nb_time_pts) # Define the arguments for the shape function args = { 'c': c, 'alpha': alpha, 'gamma': gamma, 'rabi_freq': rabi_frequency, 't_end': end_time, } unfiltered_shape = np.zeros(nb_time_pts) if shape == 'slepian': from scipy.signal.windows import dpss unfiltered_shape = dpss(nb_time_pts, alpha) else: i = 0 for t in times: unfiltered_shape[i] = shapes[shape](t, args) i += 1 b, a = prepare_filter(omega0_max / (2 * np.pi) - 400e6, omega0_max / (2 * np.pi) + 400e6, nb_time_pts / end_time, order=3) filtered_shape = lfilter(b, a, unfiltered_shape) return filtered_shape
def get_spectrogram(self, nframes, lframes): """ Go through the data frame by frame and perform transformation. They can be plotted using pcolormesh x, y and z are ndarrays and have the same shape. In order to access the contents use these kind of indexing as below: #Slices parallel to frequency axis nrows = np.shape(x)[0] for i in range (nrows): plt.plot(x[i,:], z[i,:]) #Slices parallel to time axis ncols = np.shape(y)[1] for i in range (ncols): plt.plot(y[:,i], z[:, i]) :return: frequency, time and power for XYZ plot, """ assert self.method in ['fft', 'welch', 'mtm'] # define an empty np-array for appending pout = np.zeros(nframes * lframes) if self.method == 'fft': sig = np.reshape(self.data_array, (nframes, lframes)) zz = np.abs(np.fft.fftshift(np.fft.fft(sig, axis=1))) elif self.method == 'welch': # go through the data array section wise and create a results array for i in range(nframes): f, p = self.get_pwelch( self.data_array[i * lframes:(i + 1) * lframes] * self.get_window(lframes)) pout[i * lframes:(i + 1) * lframes] = p # fold the results array to the mesh grid zz = np.reshape(pout, (nframes, lframes)) elif self.method == 'mtm': mydpss = dpss(M=lframes, NW=4, Kmax=6) #f = self.get_fft_freqs_only(x[0:lframes]) sig = np.reshape(self.data_array, (nframes, lframes)) zz = pmtm(sig, mydpss, axis=1) # create a mesh grid from 0 to nframes -1 in Y direction xx, yy = np.meshgrid(np.arange(lframes), np.arange(nframes)) yy = yy * lframes / self.fs xx = xx - xx[-1, -1] / 2 xx = xx * self.fs / lframes return xx, yy, zz
def integr_slepian(x, *arg): from scipy.integrate import simps from scipy.signal.windows import dpss c, alpha, gamma, a, rabi_frequency, rotation_angle = arg #TODO: Add smarter way to recover the proper frequency to consider here omega0_max = 2 * np.pi * 10.1e9 # 10.1GHz (valid for 2 qubits simulation) nb_time_pts = np.ceil(x / (1. / (omega0_max) / 100)).astype(int) integral = simps(dpss(nb_time_pts, alpha), np.linspace(-x / 2, x / 2, nb_time_pts)) return rotation_angle / rabi_frequency - integral
def gen_sequences(self, eigenvalue): ''' generate the discrete prolate spheroidal sequences in the time domain ''' try: time_0 = time.time() self.vecs, self.vals = windows.dpss(self.N, self.N*self.W, self.K, return_ratios=True) except: diag_main = ((self.N-1)/2-np.arange(self.N))**2 * np.cos(2*np.pi*self.W) diag_off = np.arange(1, self.N) * np.arange(self.N-1, 0, -1) / 2 vecs = eigh_tridiagonal(diag_main, diag_off, select='i', select_range=(self.N-self.K,self.N-1))[1] self.vecs = (vecs * np.where(vecs[0,:]>0, 1, -1)).T[::-1] # normalized energy, polarity follows Slepian convention if eigenvalue: A = toeplitz(np.insert( np.sin(2*np.pi*self.W*np.arange(1,self.N))/(np.pi*np.arange(1,self.N)), 0, 2*self.W )) self.vals = np.diag(self.vecs @ A @ self.vecs.T) # @ is matrix multiplication
def tapers(self, N, NW, L): """ Compute data tapers (which are discrete prolate spheroidal sequences (dpss)) Uses scipy implementation, see: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.dpss.html :param N: Size of each taper :param NW: Half Bandwidth :param L: Number of tapers :return: NumPy array of data tapers """ # Note the original ASPIRE implementation is negated from original scipy... # but at time of writing subsequent code was agnostic to sign. return dpss(M=N, NW=NW, Kmax=L, return_ratios=False).T
def pds_mtm(t, x, f_res): '''PDS_MTM - Multi-taper spectral estimate This is DW's adaptation of Adam Taylor's PDS_MTM code ff,Pxx = PDS_MTM(tt,xx,fres) calculates one-side multi-taper spectrogram. TT [T] indicates time points. XX [T] is the (optical) data. FRES [scalar] is the half-width of the transform of the tapers used; It must be in reciprocal units of those of TT. FF [F] is the resulting (one-sided) frequency base. Pxx [FxK] are the spectral estimates from each individual taper Note that the nature of the beast is that the output Pxx has a full width of 2*FRES even if the signal XX is perfectly sinusoidal.''' # From Adam's comments: # t is a col vector # elements of t are evenly spaced and increasing # x is a real matrix with the same number of rows as t # f_res is the half-width of the transform of the tapers used # it must be in reciprocal units of those of t # N_fft is the length to which data is zero-padded before FFTing # this works on the columns of x independently # # f is the frequncy base, which is one-sided # Pxx's cols are the the one-sided spectral estimates of the cols of x # Pxxs is 3D, (frequency samples)x(cols of x)x(tapers), gives the spectrum # estimate for each taper # # we assume that x is real, and return the one-side periodogram tapers = None N_fft = 2**np.ceil(np.log2(len(t))) N = len(t) # number of time samples dt =(t[N]-t[1])/(N-1) fs=1/dt # compute nw and K nw = N*dt*f_res K = np.floor(2*nw-1) tapers = dpss(N,nw,K) tapers = np.reshape(tapers, [N, 1, K])
def mtspectrumc(data, params): ''' Function responsible for calculating Multi-taper spectrum. :param data: numpy array, required. 2-D matrix [samples, trials]. It contains the data to process. :param params: dict, required. Contained the executions parameters and necessary constants for the calculations of spectrum of the input data. params = dict(fs = srate, fpass = [lowpass, highpass], tapers = [2, 2, 1], trialave = 1) :return s: numpy array. Contain the aritmetic mean of the spectral power caculated with the fft. :return f: numpy array. Contains the frecuens obtained from the limits in fpass. ''' (samples, trials) = data.shape tapers, pad, fs, fpass, err, trialave = get_params(params) #get params nfft = 2**(ceil(log(samples, 2))) #calculate dimensions of window f, find = getfgrid(fs, nfft, fpass) #get de frequencies #Calculate a matrix of tapering windows tapers = dpss(samples, tapers[0], tapers[1]).transpose() tapers = tapers * sqrt(fs) #Resize the data to calculate the spectrum power with fft (r, c) = tapers.shape tapers = tapers.reshape((r, c, 1)) matrix_one = ones((samples, c, trials), dtype=float32) tapers = tapers * matrix_one data = data.reshape((samples, trials, 1)) matrix_one = ones((samples, trials, c), dtype=float32) data = (data * matrix_one).transpose(0, 2, 1) data_proj = data * tapers #calculate the spectrum power with fft j = fft(data_proj, nfft, axis=0) / fs #getting the values. j = j[find == True, :, :] #Eliminate the imaginary part of the data. s = real(mean(conj(j) * j, axis=1)) if trialave == 1: s = squeeze(mean(s, axis=1)) else: s = squeeze(s) return s, f
def slepian_basis_gen(n_time_slots,discretization_time,bandwidth,min_digitization): """Full bandwidth in Hz, # of cycles in a second Slepian sequence also known as discrete prolate spheroidal sequence, or DPSS Uses scipy.signal.windows.dpss """ NW = bandwidth*discretization_time *n_time_slots n_eigenbasis = np.int(NW*2) slepian_basis = windows.dpss(n_time_slots, NW, n_eigenbasis) basis_max = np.amax(abs(slepian_basis), axis=1) #basis_start = np.amax(abs(slepian_basis[-1:1]), axis=1) n_valid_basis = n_eigenbasis for i in range(n_eigenbasis): #print((slepian_basis[i][0]-(slepian_basis[-1][1]-(slepian_basis[-1][0])))/basis_max[i], (min_digitization*(i+1))) if((slepian_basis[i][0]-(slepian_basis[-1][1]-(slepian_basis[-1][0])))/basis_max[i] > (min_digitization*(i+1))): n_valid_basis = i break if(n_valid_basis < 1): print("Error: No valid basis! Bandwidth too low or time too short ") return slepian_basis
def mtm_svd_lfv(ts2d,nw,kk,dt) : # Compute spectrum at each grid point p, n = ts2d.shape # Remove the mean and divide by std vm = np.nanmean(ts2d, axis=0) # mean vmrep = repmat(vm,ts2d.shape[0],1) ts2d = ts2d - vmrep vs = np.nanstd(ts2d, axis=0) # standard deviation vsrep = repmat(vs,ts2d.shape[0],1) ts2d = np.divide(ts2d,vsrep) ts2d = np.nan_to_num(ts2d) # Slepian tapers psi = dpss(p,nw,kk) npad = 2**int(np.ceil(np.log2(abs(p)))+2) nf = int(npad/2) ddf = 1./(npad*dt) fr = np.arange(0,nf)*ddf # Get the matrix of spectrums psimats = [] for k in range(kk): psimat2 = np.transpose( repmat(psi[k,:],ts2d.shape[1],1) ) psimat2 = np.multiply(psimat2,ts2d) psimats.append(psimat2) psimats=np.array(psimats) nev = np.fft.fft(psimats,n=npad,axis=1) nev = np.fft.fftshift(nev,axes=(1)) nev = nev[:,nf:,:] # Calculate svd for each frequency lfvs = np.zeros(nf)*np.nan for j in range(nf) : U,S,V = np.linalg.svd(nev[:,j,:], full_matrices=False) lfvs[j] = S[0]**2/(np.nansum(S[1:])**2) return fr, lfvs
def mtm_svd_recon(ts2d, nw, kk, dt, fo) : imode = 0 lan = 0 vw = 0 # Compute spectrum at each grid point p, n = ts2d.shape # Remove the mean and divide by std vm = np.nanmean(ts2d, axis=0) # mean vmrep = repmat(vm,ts2d.shape[0],1) ts2d = ts2d - vmrep vs = np.nanstd(ts2d, axis=0) # standard deviation vsrep = repmat(vs,ts2d.shape[0],1) ts2d = np.divide(ts2d,vsrep) ts2d = np.nan_to_num(ts2d) # Slepian tapers psi = dpss(p,nw,kk) npad = 2**int(np.ceil(np.log2(abs(p)))+2) nf = int(npad/2) ddf = 1./(npad*dt) fr = np.arange(0,nf)*ddf # Get the matrix of spectrums psimats = [] for k in range(kk): psimat2 = np.transpose( repmat(psi[k,:],ts2d.shape[1],1) ) psimat2 = np.multiply(psimat2,ts2d) psimats.append(psimat2) psimats=np.array(psimats) nev = np.fft.fft(psimats,n=npad,axis=1) nev = np.fft.fftshift(nev,axes=(1)) nev = nev[:,nf:,:] # Initialiser les matrices de sorties S = np.ones((kk, len(fo)))*np.nan vexp = [] ; totvarexp = [] ; iis = [] D = vsrep envmax = np.zeros(len(fo))*np.nan for i1 in range(len(fo)): # closest frequency iif = (np.abs(fr - fo[i1])).argmin() iis.append(iif) ff0 = fr[iif] print('( %i ) %.2f cyclesyr | %.2f yr'%(iif,ff0,1/ff0)) U,S0,Vh = np.linalg.svd(nev[:,iif,:].T,full_matrices=False) ##V = Vh.T.conj() V = Vh S[:,i1] = S0 env1 = envel(ff0, iif, fr, dt, ddf, p, kk, psi, V) # condition 1 cs=[1] sn=[0] c=np.cos(2*np.pi*ff0*dt) s=np.sin(2*np.pi*ff0*dt) for i2 in range(1,p): cs.append( cs[i2-1]*c-sn[i2-1]*s ) sn.append( cs[i2-1]*s+sn[i2-1]*c ) CS = [complex(cs[i], sn[i]) for i in range(len(cs))] CS=np.conj(CS) # Reconstructions R = np.real( D * S[imode, i1] * np.outer(U[:,imode], CS*env1).T ) vsr=np.var(R,axis=0) vexp.append( vsr/(vs**2)*100 ) totvarexp.append( np.nansum(vsr)/np.nansum(vs**2)*100 ) return vexp, totvarexp, iis
def mtm_svd_conf(ts2d,nw,kk,dt,niter,sl) : # Compute spectrum at each grid point p, n = ts2d.shape npad = 2**int(np.ceil(np.log2(abs(p)))+2) nf = int(npad/2) ddf = 1./(npad*dt) fr = np.arange(0,nf)*ddf # range of frequencies fran = [0, 0.5/dt] nfmin = (np.abs(fr-fran[0])).argmin() nfmax = (np.abs(fr-fran[1])).argmin() fr = fr[nfmin:nfmax] q = [int(niter*each) for each in sl] # Remove the mean and divide by std vm = np.nanmean(ts2d, axis=0) # mean vmrep = repmat(vm,ts2d.shape[0],1) ts2d = ts2d - vmrep vs = np.nanstd(ts2d, axis=0) # standard deviation vsrep = repmat(vs,ts2d.shape[0],1) ts2d = np.divide(ts2d,vsrep) ts2d = np.nan_to_num(ts2d) # Slepian tapers psi = dpss(p,nw,kk) partvar = np.ones((niter, len(fr)+1))*np.nan for it in range(niter): print('Iter %i'%it) shr = np.random.permutation(ts2d) # random permutation of each time series # Spectral estimation psimats = [] for k in range(kk): psimat2 = np.transpose( repmat(psi[k,:],shr.shape[1],1) ) psimat2 = np.multiply(psimat2,shr) psimats.append(psimat2) psimats=np.array(psimats) nevconf = np.fft.fft(psimats,n=npad,axis=1) nevconf = np.fft.fftshift(nevconf,axes=(1)) nevconf = nevconf[:,nf:,:] # Calculate svd for each frequency for j in range(nfmin,nfmax) : U,S,V = np.linalg.svd(nevconf[:,j,:], full_matrices=False) partvar[it,j] = S[0]**2/(np.nansum(S[1:])**2) np.sort(partvar, axis=1) freq_sec = nw/(p*dt) ibs = (np.abs(fr-freq_sec)).argmin() ; ibs=range(ibs) ibns = range(ibs[-1]+1, len(fr)) fray = 1./(p*dt) fbw = 2*nw*fray ifbw = int(round(fbw/(fr[2]-fr[1]))) evalper = np.zeros((len(q),len(fr))) for i in range(len(q)): y2 = np.zeros(len(fr)) y = partvar[q[i],:] ### right order indices? y1 = signal.medfilt(y,ifbw) y2[ibs] = np.mean(y[ibs]) a = np.polyfit(fr[ibns],y1[ibns],10) y2[ibns] = np.polyval(a, fr[ibns]) evalper[i,:] = y2 return fr, evalper
def test_basic(self): # Test against hardcoded data for k, v in dpss_data.items(): win, ratios = windows.dpss(*k, return_ratios=True) assert_allclose(win, v[0], atol=1e-7, err_msg=k) assert_allclose(ratios, v[1], rtol=1e-5, atol=1e-7, err_msg=k)
def multitaper(P, D, dt, tshift, regul): """ Output has to be filtered (Noise appears in high-frequency)! multitaper: takes Ved-Kathrins code and changes inputs to make it run like Helffrich algorithm, minus normalization and with questionable calculation of pre-event noise (have to double check this and compare it to the regular FFT based estimate without any tapers) INPUT: P - component containing source time function D - component containing converted wave dt - sampling interval tshift - time shift of primary arrival regul - regularization, either 'fqd' for frequency dependent or 'con' for constant Findings: when P arrival is not in the center of the window, the amplitudes are not unity at the beginning and decreasing from there on. Instead they peak at the time shift which corresponds to the middle index in the P time window. TB = time bandwidth product (usually between 2 and 4) NT = number of tapers to use, has to be <= 2*TB-1 TB = 4; NT = 7; #choice of TB = 4, NT = 3 is supposed to be optimal t0 = -5; t1 = max(time); # This defines the beginning and end of the lag times for the receiver function changed by KS June 2016, added coherence and variance estimate; output RF in time domain (rf), RF in freq. domain (tmpp), and variance of RF (var_rf); can be used as input for multitaper_weighting.m the input "regul" defines which type of regularization is used, regul='fqd' defines frequency dependent regularisation from pre-event noise, regul='con' defines adding a constant value (here maximum of pre-event noise) as regularisation""" # wavelet always in the center of the window # Original from Ved's # win_len = tshift*2; # Modification to get P&L # win_len = length(P)*dt; # Modificaiton to get Helffrich win_len = 50 Nwin = round(win_len / dt) # Fraction of overlap overlap between moving time windows. As your TB # increases, the frequency smearing gets worse, which means that the RFs # degrate at shorter and shorter lag times. Therefore, as you increase TB, # you should also increase Poverlap. Poverlap = 0.75 # Length of waveforms; nh = len(P) # Create moving time windowed slepians starts = np.arange(0, nh - Nwin + 1, round((1 - Poverlap) * Nwin)) # tapernumber, bandwith # is in general put to NT=3, and bandwidth to 2.5 # TB=2.5; # #NT=2*TB-1; #4 tapers # NT=3; TB = 4 # NT=2*TB-1; #4 tapers NT = 3 # Construct Slepians Etmp, lambdas = dpss(Nwin, TB, Kmax=NT, return_ratios=True) E = np.zeros([len(starts) * NT, nh]) n = 0 NUM = np.zeros([NT, len(P)]) DEN = np.zeros([NT, len(D)]) DUM = np.zeros([NT, len(D)]) ESTP = np.zeros([NT, len(P)]) ESTD = np.zeros([NT, len(D)]) # finding frequency dependent regularisation parameter DEN_noise # added: KS 26.06.2016 Pn = np.zeros(np.shape(P)) # Pn(3/dt:(tshift-5)/dt)=P(3/dt:(tshift-5)/dt) Pn[:round((tshift - 2) / dt)] = P[:round((tshift - 2) / dt)] # pre-event noise: starting 3s after trace start # stop 10s before theoretical start of P # wave to aviod including it DEN_noise = np.zeros([NT, len(P)]) # Multitaper # SR: problem here is how the loop is done ... there's only a peak # at the pulse because the two windows of the num and den are moving # together. One should first comput the entire estimate of the wavelet # and the data for each valie of k, and then do the sum of products for # each k!!! for k in range(NT): for j in range(len(starts)): E[n, starts[j]:(starts[j] + Nwin)] = np.transpose(Etmp[k, :]) tmp1 = np.fft.fft(np.multiply(E[n, :], P)) tmp2 = np.fft.fft(np.multiply(E[n, :], D)) NUM[k, :] = NUM[k, :] + np.multiply(lambdas[k] * tmp1.conj(), tmp2) DEN[k, :] = DEN[k, :] + np.multiply(lambdas[k] * tmp1.conj(), tmp1) ESTP[k, :] = ESTP[k, :] + lambdas[k] * tmp1 ESTD[k, :] = ESTD[k, :] + lambdas[k] * tmp2 # DUM only from D trace (converted wave component) used in # coherence estimate DUM[k, :] = DUM[k, :] + np.multiply(lambdas[k] * tmp2.conj(), tmp2) # pre-event noise # always stick to first time window tmp1n = np.fft.fft(np.multiply(E[n, :], Pn)) DEN_noise[k, :] = DEN_noise[k, :] +\ np.multiply(lambdas[k]*tmp1n.conj(), tmp1n) n = n + 1 # max_imag_ep = max(abs(np.imag(np.fft.ifft(sum(ESTP))))) # max_imag_ed = max(abs(np.imag(np.fft.ifft(sum(ESTD))))) # ep = np.real(np.fft.ifft(sum(ESTP))); # ed = np.real(np.fft.ifft(sum(ESTD))); # Calculate optimal RF with frequency-dependend regularisation freqdep = regul == 'fqd' const = regul == 'con' if freqdep: tmpp = np.divide( np.sum(np.multiply(ESTP.conj(), ESTD), axis=0), np.sum(np.multiply(ESTP.conj(), ESTP), axis=0) + sum(DEN_noise)) * 1 / dt tmpp_l = np.divide( np.sum(np.multiply(ESTP.conj(), ESTP), axis=0), np.sum(np.multiply(ESTP.conj(), ESTP), axis=0) + sum(DEN_noise)) * 1 / dt N2 = np.floor(nh / 2) + 1 for i in range(int(N2)): fac = np.cos(np.pi / 2 * i / N2)**2 tmpp[i] = tmpp[i] * fac elif const: # ordinary regularisation with adding only a constant value eps = DEN_noise.real.max() + 1 tmpp = np.divide(np.sum(np.multiply(ESTP.conj(), ESTD), axis=0), np.sum(np.multiply(ESTP.conj(), ESTP) + eps, axis=0)) * 1 / dt tmpp_l = np.divide( np.sum(np.multiply(ESTP.conj(), ESTD), axis=0), np.sum(np.multiply(ESTP.conj(), ESTP) + eps, axis=0)) * 1 / dt else: raise Exception( 'Regularization not defined (your input: regul=', regul, """). Use either "fqd" for frequency-dependent or "con" for constant value regularization.""") # RF without variance weighting tmp1 = np.real(np.fft.ifft(tmpp)) tmp1_l = np.fft.ifft(tmpp_l).real # Interpolate to desired N = len(P) rf = tmp1[round(N - tshift / dt):N] rf = np.append(rf, tmp1[:N - round(tshift / dt)]) # rf[round(tshift/dt):N] = tmp1[:N-round(tshift/dt)] lrf = tmp1_l[round(N - tshift / dt):N] lrf = np.append(lrf, tmp1_l[:round(N - tshift / dt)]) # lrf[tshift/dt:N] = tmp1_l[:N-tshift/dt] #### # Coherence and Variance of RF # added: KS 26.06.2016 # C_rf = np.divide(NUM, np.sqrt(np.multiply(DUM, DEN))) # var_rf = np.zeros(len(C_rf)) # for ii in range(len(C_rf)): # var_rf[ii] = ((1-abs(C_rf[ii])**2)/((NT-1)*abs(C_rf[ii])**2))*(abs(tmpp[ii])**2) var_rf = None return rf, lrf, var_rf, tmpp
Estimate the power spectral density of the input signal. signal: n-dimensional array of real or complex values dpss: the Slepian matrix axis: axis along which to apply the Slepian windows. Default is the last one. ''' # conversion to positive-only index axis_p = (axis + signal.ndim) % signal.ndim sig_exp_shape = list(signal.shape[:axis]) + [1] + list(signal.shape[axis:]) tap_exp_shape = [1] * axis_p + \ list(dpss.shape) + [1] * (signal.ndim - 1 - axis_p) signal_tapered = signal.reshape(sig_exp_shape) * dpss.reshape( tap_exp_shape) return np.fft.fftshift(np.mean(np.absolute( np.fft.fft(signal_tapered, axis=axis_p + 1))**2, axis=axis_p), axes=axis_p) # ------------------------ if __name__ == '__main__': # a small test # Using traditional values used by Fritz, i.e. NW=4, Max_K = 2x NW-2 = 6 mydpss = dpss(M=1024, NW=4, Kmax=6) sig = np.vectorize(complex)(np.random.rand(1024), np.random.rand(1024)) print(pmtm(sig, mydpss)) mydpss = dpss(M=128, NW=4, Kmax=6) sig = np.reshape(sig, (8, 128)) print(pmtm(sig, mydpss, axis=1))
def test_dpss(self): win1 = windows.get_window(('dpss', 3), 64, fftbins=False) win2 = windows.dpss(64, 3) assert_array_almost_equal(win1, win2, decimal=4)
alon_stimreals = extract_files_to_list(alon_recon, alon_stim) alon_reconstructed_stimuli = extract_files_to_list(alon_recon, alon_recons) alon_recon = alon_reconstructed_stimuli[0].T.values alon_recon.resize(2400) alon_real_stim = alon_stimreals[0].T alon_stim_mean = alon_real_stim.mean(axis=1).values - 0.4 alon_stim_mean.resize(2400) # set up coherence parameters and multitaper windows window_len = 256 NW = 2.5 n_tapers = 4 overlap = 0.5 windows, eigvals = dpss(window_len, NW, n_tapers, return_ratios=True) # do coherence between mean stimulus and reconstruction mean_multitaper_df = pd.DataFrame() for window, eigval in zip(windows, eigvals): freq, cohere = scipy.signal.coherence(alon_stim_mean, alon_recon, fs=1000, window=window) cohere = pd.DataFrame(cohere) cohere_scale = cohere * eigval mean_multitaper_df = pd.concat([mean_multitaper_df, cohere_scale], axis=1, ignore_index=True) coherence_from_mean = mean_multitaper_df.mean(axis=1) # do coherence between each stimulus and reconstruction and then take mean
# We can compare the window to `kaiser`, which was invented as an alternative # that was easier to calculate [Re991e28c1f6b-3]_ (example adapted from # `here <https://ccrma.stanford.edu/~jos/sasp/Kaiser_DPSS_Windows_Compared.html>`_): import numpy as np import matplotlib.pyplot as plt from scipy.signal import windows, freqz N = 51 fig, axes = plt.subplots(3, 2, figsize=(5, 7)) for ai, alpha in enumerate((1, 3, 5)): win_dpss = windows.dpss(N, alpha) beta = alpha*np.pi win_kaiser = windows.kaiser(N, beta) for win, c in ((win_dpss, 'k'), (win_kaiser, 'r')): win /= win.sum() axes[ai, 0].plot(win, color=c, lw=1.) axes[ai, 0].set(xlim=[0, N-1], title=r'$\alpha$ = %s' % alpha, ylabel='Amplitude') w, h = freqz(win) axes[ai, 1].plot(w, 20 * np.log10(np.abs(h)), color=c, lw=1.) axes[ai, 1].set(xlim=[0, np.pi], title=r'$\beta$ = %0.2f' % beta, ylabel='Magnitude (dB)') for ax in axes.ravel(): ax.grid(True) axes[2, 1].legend(['DPSS', 'Kaiser']) fig.tight_layout() plt.show() # And here are examples of the first four windows, along with their # concentration ratios:
def multitaper_spectrogram(data, fs, frequency_range=None, time_bandwidth=5, num_tapers=None, window_params=None, min_nfft=0, detrend_opt='linear', multiprocess=False, cpus=False, weighting='unity', plot_on=True, clim_scale=True, verbose=True, xyflip=False): """ Compute multitaper spectrogram of timeseries data Usage: mt_spectrogram, stimes, sfreqs = multitaper_spectrogram(data, fs, frequency_range=None, time_bandwidth=5, num_tapers=None, window_params=None, min_nfft=0, detrend_opt='linear', multiprocess=False, cpus=False, weighting='unity', plot_on=True, clim_scale=true, verbose=True, xyflip=False): Arguments: data (1d np.array): time series data -- required fs (float): sampling frequency in Hz -- required frequency_range (list): 1x2 list - [<min frequency>, <max frequency>] (default: [0 nyquist]) time_bandwidth (float): time-half bandwidth product (window duration*half bandwidth of main lobe) (default: 5 Hz*s) num_tapers (int): number of DPSS tapers to use (default: [will be computed as floor(2*time_bandwidth - 1)]) window_params (list): 1x2 list - [window size (seconds), step size (seconds)] (default: [5 1]) detrend_opt (string): detrend data window ('linear' (default), 'constant', 'off') (Default: 'linear') min_nfft (int): minimum allowable NFFT size, adds zero padding for interpolation (closest 2^x) (default: 0) multiprocess (bool): Use multiprocessing to compute multitaper spectrogram (default: False) cpus (int): Number of cpus to use if multiprocess = True (default: False). Note: if default is left as False and multiprocess = True, the number of cpus used for multiprocessing will be all available - 1. weighting (str): weighting of tapers ('unity' (default), 'eigen', 'adapt'); plot_on (bool): plot results (default: True) clim_scale (bool): automatically scale the colormap on the plotted spectrogram (default: true) verbose (bool): display spectrogram properties (default: true) xyflip (bool): transpose the mt_spectrogram output (default: false) Returns: mt_spectrogram (TxF np array): spectral power matrix stimes (1xT np array): timepoints (s) in mt_spectrogram sfreqs (1xF np array)L frequency values (Hz) in mt_spectrogram Example: In this example we create some chirp data and run the multitaper spectrogram on it. import numpy as np # import numpy from scipy.signal import chirp # import chirp generation function # Set spectrogram params fs = 200 # Sampling Frequency frequency_range = [0, 25] # Limit frequencies from 0 to 25 Hz time_bandwidth = 3 # Set time-half bandwidth num_tapers = 5 # Set number of tapers (optimal is time_bandwidth*2 - 1) window_params = [4, 1] # Window size is 4s with step size of 1s min_nfft = 0 # No minimum nfft detrend_opt = 'constant' # detrend each window by subtracting the average multiprocess = True # use multiprocessing cpus = 3 # use 3 cores in multiprocessing weighting = 'unity' # weight each taper at 1 plot_on = True # plot spectrogram clim_scale = False # don't auto-scale the colormap verbose = True # print extra info xyflip = False # do not transpose spect output matrix # Generate sample chirp data t = np.arange(1/fs, 600, 1/fs) # Create 10 min time array from 1/fs to 600 stepping by 1/fs f_start = 1 # Set chirp freq range min (Hz) f_end = 20 # Set chirp freq range max (Hz) data = chirp(t, f_start, t[-1], f_end, 'logarithmic') # Compute the multitaper spectrogram spect, stimes, sfreqs = multitaper_spectrogram(data, fs, frequency_range, time_bandwidth, num_tapers, window_params, min_nfft, detrend_opt, multiprocess, cpus, weighting, plot_on, clim_scale, verbose, xyflip): This code is companion to the paper: "Sleep Neurophysiological Dynamics Through the Lens of Multitaper Spectral Analysis" Michael J. Prerau, Ritchie E. Brown, Matt T. Bianchi, Jeffrey M. Ellenbogen, Patrick L. Purdon December 7, 2016 : 60-92 DOI: 10.1152/physiol.00062.2015 which should be cited for academic use of this code. A full tutorial on the multitaper spectrogram can be found at: # http://www.sleepEEG.org/multitaper Copyright 2021 Michael J. Prerau Laboratory. - http://www.sleepEEG.org Authors: Michael J. Prerau, Ph.D., Thomas Possidente Last modified - 2/18/2021 Thomas Possidente __________________________________________________________________________________________________________________ """ # Process user input [ data, fs, frequency_range, time_bandwidth, num_tapers, winsize_samples, winstep_samples, window_start, num_windows, nfft, detrend_opt, plot_on, verbose ] = process_input(data, fs, frequency_range, time_bandwidth, num_tapers, window_params, min_nfft, detrend_opt, plot_on, verbose) # Set up spectrogram parameters [window_idxs, stimes, sfreqs, freq_inds] = process_spectrogram_params(fs, nfft, frequency_range, window_start, winsize_samples) # Display spectrogram parameters if verbose: display_spectrogram_props(fs, time_bandwidth, num_tapers, [winsize_samples, winstep_samples], frequency_range, detrend_opt) # Split data into segments and preallocate data_segments = data[window_idxs] # COMPUTE THE MULTITAPER SPECTROGRAM # STEP 1: Compute DPSS tapers based on desired spectral properties # STEP 2: Multiply the data segment by the DPSS Tapers # STEP 3: Compute the spectrum for each tapered segment # STEP 4: Take the mean of the tapered spectra # Compute DPSS tapers (STEP 1) dpss_tapers, dpss_eigen = dpss(winsize_samples, time_bandwidth, num_tapers, return_ratios=True) dpss_eigen = np.reshape(dpss_eigen, (num_tapers, 1)) # pre-compute weights if weighting == 'eigen': wt = dpss_eigen / num_tapers elif weighting == 'unity': wt = np.ones(num_tapers) / num_tapers wt = np.reshape(wt, (num_tapers, 1)) # reshape as column vector else: wt = 0 tic = timeit.default_timer() # start timer # set all but 1 arg of calc_mts_segment to constant (so we only have to supply one argument later) calc_mts_segment_plus_args = partial(calc_mts_segment, dpss_tapers=dpss_tapers, nfft=nfft, freq_inds=freq_inds, detrend_opt=detrend_opt, num_tapers=num_tapers, dpss_eigen=dpss_eigen, weighting=weighting, wt=wt) if multiprocess: # use multiprocessing if not cpus: # if cpus not specfied, use all but 1 pool = Pool(cpu_count() - 1) else: # else us specified number pool = Pool(cpus) # Compute multiprocess multitaper spect. mt_spectrogram = pool.map(calc_mts_segment_plus_args, data_segments) pool.close() pool.join() else: # if no multiprocessing, compute normally mt_spectrogram = np.apply_along_axis(calc_mts_segment_plus_args, 1, data_segments) # Compute one-sided PSD spectrum mt_spectrogram = np.asarray(mt_spectrogram) mt_spectrogram = mt_spectrogram.T dc_select = np.where(sfreqs == 0) nyquist_select = np.where(sfreqs == fs / 2) select = np.setdiff1d(np.arange(0, len(sfreqs)), [dc_select, nyquist_select]) mt_spectrogram = np.vstack([ mt_spectrogram[dc_select[0], :], 2 * mt_spectrogram[select, :], mt_spectrogram[nyquist_select[0], :] ]) / fs # Flip if requested if xyflip: mt_spectrogram = np.transpose(mt_spectrogram) # End timer and get elapsed compute time toc = timeit.default_timer() elapsed_time = toc - tic if verbose: print("\n Multitaper compute time: " + str(elapsed_time) + " seconds") # Plot multitaper spectrogram if plot_on: # Eliminate bad data from colormap scaling spect_data = mt_spectrogram clim = np.percentile( spect_data, [5, 95]) # Scale colormap from 5th percentile to 95th plt.figure(1, figsize=(10, 5)) librosa.display.specshow(nanpow2db(mt_spectrogram), x_axis='time', y_axis='linear', x_coords=stimes, y_coords=sfreqs, shading='auto', cmap="jet") plt.colorbar(label='Power (dB)') plt.xlabel("Time (HH:MM:SS)") plt.ylabel("Frequency (Hz)") if clim_scale: plt.clim(clim) # actually change colorbar scale plt.show() # Put outputs into better format for output #stimes = np.mat(stimes) #sfreqs = np.mat(sfreqs) if all(mt_spectrogram.flatten() == 0): print("\n Data was all zeros, no output") return mt_spectrogram, stimes, sfreqs
def multi_taper_fft(Win, spika, spiko): TT = spika.shape[1] N = Win Trials = spika.shape[0] TTa = 1 + np.floor(Win / 2) TTb = TT - np.floor(Win / 2) TTb = int(TTb) W = 2.5 NW = np.floor(2 * ((Win / 1000) * W)) / 2 tapers = np.array(windows.dpss(Win, NW, 4)) # print("TTTq" , tap[0]) KW = np.floor(2 * NW - 1) pad = 0 fpass = np.asarray([0.001, 0.088]) Fs = 1 # nfft = np.power(2, nextpow2(N)) df = Fs / nfft freqreal = np.arange(0, Fs - df + df, df) #all possible frequencied # findx = [(freqreal >= fpass[0] ) and (freqreal <= fpass[1])] findx = np.where(np.logical_and(freqreal >= fpass[0], freqreal <= fpass[1])) findx = findx[0] # findx = np.where((freqreal >= fpass[0]) & (freqreal <= fpass[1])) f = freqreal[findx] for r in range(2): zcross_pow = zispika_pow = zispike_pow = zspike_count = zspika_count = np.empty( [Trials, nfft]) if r == 0: tr_perm = np.arange(0, Trials) else: mid = np.floor(Trials / 8) + np.floor(np.random.rand() * (3 * Trials / 4)) tr_perm = np.append(np.arange(mid, Trials), np.arange(1, mid - 1)) for tr in range(Trials): cross_pow = [] ispika_pow = [] ispike_pow = [] spike_count = [] aspike_count = [] # spika_count = [] TTa = 0 Tstep = np.floor(Win / 4) TTb = Win while (TTb <= TT): # TTbu = int(TTb)+1 aspiker = spika[tr, TTa:TTb] naspiker = aspiker - np.mean(aspiker) #TODO differnet numbers nspiker = spiko[tr_perm[tr], TTa:TTb] spiker = nspiker - np.mean(nspiker) for kt in range(int(KW)): aspikero = np.multiply(naspiker, tapers[kt, :]) spikero = np.multiply(spiker, tapers[kt, :]) J1 = np.fft.fft(aspikero, nfft) J2 = np.fft.fft(spikero, nfft) if (len(ispika_pow) == 0): ispika_pow = np.multiply(J1, np.conj(J1)) ispike_pow = np.multiply(J2, np.conj(J2)) cross_pow = np.multiply(J1, np.conj(J2)) spike_count = nspiker.sum() aspike_count = aspiker.sum() else: ispika_pow = ispika_pow + np.multiply(J1, np.conj(J1)) ispike_pow = ispike_pow + np.multiply(J2, np.conj(J2)) cross_pow = cross_pow + np.multiply(J1, np.conj(J2)) spike_count = spike_count + nspiker.sum() aspike_count = aspike_count + aspiker.sum() TTa = TTa + Tstep TTb = TTb + Tstep zcross_pow[tr] = cross_pow zispika_pow[tr] = ispika_pow zispike_pow[tr] = ispike_pow zspike_count[tr] = spike_count zspika_count[tr] = aspike_count if (r == 0): cohjack = [] phajack = [] spajack = [] spijack = [] for tr in range(Trials): cross_pow = np.delete(zcross_pow, tr, axis=0) ispike_pow = np.delete(zispike_pow, tr, axis=0) ispika_pow = np.delete(zispika_pow, tr, axis=0) aspike_count = np.delete(zspike_count, tr, axis=0) spike_count = np.delete(zspika_count, tr, axis=0) cross_pow = np.sum(cross_pow, axis=0) ispike_pow = np.sum(ispike_pow, axis=0) ispika_pow = np.sum(ispika_pow, axis=0) spike_count = np.sum(spike_count, axis=0) aspike_count = np.sum(aspike_count, axis=0) coh = np.divide( cross_pow, np.multiply(np.sqrt(ispike_pow), np.sqrt(ispika_pow))) #coh mokhtalete! coho = np.abs(coh[findx]) phaso = np.angle(coh(findx)) #TODO spika_pow = ispika_pow[findx] / aspike_count spike_pow = ispike_pow[findx] / spike_count ifcoher = 1000 * freqreal[findx] cohjack = np.append(cohjack, coho) phajack = np.append(phajack, coh) spajack = np.append(spajack, spika_pow) spijack = np.append(spijack, spike_pow) coho = np.mean(cohjack) scoho = np.std(cohjack) * np.sqrt(Trials - 1) pha = np.mean(phajack) phaso = np.angle(pha[findx]) #TODO phabo = [] for iz in range(len(phajack)): y = np.angle(phajack[iz, :]) it = np.where((y[findx] - phaso) > np.pi) y[findx[it]] = y[findx[it]] - (2 * np.pi) it = np.where((y[findx] - phaso) < -np.pi) y[findx[it]] = y[findx[it]] + (2 * np.pi) phabo = np.append(phaso, y[findx]) sphaso = np.std(phabo) * np.sqrt(Trials - 1) spika_pow = np.mean(spajack) sspika_pow = np.std(spajack) * np.sqrt(Trials - 1) spike_pow = np.mean(spijack) sspike_pow = np.std(spijack) * np.sqrt(Trials - 1) else: cross_pow = np.sum(zcross_pow) ispike_pow = np.sum(zispike_pow) ispika_pow = np.sum(zispika_pow) coh = np.divide( cross_pow, np.multiply(np.sqrt(ispike_pow), np.sqrt(ispika_pow))) rcoho = np.abs(coh[findx]) rphaso = np.angle(coh[findx]) #TODO
def _dpss_wavelet(sfreq, freqs, n_cycles=7, time_bandwidth=4.0, zero_mean=False): """Compute Wavelets for the given frequency range Parameters ---------- sfreq : float Sampling Frequency. freqs : ndarray, shape (n_freqs,) The frequencies in Hz. n_cycles : float | ndarray, shape (n_freqs,) The number of cycles globally or for each frequency. Defaults to 7. time_bandwidth : float, (optional) Time x Bandwidth product. The number of good tapers (low-bias) is chosen automatically based on this to equal floor(time_bandwidth - 1). Default is 4.0, giving 3 good tapers. Returns ------- Ws : list of array Wavelets time series """ Ws = list() if time_bandwidth < 2.0: raise ValueError("time_bandwidth should be >= 2.0 for good tapers") n_taps = int(np.floor(time_bandwidth - 1)) n_cycles = np.atleast_1d(n_cycles) if n_cycles.size != 1 and n_cycles.size != len(freqs): raise ValueError("n_cycles should be fixed or defined for " "each frequency.") for m in range(n_taps): Wm = list() for k, f in enumerate(freqs): if len(n_cycles) != 1: this_n_cycles = n_cycles[k] else: this_n_cycles = n_cycles[0] t_win = this_n_cycles / float(f) t = np.arange(0., t_win, 1.0 / sfreq) # Making sure wavelets are centered before tapering oscillation = np.exp(2.0 * 1j * np.pi * f * (t - t_win / 2.)) # Get dpss tapers tapers, conc = dpss(t.shape[0], time_bandwidth / 2., n_taps, return_ratios=True) Wk = oscillation * tapers[m] if zero_mean: # to make it zero mean real_offset = Wk.mean() Wk -= real_offset Wk /= sqrt(0.5) * linalg.norm(Wk.ravel()) Wm.append(Wk) Ws.append(Wm) return Ws
def pmtm(x, dt, nw=3, cl=0.95): """Returns Thomson’s multitaper power spectral density (PSD) estimate, pxx, of the input signal, x. Slightly modified from Peter Huybers's matlab code, pmtmPH.m Parameters ---------- x : numpy array Time series to analyze dt : float Time step nw : float The time-halfbandwidth product cl : float Confidence interval to calculate and display Returns ------- P : numpy array PSD estimate s : numpy array Associated frequencies ci : numpy array Associated confidence interval """ from scipy.signal import windows nfft = np.shape(x)[0] nx = np.shape(x)[0] k = min(np.round(2. * nw), nx) k = int(max(k - 1, 1)) s = np.arange(0, 1 / dt, 1 / (nfft * dt)) # Compute the discrete prolate spheroidal sequences [E, V] = windows.dpss(nx, nw, k, return_ratios=True) E = E.T # Compute the windowed DFTs. Pk = np.abs(np.fft.fft(E * x[:, np.newaxis], nfft, axis=0))**2 if k > 1: sig2 = np.dot(x[np.newaxis, :], x[:, np.newaxis])[0][0] / nx # initial spectrum estimate P = ((Pk[:, 0] + Pk[:, 1]) / 2)[:, np.newaxis] Ptemp = np.zeros((nfft, 1)) P1 = np.zeros((nfft, 1)) tol = .0005 * sig2 / nfft a = sig2 * (1 - V) while (np.sum(np.abs(P - P1) / nfft) > tol): b = np.repeat(P, k, axis=-1) / (P * V[np.newaxis, :] + np.ones( (nfft, 1)) * a[np.newaxis, :]) wk = (b**2) * (np.ones((nfft, 1)) * V[np.newaxis, :]) P1 = (np.sum(wk * Pk, axis=-1) / np.sum(wk, axis=-1))[:, np.newaxis] Ptemp = np.empty_like(P1) Ptemp[:] = P1 P1 = np.empty_like(P) P1[:] = P P = np.empty_like(Ptemp) P[:] = Ptemp # Determine equivalent degrees of freedom, see Percival and Walden 1993. v = ((2 * np.sum( (b**2) * (np.ones((nfft, 1)) * V[np.newaxis, :]), axis=-1)**2) / np.sum( (b**4) * (np.ones((nfft, 1)) * V[np.newaxis, :]**2), axis=-1)) else: P = np.empty_like(Pk) P[:] = Pk v = 2 * np.ones((nfft, 1)) select = (np.arange(0, (nfft + 1) / 2.)).astype('int') P = P[select].flatten() s = s[select].flatten() v = v[select].flatten() # Chi-squared 95% confidence interval # approximation from Chamber's et al 1983; see Percival and Walden p.256, 1993 ci = np.empty((np.shape(v)[0], 2)) ci[:, 0] = 1. / (1 - 2 / (9 * v) - 1.96 * np.sqrt(2 / (9 * v)))**3 ci[:, 1] = 1. / (1 - 2 / (9 * v) + 1.96 * np.sqrt(2 / (9 * v)))**3 return P, s, ci
def pmtm(data, NW = 4, Fs = None, NFFT = None, kind = 'chronux'): ''' Compute the power spectrum via Multitapering. If the number of tapers == 1, it is a stft (short-time fourier transform) Parameters ---------- data : np.ndarray, shape (n_samples, n_channels) Input data vector. NW : int / float, optional Time Half-Bandwidth Product. The default is 4. Fs : int / float, optional Sampling frequency. The default is 2*np.pi. NFFT : int, optional Length of the signal for the FFT analisys. The default value is max(256, 2**nextpow2(n_samples)). kind : str, optional Type of computation. It can be "milekovic" or "chronux". The default value is chronux. Returns ------- Sk : np.ndarray, shape (n_windows, n_channels) Power spectrum computed via MTM. ''' if kind not in ['milekovic','chronux']: raise Exception('ERROR: kinds can only be "milekovic" or "chronux". You inputed "{}"!'.format(kind)) # Number of channels if data.ndim == 1: data = np.expand_dims(data, axis=1) else: data = transpose(data, 'column') # Data length N = data.shape[0] n_channels = data.shape[1] if Fs == None: Fs = 2*np.pi # set the NFFT if NFFT==None: NFFT = max(256, 2**nextpow2(N)) w = pmtm_params(Fs, NFFT) # Compute tapers if kind in ['chronux']: if NW == 1: tapers = np.expand_dims(np.hamming(N),1) * np.sqrt(Fs) else: tapers = dpss(N, NW, Kmax=2*NW-1) * np.sqrt(Fs) elif kind in ['milekovic']: if NW == 1: tapers = np.expand_dims(np.hamming(N)/np.linalg.norm(np.hamming(N)),1) else: tapers = dpss(N, NW, Kmax=2*NW-1) tapers = transpose(tapers,'column') n_tapers = tapers.shape[1] # Add channel indices to tapers tapers = np.tile(np.expand_dims(tapers,2),(1,1,n_channels)) # Add taper indices to data data = np.tile(np.expand_dims(data,1),(1,n_tapers,1)) data_proj = data*tapers if kind in ['chronux']: Sk_complex = (np.fft.fft(data_proj.T, NFFT)/Fs).T elif kind in ['milekovic']: Sk_complex = (np.fft.fft(data_proj.T, NFFT)).T # Sk = np.mean(abs(Sk_complex), axis = 1) # Sk = np.empty((NFFT, n_channels)) # Sk[:] = np.NaN # for channel in range(n_channels): # # Compute the FFT # Sk_complex = np.fft.fft(np.multiply(tapers.transpose(), data[:,channel]), NFFT)/Fs # # Compute the whole power spectrum [Power] # # Sk[:,channel] = np.mean(abs(Sk_complex)**2, axis = 0) # Sk[:,channel] = np.mean(abs(Sk_complex), axis = 0) return Sk_complex, w, NFFT