def cross_spectral_density(s1, s2, sample_rate, window_length, increment, min_freq=0, max_freq=np.inf): """ Computes the cross-spectral density between signals s1 and s2 by computing the product of their power spectra. First the Gaussian-windowed spectrograms of s1 and s2 are computed, then the product of the power spectra at each time point is computed, then the products are averaged across time to produce an estimate of the cross spectral density. :param s1: The first signal. :param s2: The second signal. :param sample_rate: The sample rates of the signals. :param window_length: The length of the window used to compute the STFT (units=seconds) :param increment: The spacing between the points of the STFT (units=seconds) :param min_freq: The minimum frequency to analyze (units=Hz, default=0) :param max_freq: The maximum frequency to analysize (units=Hz, default=nyquist frequency) :return: freq,csd: The frequency bands evaluated, and the cross-spectral density. """ # compute the complex-spectrograms of the signals t1, freq1, tf1, rms1 = gaussian_stft(s1, sample_rate, window_length=window_length, increment=increment, min_freq=min_freq, max_freq=max_freq) t2, freq2, tf2, rms2 = gaussian_stft(s2, sample_rate, window_length=window_length, increment=increment, min_freq=min_freq, max_freq=max_freq) # multiply the complex spectrograms csd = tf1 * tf2 # take the power spectrum csd = np.abs(csd) # average across time csd = csd.mean(axis=1) return freq1, csd
def test_delta(self): dur = 30. sample_rate = 1e3 nt = int(dur * sample_rate) t = np.arange(nt) / sample_rate freqs = np.linspace(0.5, 1.5, nt) # freqs = np.ones_like(t)*2. s = np.sin(2 * np.pi * freqs * t) center_freqs = np.arange(0.5, 4.5, 0.5) psi = lambda _t, _f, _bw: (np.pi * _bw**2)**(-0.5) * np.exp( 2 * np.pi * complex(0, 1) * _f * _t) * np.exp(-_t**2 / _bw**2) """ scalogram = np.zeros([len(center_freqs), nt]) bandwidth = 1. nstd = 6 nwt = int(bandwidth*nstd*sample_rate) wt = np.arange(nwt) / sample_rate for k,f in enumerate(center_freqs): w = psi(wt, f, bandwidth) scalogram[k, :] = convolve1d(s, w) """ win_len = 2. spec_t, spec_freq, spec, spec_rms = gaussian_stft( s, sample_rate, win_len, 100e-3) fi = (spec_freq < 10) & (spec_freq > 0) plt.figure() gs = plt.GridSpec(100, 1) ax = plt.subplot(gs[:30, 0]) plt.plot(t, s, 'k-', linewidth=4.0, alpha=0.7) wa = WaveletAnalysis(s, dt=1. / sample_rate, frequency=True) ax = plt.subplot(gs[35:, 0]) power = wa.wavelet_power scales = wa.scales t = wa.time T, S = np.meshgrid(t, scales) # ax.contourf(T, S, power, 100) # ax.set_yscale('log') # plt.imshow(np.abs(scalogram)**2, interpolation='nearest', aspect='auto', cmap=plt.cm.afmhot_r, origin='lower', # extent=[t.min(), t.max(), min(center_freqs), max(center_freqs)]) plot_spectrogram(spec_t, spec_freq[fi], np.abs(spec[fi, :])**2, ax=ax, colorbar=False, colormap=plt.cm.afmhot_r) plt.plot(t, freqs, 'k-', alpha=0.7, linewidth=4.0) plt.axis('tight') plt.show()
def load_spectrogram(self, stim_id): sound = self.sound_manager.reconstruct(stim_id) t, freq, timefreq, rms = gaussian_stft(sound.squeeze(), float(sound.samplerate), self.window_length, self.spec_inc, min_freq=self.freq_range[0], max_freq=self.freq_range[1]) timefreq = np.abs(timefreq)**2 self.specs[stim_id] = (t, freq, timefreq, sound) if self.spec_freq is None: self.spec_freq = freq else: assert np.abs(self.spec_freq - freq).sum( ) == 0, "Spectrogram frequency differs for stim %d" % stim_id
def spectrogram(s, sample_rate, spec_sample_rate, freq_spacing, min_freq=0, max_freq=None, nstd=2, cmplx=True): """ Given a sound pressure waveform, s, compute the complex spectrogram. See documentation on gaussian_stft for additional details. Returns: t, freq, timefreq, rms t: array of time values to use as x axis freq: array of frequencies to use as y axis timefreq: the spectrogram (a time-frequency represnetaion) rms : the time varying average Arguments: REQUIRED: s: sound pressssure waveform sample_rate: sampling rate for s in Hz spec_sample_rate: sampling rate for the output spectrogram in Hz. This variable sets the overlap for the windows in the STFFT. freq_spacing: the time-frequency scale for the spectrogram in Hz. This variable determines the width of the gaussian window. OPTIONAL complex = False: returns the absolute value use min_freq and max_freq to save space nstd = number of standard deviations of the gaussian in one window. """ # We need units here!! increment = 1.0 / spec_sample_rate window_length = nstd / (2.0 * np.pi * freq_spacing) t, freq, timefreq, rms = gaussian_stft(s, sample_rate, window_length, increment, nstd=nstd, min_freq=min_freq, max_freq=max_freq) # rms = spec.std(axis=0, ddof=1) if cmplx == False: timefreq = np.abs(timefreq) return t, freq, timefreq, rms
def compare_stims(exp_file, stim_file, seg_file, bs_file): exp = Experiment.load(exp_file, stim_file) spec_colormap() all_stim_ids = list() for ekey in list(exp.epoch_table.keys()): etable = exp.epoch_table[ekey] stim_ids = etable['id'].unique() all_stim_ids.extend(stim_ids) stim_ids = np.unique(all_stim_ids) # read the manual segmentation data man_segs = dict() with open(seg_file, 'r') as f: lns = f.readlines() for ln in lns: x = ln.split(",") stim_id = int(x[0]) stimes = [float(f) for f in x[1:]] assert len( stimes) % 2 == 0, "Uneven # of syllables for stim %d" % stim_id ns = len(stimes) / 2 man_segs[stim_id] = np.array(stimes).reshape([ns, 2]) # get the automated segmentation algo_segs = dict() bst = BiosoundTransform.load(bs_file) for stim_id in stim_ids: i = bst.stim_df.stim_id == stim_id d = list(zip(bst.stim_df[i].start_time, bst.stim_df[i].end_time)) d.sort(key=operator.itemgetter(0)) algo_segs[stim_id] = np.array(d) for stim_id in stim_ids: # get the raw sound pressure waveform wave = exp.sound_manager.reconstruct(stim_id) wave_sr = wave.samplerate wave = np.array(wave).squeeze() wave_t = np.arange(len(wave)) / wave_sr # compute the amplitude envelope amp_env = temporal_envelope(wave, wave_sr, cutoff_freq=200.0) amp_env /= amp_env.max() # compute the spectrogram spec_sr = 1000. spec_t, spec_freq, spec, spec_rms = gaussian_stft(wave, float(wave_sr), 0.007, 1. / spec_sr, min_freq=300., max_freq=8000.) spec = np.abs(spec)**2 log_transform(spec, dbnoise=70) figsize = (23, 12) plt.figure(figsize=figsize) ax = plt.subplot(2, 1, 1) plot_spectrogram(spec_t, spec_freq, spec, ax=ax, colorbar=False, fmin=300., fmax=8000., colormap='SpectroColorMap') for k, (stime, etime) in enumerate(algo_segs[stim_id]): plt.axvline(stime, c='k', linewidth=2.0, alpha=0.8) plt.axvline(etime, c='k', linewidth=2.0, alpha=0.8) plt.text(stime, 7000., str(k + 1), fontsize=14) plt.text(etime, 7000., str(k + 1), fontsize=14) plt.title('Algorithm Segmentation') plt.axis('tight') ax = plt.subplot(2, 1, 2) plot_spectrogram(spec_t, spec_freq, spec, ax=ax, colorbar=False, fmin=300., fmax=8000., colormap='SpectroColorMap') for k, (stime, etime) in enumerate(man_segs[stim_id]): plt.axvline(stime, c='k', linewidth=2.0, alpha=0.8) plt.axvline(etime, c='k', linewidth=2.0, alpha=0.8) plt.text(stime, 7000., str(k + 1), fontsize=14) plt.text(etime, 7000., str(k + 1), fontsize=14) plt.title('Manual Segmentation') plt.axis('tight') plt.show()
def show_stim(self, exp, stim_id): # get the raw sound pressure waveform wave = exp.sound_manager.reconstruct(stim_id) wave_sr = wave.samplerate wave = np.array(wave).squeeze() wave_t = np.arange(len(wave)) / wave_sr # compute the amplitude envelope amp_env = temporal_envelope(wave, wave_sr, cutoff_freq=200.0) amp_env /= amp_env.max() # compute the spectrogram spec_sr = 1000. spec_t, spec_freq, spec, spec_rms = gaussian_stft(wave, float(wave_sr), 0.007, 1. / spec_sr, min_freq=300., max_freq=8000.) spec = np.abs(spec)**2 log_transform(spec, dbnoise=70) spec_ax = None click_points = list() fig = None def _render(): plt.sca(spec_ax) plt.cla() plot_spectrogram(spec_t, spec_freq, spec, ax=spec_ax, colorbar=False, fmin=300., fmax=8000., colormap='SpectroColorMap') plt.plot(wave_t, amp_env * 8000, 'k-', linewidth=3.0, alpha=0.7) plt.axis('tight') for k, cp in enumerate(click_points): snum = int(k / 2) plt.axvline(cp, c='k', linewidth=2.0, alpha=0.8) plt.text(cp, 7000., str(snum), fontsize=14) plt.draw() def _onclick(event): click_points.append(event.xdata) _render() def _onkey(event): _k = str(event.key) if _k == 'delete': if len(click_points) > 0: click_points.remove(click_points[-1]) _render() figsize = (23, 12) fig = plt.figure(figsize=figsize) bpress_cid = fig.canvas.mpl_connect('button_press_event', _onclick) kpress_cid = fig.canvas.mpl_connect('key_press_event', _onkey) gs = plt.GridSpec(100, 1) ax = plt.subplot(gs[:30, 0]) plt.plot(wave_t, wave, 'k-') plt.plot(wave_t, amp_env * wave.max(), 'r-', linewidth=2.0, alpha=0.8) plt.xlabel('Time (s)') plt.ylabel('Waveform') plt.axis('tight') spec_ax = plt.subplot(gs[45:, 0]) _render() plt.show() assert len( click_points) % 2 == 0, "Must have an even number of click points!" return click_points
def transform(self, experiment, stim_types_to_segment=('Ag', 'Di', 'Be', 'DC', 'Te', 'Ne', 'LT', 'Th', 'song'), plot=False, excluded_types=tuple()): assert isinstance( experiment, Experiment ), 'experiment argument must be an instance of class Experiment!' self.bird = experiment.bird_name all_stim_ids = list() # iterate through the segments and get the stim ids from each epoch table for seg in experiment.get_all_segments(): etable = experiment.get_epoch_table(seg) stim_ids = etable['id'].unique() all_stim_ids.extend(stim_ids) stim_ids = np.unique(all_stim_ids) stim_data = { 'stim_id': list(), 'stim_type': list(), 'start_time': list(), 'end_time': list(), 'order': list() } for aprop in self.acoustic_props: stim_data[aprop] = list() # specify type-specific thresholds for segmentation seg_params = { 'default': { 'min_thresh': 0.05, 'max_thresh': 0.25 }, 'Ag': { 'min_thresh': 0.05, 'max_thresh': 0.10 }, 'song': { 'min_thresh': 0.05, 'max_thresh': 0.10 }, 'Di': { 'min_thresh': 0.15, 'max_thresh': 0.20 } } for stim_id in stim_ids: print('Transforming stimulus {}'.format(stim_id)) # get sound type si = experiment.stim_table['id'] == str(stim_id) assert si.sum( ) == 1, "Zero or more than one stimulus defined for id=%d, (si.sum()=%d)" % ( stim_id, si.sum()) stim_type = experiment.stim_table['type'][si].values[0] if stim_type == 'call': stim_type = experiment.stim_table['callid'][si].values[0] if stim_type in excluded_types: continue # get the stimulus waveform and sample rate sound = experiment.sound_manager.reconstruct(stim_id) waveform = np.array(sound.squeeze()) sample_rate = float(sound.samplerate) stim_dur = len(waveform) / sample_rate if stim_type in stim_types_to_segment: # compute the spectrogram of the stim spec_sample_rate = 1000. spec_t, spec_freq, spec_stft, spec_rms = gaussian_stft( waveform, sample_rate, 0.007, 1.0 / spec_sample_rate) spec = np.abs(spec_stft) nz = spec > 0 spec[nz] = 20 * np.log10(spec[nz]) + 50 spec[spec < 0] = 0 # compute the amplitude envelope amp_env = spec_rms amp_env -= amp_env.min() amp_env /= amp_env.max() # segment the amplitude envelope minimum_isi = int(4e-3 * spec_sample_rate) if stim_type in seg_params: min_thresh = seg_params[stim_type]['min_thresh'] max_thresh = seg_params[stim_type]['max_thresh'] else: min_thresh = seg_params['default']['min_thresh'] max_thresh = seg_params['default']['max_thresh'] syllable_times = break_envelope_into_events( amp_env, threshold=min_thresh, merge_thresh=minimum_isi, max_amp_thresh=max_thresh) if plot: plt.figure() ax = plt.subplot(111) plot_spectrogram(spec_t, spec_freq, np.abs(spec), ax=ax, fmin=300.0, fmax=8000.0, colormap=plt.cm.afmhot, colorbar=False) sfd = spec_freq.max() - spec_freq.min() amp_env *= sfd amp_env += spec_freq.min() tline = sfd * min_thresh + amp_env.min() tline2 = sfd * max_thresh + amp_env.min() plt.axhline(tline, c='w', alpha=0.50) plt.axhline(tline2, c='w', alpha=0.50) plt.plot(spec_t, amp_env, 'w-', linewidth=2.0, alpha=0.75) for k, (si, ei, max_amp) in enumerate(syllable_times): plt.plot(spec_t[si], 0, 'go', markersize=8) plt.plot(spec_t[ei], 0, 'ro', markersize=8) plt.title('stim %d, %s, minimum_isi=%d' % (stim_id, stim_type, minimum_isi)) plt.axis('tight') plt.show() the_order = 0 for k, (si, ei, max_amp) in enumerate(syllable_times): sii = int((si / spec_sample_rate) * sample_rate) eii = int((ei / spec_sample_rate) * sample_rate) s = waveform[sii:eii] if len(s) < 1024: continue bs = BioSound(soundWave=s, fs=sample_rate) bs.spectrum(f_high=8000.) bs.ampenv() bs.fundest() stime = sii / sample_rate etime = eii / sample_rate stim_data['stim_id'].append(stim_id) stim_data['stim_type'].append(stim_type) stim_data['start_time'].append(stime) stim_data['end_time'].append(etime) stim_data['order'].append(the_order) the_order += 1 for aprop in self.acoustic_props: aval = getattr(bs, aprop) if aval is None: aval = -1 stim_data[aprop].append(aval) else: bs = BioSound(soundWave=waveform, fs=sample_rate) bs.spectrum(f_high=8000.) bs.ampenv() bs.fundest() stim_data['stim_id'].append(stim_id) stim_data['stim_type'].append(stim_type) stim_data['start_time'].append(0) stim_data['end_time'].append(stim_dur) stim_data['order'].append(0) for aprop in self.acoustic_props: aval = getattr(bs, aprop) stim_data[aprop].append(aval) self.stim_data = stim_data self.stim_df = pd.DataFrame(self.stim_data)
def coherence_jn(s1, s2, sample_rate, window_length, increment, min_freq=0, max_freq=None, return_coherency=False): """ Computes the coherence between two signals by averaging across time-frequency representations created using a Gaussian-windowed Short-time Fourier Transform. Uses jacknifing to estimate the variance of the coherence. :param s1: The first signal :param s2: The second signal :param sample_rate: The sample rates of the signals. :param window_length: The length of the window used to compute the STFT (units=seconds) :param increment: The spacing between the points of the STFT (units=seconds) :param min_freq: The minimum frequency to analyze (units=Hz, default=0) :param max_freq: The maximum frequency to analyze (units=Hz, default=nyquist frequency) :param return_coherency: Whether or not to return the time domain coherency (default=False) :return: freq,coherence,coherence_var,phase_coherence,phase_coherence_var,[coherency,coherency_t]: freq is an array of frequencies that the coherence was computed at. coherence is an array of length len(freq) that contains the coherence at each frequency. coherence_var is the variance of the coherence. phase_coherence is the average cosine phase difference at each frequency, and phase_coherence_var is the variance of that measure. coherency is only returned if return_coherency=True, it is the inverse fourier transform of the complex- valued coherency. """ if s1.shape != s2.shape: raise AssertionError('s1 and s2 must have the same shape') if s1.ndim == 1: s1, s2 = [np.array(i, ndmin=2) for i in [s1, s2]] tf1, tf2 = list(), list() for i, (is1, is2) in enumerate(zip(s1, s2)): t1, freq1, itf1, rms1 = gaussian_stft(is1, sample_rate, window_length=window_length, increment=increment, min_freq=min_freq, max_freq=max_freq) t2, freq2, itf2, rms2 = gaussian_stft(is2, sample_rate, window_length=window_length, increment=increment, min_freq=min_freq, max_freq=max_freq) tf1.append(itf1) tf2.append(itf2) tf1, tf2 = [np.hstack(i) for i in [tf1, tf2]] cross_spec12 = tf1 * np.conj(tf2) ps1 = np.abs(tf1)**2 ps2 = np.abs(tf2)**2 # compute the coherence using all the data csd = cross_spec12.sum(axis=1) denom = ps1.sum(axis=1) * ps2.sum(axis=1) c_amp = np.abs(csd) / np.sqrt(denom) cohe = c_amp**2 # compute the phase coherence using all the data c_phase = np.cos(np.angle(csd)) # make leave-one-out estimates of the complex coherence jn_estimates_amp = list() jn_estimates_phase = list() jn_estimates_cohe = list() njn = tf1.shape[1] for k in range(njn): i = np.ones([njn], dtype='bool') i[k] = False csd = cross_spec12[:, i].sum(axis=1) denom = ps1[:, i].sum(axis=1) * ps2[:, i].sum(axis=1) c_amp = np.abs(csd) / np.sqrt(denom) jn_estimates_amp.append(c_amp) jn_estimates_cohe.append(njn * cohe - (njn - 1) * (c_amp**2)) c_phase = np.cos(np.angle(csd)) jn_estimates_phase.append(c_phase) jn_estimates_amp = np.array(jn_estimates_amp) jn_estimates_cohe = np.array(jn_estimates_cohe) jn_estimates_phase = np.array(jn_estimates_phase) # estimate the variance of the coherence jn_mean_amp = jn_estimates_amp.mean(axis=0) jn_diff_amp = (jn_estimates_amp - jn_mean_amp)**2 c_var_amp = ((njn - 1) / float(njn)) * jn_diff_amp.sum(axis=0) cohe_unbiased = jn_estimates_cohe.mean(axis=0) cohe_se = jn_estimates_cohe.std(axis=0) / np.sqrt(njn) # estimate the variance of the phase coherence jn_mean_phase = jn_estimates_phase.mean(axis=0) jn_diff_phase = (jn_estimates_phase - jn_mean_phase)**2 c_phase_var = ((njn - 1) / float(njn)) * jn_diff_phase.sum(axis=0) assert c_amp.max() <= 1.0, "c_amp.max()=%f" % c_amp.max() assert c_amp.min() >= 0.0, "c_amp.min()=%f" % c_amp.min() assert np.sum(np.isnan(c_amp)) == 0, "NaNs in c_amp!" if return_coherency: # compute the complex-valued coherency z = csd / denom # make the complex-valued coherency symmetric around zero sym_z = np.zeros([len(z) * 2 - 1], dtype='complex') sym_z[len(z) - 1:] = z sym_z[:len(z) - 1] = (z[1:])[::-1] # do an fft shift so the inverse fourier transform works sym_z = fftshift(sym_z) if len(sym_z) % 2 == 1: # shift zero from end of shift_lags to beginning sym_z = np.roll(sym_z, 1) coherency = ifft(sym_z) coherency = fftshift(coherency.real) dt = 1. / sample_rate hc = (len(coherency) - 1) / 2 coherency_t = np.arange(-hc, hc + 1, 1) * dt """ import matplotlib.pyplot as plt plt.figure() plt.plot(coherency_t, coherency, 'k-') plt.axis('tight') plt.show() """ return freq1, c_amp, c_var_amp, c_phase, c_phase_var, coherency, coherency_t, cohe_unbiased, cohe_se else: return freq1, c_amp, c_var_amp, c_phase, c_phase_var, cohe_unbiased, cohe_se