def calculate_cwt(self, f_center=None, verbose=False, optimize=False, fit=False): ''' Calculate instantaneous frequency using continuous wavelet transfer wavelet specified in self.wavelet. See PyWavelets CWT documentation Parameters ---------- Optimize : bool, optionals Currently placeholder for iteratively determining wavelet scales ''' # wavlist = pywt.wavelist(kind='continuous') # w0, wavelet_increment, cwt_scale = self.__get_cwt__() # determine if scales will capture the relevant frequency if not f_center: f_center = self.drive_freq dt = 1 / self.sampling_rate sc = pywt.scale2frequency(self.wavelet, self.scales) / dt if self.verbose: print('Wavelet scale from', np.min(sc), 'to', np.max(sc)) if f_center < np.min(sc) or f_center > np.max(sc): raise ValueError('Choose a scale that captures frequency of interest') if optimize: print('!') drive_bin = self.scales[np.searchsorted(sc, f_center)] hi = int(1.2 * drive_bin) lo = int(0.8 * drive_bin) self.scales = np.arange(hi, lo, -0.1) spectrogram, freq = pywt.cwt(self.signal, self.scales, self.wavelet, sampling_period=dt) if not fit: inst_freq, amplitude, _ = parab.ridge_finder(np.abs(spectrogram), np.arange(len(freq))) # slow serial curve fitting else: inst_freq = np.zeros(self.n_points) amplitude = np.zeros(self.n_points) for c in range(spectrogram.shape[1]): SIG = spectrogram[:, c] if fit: pk = np.argmax(np.abs(SIG)) popt = np.polyfit(np.arange(20), np.abs(SIG[pk - 10:pk + 10]), 2) inst_freq[c] = -0.5 * popt[1] / popt[0] amplitude[c] = np.abs(SIG)[pk] # rescale to correct frequency inst_freq = pywt.scale2frequency(self.wavelet, inst_freq + self.scales[0]) / dt phase = spg.cumtrapz(inst_freq) phase = np.append(phase, phase[-1]) tidx = int(self.tidx * len(inst_freq) / self.n_points) self.amplitude = amplitude self.inst_freq_raw = inst_freq self.inst_freq = -1 * (inst_freq - inst_freq[tidx]) # -1 due to way scales are ordered self.spectrogram = np.abs(spectrogram) self.wavelet_freq = freq # the wavelet frequencies # subtract the w*t line (drive frequency line) from phase start = int(0.3 * tidx) end = int(0.7 * tidx) xfit = np.polyfit(np.arange(start, end), phase[start:end], 1) phase -= (xfit[0] * np.arange(len(inst_freq))) + xfit[1] self.phase = phase return
def calculate_stft(self, time_res=20e-6, nfft=200): ''' Sliding FFT approach Parameters ---------- time_res : float, optional What timescale to evaluate each FFT over fit : bool, optional Fits a parabola to the frequency peak to get the actual frequency Otherwise defaults to parabolic interpolation (see parab.fit) nfft : int Length of FFT calculated in the spectrogram. More points gets much slower but the longer the FFT the finer the frequency bin spacing ''' pts_per_ncycle = int(time_res * self.sampling_rate) if nfft < pts_per_ncycle: print('Error with nfft setting') nfft = pts_per_ncycle # drivebin = int(self.drive_freq / (self.sampling_rate / nfft )) freq, times, spectrogram = sps.spectrogram(self.signal, self.sampling_rate, nperseg=pts_per_ncycle, noverlap=pts_per_ncycle - 1, nfft=nfft, window=self.window, mode='magnitude') # Parabolic ridge finder inst_freq, amplitude, _ = parab.ridge_finder(spectrogram, freq) # Correctly pad the signals _pts = self.n_points - len(inst_freq) _pre = int(np.floor(_pts / 2)) _post = int(np.ceil(_pts / 2)) inst_freq = np.pad(inst_freq, (_pre, _post)) amplitude = np.pad(amplitude, (_pre, _post)) phase = spg.cumtrapz(inst_freq) phase = np.append(phase, phase[-1]) tidx = int(self.tidx * len(inst_freq) / self.n_points) self.amplitude = amplitude self.inst_freq_raw = inst_freq self.inst_freq = inst_freq - inst_freq[tidx] self.spectrogram = spectrogram self.stft_freq = freq self.stft_times = times # subtract the w*t line (drive frequency line) from phase start = int(0.3 * tidx) end = int(0.7 * tidx) xfit = np.polyfit(np.arange(start, end), phase[start:end], 1) phase -= (xfit[0] * np.arange(len(inst_freq))) + xfit[1] self.phase = phase return
def calculate_stft(self, nfft=200, calc_phase=False): ''' Sliding FFT approach :param nfft: Length of FFT calculated in the spectrogram. More points gets much slower but the longer the FFT the finer the frequency bin spacing :type nfft: int :param calc_phase: Calculates teh Phase (not usually needed) :type calc_phase: bool, optional ''' pts_per_ncycle = int(self.fft_time_res * self.sampling_rate) if nfft < pts_per_ncycle: print('Error with nfft setting') nfft = pts_per_ncycle if pts_per_ncycle > len(self.signal): pts_per_ncycle = len(self.signal) # drivebin = int(self.drive_freq / (self.sampling_rate / nfft )) freq, times, spectrogram = sps.spectrogram(self.signal, self.sampling_rate, nperseg=pts_per_ncycle, noverlap=pts_per_ncycle - 1, nfft=nfft, window=self.window, mode='magnitude') # Parabolic ridge finder inst_freq, amplitude, _ = parab.ridge_finder(spectrogram, freq) # Correctly pad the signals _pts = self.n_points - len(inst_freq) _pre = int(np.floor(_pts / 2)) _post = int(np.ceil(_pts / 2)) inst_freq = np.pad(inst_freq, (_pre, _post)) amplitude = np.pad(amplitude, (_pre, _post)) if calc_phase: phase = spg.cumtrapz(inst_freq) phase = np.append(phase, phase[-1]) else: phase = np.zeros(len(inst_freq)) tidx = int(self.tidx * len(inst_freq) / self.n_points) self.amplitude = amplitude self.inst_freq_raw = inst_freq self.inst_freq = inst_freq - inst_freq[tidx] self.spectrogram = spectrogram self.stft_freq = freq self.stft_times = times # subtract the w*t line (drive frequency line) from phase if calc_phase: start = int(0.3 * tidx) end = int(0.7 * tidx) xfit = np.polyfit(np.arange(start, end), phase[start:end], 1) phase -= (xfit[0] * np.arange(len(inst_freq))) + xfit[1] self.phase = phase return