def single_taper_spectrum(data, delta, taper_name=None): """ Returns the spectrum and the corresponding frequencies for data with the given taper. """ length = len(data) good_length = length // 2 + 1 # Create the frequencies. # XXX: This might be some kind of hack freq = abs(np.fft.fftfreq(length, delta)[:good_length]) # Create the tapers. if taper_name == 'bartlett': taper = np.bartlett(length) elif taper_name == 'blackman': taper = np.blackman(length) elif taper_name == 'boxcar': taper = np.ones(length) elif taper_name == 'hamming': taper = np.hamming(length) elif taper_name == 'hanning': taper = np.hanning(length) elif 'kaiser' in taper_name: taper = np.kaiser(length, beta=14) # Should never happen. else: msg = 'Something went wrong.' raise Exception(msg) # Detrend the data. data = detrend(data) # Apply the taper. data *= taper spec = abs(np.fft.rfft(data)) ** 2 return spec, freq
def MoninObukhov_length(u,v,w, T): """Calculate the Monin Obukhov length Not validated!!! parameters ---------- u : array_like Horizontal wind fluctuations in mean wind direction v : array_like Horizontal wind fluctuations in perpendicular to mean wind direction w : array_like Vertical wind fluctuations T : array_like Potential temperature (close to sonic temperature) """ K = 0.4 g = 9.82 u = detrend(u) u = u-np.mean(u) v = v-np.mean(v) w = w-np.mean(w) u_star = (np.mean(u*w)**2+np.mean(v*w)**2)**(1/4) wT = np.mean(w*T) return -u_star ** 3 * (T.mean() + 273.15) / (K * g * wT)
def fftByChannel(self, tab): fft_powers = np.ndarray((self.NR_OF_CHANNELS, self.INPUT_RATE / 2 - 1)) for j in range(self.NR_OF_CHANNELS): if tab.CHANNEL_SELECTIONS[j] == 1: sp = np.abs(np.fft.fft(detrend(tab.CHANNEL_DEQUES[j]), 512)) sp = sp[1:256] sp_real = sp.real fft_powers[j] = 10 * np.log10(sp_real.clip(min=0.000001)) # fft_powers[j] = sp_real.clip(min=0.000001) else: # TODO - this is a hack to not get all-zero arrays in the result fft_powers[j] = [0.0000001] * 255 return fft_powers
def detrend_taper_rotate(eventdir, sacfiles): """preprocess performs the demean,detrend,taper and rotation into radial and transverse components. It saves these at STACK_R.sac and STACK_T.sac""" ev = [] # READ 3 Component SAC files into object array. for i in range(3): ff = os.path.join(eventdir, sacfiles[i]) st = read(ff) ev.append(st[0]) # Calculate values to be used in transformations dt = ev[1].stats.delta pslow = ev[1].stats.sac['user0'] baz = ev[1].stats.sac['baz'] PP = ev[1].stats.sac['t7'] N = ev[1].stats.npts # Begin seismogram 50 seconds before P arrival # Here we either a full size taper, or a short taper padded with zeros if PP and (PP < ev[1].stats.sac['e'] ): nend = (PP - ev[1].stats.sac['b'] - 0.5)/dt # Window out 1/2 second before PP ctap = np.append( cosTaper(nend), np.zeros(N-nend + 1) ) else: ctap = cosTaper(N) # detrend, taper all three components for i in range(3): ####### DETREND & TAPER ################# ev[i].data = detrend(ev[i].data) * ctap # R, T = rotate(N, E) ev[1].data, ev[0].data = rotate(ev[1].data, ev[0].data, baz) # Call freetran and rotate into P and S space ev[1].data, ev[2].data = freetran( ev[1].data, ev[2].data, pslow, 6.06, 3.5) # Save freetran transformed data objects ev[1].write(os.path.join(eventdir,'stack_P.sac'), format='SAC') ev[2].write(os.path.join(eventdir,'stack_S.sac'), format='SAC')
def single_taper_spectrum(data, delta, taper_name=None): """ Returns the spectrum and the corresponding frequencies for data with the given taper. """ length = len(data) good_length = length // 2 + 1 # Create the frequencies. # XXX: This might be some kind of hack # Create the tapers. if taper_name == "bartlett": taper = np.bartlett(length) elif taper_name == "blackman": taper = np.blackman(length) elif taper_name == "boxcar": taper = np.ones(length) elif taper_name == "hamming": taper = np.hamming(length) elif taper_name == "hanning": taper = np.hanning(length) elif "kaiser" in taper_name: taper = np.kaiser(length, beta=14) # Should never happen. else: msg = "Something went wrong." raise Exception(msg) # Detrend the data. data = detrend(data) # Apply the taper. data *= taper # TLQ HACK nfft = int(2 ** nextpow2(len(data))) spec = abs(np.fft.rfft(data)) ** 2 freq = abs(np.fft.fftfreq(length, delta)[:good_length]) # freq = abs(scipy.fftpack.fftfreq(nfft, delta))[:nfft/2] # spec = abs(scipy.fftpack.rfft(data, n=nfft))[:nfft/2] ** 2 return spec, freq
def single_taper_spectrum(data, delta, taper_name=None): """ Returns the spectrum and the corresponding frequencies for data with the given taper. """ length = len(data) good_length = length // 2 + 1 # Create the frequencies. # XXX: This might be some kind of hack # Create the tapers. if taper_name == 'bartlett': taper = np.bartlett(length) elif taper_name == 'blackman': taper = np.blackman(length) elif taper_name == 'boxcar': taper = np.ones(length) elif taper_name == 'hamming': taper = np.hamming(length) elif taper_name == 'hanning': taper = np.hanning(length) elif 'kaiser' in taper_name: taper = np.kaiser(length, beta=14) # Should never happen. else: msg = 'Something went wrong.' raise Exception(msg) # Detrend the data. data = detrend(data) # Apply the taper. data *= taper #TLQ HACK nfft = int(2**nextpow2(len(data))) spec = abs(np.fft.rfft(data)) ** 2 freq = abs(np.fft.fftfreq(length, delta)[:good_length]) # freq = abs(scipy.fftpack.fftfreq(nfft, delta))[:nfft/2] # spec = abs(scipy.fftpack.rfft(data, n=nfft))[:nfft/2] ** 2 return spec, freq
def fft(self, x, win, nperseg, noverlap): if nperseg == 1 and noverlap == 0: result = x[..., np.newaxis] else: step = nperseg - noverlap shape = x.shape[:-1] + ((x.shape[-1] - noverlap) // step, nperseg) strides = x.strides[:-1] + (step * x.strides[-1], x.strides[-1]) result = np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides) result = signaltools.detrend(result, type='constant', axis=-1) result = win * result if np.iscomplexobj(x): func = fftpack.fft else: result = result.real func = np.fft.rfft result = func(result, n=nperseg) return result
def detrend_func(d): return signaltools.detrend(d, type=detrend, axis=-1)
def getrr_v1(data, fps=125.0, min_rr=300, peakfindrange=200, slopewidthrange=100, smooth_slope_ycenters=0.5, discard_short_peaks=False, interpolate=True, interp_n=50, convert_to_ms=False, plt=None, getslopes=False): if data.shape[0] < fps: return [], [], [] data = np.copy(data) if convert_to_ms: data[:, 0] *= 1000.0 if fps >= 250: factor = int(fps / 250) data = data[np.arange(0, len(data), factor), :] firstsecond = (data[int(fps), 0] - data[0, 0]) if firstsecond < 900: raise Warning( "Warning: FPS set to " + str(fps) + ", but timeframe of first FPS data points is " + str(firstsecond) + "ms (set convert_to_ms flag to convert between seconds and ms)") data[:, 1] /= np.std(data[:, 1]) data[:, 1] = detrend(data[:, 1]) # better detrending #data[:,1] -= strongsmoothing(data[:,1], 4) data[:, 1] = highpass(highpass(data[:, 1], fps), fps) # highpass detrend data[:, 1] = lowpass_fft(data[:, 1], fps, cf=3, tw=0.2) #slight noise filtering # outlier removal mn, mx = np.min(data[:, 1]), np.max(data[:, 1]) m = min(abs(mn), abs(mx)) containthreshold = 0.001 N = 100 step = m / float(N) for i in range(N): n = len(np.where(data[:, 1] < -m)[0]) + len( np.where(data[:, 1] > m)[0]) if n > containthreshold * len(data[:, 1]): break m -= step mn, mx = -m, m data[data[:, 1] < mn, 1] = mn data[data[:, 1] > mx, 1] = mx data[:, 1] /= np.std(data[:, 1]) data[:, 1] = detrend(data[:, 1]) # savgol peaks may be off (lower than the actual peak) - do a local max to deal with that maxdata = np.zeros((len(data), )) for i in range(len(maxdata)): mini = max(0, i - 1) maxi = min(len(maxdata), i + 2) maxdata[i] = np.max(data[mini:maxi, 1]) # get initial maxima and minima filtered = savgol_filter(data[:, 1], SAVGOL_WINDOWSIZE, SAVGOL_DEGREE) mintf = np.array(heartbeat_localmax(-1 * filtered)) maxtf = np.array(heartbeat_localmax(filtered)) # get trend of peaks hyp_peakidx = np.where(maxtf)[0] peakidx = [] max_window = min_rr for p in hyp_peakidx: mini = np.max((0, (p - int(fps * max_window / 1e3)))) maxi = np.min((len(maxdata), (p + int(fps * max_window / 1e3)))) m = mini + np.argmax(maxdata[mini:maxi]) while m < len(maxdata) - 1 and maxdata[m + 1] > maxdata[m]: m += 1 while m > 0 and maxdata[m - 1] > maxdata[m]: m -= 1 if m < len(data): peakidx.append(m) peakidx = np.unique(peakidx) f = interp1d(data[peakidx, 0].flatten(), filtered[peakidx].flatten(), bounds_error=False, kind='linear') # quadratic macrotrend = f(data[:, 0].flatten()) macrotrend[np.where(np.isnan(macrotrend))] = np.mean( macrotrend[np.where(~np.isnan(macrotrend))]) macrotrend = savgol_filter(macrotrend, TREND_SAVGOL_WINDOWSIZE, SAVGOL_DEGREE) # delete if not high or low enough """"T = macrotrend+MIN_PEAK_DIST_IN_STDEVS*np.std(filtered) # can leave minima - won't matter mintf[np.where(maxdata>T)[0]] = False T = macrotrend-MIN_PEAK_DIST_IN_STDEVS*np.std(filtered) maxtf[np.where(maxdata<T)[0]] = False""" minindices = np.where(mintf)[0] maxindices = np.where(maxtf)[0] if plt: plt.plot(data[:, 0], macrotrend - MIN_PEAK_DIST_IN_STDEVS * np.std(filtered)) #plt.plot(data[:,0], macrotrend+MIN_PEAK_DIST_IN_STDEVS*np.std(filtered), 'k') plt.scatter(data[minindices, 0], filtered[minindices], c='g', s=10) plt.scatter(data[maxindices, 0], filtered[maxindices], c='b', s=8) # ensure we start with min i = 0 while i < len(maxindices) and maxindices[i] < minindices[0]: i += 1 maxindices = maxindices[i:] i = len(minindices) - 1 while i > 1 and minindices[i] > maxindices[-1]: i -= 1 minindices = minindices[:(i + 1)] # min,min or max,max n = np.min((len(maxindices), len(minindices))) for i in range(n): needchange = True while needchange: needchange = False if i < len(minindices) - 1 and len( np.where((maxindices > minindices[i]) & ( maxindices < minindices[i + 1]))[0]) == 0: # min,min minindices = np.delete(minindices, i) needchange = True if i < len(maxindices) - 1 and len( np.where((minindices > maxindices[i]) & ( minindices < maxindices[i + 1]))[0]) == 0: # min,min maxindices = np.delete(maxindices, i + 1) needchange = True maxindices = maxindices[:len(minindices)] # get rid of too small inter-beat intervals maxindices, minindices = remove_double_beats(data, filtered, maxindices, minindices, min_rr=min_rr) maxindices, minindices = remove_double_beats(data, filtered, maxindices, minindices, min_rr=min_rr) if plt: #plt.plot(data[:,0], data[:,1], '--k', linewidth=3) plt.plot(data[:, 0], maxdata) plt.plot(data[:, 0], macrotrend, 'r', linewidth=0.2) plt.plot(data[:, 0], filtered, 'k', linewidth=0.5) #plt.scatter(data[:,0], data[:,1], s=20, c='b') mean_slopemid = (np.mean(maxdata[maxindices]) + np.mean(data[minindices, 1])) / 2.0 max_slopeheight = np.mean(maxdata[maxindices]) - np.mean(data[minindices, 1]) std_slopeheight = np.std(maxdata[maxindices]) + np.std(data[minindices, 1]) slopebeats = [] slopes = [] n = np.min((len(maxindices), len(minindices))) # loop through each upslope (min->max) and find an exact beat location in its middle for i in range(n - 1): if discard_short_peaks: slopeheight = maxdata[maxindices[i]] - data[minindices[i], 1] if slopeheight < max_slopeheight - 2 * std_slopeheight - MIN_PEAK_DIST_IN_STDEVS * np.std( filtered): if plt: print slopeheight, "<", max_slopeheight - 2 * std_slopeheight - MIN_PEAK_DIST_IN_STDEVS * np.std( filtered) plt.scatter(data[maxindices[i], 0], maxdata[maxindices[i]], 180, c='k') continue if interpolate: # interpolated #ymid = (1-smooth_slope_ycenters)*np.mean([filtered[minindices[i]], filtered[maxindices[i]]]) ymid = smooth_slope_ycenters * mean_slopemid + ( 1 - smooth_slope_ycenters) * np.mean( [maxdata[minindices[i]], 0.5 * maxdata[maxindices[i]]]) idxrange = crossing_bracket_indices(data[:, 1], minindices[i], maxindices[i], crossing=ymid) mni = max(0, idxrange[0] - 2) mxi = idxrange[1] + 2 if mxi > len(data) - 1: mxi = len(data) - 1 ii = np.linspace(data[mni, 0], data[mxi - 1, 0], interp_n) f = interp1d(data[mni:mxi, 0], data[mni:mxi, 1], kind='linear') # quadratic idata = f(ii) iidxrange = crossing_bracket_indices(idata, crossing=ymid) try: k = (idata[iidxrange][-1] - idata[iidxrange][0]) / ( ii[iidxrange][-1] - ii[iidxrange][0]) except: k = np.float32.max() if plt: plt.plot(ii, idata, c='m', linewidth=2) plt.scatter(ii[iidxrange], idata[iidxrange], s=40, c='c') #k = (idata[iidxrange][-1]-idata[iidxrange][0])/(ii[iidxrange][-1]-ii[iidxrange][0]) #plt.plot([ii[0]-100, ii[0]+200], [idata[0]-100*k, idata[0]+200*k], '--k', linewidth=2) #ymid = (1-smooth_slope_ycenters)*np.mean([filtered[minindices[i]], filtered[maxindices[i]]]) ymid = smooth_slope_ycenters * mean_slopemid + ( 1 - smooth_slope_ycenters) * np.mean( [maxdata[minindices[i]], 0.5 * maxdata[maxindices[i]]]) x, y = ii[iidxrange], idata[iidxrange] #x, y = ii[iidxrange].reshape(-1,1), idata[iidxrange].reshape(-1,1) #model = LinearRegression() #model.fit(y.reshape(-1,1), x.reshape(-1,1)) #xmid = model.predict([[ymid]]) # #xmid2 = [x[0]+y[0]*(x[1]-x[0])/(y[1]-y[0])] f = interp1d(y.flatten(), x.flatten(), bounds_error=False, kind='linear') # quadratic xmid = f([ymid]) if np.isnan(xmid[0]): xmid = [x[0] + y[0] * (x[1] - x[0]) / (y[1] - y[0])] if xmid < x[0] or xmid >= x[1]: xmid = [np.mean([x[0], x[1]])] slopebeats.append(xmid[0]) slopes.append(k) else: mididx = minindices[i] + (maxindices[i] - minindices[i]) / 2 if mididx - int(mididx) > 0.5 or int(mididx) >= len(data) - 1: idxrange = [int(mididx) - 2, int(mididx)] else: idxrange = [int(mididx) - 1, int(mididx)] x, y = data[idxrange, 0], data[idxrange, 1] ymid = (1 - smooth_slope_ycenters) * np.mean( [filtered[minindices[i]], filtered[maxindices[i]]]) f = interp1d(y.flatten(), x.flatten(), bounds_error=False, kind='linear') # quadratic xmid = f([ymid]) slopebeats.append(xmid[0]) if plt: try: plt.scatter(data[idxrange, 0], data[idxrange, 1], s=40, c='y') plt.scatter(data[minindices[i], 0], maxdata[minindices[i]], c='g', s=100) plt.scatter(data[maxindices[i], 0], maxdata[maxindices[i]], c='b', s=80) plt.plot(x, y) #yp = data[minindices[i]:maxindices[i], 1] #xp = model.predict(yp.reshape(-1,1)) #plt.plot(xp, yp, '--k', linewidth=2) plt.scatter(xmid, ymid, s=80, c='r') except Exception, e: pass
def _welch(x, y, fs=1.0, window='hanning', nperseg=256, noverlap=None, nfft=None, detrend='constant', scaling='density', axis=-1): """ A helper function to estimate cross spectral density using Welch's method. This function is a slightly modified version of `scipy.signal.welch()` with modifications based on `matplotlib.mlab._spectral_helper()`. Welch's method [1]_ computes an estimate of the cross spectral density by dividing the data into overlapping segments, computing a modified periodogram for each segment and averaging the cross-periodograms. Parameters ---------- x, y : array_like Time series of measurement values fs : float, optional Sampling frequency of the `x` and `y` time series in units of Hz. Defaults to 1.0. window : str or tuple or array_like, optional Desired window to use. See `get_window` for a list of windows and required parameters. If `window` is array_like it will be used directly as the window and its length will be used for nperseg. Defaults to 'hanning'. nperseg : int, optional Length of each segment. Defaults to 256. noverlap: int, optional Number of points to overlap between segments. If None, ``noverlap = nperseg / 2``. Defaults to None. nfft : int, optional Length of the FFT used, if a zero padded FFT is desired. If None, the FFT length is `nperseg`. Defaults to None. detrend : str or function, optional Specifies how to detrend each segment. If `detrend` is a string, it is passed as the ``type`` argument to `detrend`. If it is a function, it takes a segment and returns a detrended segment. Defaults to 'constant'. scaling : { 'density', 'spectrum' }, optional Selects between computing the power spectral density ('density') where Pxx has units of V**2/Hz if x is measured in V and computing the power spectrum ('spectrum') where Pxx has units of V**2 if x is measured in V. Defaults to 'density'. axis : int, optional Axis along which the periodogram is computed; the default is over the last axis (i.e. ``axis=-1``). Returns ------- f : ndarray Array of sample frequencies. Pxy : ndarray Cross spectral density or cross spectrum of x and y. Notes ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default 'hanning' window an overlap of 50% is a reasonable trade off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. If `noverlap` is 0, this method is equivalent to Bartlett's method [2]_. References ---------- .. [1] P. Welch, "The use of the fast Fourier transform for the estimation of power spectra: A method based on time averaging over short, modified periodograms", IEEE Trans. Audio Electroacoust. vol. 15, pp. 70-73, 1967. .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra", Biometrika, vol. 37, pp. 1-16, 1950. """ # TODO: This function should be replaced by `scipy.signal.csd()`, which # will appear in SciPy 0.16.0. # The checks for if y is x are so that we can use the same function to # obtain both power spectrum and cross spectrum without doing extra # calculations. same_data = y is x # Make sure we're dealing with a numpy array. If y and x were the same # object to start with, keep them that way x = np.asarray(x) if same_data: y = x else: if x.shape != y.shape: raise ValueError("x and y must be of the same shape.") y = np.asarray(y) if x.size == 0: return np.empty(x.shape), np.empty(x.shape) if axis != -1: x = np.rollaxis(x, axis, len(x.shape)) if not same_data: y = np.rollaxis(y, axis, len(y.shape)) if x.shape[-1] < nperseg: warnings.warn('nperseg = %d, is greater than x.shape[%d] = %d, using ' 'nperseg = x.shape[%d]' % (nperseg, axis, x.shape[axis], axis)) nperseg = x.shape[-1] if isinstance(window, string_types) or type(window) is tuple: win = get_window(window, nperseg) else: win = np.asarray(window) if len(win.shape) != 1: raise ValueError('window must be 1-D') if win.shape[0] > x.shape[-1]: raise ValueError('window is longer than x.') nperseg = win.shape[0] if scaling == 'density': scale = 1.0 / (fs * (win * win).sum()) elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: raise ValueError('Unknown scaling: %r' % scaling) if noverlap is None: noverlap = nperseg // 2 elif noverlap >= nperseg: raise ValueError('noverlap must be less than nperseg.') if nfft is None: nfft = nperseg elif nfft < nperseg: raise ValueError('nfft must be greater than or equal to nperseg.') if not hasattr(detrend, '__call__'): detrend_func = lambda seg: signaltools.detrend(seg, type=detrend) elif axis != -1: # Wrap this function so that it receives a shape that it could # reasonably expect to receive. def detrend_func(seg): seg = np.rollaxis(seg, -1, axis) seg = detrend(seg) return np.rollaxis(seg, axis, len(seg.shape)) else: detrend_func = detrend step = nperseg - noverlap indices = np.arange(0, x.shape[-1] - nperseg + 1, step) for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind + nperseg]) xft = fftpack.fft(x_dt * win, nfft) if same_data: yft = xft else: y_dt = detrend_func(y[..., ind:ind + nperseg]) yft = fftpack.fft(y_dt * win, nfft) if k == 0: Pxy = (xft * yft.conj()) else: Pxy *= k / (k + 1.0) Pxy += (xft * yft.conj()) / (k + 1.0) Pxy *= scale f = fftpack.fftfreq(nfft, 1.0 / fs) if axis != -1: Pxy = np.rollaxis(Pxy, -1, axis) return f, Pxy
def welch(x, fs=1.0, window='hanning', nperseg=256, noverlap=None, nfft=None, detrend='constant', return_onesided=True, scaling='density', axis=-1): """ Estimate power spectral density using Welch's method. Welch's method [1]_ computes an estimate of the power spectral density by dividing the data into overlapping segments, computing a modified periodogram for each segment and averaging the periodograms. Parameters ---------- x : array_like Time series of measurement values fs : float, optional Sampling frequency of the `x` time series in units of Hz. Defaults to 1.0. window : str or tuple or array_like, optional Desired window to use. See `get_window` for a list of windows and required parameters. If `window` is array_like it will be used directly as the window and its length will be used for nperseg. Defaults to 'hanning'. nperseg : int, optional Length of each segment. Defaults to 256. noverlap: int, optional Number of points to overlap between segments. If None, ``noverlap = nperseg / 2``. Defaults to None. nfft : int, optional Length of the FFT used, if a zero padded FFT is desired. If None, the FFT length is `nperseg`. Defaults to None. detrend : str or function or False, optional Specifies how to detrend each segment. If `detrend` is a string, it is passed as the ``type`` argument to `detrend`. If it is a function, it takes a segment and returns a detrended segment. If `detrend` is False, no detrending is done. Defaults to 'constant'. return_onesided : bool, optional If True, return a one-sided spectrum for real data. If False return a two-sided spectrum. Note that for complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the power spectral density ('density') where Pxx has units of V**2/Hz if x is measured in V and computing the power spectrum ('spectrum') where Pxx has units of V**2 if x is measured in V. Defaults to 'density'. axis : int, optional Axis along which the periodogram is computed; the default is over the last axis (i.e. ``axis=-1``). Returns ------- f : ndarray Array of sample frequencies. Pxx : ndarray Power spectral density or power spectrum of x. See Also -------- periodogram: Simple, optionally modified periodogram lombscargle: Lomb-Scargle periodogram for unevenly sampled data Notes ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default 'hanning' window an overlap of 50% is a reasonable trade off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. If `noverlap` is 0, this method is equivalent to Bartlett's method [2]_. .. versionadded:: 0.12.0 References ---------- .. [1] P. Welch, "The use of the fast Fourier transform for the estimation of power spectra: A method based on time averaging over short, modified periodograms", IEEE Trans. Audio Electroacoust. vol. 15, pp. 70-73, 1967. .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra", Biometrika, vol. 37, pp. 1-16, 1950. Examples -------- >>> from scipy import signal >>> import matplotlib.pyplot as plt Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by 0.001 V**2/Hz of white noise sampled at 10 kHz. >>> fs = 10e3 >>> N = 1e5 >>> amp = 2*np.sqrt(2) >>> freq = 1234.0 >>> noise_power = 0.001 * fs / 2 >>> time = np.arange(N) / fs >>> x = amp*np.sin(2*np.pi*freq*time) >>> x += np.random.normal(scale=np.sqrt(noise_power), size=time.shape) Compute and plot the power spectral density. >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024) >>> plt.semilogy(f, Pxx_den) >>> plt.ylim([0.5e-3, 1]) >>> plt.xlabel('frequency [Hz]') >>> plt.ylabel('PSD [V**2/Hz]') >>> plt.show() If we average the last half of the spectral density, to exclude the peak, we can recover the noise power on the signal. >>> np.mean(Pxx_den[256:]) 0.0009924865443739191 Now compute and plot the power spectrum. >>> f, Pxx_spec = signal.welch(x, fs, 'flattop', 1024, scaling='spectrum') >>> plt.figure() >>> plt.semilogy(f, np.sqrt(Pxx_spec)) >>> plt.xlabel('frequency [Hz]') >>> plt.ylabel('Linear spectrum [V RMS]') >>> plt.show() The peak height in the power spectrum is an estimate of the RMS amplitude. >>> np.sqrt(Pxx_spec.max()) 2.0077340678640727 """ x = np.asarray(x) if x.size == 0: return np.empty(x.shape), np.empty(x.shape) if axis != -1: x = np.rollaxis(x, axis, len(x.shape)) if x.shape[-1] < nperseg: warnings.warn('nperseg = %d, is greater than x.shape[%d] = %d, using ' 'nperseg = x.shape[%d]' % (nperseg, axis, x.shape[axis], axis)) nperseg = x.shape[-1] if isinstance(window, string_types) or type(window) is tuple: win = get_window(window, nperseg) else: win = np.asarray(window) if len(win.shape) != 1: raise ValueError('window must be 1-D') if win.shape[0] > x.shape[-1]: raise ValueError('window is longer than x.') nperseg = win.shape[0] # numpy 1.5.1 doesn't have result_type. outdtype = (np.array([x[0]]) * np.array([1], 'f')).dtype.char.lower() if win.dtype != outdtype: win = win.astype(outdtype) if scaling == 'density': scale = 1.0 / (fs * (win*win).sum()) elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: raise ValueError('Unknown scaling: %r' % scaling) if noverlap is None: noverlap = nperseg // 2 elif noverlap >= nperseg: raise ValueError('noverlap must be less than nperseg.') if nfft is None: nfft = nperseg elif nfft < nperseg: raise ValueError('nfft must be greater than or equal to nperseg.') if not detrend: detrend_func = lambda seg: seg elif not hasattr(detrend, '__call__'): detrend_func = lambda seg: signaltools.detrend(seg, type=detrend) elif axis != -1: # Wrap this function so that it receives a shape that it could # reasonably expect to receive. def detrend_func(seg): seg = np.rollaxis(seg, -1, axis) seg = detrend(seg) return np.rollaxis(seg, axis, len(seg.shape)) else: detrend_func = detrend step = nperseg - noverlap indices = np.arange(0, x.shape[-1]-nperseg+1, step) if np.isrealobj(x) and return_onesided: outshape = list(x.shape) if nfft % 2 == 0: # even outshape[-1] = nfft // 2 + 1 Pxx = np.empty(outshape, outdtype) for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind+nperseg]) xft = my_rfft(x_dt*win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) # fftpack.rfft returns the positive frequency part of the fft # as real values, packed r r i r i r i ... # this indexing is to extract the matching real and imaginary # parts, while also handling the pure real zero and nyquist # frequencies. if k == 0: Pxx[..., (0,-1)] = xft[..., (0,-1)]**2 Pxx[..., 1:-1] = xft[..., 1:-1:2]**2 + xft[..., 2::2]**2 else: Pxx *= k/(k+1.0) Pxx[..., (0,-1)] += xft[..., (0,-1)]**2 / (k+1.0) Pxx[..., 1:-1] += (xft[..., 1:-1:2]**2 + xft[..., 2::2]**2) \ / (k+1.0) else: # odd outshape[-1] = (nfft+1) // 2 Pxx = np.empty(outshape, outdtype) for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind+nperseg]) xft = my_rfft(x_dt*win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) #print (nfft, xft.shape, xft_2.shape) if k == 0: Pxx[..., 0] = xft[..., 0]**2 Pxx[..., 1:] = xft[..., 1::2]**2 + xft[..., 2::2]**2 else: Pxx *= k/(k+1.0) Pxx[..., 0] += xft[..., 0]**2 / (k+1) Pxx[..., 1:] += (xft[..., 1::2]**2 + xft[..., 2::2]**2) \ / (k+1.0) Pxx[..., 1:-1] *= 2*scale Pxx[..., (0,-1)] *= scale f = np.arange(Pxx.shape[-1]) * (fs/nfft) else: for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind+nperseg]) xft = my_rfft(x_dt*win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) if k == 0: Pxx = (xft * xft.conj()).real else: Pxx *= k/(k+1.0) Pxx += (xft * xft.conj()).real / (k+1.0) Pxx *= scale f = fftpack.fftfreq(nfft, 1.0/fs) if axis != -1: Pxx = np.rollaxis(Pxx, -1, axis) return f, Pxx
def welch(x, fs=1.0, window='hanning', nperseg=256, noverlap=None, nfft=None, detrend='constant', return_onesided=True, scaling='density', axis=-1): """ Estimate power spectral density using Welch's method. Welch's method [1]_ computes an estimate of the power spectral density by dividing the data into overlapping segments, computing a modified periodogram for each segment and averaging the periodograms. Parameters ---------- x : array_like Time series of measurement values fs : float, optional Sampling frequency of the `x` time series in units of Hz. Defaults to 1.0. window : str or tuple or array_like, optional Desired window to use. See `get_window` for a list of windows and required parameters. If `window` is array_like it will be used directly as the window and its length will be used for nperseg. Defaults to 'hanning'. nperseg : int, optional Length of each segment. Defaults to 256. noverlap: int, optional Number of points to overlap between segments. If None, ``noverlap = nperseg / 2``. Defaults to None. nfft : int, optional Length of the FFT used, if a zero padded FFT is desired. If None, the FFT length is `nperseg`. Defaults to None. detrend : str or function or False, optional Specifies how to detrend each segment. If `detrend` is a string, it is passed as the ``type`` argument to `detrend`. If it is a function, it takes a segment and returns a detrended segment. If `detrend` is False, no detrending is done. Defaults to 'constant'. return_onesided : bool, optional If True, return a one-sided spectrum for real data. If False return a two-sided spectrum. Note that for complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the power spectral density ('density') where Pxx has units of V**2/Hz if x is measured in V and computing the power spectrum ('spectrum') where Pxx has units of V**2 if x is measured in V. Defaults to 'density'. axis : int, optional Axis along which the periodogram is computed; the default is over the last axis (i.e. ``axis=-1``). Returns ------- f : ndarray Array of sample frequencies. Pxx : ndarray Power spectral density or power spectrum of x. See Also -------- periodogram: Simple, optionally modified periodogram lombscargle: Lomb-Scargle periodogram for unevenly sampled data Notes ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default 'hanning' window an overlap of 50% is a reasonable trade off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. If `noverlap` is 0, this method is equivalent to Bartlett's method [2]_. .. versionadded:: 0.12.0 References ---------- .. [1] P. Welch, "The use of the fast Fourier transform for the estimation of power spectra: A method based on time averaging over short, modified periodograms", IEEE Trans. Audio Electroacoust. vol. 15, pp. 70-73, 1967. .. [2] M.S. Bartlett, "Periodogram Analysis and Continuous Spectra", Biometrika, vol. 37, pp. 1-16, 1950. Examples -------- >>> from scipy import signal >>> import matplotlib.pyplot as plt Generate a test signal, a 2 Vrms sine wave at 1234 Hz, corrupted by 0.001 V**2/Hz of white noise sampled at 10 kHz. >>> fs = 10e3 >>> N = 1e5 >>> amp = 2*np.sqrt(2) >>> freq = 1234.0 >>> noise_power = 0.001 * fs / 2 >>> time = np.arange(N) / fs >>> x = amp*np.sin(2*np.pi*freq*time) >>> x += np.random.normal(scale=np.sqrt(noise_power), size=time.shape) Compute and plot the power spectral density. >>> f, Pxx_den = signal.welch(x, fs, nperseg=1024) >>> plt.semilogy(f, Pxx_den) >>> plt.ylim([0.5e-3, 1]) >>> plt.xlabel('frequency [Hz]') >>> plt.ylabel('PSD [V**2/Hz]') >>> plt.show() If we average the last half of the spectral density, to exclude the peak, we can recover the noise power on the signal. >>> np.mean(Pxx_den[256:]) 0.0009924865443739191 Now compute and plot the power spectrum. >>> f, Pxx_spec = signal.welch(x, fs, 'flattop', 1024, scaling='spectrum') >>> plt.figure() >>> plt.semilogy(f, np.sqrt(Pxx_spec)) >>> plt.xlabel('frequency [Hz]') >>> plt.ylabel('Linear spectrum [V RMS]') >>> plt.show() The peak height in the power spectrum is an estimate of the RMS amplitude. >>> np.sqrt(Pxx_spec.max()) 2.0077340678640727 """ x = np.asarray(x) if x.size == 0: return np.empty(x.shape), np.empty(x.shape) if axis != -1: x = np.rollaxis(x, axis, len(x.shape)) if x.shape[-1] < nperseg: warnings.warn('nperseg = %d, is greater than x.shape[%d] = %d, using ' 'nperseg = x.shape[%d]' % (nperseg, axis, x.shape[axis], axis)) nperseg = x.shape[-1] if isinstance(window, string_types) or type(window) is tuple: win = get_window(window, nperseg) else: win = np.asarray(window) if len(win.shape) != 1: raise ValueError('window must be 1-D') if win.shape[0] > x.shape[-1]: raise ValueError('window is longer than x.') nperseg = win.shape[0] # numpy 1.5.1 doesn't have result_type. outdtype = (np.array([x[0]]) * np.array([1], 'f')).dtype.char.lower() if win.dtype != outdtype: win = win.astype(outdtype) if scaling == 'density': scale = 1.0 / (fs * (win * win).sum()) elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: raise ValueError('Unknown scaling: %r' % scaling) if noverlap is None: noverlap = nperseg // 2 elif noverlap >= nperseg: raise ValueError('noverlap must be less than nperseg.') if nfft is None: nfft = nperseg elif nfft < nperseg: raise ValueError('nfft must be greater than or equal to nperseg.') if not detrend: detrend_func = lambda seg: seg elif not hasattr(detrend, '__call__'): detrend_func = lambda seg: signaltools.detrend(seg, type=detrend) elif axis != -1: # Wrap this function so that it receives a shape that it could # reasonably expect to receive. def detrend_func(seg): seg = np.rollaxis(seg, -1, axis) seg = detrend(seg) return np.rollaxis(seg, axis, len(seg.shape)) else: detrend_func = detrend step = nperseg - noverlap indices = np.arange(0, x.shape[-1] - nperseg + 1, step) if np.isrealobj(x) and return_onesided: outshape = list(x.shape) if nfft % 2 == 0: # even outshape[-1] = nfft // 2 + 1 Pxx = np.empty(outshape, outdtype) for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind + nperseg]) xft = my_rfft(x_dt * win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) # fftpack.rfft returns the positive frequency part of the fft # as real values, packed r r i r i r i ... # this indexing is to extract the matching real and imaginary # parts, while also handling the pure real zero and nyquist # frequencies. if k == 0: Pxx[..., (0, -1)] = xft[..., (0, -1)]**2 Pxx[..., 1:-1] = xft[..., 1:-1:2]**2 + xft[..., 2::2]**2 else: Pxx *= k / (k + 1.0) Pxx[..., (0, -1)] += xft[..., (0, -1)]**2 / (k + 1.0) Pxx[..., 1:-1] += (xft[..., 1:-1:2]**2 + xft[..., 2::2]**2) \ / (k+1.0) else: # odd outshape[-1] = (nfft + 1) // 2 Pxx = np.empty(outshape, outdtype) for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind + nperseg]) xft = my_rfft(x_dt * win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) #print (nfft, xft.shape, xft_2.shape) if k == 0: Pxx[..., 0] = xft[..., 0]**2 Pxx[..., 1:] = xft[..., 1::2]**2 + xft[..., 2::2]**2 else: Pxx *= k / (k + 1.0) Pxx[..., 0] += xft[..., 0]**2 / (k + 1) Pxx[..., 1:] += (xft[..., 1::2]**2 + xft[..., 2::2]**2) \ / (k+1.0) Pxx[..., 1:-1] *= 2 * scale Pxx[..., (0, -1)] *= scale f = np.arange(Pxx.shape[-1]) * (fs / nfft) else: for k, ind in enumerate(indices): x_dt = detrend_func(x[..., ind:ind + nperseg]) xft = my_rfft(x_dt * win, nfft) #xft = fftpack.rfft(x_dt*win, nfft) if k == 0: Pxx = (xft * xft.conj()).real else: Pxx *= k / (k + 1.0) Pxx += (xft * xft.conj()).real / (k + 1.0) Pxx *= scale f = fftpack.fftfreq(nfft, 1.0 / fs) if axis != -1: Pxx = np.rollaxis(Pxx, -1, axis) return f, Pxx
def ndim_welch(x, nperseg=256, window='hanning', scaling='density', detrend='constant', fs=1.0, axis=-1, padded=False): """Multidimensional Welch method: Calculating Power Spectral Density for time series of multiple [and one] dimension.""" ################################################################## # checking whether the time series lengths are longer than window # length for welch method. If negative the size has been set to time series # length. (Taken from original scipy.signal.welch) if (not padded) and x.shape[-1] < nperseg: warnings.warn('nperseg = %d, is greater than x.shape[%d] = %d, using ' 'nperseg = x.shape[%d]' % (nperseg, axis, x.shape[axis], axis)) nperseg = x.shape[-1] ######################################################### # setting the window as is done in original scipy.signal if isinstance(window, string_types) or type(window) is tuple: win = get_window(window, nperseg) else: win = np.asarray(window) if len(win.shape) != 1: raise ValueError('window must be 1-D') if win.shape[0] > x.shape[-1]: raise ValueError('window is longer than x.') nperseg = win.shape[0] ###################################################### #setting the scale as is done in original scipy.signal if scaling == 'density': scale = 1.0 / (fs * (win * win).sum()) elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: raise ValueError('Unknown scaling: %r' % scaling) ######################################################### # windowing the signal # (turning the multidimensional time series into multiple t # time series of windowed sections using the function 'win_seg') windowed_sig = win_sig(x, nperseg, padded) ################################## # detrending step if not detrend: detrend_func = lambda seg: seg elif not hasattr(detrend, '__call__'): detrend_func = lambda seg: signaltools.detrend(seg, type=detrend) elif axis != -1: # Wrap this function so that it receives a shape that it could # reasonably expect to receive. def detrend_func(seg): seg = np.rollaxis(seg, -1, axis) seg = detrend(seg) return np.rollaxis(seg, axis, len(seg.shape)) else: detrend_func = detrend windowed_sig = detrend_func(windowed_sig) ####################################### # spectral density estimation # multiplying by window windowed_sig = np.multiply(win, windowed_sig) # calculating the fourier transform windowed_seg_fft = fft(windowed_sig) windowed_fft = windowed_seg_fft.T # returning the spectral density with calcualting outerproducts to get the # crossspectrum matrix and also returning the frequency set spec_density = np.mean( np.einsum('...i,...j->...ij', windowed_fft, windowed_fft.conjugate()) * scale, axis=1) spec_freq = fftfreq(nperseg) return spec_freq, np.squeeze(spec_density).real
def ndim_welch(x,nperseg=256,window='hanning',scaling = 'density',detrend='constant',fs=1.0,axis=-1,padded=False): """Multidimensional Welch method: Calculating Power Spectral Density for time series of multiple [and one] dimension.""" ################################################################## # checking whether the time series lengths are longer than window # length for welch method. If negative the size has been set to time series # length. (Taken from original scipy.signal.welch) if (not padded) and x.shape[-1] < nperseg: warnings.warn('nperseg = %d, is greater than x.shape[%d] = %d, using ' 'nperseg = x.shape[%d]' % (nperseg, axis, x.shape[axis], axis)) nperseg = x.shape[-1] ######################################################### # setting the window as is done in original scipy.signal if isinstance(window, string_types) or type(window) is tuple: win = get_window(window, nperseg) else: win = np.asarray(window) if len(win.shape) != 1: raise ValueError('window must be 1-D') if win.shape[0] > x.shape[-1]: raise ValueError('window is longer than x.') nperseg = win.shape[0] ###################################################### #setting the scale as is done in original scipy.signal if scaling == 'density': scale = 1.0 / (fs * (win*win).sum()) elif scaling == 'spectrum': scale = 1.0 / win.sum()**2 else: raise ValueError('Unknown scaling: %r' % scaling) ######################################################### # windowing the signal # (turning the multidimensional time series into multiple t # time series of windowed sections using the function 'win_seg') windowed_sig=win_sig(x,nperseg,padded) ################################## # detrending step if not detrend: detrend_func = lambda seg: seg elif not hasattr(detrend, '__call__'): detrend_func = lambda seg: signaltools.detrend(seg, type=detrend) elif axis != -1: # Wrap this function so that it receives a shape that it could # reasonably expect to receive. def detrend_func(seg): seg = np.rollaxis(seg, -1, axis) seg = detrend(seg) return np.rollaxis(seg, axis, len(seg.shape)) else: detrend_func = detrend windowed_sig=detrend_func(windowed_sig) ####################################### # spectral density estimation # multiplying by window windowed_sig=np.multiply(win,windowed_sig) # calculating the fourier transform windowed_seg_fft=fft(windowed_sig) windowed_fft=windowed_seg_fft.T # returning the spectral density with calcualting outerproducts to get the # crossspectrum matrix and also returning the frequency set spec_density=np.mean(np.einsum('...i,...j->...ij',windowed_fft,windowed_fft.conjugate())*scale,axis=1) spec_freq=fftfreq(nperseg) return spec_freq,np.squeeze(spec_density).real
def DetrendFunc(self, d): return signaltools.detrend(d, type='constant', axis=-1)