def cross_wavelet(signal_1, signal_2, period, mother='morlet'): signal_1 = (signal_1 - signal_1.mean()) / signal_1.std() # Normalizing signal_2 = (signal_2 - signal_2.mean()) / signal_2.std() # Normalizing W12, cross_coi, freq, signif = wavelet.xwt(signal_1, signal_2, period, dj=1 / 100, s0=-1, J=-1, significance_level=0.95, wavelet=mother, normalize=True) cross_power = np.abs(W12)**2 cross_sig = np.ones([1, signal_1.size]) * signif[:, None] cross_sig = cross_power / cross_sig cross_period = 1 / freq WCT, aWCT, corr_coi, freq, sig = wavelet.wct(signal_1, signal_2, period, dj=1 / 100, s0=-1, J=-1, sig=False, significance_level=0.95, wavelet=mother, normalize=True) cor_sig = np.ones([1, signal_1.size]) * sig[:, None] cor_sig = np.abs(WCT) / cor_sig cor_period = 1 / freq t1 = np.linspace(0, period * signal_1.size, signal_1.size) idx = find_closest(cor_period, corr_coi.max()) t1 /= 60 cross_period /= 60 cor_period /= 60 cross_coi /= 60 corr_coi /= 60 return W12, WCT, aWCT, cor_period, corr_coi, cor_sig, idx, t1
# Due to the difference in the time series, the second signal # has to be trimmed for the XWT process. s2 = s2[np.argwhere((t2 >= min(t1)) & (t2 <= max(t1))).flatten()] ''' Calculate the cross wavelet transform (XWT). The XWT finds regions in time frequency space where the time series show high common power. Torrence and Compo (1998) state that the percent point function -- PPF (inverse of the cumulative distribution function) -- of a chi-square distribution at 95% confidence and two degrees of freedom is Z2(95%)=3.999. However, calculating the PPF using chi2.ppf gives Z2(95%)=5.991. To ensure similar significance intervals as in Grinsted et al. (2004), one has to use confidence of 86.46%. ''' W12, cross_coi, freq, signif = wavelet.xwt(s1, s2, dt, dj=1/12, s0=-1, J=-1, significance_level=0.8646, wavelet='morlet', normalize=True) cross_power = np.abs(W12)**2 cross_sig = np.ones([1, n]) * signif[:, None] cross_sig = cross_power / cross_sig # Power is significant where ratio > 1 cross_period = 1/freq '''Calculate the wavelet coherence (WTC). The WTC finds regions in time frequency space where the two time seris co-vary, but do not necessarily have high power.''' WCT, aWCT, corr_coi, freq, sig = wavelet.wct(s1, s2, dt, dj=1/12, s0=-1, J=-1,
def feature_gen(s1, s2, Mean, Std): #time domain features f_t1 = np.max(s1) f_t2 = np.min(s1) f_t3 = np.mean(s1) f_t4 = np.std(s1) f_t5 = f_t1 - f_t2 f_t6 = np.percentile(s1, 25) f_t7 = np.percentile(s1, 50) f_t8 = np.percentile(s1, 75) f_t9 = skew(s1) f_t10 = kurtosis(s1) f_t11 = np.mean(np.absolute(s1)) f_t12 = np.where(np.diff(np.sign([i for i in s1 if i])))[0].shape[0] #zero crossings f_t13 = np.where(np.diff(np.sign([i for i in np.gradient(s1) if i])))[0].shape[0] #slope sign change s1 = (s1 - Mean) / Std #pre-processing now dt = 1 W_complex, _, _, _ = wavelet.xwt(s1, s2, dt, dj=1 / DJ) W = np.abs(W_complex) #row->scale, col->time phi = np.abs(np.angle(W_complex)) assert (phi.shape == W_complex.shape) total_scales = W.shape[0] total_time = W.shape[1] accum, accum_sq = get_sums(W) accum_phase, accum_sq_phase = get_sums(phi) phi_sum = np.sum(phi) W_sum = np.sum(W) #frequency domain features f1 = accum / W_sum f2 = np.sqrt(accum_sq / W_sum) f3 = W_sum / np.max(W) s_min, t_min = np.unravel_index(W.argmin(), W.shape) s_max, t_max = np.unravel_index(W.argmax(), W.shape) x = total_scales * total_time eps = 1e-5 f4 = W_sum / (x + eps) #adding small eps to avoid divide by zero error f5 = np.sqrt((np.sum((np.square(f4 - W)))) / (x + eps)) f6 = s_max f7 = t_max f8 = s_min f81 = t_min f9 = np.sum( np.multiply( W, np.arange(1, total_scales + 1).reshape(-1, 1) * np.ones_like(W))) / (x + eps) f10 = np.sum(np.square(W)) f11 = f10 / (x + eps) f12 = np.sqrt(np.sum(np.square(W)) / (x + eps)) w = np.array(W) s = 0 for i in range(1, total_scales): s += np.sum(np.square(w[i, :] - w[i - 1, :])) consec_scale_diff = np.sqrt(s) f14 = consec_scale_diff f15 = f14 / (x + eps) f16 = np.sqrt((f14**2) / (x + eps)) f17 = np.log10(f14) f18 = W_sum f19 = accum_phase / phi_sum f20 = np.sqrt(accum_sq_phase / phi_sum) f21 = phi_sum f22 = phi_sum / (np.max(phi)) f23 = phi_sum / (x + eps) f24 = np.sqrt((np.sum(np.square((f22 - phi)))) / (x + eps)) f25 = np.sum( np.multiply( phi, np.arange(1, total_scales + 1).reshape(-1, 1) * np.ones_like(phi))) f26 = np.sqrt(np.sum( np.square(s1 - s2))) #euclidean distance between s1 and mcv ? f27 = cosine_similarity(s1.reshape(1, -1), s2.reshape(1, -1), dense_output=True).reshape(1, 1)[0][0] corr = correntropy(s1, s2) f28 = np.mean(corr) f29 = kurtosis(corr) f30 = skew(corr) f31 = moment(corr, moment=2) f32 = moment(corr, moment=3) f33 = trim_mean(corr, 0.1) f = [ f_t1, f_t2, f_t3, f_t4, f_t5, f_t6, f_t7, f_t8, f_t9, f_t10, f_t11, f_t12, f_t13, f1, f2, f3, f4, f5, f6, f7, f8, f81, f9, f10, f11, f12, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, f31, f32, f33 ] return np.array(f)
# II. Cross-wavelet transform # =========================== # Due to the difference in the time series, the second signal has to be # trimmed for the XWT process. s2 = s2[np.argwhere((t2 >= min(t1)) & (t2 <= max(t1))).flatten()] # Calculate the cross wavelet transform (XWT). The XWT finds regions in time # frequency space where the time series show high common power. Torrence and # Compo (1998) state that the percent point function -- PPF (inverse of the # cumulative distribution function) -- of a chi-square distribution at 95% # confidence and two degrees of freedom is Z2(95%)=3.999. However, calculating # the PPF using chi2.ppf gives Z2(95%)=5.991. To ensure similar significance # intervals as in Grinsted et al. (2004), one has to use confidence of 86.46%. W12, cross_coi, freq, signif = wavelet.xwt(s1, s2, dt, dj=1/12, s0=-1, J=-1, significance_level=0.8646, wavelet='morlet', normalize=True) cross_power = np.abs(W12)**2 cross_sig = np.ones([1, n]) * signif[:, None] cross_sig = cross_power / cross_sig # Power is significant where ratio > 1 cross_period = 1/freq # Calculate the wavelet coherence (WTC). The WTC finds regions in time # frequency space where the two time seris co-vary, but do not necessarily have # high power. WCT, aWCT, corr_coi, freq, sig = wavelet.wct(s1, s2, dt, dj=1/12, s0=-1, J=-1, significance_level=0.8646, wavelet='morlet', normalize=True, cache=True)
def plot_cross_wavelet(wave1, wave2, sample_times, min_freq=1, max_freq=256, sig=False, ax=None, title="Cross-Wavelet", plot_coi=True, plot_period=False, resolution=12, all_arrows=True, quiv_x=5, quiv_y=24, block=None): """ Plot cross wavelet correlation between wave1 and wave2 using pycwt. TODO Fix this function TODO also test out sig on a large dataset Parameters ---------- wave1 : np.ndarray The values of the first waveform. wave2 : np.ndarray The values of the second waveform. sample_times : np.ndarray The times at which waveform samples occur. min_freq : float Supposed to be minimum frequency, but not quite working. max_freq : float Supposed to be max frequency, but not quite working. sig : bool, default False Optional Should significance of waveform coherence be calculated. ax : plt.axe, default None Optional ax object to plot into. title : str, default "Wavelet Coherence" Optional title for the graph plot_coi : bool, default True Should the cone of influence be plotted plot_period : bool Should the y-axis be in period or in frequency (Hz) resolution : int How many wavelets should be at each level of the graph all_arrows : bool Should phase arrows be plotted uniformly or only at high coherence quiv_x : float sets quiver window in time domain in seconds quiv_y : float sets number of quivers evenly distributed across freq limits block : [int, int] Plots only points between ints. Returns ------- tuple : (fig, result) Where fig is a matplotlib Figure and result is a tuple consisting of WCT, aWCT, coi, freq, sig WCT - 2D numpy array with coherence values aWCT - 2D numpy array with same shape as aWCT indicating phase angles coi - 1D numpy array with a frequency value for each time freq - 1D numpy array with the frequencies wavelets were calculated at sig - 2D numpy array indicating where data is significant by monte carlo """ t = np.asarray(sample_times) dt = np.mean(np.diff(t)) # Set up the scales to match min max input frequencies dj = resolution s0 = 1 / max_freq if s0 < (2 * dt): s0 = 2 * dt max_J = 1 / min_freq J = dj * np.int(np.round(np.log2(max_J / np.abs(s0)))) # Do the actual calculation W12, coi, freq, signif = wavelet.xwt( wave1, wave2, dt, # Fixed params dj=(1.0 / dj), s0=s0, J=J, significance_level=0.8646, normalize=True, ) cross_power = np.abs(W12)**2 if np.max(W12) > 6**2 or np.min(W12) < 0: print('W12 was out of range: min{},max{}'.format( np.min(W12), np.max(W12))) cross_power = np.clip(cross_power, 0, 6**2) # print('cross max:', np.max(cross_power)) # print('cross min:', np.min(cross_power)) cross_sig = np.ones([1, len(t)]) * signif[:, None] cross_sig = cross_power / cross_sig # Power is significant where ratio > 1 # Convert frequency to period if necessary if plot_period: y_vals = np.log2(1 / freq) if not plot_period: y_vals = np.log2(freq) if ax is None: fig, ax = plt.subplots() else: fig = None # Set the x and y axes of the plot extent_corr = [t.min(), t.max(), 0, max(y_vals)] # Fill the plot with the magnitude squared correlation values # That is, MSC = abs(Pxy) ^ 2 / (Pxx * Pyy) # TODO I think this might be the wrong way to plot this # It assumes that the samples are linearly spaced im = NonUniformImage(ax, interpolation='bilinear', extent=extent_corr) if plot_period: im.set_data(t, y_vals, cross_power) else: im.set_data(t, y_vals[::-1], cross_power[::-1, :]) ax.images.append(im) # pcm = ax.pcolormesh(WCT) # Plot the cone of influence - Periods greater than # those are subject to edge effects. if plot_coi: # Performed by plotting a polygon x_positions = np.zeros(shape=(len(t), )) x_positions = t y_positions = np.zeros(shape=(len(t), )) if plot_period: y_positions = np.log2(coi) else: y_positions = np.log2(1 / coi) ax.plot(x_positions, y_positions, 'w--', linewidth=2, c="w") # Plot the significance level contour plot if sig: ax.contour(t, y_vals, cross_sig, [-99, 1], colors='k', linewidths=2, extent=extent_corr) # Add limits, titles, etc. ax.set_ylim(min(y_vals), max(y_vals)) if block: ax.set_xlim(t[block[0]], t[int(block[1] * 1 / dt)]) else: ax.set_xlim(t.min(), t.max()) # TODO split graph into smaller time chunks # Test for smaller timescale # quiv_x = 1 # Add the colorbar to the figure if fig is not None: fig.colorbar(im) else: plt.colorbar(im, ax=ax, use_gridspec=True) if plot_period: y_ticks = np.linspace(min(y_vals), max(y_vals), 8) # TODO improve ticks y_ticks = [ np.log2(x) for x in [0.004, 0.008, 0.016, 0.032, 0.064, 0.125, 0.25, 0.5, 1] ] y_labels = [str(x) for x in (np.round(np.exp2(y_ticks), 3))] else: y_ticks = np.linspace(min(y_vals), max(y_vals), 8) # TODO improve ticks # y_ticks = [np.log2(x) for x in [256, 128, 64, 32, 16, 8, 4, 2, 1]] y_ticks = [np.log2(x) for x in [64, 32, 16, 8, 4, 2, 1]] y_labels = [str(x) for x in (np.round(np.exp2(y_ticks), 3))] plt.yticks(y_ticks, y_labels) ax.set_title(title) ax.set_xlabel("Time (s)") if plot_period: ax.set_ylabel("Period") else: ax.set_ylabel("Frequency (Hz)") return (fig, [W12, coi, freq, sig])
def cross_wavelet(self, signal_1, signal_2, mother='morlet', plot=True): signal_1 = (signal_1 - signal_1.mean()) / signal_1.std() # Normalizing signal_2 = (signal_2 - signal_2.mean()) / signal_2.std() # Normalizing W12, cross_coi, freq, signif = wavelet.xwt(signal_1, signal_2, self.period, dj=1/100, s0=-1, J=-1, significance_level=0.95, wavelet=mother, normalize=True) cross_power = np.abs(W12)**2 cross_sig = np.ones([1, signal_1.size]) * signif[:, None] cross_sig = cross_power / cross_sig cross_period = 1/freq WCT, aWCT, corr_coi, freq, sig = wavelet.wct(signal_1, signal_2, self.period, dj=1/100, s0=-1, J=-1, sig=False,significance_level=0.95, wavelet=mother, normalize=True) cor_sig = np.ones([1, signal_1.size]) * sig[:, None] cor_sig = np.abs(WCT) / cor_sig cor_period = 1/freq angle = 0.5 * np.pi - aWCT u, v = np.cos(angle), np.sin(angle) t1 = np.linspace(0,self.period*signal_1.size,signal_1.size) ## indices for stuff idx = self.find_closest(cor_period,corr_coi.max()) ## Into minutes t1 /= 60 cross_period /= 60 cor_period /= 60 cross_coi /= 60 corr_coi /= 60 fig1, ax1 = plt.subplots(nrows=1,ncols=1, sharex=True, sharey=True, figsize=(12,12)) extent_cross = [t1.min(),t1.max(),0,max(cross_period)] extent_corr = [t1.min(),t1.max(),0,max(cor_period)] im1 = NonUniformImage(ax1, interpolation='nearest', extent=extent_cross) im1.set_cmap('cubehelix') im1.set_data(t1, cross_period[:idx], cross_power[:idx,:]) ax1.images.append(im1) ax1.contour(t1, cross_period[:idx], cross_sig[:idx,:], [-99, 1], colors='k', linewidths=2, extent=extent_cross) ax1.fill(np.concatenate([t1, t1[-1:]+self.period, t1[-1:]+self.period,t1[:1]-self.period, t1[:1]-self.period]), (np.concatenate([cross_coi,[1e-9], cross_period[-1:], cross_period[-1:], [1e-9]])), 'k', alpha=0.3,hatch='x') ax1.set_title('Cross-Wavelet') # ax1.quiver(t1[::3], cross_period[::3], u[::3, ::3], # v[::3, ::3], units='width', angles='uv', pivot='mid', # linewidth=1.5, edgecolor='k', headwidth=10, headlength=10, # headaxislength=5, minshaft=2, minlength=5) ax1.set_ylim(([min(cross_period), cross_period[idx]])) ax1.set_xlim(t1.min(),t1.max()) fig2, ax2 = plt.subplots(nrows=1,ncols=1, sharex=True, sharey=True, figsize=(12,12)) fig2.subplots_adjust(right=0.8) cbar_ax_1 = fig2.add_axes([0.85, 0.05, 0.05, 0.35]) im2 = NonUniformImage(ax2, interpolation='nearest', extent=extent_corr) im2.set_cmap('cubehelix') im2.set_data(t1, cor_period[:idx], np.log10(WCT[:idx,:])) ax2.images.append(im2) ax2.contour(t1, cor_period[:idx], cor_sig[:idx,:], [-99, 1], colors='k', linewidths=2, extent=extent_corr) ax2.fill(np.concatenate([t1, t1[-1:]+self.period, t1[-1:]+self.period,t1[:1]-self.period, t1[:1]-self.period]), (np.concatenate([corr_coi,[1e-9], cor_period[-1:], cor_period[-1:], [1e-9]])), 'k', alpha=0.3,hatch='x') ax2.set_title('Cross-Correlation') # ax2.quiver(t1[::3], cor_period[::3], u[::3,::3], v[::3,::3], # units='height', angles='uv', pivot='mid',linewidth=1.5, edgecolor='k', # headwidth=10, headlength=10, headaxislength=5, minshaft=2, minlength=5) ax2.set_ylim(([min(cor_period), cor_period[idx]])) ax2.set_xlim(t1.min(),t1.max()) fig2.colorbar(im2, cax=cbar_ax_1) plt.show() plt.figure(figsize=(12,12)) im3= plt.imshow(np.rad2deg(aWCT), origin='lower',interpolation='nearest', cmap='seismic', extent=extent_corr) plt.fill(np.concatenate([t1, t1[-1:]+self.period, t1[-1:]+self.period,t1[:1]-self.period, t1[:1]-self.period]), (np.concatenate([corr_coi,[1e-9], cor_period[-1:], cor_period[-1:], [1e-9]])), 'k', alpha=0.3,hatch='x') plt.ylim(([min(cor_period), cor_period[idx]])) plt.xlim(t1.min(),t1.max()) plt.colorbar(im3) plt.show() return