def power_spectrum_from_acf(s, sample_rate, lags): """ Compute the power spectrum of a signal s by taking the FFT of the auto-correlation function. :param s: The signal. :param sample_rate: The sample rate of the signal s. :param lags: integer-valued lags, should be symmetric around zero. :return: freq,psd: The frequency of the power spectrum and the power spectrum """ acf = correlation_function(s, s, lags, mean_subtract=True, normalize=True) psd = np.abs(fft(acf))**2 freq = fftfreq(len(acf), d=1. / sample_rate) i = freq >= 0 return freq[i], psd[i]
def draw_coherency_matrix( bird, block, segment, hemi, stim_id, trial, syllable_index, data_dir="/auto/tdrive/mschachter/data", exp=None, save=True, ): # load up the experiment if exp is None: bird_dir = os.path.join(data_dir, bird) exp_file = os.path.join(bird_dir, "%s.h5" % bird) stim_file = os.path.join(bird_dir, "stims.h5") exp = Experiment.load(exp_file, stim_file) seg = exp.get_segment(block, segment) # get the start and end times of the stimulus etable = exp.get_epoch_table(seg) i = etable["id"] == stim_id stim_times = zip(etable[i]["start_time"].values, etable[i]["end_time"].values) stim_times.sort(key=operator.itemgetter(0)) start_time, end_time = stim_times[trial] stim_dur = float(end_time - start_time) # get a slice of the LFP lfp_data = exp.get_lfp_slice(seg, start_time, end_time) electrode_indices, lfps, sample_rate = lfp_data[hemi] # rescale the LFPs to they are in uV lfps *= 1e6 # get the log spectrogram of the stimulus stim_spec_t, stim_spec_freq, stim_spec = exp.get_spectrogram_slice(seg, start_time, end_time) stim_spec_t = np.linspace(0, stim_dur, len(stim_spec_t)) stim_spec_dt = np.diff(stim_spec_t)[0] nz = stim_spec > 0 stim_spec[nz] = 20 * np.log10(stim_spec[nz]) + 100 stim_spec[stim_spec < 0] = 0 # get the amplitude envelope amp_env = stim_spec.std(axis=0, ddof=1) amp_env -= amp_env.min() amp_env /= amp_env.max() # segment the amplitude envelope into syllables merge_thresh = int(0.002 * sample_rate) events = break_envelope_into_events(amp_env, threshold=0.05, merge_thresh=merge_thresh) # translate the event indices into actual times events *= stim_spec_dt syllable_start, syllable_end, syllable_max_amp = events[syllable_index] syllable_si = int(syllable_start * sample_rate) syllable_ei = int(syllable_end * sample_rate) # compute all cross and auto-correlations if hemi == "L": electrode_order = ROSTRAL_CAUDAL_ELECTRODES_LEFT else: electrode_order = ROSTRAL_CAUDAL_ELECTRODES_RIGHT lags = np.arange(-20, 21) lags_ms = (lags / sample_rate) * 1e3 window_fraction = 0.35 noise_db = 25.0 nelectrodes = len(electrode_order) cross_mat = np.zeros([nelectrodes, nelectrodes, len(lags)]) for i in range(nelectrodes): for j in range(nelectrodes): lfp1 = lfps[i, syllable_si:syllable_ei] lfp2 = lfps[j, syllable_si:syllable_ei] if i != j: x = coherency(lfp1, lfp2, lags, window_fraction=window_fraction, noise_floor_db=noise_db) else: x = correlation_function(lfp1, lfp2, lags) _e1 = electrode_indices[i] _e2 = electrode_indices[j] i1 = electrode_order.index(_e1) i2 = electrode_order.index(_e2) # print 'i=%d, j=%d, e1=%d, e2=%d, i1=%d, i2=%d' % (i, j, _e1, _e2, i1, i2) cross_mat[i1, i2, :] = x # make a plot figsize = (24.0, 13.5) fig = plt.figure(figsize=figsize) plt.subplots_adjust(top=0.95, bottom=0.05, left=0.03, right=0.99, hspace=0.10) gs = plt.GridSpec(nelectrodes, nelectrodes) for i in range(nelectrodes): for j in range(i + 1): ax = plt.subplot(gs[i, j]) plt.axhline(0, c="k") plt.axvline(0, c="k") _e1 = electrode_order[i] _e2 = electrode_order[j] plt.plot(lags_ms, cross_mat[i, j, :], "k-", linewidth=2.0) plt.xticks([]) plt.yticks([]) plt.axis("tight") plt.ylim(-0.5, 1.0) if j == 0: plt.ylabel("E%d" % electrode_order[i]) if i == nelectrodes - 1: plt.xlabel("E%d" % electrode_order[j]) if save: fname = os.path.join(get_this_dir(), "coherency_matrix.svg") plt.savefig(fname, facecolor="w", edgecolor="none")
def fundEstimator(soundIn, fs, t=None, debugFig = 0, maxFund = 1500, minFund = 300, lowFc = 200, highFc = 6000, minSaliency = 0.5): """ Estimates the fundamental frequency of a complex sound. soundIn is the sound pressure waveformlog spectrogram. fs is the sampling rate t is a vector of time values in s at which the fundamental will be estimated. The sound must include at least 1024 sample points The optional parameter with defaults are Some user parameters (should be part of the function at some time) debugFig = 0 Set to zero to eliminate figures. maxFund = 1500 Maximum fundamental frequency minFund = 300 Minimum fundamental frequency lowFc = 200 Low frequency cut-off for band-passing the signal prior to auto-correlation. highFc = 6000 High frequency cut-off minSaliency = 0.5 Threshold in the auto-correlation for minimum saliency - returns NaN for pitch values is saliency is below this number Returns sal - the time varying pitch saliency - a number between 0 and 1 corresponding to relative size of the first auto-correlation peak fund - the time-varying fundamental in Hz at the same resolution as the spectrogram. fund2 - a second peak in the spectrum - not a multiple of the fundamental a sign of a second voice form1 - the first formant, if it exists form2 - the second formant, if it exists form3 - the third formant, if it exists soundLen - length of sal, fund, fund2, form1, form2, form3 """ # Band-pass filtering signal prior to auto-correlation soundLen = len(soundIn) nfilt = 1024 if soundLen < 1024: print 'Error in fundEstimator: sound too short for bandpass filtering, len(soundIn)=%d\n' % soundLen return (0, 0, 0, 0, 0, 0, 0) # high pass filter the signal highpassFilter = firwin(nfilt-1, 2*lowFc/fs, pass_zero=False) padlen = min(soundLen-10, 3*len(highpassFilter)) soundIn = filtfilt(highpassFilter, [1.0], soundIn, padlen=padlen) # low pass filter the signal lowpassFilter = firwin(nfilt, 2*highFc/fs) padlen = min(soundLen-10, 3*len(lowpassFilter)) soundIn = filtfilt(lowpassFilter, [1.0], soundIn, padlen=padlen) # Plot a spectrogram? if debugFig: plt.figure(9) (tDebug ,freqDebug ,specDebug , rms) = spectrogram(soundIn, fs, 1000.0, 50, min_freq=0, max_freq=10000, nstd=6, log=True, noise_level_db=50, rectify=True) plot_spectrogram(tDebug, freqDebug, specDebug) # Initializations and useful variables if t is None: # initialize t to be spaced by 500us increments sound_dur = len(soundIn) / fs _si = 1e-3 npts = int(sound_dur / _si) t = np.arange(npts) * _si nt=len(t) soundRMS = np.zeros(nt) fund = np.zeros(nt) fund2 = np.zeros(nt) sal = np.zeros(nt) form1 = np.zeros(nt) form2 = np.zeros(nt) form3 = np.zeros(nt) # Calculate the size of the window for the auto-correlation alpha = 5 # Number of sd in the Gaussian window winLen = int(np.fix((2.0*alpha/minFund)*fs)) # Length of Gaussian window based on minFund if (winLen%2 == 0): # Make a symmetric window winLen += 1 winLen2 = 2**12+1 # This looks like a good size for LPC - 4097 points gt, w = gaussian_window(winLen, alpha) gt2, w2 = gaussian_window(winLen2, alpha) maxlags = int(2*ceil((float(fs)/minFund))) # First calculate the rms in each window for it in range(nt): tval = t[it] # Center of window in time tind = int(np.fix(tval*fs)) # Center of window in ind tstart = tind - (winLen-1)/2 tend = tind + (winLen-1)/2 if tstart < 0: winstart = - tstart tstart = 0 else: winstart = 0 if tend >= soundLen: windend = winLen - (tend-soundLen+1) - 1 tend = soundLen-1 else: windend = winLen-1 soundWin = soundIn[tstart:tend]*w[winstart:windend] soundRMS[it] = np.std(soundWin) soundRMSMax = max(soundRMS) # Calculate the auto-correlation in windowed segments and obtain 4 guess values of the fundamental # fundCorrGuess - guess from the auto-correlation function # fundCorrAmpGuess - guess form the amplitude of the auto-correlation function # fundCepGuess - guess from the cepstrum # fundStackGuess - guess taken from a fit of the power spectrum with a harmonic stack, using the fundCepGuess as a starting point # Current version use fundStackGuess as the best estimate... soundlen = 0 for it in range(nt): fund[it] = float('nan') sal[it] = float('nan') fund2[it] = float('nan') form1[it] = float('nan') form2[it] = float('nan') form3[it] = float('nan') if (soundRMS[it] < soundRMSMax*0.1): continue soundlen += 1 tval = t[it] # Center of window in time tind = int(np.fix(tval*fs)) # Center of window in ind tstart = tind - (winLen-1)/2 tend = tind + (winLen-1)/2 if tstart < 0: winstart = - tstart tstart = 0 else: winstart = 0 if tend >= soundLen: windend = winLen - (tend-soundLen+1) - 1 tend = soundLen-1 else: windend = winLen-1 tstart2 = tind - (winLen2-1)/2 tend2 = tind + (winLen2-1)/2 if tstart2 < 0: winstart2 = - tstart2 tstart2 = 0 else: winstart2 = 0 if tend2 >= soundLen: windend2 = winLen2 - (tend2-soundLen+1) - 1 tend2 = soundLen-1 else: windend2 = winLen2-1 soundWin = soundIn[tstart:tend]*w[winstart:windend] soundWin2 = soundIn[tstart2:tend2]*w2[winstart2:windend2] # Apply LPC to get time-varying formants and one additional guess for the fundamental frequency A, E, K = talkbox.lpc(soundWin2, 8) # 8 degree polynomial rts = np.roots(A) # Find the roots of A rts = rts[np.imag(rts)>=0] # Keep only half of them angz = np.arctan2(np.imag(rts),np.real(rts)) # Calculate the frequencies and the bandwidth of the formants frqsFormants = angz*(fs/(2*np.pi)) indices = np.argsort(frqsFormants) bw = -1/2*(fs/(2*np.pi))*np.log(np.abs(rts)) # Keep formants above 1000 Hz and with bandwidth < 1000 formants = [] for kk in indices: if ( frqsFormants[kk]>1000 and bw[kk] < 1000): formants.append(frqsFormants[kk]) formants = np.array(formants) if len(formants) > 0 : form1[it] = formants[0] if len(formants) > 1 : form2[it] = formants[1] if len(formants) > 2 : form3[it] = formants[2] # Calculate the auto-correlation lags = np.arange(-maxlags, maxlags+1, 1) autoCorr = correlation_function(soundWin, soundWin, lags) ind0 = int(mlab.find(lags == 0)) # need to find lag zero index # find peaks indPeaksCorr = detect_peaks(autoCorr, mph=max(autoCorr)/10) # Eliminate center peak and all peaks too close to middle indPeaksCorr = np.delete(indPeaksCorr,mlab.find( (indPeaksCorr-ind0) < fs/maxFund)) pksCorr = autoCorr[indPeaksCorr] # Find max peak if len(pksCorr)==0: pitchSaliency = 0.1 # 0.1 goes with the detection of peaks greater than max/10 else: indIndMax = mlab.find(pksCorr == max(pksCorr))[0] indMax = indPeaksCorr[indIndMax] fundCorrGuess = fs/abs(lags[indMax]) pitchSaliency = autoCorr[indMax]/autoCorr[ind0] sal[it] = pitchSaliency if sal[it] < minSaliency: continue # Calculate the envelope of the auto-correlation after rectification envCorr = temporal_envelope(autoCorr, fs, cutoff_freq=maxFund, resample_rate=None) locsEnvCorr = detect_peaks(envCorr, mph=max(envCorr)/10) pksEnvCorr = envCorr[locsEnvCorr] # The max peak should be around zero indIndEnvMax = mlab.find(pksEnvCorr == max(pksEnvCorr)) # Take the first peak not in the middle if indIndEnvMax+2 > len(locsEnvCorr): fundCorrAmpGuess = fundCorrGuess indEnvMax = indMax else: indEnvMax = locsEnvCorr[indIndEnvMax+1] fundCorrAmpGuess = fs/lags[indEnvMax] # Calculate power spectrum and cepstrum Y = fft(soundWin, n=winLen+1) f = (fs/2.0)*(np.array(range((winLen+1)/2+1), dtype=float)/float((winLen+1)/2)) fhigh = mlab.find(f >= highFc)[0] powSound = 20.0*np.log10(np.abs(Y[0:(winLen+1)/2+1])) # This is the power spectrum powSoundGood = powSound[0:fhigh] maxPow = max(powSoundGood) powSoundGood = powSoundGood - maxPow # Set zero as the peak amplitude powSoundGood[powSoundGood < - 60] = -60 # Calculate coarse spectral enveloppe p = np.polyfit(f[0:fhigh], powSoundGood, 3) powAmp = np.polyval(p, f[0:fhigh]) # Cepstrum CY = dct(powSoundGood-powAmp, norm = 'ortho') tCY = 2000.0*np.array(range(len(CY)))/fs # Units of Cepstrum in ms fCY = 1000.0/tCY # Corresponding fundamental frequency in Hz. lowInd = mlab.find(fCY<lowFc) if lowInd.size > 0: flowCY = mlab.find(fCY < lowFc)[0] else: flowCY = fCY.size fhighCY = mlab.find(fCY < highFc)[0] # Find peak of Cepstrum indPk = mlab.find(CY[fhighCY:flowCY] == max(CY[fhighCY:flowCY]))[-1] indPk = fhighCY + indPk fmass = 0 mass = 0 indTry = indPk while (CY[indTry] > 0): fmass = fmass + fCY[indTry]*CY[indTry] mass = mass + CY[indTry] indTry = indTry + 1 if indTry >= len(CY): break indTry = indPk - 1 if (indTry >= 0 ): while (CY[indTry] > 0): fmass = fmass + fCY[indTry]*CY[indTry] mass = mass + CY[indTry] indTry = indTry - 1 if indTry < 0: break fGuess = fmass/mass if (fGuess == 0 or np.isnan(fGuess) or np.isinf(fGuess) ): # Failure of cepstral method fGuess = fundCorrGuess fundCepGuess = fGuess # Force fundamendal to be bounded if (fundCepGuess > maxFund ): i = 2 while(fundCepGuess > maxFund): fundCepGuess = fGuess/i i += 1 elif (fundCepGuess < minFund): i = 2 while(fundCepGuess < minFund): fundCepGuess = fGuess*i i += 1 # Fit Gaussian harmonic stack maxPow = max(powSoundGood-powAmp) # This is the matlab code... # fundFitCep = NonLinearModel.fit(f(1:fhigh)', powSoundGood'-powAmp, @synSpect, [fundCepGuess ones(1,9).*log(maxPow)]) # modelPowCep = synSpect(double(fundFitCep.Coefficients(:,1)), f(1:fhigh)) vars = np.concatenate(([fundCepGuess], np.ones(9)*np.log(maxPow))) bout = leastsq(residualSyn, vars, args = (f[0:fhigh], powSoundGood-powAmp)) modelPowCep = synSpect(bout[0], f[0:fhigh]) errCep = sum((powSoundGood - powAmp - modelPowCep)**2) vars = np.concatenate(([fundCepGuess*2], np.ones(9)*np.log(maxPow))) bout2 = leastsq(residualSyn, vars, args = (f[0:fhigh], powSoundGood-powAmp)) modelPowCep2 = synSpect(bout2[0], f[0:fhigh]) errCep2 = sum((powSoundGood - powAmp - modelPowCep2)**2) if errCep2 < errCep: bout = bout2 modelPowCep = modelPowCep2 fundStackGuess = bout[0][0] if (fundStackGuess > maxFund) or (fundStackGuess < minFund ): fundStackGuess = float('nan') # A second cepstrum for the second voice # CY2 = dct(powSoundGood-powAmp'- modelPowCep) fund[it] = fundStackGuess if not np.isnan(fundStackGuess): powLeft = powSoundGood- powAmp - modelPowCep maxPow2 = max(powLeft) f2 = 0 if ( maxPow2 > maxPow*0.5): # Possible second peak in central area as indicator of second voice. f2 = f[mlab.find(powLeft == maxPow2)] if ( f2 > 1000 and f2 < 4000): if (pitchSaliency > minSaliency): fund2[it] = f2 #% modelPowCorrAmp = synSpect(double(fundFitCorrAmp.Coefficients(:,1)), f(1:fhigh)) #% #% errCorr = sum((powSoundGood - powAmp' - modelPowCorr).^2) #% errCorrAmp = sum((powSoundGood - powAmp' - modelPowCorrAmp).^2) #% errCorrSum = sum((powSoundGood - powAmp' - (modelPowCorr+modelPowCorrAmp) ).^2) #% #% f1 = double(fundFitCorr.Coefficients(1,1)) #% f2 = double(fundFitCorrAmp.Coefficients(1,1)) #% #% if (pitchSaliency > minSaliency) #% if (errCorr < errCorrAmp) #% fund(it) = f1 #% if errCorrSum < errCorr #% fund2(it) = f2 #% end #% else #% fund(it) = f2 #% if errCorrSum < errCorrAmp #% fund2(it) = f1 #% end #% end #% #% end if (debugFig ): plt.figure(10) plt.subplot(4,1,1) plt.cla() plt.plot(soundWin) # f1 = double(fundFitCorr.Coefficients(1,1)) # f2 = double(fundFitCorrAmp.Coefficients(1,1)) titleStr = 'Saliency = %.2f Pitch AC = %.2f (Hz) Pitch ACA = %.2f Pitch C %.2f (Hz)' % (pitchSaliency, fundCorrGuess, fundCorrAmpGuess, fundStackGuess) plt.title(titleStr) plt.subplot(4,1,2) plt.cla() plt.plot(1000*(lags/fs), autoCorr) plt.plot([1000.*lags[indMax]/fs, 1000*lags[indMax]/fs], [0, autoCorr[ind0]], 'k') plt.plot(1000.*lags/fs, envCorr, 'r', linewidth= 2) plt.plot([1000*lags[indEnvMax]/fs, 1000*lags[indEnvMax]/fs], [0, autoCorr[ind0]], 'g') plt.xlabel('Time (ms)') plt.subplot(4,1,3) plt.cla() plt.plot(f[0:fhigh],powSoundGood) plt.axis([0, highFc, -60, 0]) plt.plot(f[0:fhigh], powAmp, 'b--') plt.plot(f[0:fhigh], modelPowCep + powAmp, 'k') # plt.plot(f(1:fhigh), modelPowCorrAmp + powAmp', 'g') for ih in range(1,6): plt.plot([fundCorrGuess*ih, fundCorrGuess*ih], [-60, 0], 'r') plt.plot([fundStackGuess*ih, fundStackGuess*ih], [-60, 0], 'k') if f2 != 0: plt.plot([f2, f2], [-60, 0], 'g') plt.xlabel('Frequency (Hz)') # title(sprintf('Err1 = %.1f Err2 = %.1f', errCorr, errCorrAmp)) plt.subplot(4,1,4) plt.cla() plt.plot(tCY, CY) # plot(tCY, CY2, 'k--') plt.plot([1000/fundCorrGuess, 1000/fundCorrGuess], [0, max(CY)], 'r') plt.plot([1000/fundStackGuess, 1000/fundStackGuess], [0, max(CY)], 'k') #% plot([(pkClosest-1)/fs (pkClosest-1)/fs], [0 max(CY)], 'g') #% if ~isempty(ipk2) #% plot([(pk2-1)/fs (pk2-1)/fs], [0 max(CY)], 'b') #% end #% for ip=1:length(pks) #% plot([(locs(ip)-1)/fs (locs(ip)-1)/fs], [0 pks(ip)/4], 'r') #% end plt.axis([0, 1000*np.size(CY)/(2*fs), 0, max(CY)]) plt.xlabel('Time (ms)') plt.pause(1) # Fix formants. meanf1 = np.mean(form1[~np.isnan(form1)]) meanf2 = np.mean(form2[~np.isnan(form2)]) meanf3 = np.mean(form3[~np.isnan(form3)]) for it in range(nt): if ~np.isnan(form1[it]): df11 = np.abs(form1[it]-meanf1) df12 = np.abs(form1[it]-meanf2) df13 = np.abs(form1[it]-meanf3) if df12 < df11: if df13 < df12: if ~np.isnan(form3[it]): df33 = np.abs(form3[it]-meanf3) if df13 < df33: form3[it] = form1[it] else: form3[it] = form1[it] else: if ~np.isnan(form2[it]): df22 = np.abs(form2[it]-meanf2) if df12 < df22: form2[it] = form1[it] else: form2[it] = form1[it] form1[it] = float('nan') if ~np.isnan(form2[it]): df21 = np.abs(form2[it]-meanf1) df22 = np.abs(form2[it]-meanf2) df23 = np.abs(form2[it]-meanf3) if df21 < df22 : if ~np.isnan(form1[it]): df11 = np.abs(form1[it]-meanf1) if df21 < df11: form1[it] = form2[it] else: form1[it] = form2[it] form2[it] = float('nan') elif df23 < df22: if ~np.isnan(form3[it]): df33 = np.abs(form3[it]-meanf3) if df23 < df33: form3[it] = form2[it] else: form3[it] = form2[it] form2[it] = float('nan') if ~np.isnan(form3[it]): df31 = np.abs(form3[it]-meanf1) df32 = np.abs(form3[it]-meanf2) df33 = np.abs(form3[it]-meanf3) if df32 < df33: if df31 < df32: if ~np.isnan(form1[it]): df11 = np.abs(form1[it]-meanf1) if df31 < df11: form1[it] = form3[it] else: form1[it] = form3[it] else: if ~np.isnan(form2[it]): df22 = np.abs(form2[it]-meanf2) if df32 < df22: form2[it] = form3[it] else: form2[it] = form3[it] form3[it] = float('nan') return (sal, fund, fund2, form1, form2, form3, soundlen)
def test_cross_psd(self): np.random.seed(1234567) sr = 1000.0 dur = 1.0 nt = int(dur * sr) t = np.arange(nt) / sr # create a simple signal freqs = list() freqs.extend(np.arange(8, 12)) freqs.extend(np.arange(60, 71)) freqs.extend(np.arange(130, 151)) s1 = np.zeros([nt]) for f in freqs: s1 += np.sin(2 * np.pi * f * t) s1 /= s1.max() # create a noise corrupted, bandpassed filtered version of s1 noise = np.random.randn(nt) * 1e-1 # s2 = convolve1d(s1, filt, mode='mirror') + noise s2 = bandpass_filter(s1, sample_rate=sr, low_freq=40., high_freq=90.) s2 /= s2.max() s2 += noise # compute the signal's power spectrums welch_freq1, welch_psd1 = welch(s1, fs=sr) welch_freq2, welch_psd2 = welch(s2, fs=sr) welch_psd_max = max(welch_psd1.max(), welch_psd2.max()) welch_psd1 /= welch_psd_max welch_psd2 /= welch_psd_max # compute the auto-correlation functions lags = np.arange(-200, 201) acf1 = correlation_function(s1, s1, lags, normalize=True) acf2 = correlation_function(s2, s2, lags, normalize=True) # compute the cross correlation functions cf12 = correlation_function(s1, s2, lags, normalize=True) coh12 = coherency(s1, s2, lags, window_fraction=0.75, noise_floor_db=100.) # do an FFT shift to the lags and the window, otherwise the FFT of the ACFs is not equal to the power # spectrum for some numerical reason shift_lags = fftshift(lags) if len(lags) % 2 == 1: # shift zero from end of shift_lags to beginning shift_lags = np.roll(shift_lags, 1) acf1_shift = correlation_function(s1, s1, shift_lags) acf2_shift = correlation_function(s2, s2, shift_lags) # compute the power spectra from the auto-spectra ps1 = fft(acf1_shift) ps1_freq = fftfreq(len(acf1), d=1.0 / sr) fi = ps1_freq > 0 ps1 = ps1[fi] assert np.sum( np.abs(ps1.imag) > 1e-8 ) == 0, "Nonzero imaginary part for fft(acf1) (%d)" % np.sum( np.abs(ps1.imag) > 1e-8) ps1_auto = np.abs(ps1.real) ps1_auto_freq = ps1_freq[fi] ps2 = fft(acf2_shift) ps2_freq = fftfreq(len(acf2), d=1.0 / sr) fi = ps2_freq > 0 ps2 = ps2[fi] assert np.sum(np.abs(ps2.imag) > 1e-8 ) == 0, "Nonzero imaginary part for fft(acf2)" ps2_auto = np.abs(ps2.real) ps2_auto_freq = ps2_freq[fi] assert np.sum(ps1_auto < 0) == 0, "negatives in ps1_auto" assert np.sum(ps2_auto < 0) == 0, "negatives in ps2_auto" # compute the cross spectral density from the correlation function cf12_shift = correlation_function(s1, s2, shift_lags, normalize=True) psd12 = fft(cf12_shift) psd12_freq = fftfreq(len(cf12_shift), d=1.0 / sr) fi = psd12_freq > 0 psd12 = np.abs(psd12[fi]) psd12_freq = psd12_freq[fi] # compute the cross spectral density from the power spectra psd12_welch = welch_psd1 * welch_psd2 psd12_welch /= psd12_welch.max() # compute the coherence from the cross spectral density cfreq,coherence,coherence_var,phase_coherence,phase_coherence_var,coh12_freqspace,coh12_freqspace_t = \ coherence_jn(s1, s2, sample_rate=sr, window_length=0.100, increment=0.050, return_coherency=True) coh12_freqspace /= np.abs(coh12_freqspace).max() # weight the coherence by one minus the normalized standard deviation coherence_std = np.sqrt(coherence_var) # cweight = coherence_std / coherence_std.sum() # coherence_weighted = (1.0 - cweight)*coherence coherence_weighted = coherence - coherence_std coherence_weighted[coherence_weighted < 0] = 0 # compute the coherence from the fft of the coherency coherence2 = fft(fftshift(coh12)) coherence2_freq = fftfreq(len(coherence2), d=1.0 / sr) fi = coherence2_freq > 0 coherence2 = np.abs(coherence2[fi]) coherence2_freq = coherence2_freq[fi] """ plt.figure() ax = plt.subplot(2, 1, 1) plt.plot(ps1_auto_freq, ps1_auto*ps2_auto, 'c-', linewidth=2.0, alpha=0.75) plt.plot(psd12_freq, psd12, 'g-', linewidth=2.0, alpha=0.9) plt.plot(ps1_auto_freq, ps1_auto, 'k-', linewidth=2.0, alpha=0.75) plt.plot(ps2_auto_freq, ps2_auto, 'r-', linewidth=2.0, alpha=0.75) plt.axis('tight') plt.legend(['denom', '12', '1', '2']) ax = plt.subplot(2, 1, 2) plt.plot(psd12_freq, coherence, 'b-') plt.axis('tight') plt.show() """ # normalize the cross-spectral density and power spectra psd12 /= psd12.max() ps_auto_max = max(ps1_auto.max(), ps2_auto.max()) ps1_auto /= ps_auto_max ps2_auto /= ps_auto_max # make some plots plt.figure() nrows = 2 ncols = 2 # plot the signals ax = plt.subplot(nrows, ncols, 1) plt.plot(t, s1, 'k-', linewidth=2.0) plt.plot(t, s2, 'r-', alpha=0.75, linewidth=2.0) plt.xlabel('Time (s)') plt.ylabel('Signal') plt.axis('tight') # plot the spectra ax = plt.subplot(nrows, ncols, 2) plt.plot(welch_freq1, welch_psd1, 'k-', linewidth=2.0, alpha=0.85) plt.plot(ps1_auto_freq, ps1_auto, 'k--', linewidth=2.0, alpha=0.85) plt.plot(welch_freq2, welch_psd2, 'r-', alpha=0.75, linewidth=2.0) plt.plot(ps2_auto_freq, ps2_auto, 'r--', linewidth=2.0, alpha=0.75) plt.axis('tight') plt.xlabel('Frequency (Hz)') plt.ylabel('Power') # plot the correlation functions ax = plt.subplot(nrows, ncols, 3) plt.axhline(0, c='k') plt.plot(lags, acf1, 'k-', linewidth=2.0) plt.plot(lags, acf2, 'r-', alpha=0.75, linewidth=2.0) plt.plot(lags, cf12, 'g-', alpha=0.75, linewidth=2.0) plt.plot(lags, coh12, 'b-', linewidth=2.0, alpha=0.75) plt.plot(coh12_freqspace_t * 1e3, coh12_freqspace, 'm-', linewidth=1.0, alpha=0.95) plt.xlabel('Lag (ms)') plt.ylabel('Correlation Function') plt.axis('tight') plt.ylim(-0.5, 1.0) handles = custom_legend(['k', 'r', 'g', 'b', 'c'], ['acf1', 'acf2', 'cf12', 'coh12', 'coh12_f']) plt.legend(handles=handles) # plot the cross spectral density ax = plt.subplot(nrows, ncols, 4) handles = custom_legend(['g', 'k', 'b'], ['CSD', 'Coherence', 'Weighted']) plt.axhline(0, c='k') plt.axhline(1, c='k') plt.plot(psd12_freq, psd12, 'g-', linewidth=3.0) plt.errorbar(cfreq, coherence, yerr=np.sqrt(coherence_var), fmt='k-', ecolor='r', linewidth=3.0, elinewidth=5.0, alpha=0.8) plt.plot(cfreq, coherence_weighted, 'b-', linewidth=3.0, alpha=0.75) plt.xlabel('Frequency (Hz)') plt.ylabel('Cross-spectral Density/Coherence') plt.legend(handles=handles) """ plt.figure() plt.axhline(0, c='k') plt.plot(lags, cf12, 'k-', alpha=1, linewidth=2.0) plt.plot(lags, coh12, 'b-', linewidth=3.0, alpha=0.75) plt.plot(coh12_freqspace_t*1e3, coh12_freqspace, 'r-', linewidth=2.0, alpha=0.95) plt.xlabel('Lag (ms)') plt.ylabel('Correlation Function') plt.axis('tight') plt.ylim(-0.5, 1.0) handles = custom_legend(['k', 'b', 'r'], ['cf12', 'coh12', 'coh12_f']) plt.legend(handles=handles) """ plt.show()
def test_cross_psd(self): np.random.seed(1234567) sr = 1000.0 dur = 1.0 nt = int(dur*sr) t = np.arange(nt) / sr # create a simple signal freqs = list() freqs.extend(np.arange(8, 12)) freqs.extend(np.arange(60, 71)) freqs.extend(np.arange(130, 151)) s1 = np.zeros([nt]) for f in freqs: s1 += np.sin(2*np.pi*f*t) s1 /= s1.max() # create a noise corrupted, bandpassed filtered version of s1 noise = np.random.randn(nt)*1e-1 # s2 = convolve1d(s1, filt, mode='mirror') + noise s2 = bandpass_filter(s1, sample_rate=sr, low_freq=40., high_freq=90.) s2 /= s2.max() s2 += noise # compute the signal's power spectrums welch_freq1,welch_psd1 = welch(s1, fs=sr) welch_freq2,welch_psd2 = welch(s2, fs=sr) welch_psd_max = max(welch_psd1.max(), welch_psd2.max()) welch_psd1 /= welch_psd_max welch_psd2 /= welch_psd_max # compute the auto-correlation functions lags = np.arange(-200, 201) acf1 = correlation_function(s1, s1, lags, normalize=True) acf2 = correlation_function(s2, s2, lags, normalize=True) # compute the cross correlation functions cf12 = correlation_function(s1, s2, lags, normalize=True) coh12 = coherency(s1, s2, lags, window_fraction=0.75, noise_floor_db=100.) # do an FFT shift to the lags and the window, otherwise the FFT of the ACFs is not equal to the power # spectrum for some numerical reason shift_lags = fftshift(lags) if len(lags) % 2 == 1: # shift zero from end of shift_lags to beginning shift_lags = np.roll(shift_lags, 1) acf1_shift = correlation_function(s1, s1, shift_lags) acf2_shift = correlation_function(s2, s2, shift_lags) # compute the power spectra from the auto-spectra ps1 = fft(acf1_shift) ps1_freq = fftfreq(len(acf1), d=1.0/sr) fi = ps1_freq > 0 ps1 = ps1[fi] assert np.sum(np.abs(ps1.imag) > 1e-8) == 0, "Nonzero imaginary part for fft(acf1) (%d)" % np.sum(np.abs(ps1.imag) > 1e-8) ps1_auto = np.abs(ps1.real) ps1_auto_freq = ps1_freq[fi] ps2 = fft(acf2_shift) ps2_freq = fftfreq(len(acf2), d=1.0/sr) fi = ps2_freq > 0 ps2 = ps2[fi] assert np.sum(np.abs(ps2.imag) > 1e-8) == 0, "Nonzero imaginary part for fft(acf2)" ps2_auto = np.abs(ps2.real) ps2_auto_freq = ps2_freq[fi] assert np.sum(ps1_auto < 0) == 0, "negatives in ps1_auto" assert np.sum(ps2_auto < 0) == 0, "negatives in ps2_auto" # compute the cross spectral density from the correlation function cf12_shift = correlation_function(s1, s2, shift_lags, normalize=True) psd12 = fft(cf12_shift) psd12_freq = fftfreq(len(cf12_shift), d=1.0/sr) fi = psd12_freq > 0 psd12 = np.abs(psd12[fi]) psd12_freq = psd12_freq[fi] # compute the cross spectral density from the power spectra psd12_welch = welch_psd1*welch_psd2 psd12_welch /= psd12_welch.max() # compute the coherence from the cross spectral density cfreq,coherence,coherence_var,phase_coherence,phase_coherence_var,coh12_freqspace,coh12_freqspace_t = \ coherence_jn(s1, s2, sample_rate=sr, window_length=0.100, increment=0.050, return_coherency=True) coh12_freqspace /= np.abs(coh12_freqspace).max() # weight the coherence by one minus the normalized standard deviation coherence_std = np.sqrt(coherence_var) # cweight = coherence_std / coherence_std.sum() # coherence_weighted = (1.0 - cweight)*coherence coherence_weighted = coherence - coherence_std coherence_weighted[coherence_weighted < 0] = 0 # compute the coherence from the fft of the coherency coherence2 = fft(fftshift(coh12)) coherence2_freq = fftfreq(len(coherence2), d=1.0/sr) fi = coherence2_freq > 0 coherence2 = np.abs(coherence2[fi]) coherence2_freq = coherence2_freq[fi] """ plt.figure() ax = plt.subplot(2, 1, 1) plt.plot(ps1_auto_freq, ps1_auto*ps2_auto, 'c-', linewidth=2.0, alpha=0.75) plt.plot(psd12_freq, psd12, 'g-', linewidth=2.0, alpha=0.9) plt.plot(ps1_auto_freq, ps1_auto, 'k-', linewidth=2.0, alpha=0.75) plt.plot(ps2_auto_freq, ps2_auto, 'r-', linewidth=2.0, alpha=0.75) plt.axis('tight') plt.legend(['denom', '12', '1', '2']) ax = plt.subplot(2, 1, 2) plt.plot(psd12_freq, coherence, 'b-') plt.axis('tight') plt.show() """ # normalize the cross-spectral density and power spectra psd12 /= psd12.max() ps_auto_max = max(ps1_auto.max(), ps2_auto.max()) ps1_auto /= ps_auto_max ps2_auto /= ps_auto_max # make some plots plt.figure() nrows = 2 ncols = 2 # plot the signals ax = plt.subplot(nrows, ncols, 1) plt.plot(t, s1, 'k-', linewidth=2.0) plt.plot(t, s2, 'r-', alpha=0.75, linewidth=2.0) plt.xlabel('Time (s)') plt.ylabel('Signal') plt.axis('tight') # plot the spectra ax = plt.subplot(nrows, ncols, 2) plt.plot(welch_freq1, welch_psd1, 'k-', linewidth=2.0, alpha=0.85) plt.plot(ps1_auto_freq, ps1_auto, 'k--', linewidth=2.0, alpha=0.85) plt.plot(welch_freq2, welch_psd2, 'r-', alpha=0.75, linewidth=2.0) plt.plot(ps2_auto_freq, ps2_auto, 'r--', linewidth=2.0, alpha=0.75) plt.axis('tight') plt.xlabel('Frequency (Hz)') plt.ylabel('Power') # plot the correlation functions ax = plt.subplot(nrows, ncols, 3) plt.axhline(0, c='k') plt.plot(lags, acf1, 'k-', linewidth=2.0) plt.plot(lags, acf2, 'r-', alpha=0.75, linewidth=2.0) plt.plot(lags, cf12, 'g-', alpha=0.75, linewidth=2.0) plt.plot(lags, coh12, 'b-', linewidth=2.0, alpha=0.75) plt.plot(coh12_freqspace_t*1e3, coh12_freqspace, 'm-', linewidth=1.0, alpha=0.95) plt.xlabel('Lag (ms)') plt.ylabel('Correlation Function') plt.axis('tight') plt.ylim(-0.5, 1.0) handles = custom_legend(['k', 'r', 'g', 'b', 'c'], ['acf1', 'acf2', 'cf12', 'coh12', 'coh12_f']) plt.legend(handles=handles) # plot the cross spectral density ax = plt.subplot(nrows, ncols, 4) handles = custom_legend(['g', 'k', 'b'], ['CSD', 'Coherence', 'Weighted']) plt.axhline(0, c='k') plt.axhline(1, c='k') plt.plot(psd12_freq, psd12, 'g-', linewidth=3.0) plt.errorbar(cfreq, coherence, yerr=np.sqrt(coherence_var), fmt='k-', ecolor='r', linewidth=3.0, elinewidth=5.0, alpha=0.8) plt.plot(cfreq, coherence_weighted, 'b-', linewidth=3.0, alpha=0.75) plt.xlabel('Frequency (Hz)') plt.ylabel('Cross-spectral Density/Coherence') plt.legend(handles=handles) """ plt.figure() plt.axhline(0, c='k') plt.plot(lags, cf12, 'k-', alpha=1, linewidth=2.0) plt.plot(lags, coh12, 'b-', linewidth=3.0, alpha=0.75) plt.plot(coh12_freqspace_t*1e3, coh12_freqspace, 'r-', linewidth=2.0, alpha=0.95) plt.xlabel('Lag (ms)') plt.ylabel('Correlation Function') plt.axis('tight') plt.ylim(-0.5, 1.0) handles = custom_legend(['k', 'b', 'r'], ['cf12', 'coh12', 'coh12_f']) plt.legend(handles=handles) """ plt.show()