def compute_spectrum_medfilt(sig, fs, filt_len=1., f_range=None): """Estimate the power spectral densitry as a smoothed FFT. Parameters ---------- sig : 1d or 2d array Time series of measurement values. fs : float Sampling rate, in Hz. filt_len : float, optional, default: 1. Length of the median filter, in Hz. f_range : list of [float, float] optional Frequency range to sub-select from the power spectrum. Returns ------- freqs : 1d array Array of sample frequencies. spectrum : 1d or 2d array Power spectral density. """ # Take the positive half of the spectrum since it's symmetrical ft = np.fft.fft(sig)[:int(np.ceil(len(sig) / 2.))] freqs = np.fft.fftfreq(len(sig), 1. / fs)[:int(np.ceil(len(sig) / 2.))] # get freq axis # Convert median filter length from Hz to samples filt_len_samp = int(int(filt_len / (freqs[1] - freqs[0])) / 2 * 2 + 1) spectrum = medfilt(np.abs(ft)**2. / (fs * len(sig)), filt_len_samp) if f_range: freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) return freqs, spectrum
def compute_spectrum_welch(sig, fs, avg_type='mean', window='hann', nperseg=None, noverlap=None, f_range=None, outlier_percent=None): """Compute the power spectral density using Welch's method. Parameters ----------- sig : 1d or 2d array Time series. fs : float Sampling rate, in Hz. avg_type : {'mean', 'median'}, optional Method to average across the windows: * 'mean' is the same as Welch's method, taking the mean across FFT windows. * 'median' uses median across FFT windows instead of the mean, to minimize outlier effect. window : str or tuple or array_like, optional, default: 'hann' Desired window to use. See scipy.signal.get_window for a list of available windows. If array_like, the array will be used as the window and its length must be nperseg. nperseg : int, optional Length of each segment, in number of samples. If None, and window is str or tuple, is set to 1 second of data. If None, and window is array_like, is set to the length of the window. noverlap : int, optional Number of points to overlap between segments. If None, noverlap = nperseg // 8. f_range : list of [float, float] optional Frequency range to sub-select from the power spectrum. outlier_percent : float, optional The percentage of outlier values to be removed. Must be between 0 and 100. Returns ------- freqs : 1d array Frequencies at which the measure was calculated. spectrum : 1d or 2d array Power spectral density. """ # Calculate the short time fourier transform with signal.spectrogram nperseg, noverlap = check_spg_settings(fs, window, nperseg, noverlap) freqs, _, spg = spectrogram(sig, fs, window, nperseg, noverlap) # Throw out outliers if indicated if outlier_percent is not None: spg = discard_outliers(spg, outlier_percent) # Average across windows spectrum = get_avg_func(avg_type)(spg, axis=-1) # Trim spectrum, if requested if f_range: freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) return freqs, spectrum
def compute_spectrum_medfilt(sig, fs, filt_len=1., f_range=None): """Compute the power spectral density as a smoothed FFT. Parameters ---------- sig : 1d or 2d array Time series. fs : float Sampling rate, in Hz. filt_len : float, optional, default: 1 Length of the median filter, in Hz. f_range : list of [float, float], optional Frequency range to sub-select from the power spectrum. Returns ------- freqs : 1d array Frequencies at which the measure was calculated. spectrum : 1d or 2d array Power spectral density. Examples -------- Compute the power spectrum of a simulated time series as a smoothed FFT: >>> from neurodsp.sim import sim_combined >>> sig = sim_combined(n_seconds=10, fs=500, ... components={'sim_powerlaw': {}, 'sim_oscillation' : {'freq': 10}}) >>> freqs, spec = compute_spectrum_medfilt(sig, fs=500) """ # Take the positive half of the spectrum, since it's symmetrical ft = np.fft.fft(sig)[:int(np.ceil(len(sig) / 2.))] freqs = np.fft.fftfreq(len(sig), 1. / fs)[:int(np.ceil(len(sig) / 2.))] # Convert median filter length from Hz to samples, and make sure it is odd filt_len_samp = int(filt_len / (freqs[1] - freqs[0])) if filt_len_samp % 2 == 0: filt_len_samp += 1 spectrum = medfilt(np.abs(ft)**2. / (fs * len(sig)), filt_len_samp) if f_range: freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) return freqs, spectrum
def compute_spectrum_medfilt(sig, fs, filt_len=1., f_range=None): """Compute the power spectral density as a smoothed FFT. Parameters ---------- sig : 1d or 2d array Time series. fs : float Sampling rate, in Hz. filt_len : float, optional, default: 1 Length of the median filter, in Hz. f_range : list of [float, float], optional Frequency range to sub-select from the power spectrum. Returns ------- freqs : 1d array Frequencies at which the measure was calculated. spectrum : 1d or 2d array Power spectral density. """ # Take the positive half of the spectrum since it's symmetrical ft = np.fft.fft(sig)[:int(np.ceil(len(sig) / 2.))] freqs = np.fft.fftfreq(len(sig), 1. / fs)[:int(np.ceil(len(sig) / 2.))] # Convert median filter length from Hz to samples, and make sure it is odd filt_len_samp = int(int(filt_len / (freqs[1] - freqs[0]))) if filt_len_samp % 2 == 0: filt_len_samp += 1 spectrum = medfilt(np.abs(ft)**2. / (fs * len(sig)), filt_len_samp) if f_range: freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) return freqs, spectrum
def compute_spectral_hist(sig, fs, window='hann', nperseg=None, noverlap=None, nbins=50, f_range=[0., 100.], cut_pct=[0., 100.]): """Compute the distribution of log10 power at each frequency from the signal spectrogram. Parameters ----------- sig : 1d array Time series of measurement values. fs : float Sampling rate, in Hz. window : str or tuple or array_like, optional, default='hann' Desired window to use. Defaults to a Hann window. See scipy.signal.get_window for a list of windows and required parameters. If array_like, the array will be used as the window and its length must be nperseg. nperseg : int, optional Length of each segment, in number of samples. If None, and window is str or tuple, is set to 1 second of data. If None, and window is array_like, is set to the length of the window. noverlap : int, optional Number of points to overlap between segments. If None, noverlap = nperseg // 2. nbins : int, optional, default: 50 Number of histogram bins to use. f_range : list of [float, float], optional, default: [0, 100] Frequency range of the spectrogram to compute the histograms, as [start, end], in Hz. cut_pct : list of [float, float], optional, default: [0, 100] Power percentile at which to draw the lower and upper bin limits, as [low, high], in Hz. Returns ------- freqs : 1d array Array of frequencies. power_bins : 1d array Histogram bins used to compute the distribution. spectral_hist : 2d array Power distribution at every frequency, nbins x fs 2D matrix. Notes ----- The histogram bins are the same for every frequency, thus evenly spacing the global min and max power. """ # Compute spectrogram of data nperseg, noverlap = check_spg_settings(fs, window, nperseg, noverlap) freqs, _, spg = spectrogram(sig, fs, window, nperseg, noverlap, return_onesided=True) # Get log10 power & limit to frequency range of interest before binning ps = np.transpose(np.log10(spg)) freqs, ps = trim_spectrum(freqs, ps, f_range) # Prepare bins for power - min and max of bins determined by power cutoff percentage power_min, power_max = np.percentile(np.ndarray.flatten(ps), cut_pct) power_bins = np.linspace(power_min, power_max, nbins + 1) # Compute histogram of power for each frequency spectral_hist = np.zeros((len(ps[0]), nbins)) for ind in range(len(ps[0])): spectral_hist[ind], _ = np.histogram(ps[:, ind], power_bins) spectral_hist[ind] = spectral_hist[ind] / sum(spectral_hist[ind]) # Flip output for more sensible plotting direction spectral_hist = np.transpose(spectral_hist) spectral_hist = np.flipud(spectral_hist) return freqs, power_bins, spectral_hist
def compute_spectrum_welch(sig, fs, avg_type='mean', window='hann', nperseg=None, noverlap=None, f_range=None, outlier_percent=None): """Compute the power spectral density using Welch's method. Parameters ---------- sig : 1d or 2d array Time series. fs : float Sampling rate, in Hz. avg_type : {'mean', 'median'}, optional Method to average across the windows: * 'mean' is the same as Welch's method, taking the mean across FFT windows. * 'median' uses median across FFT windows instead of the mean, to minimize outlier effects. window : str or tuple or array_like, optional, default: 'hann' Desired window to use. See scipy.signal.get_window for a list of available windows. If array_like, the array will be used as the window and its length must be nperseg. nperseg : int, optional Length of each segment, in number of samples. If None, and window is str or tuple, is set to 1 second of data. If None, and window is array_like, is set to the length of the window. noverlap : int, optional Number of points to overlap between segments. If None, noverlap = nperseg // 8. f_range : list of [float, float], optional Frequency range to sub-select from the power spectrum. outlier_percent : float, optional The percentage of outlier values to be removed. Must be between 0 and 100. Returns ------- freqs : 1d array Frequencies at which the measure was calculated. spectrum : 1d or 2d array Power spectral density. Notes ----- - Welch's method ([1]_) computes a power spectra by averaging over windowed FFTs. References ---------- .. [1] Welch, P. (1967). The use of fast Fourier transform for the estimation of power spectra: A method based on time averaging over short, modified periodograms. IEEE Transactions on Audio and Electroacoustics, 15(2), 70–73. DOI: https://doi.org/10.1109/TAU.1967.1161901 Examples -------- Compute the power spectrum of a simulated time series using Welch's method: >>> from neurodsp.sim import sim_combined >>> sig = sim_combined(n_seconds=10, fs=500, ... components={'sim_powerlaw': {}, 'sim_oscillation': {'freq': 10}}) >>> freqs, spec = compute_spectrum_welch(sig, fs=500) """ # Calculate the short time Fourier transform with signal.spectrogram nperseg, noverlap = check_spg_settings(fs, window, nperseg, noverlap) freqs, _, spg = spectrogram(sig, fs, window, nperseg, noverlap) # Throw out outliers if indicated if outlier_percent is not None: spg = discard_outliers(spg, outlier_percent) # Average across windows spectrum = get_avg_func(avg_type)(spg, axis=-1) # Trim spectrum, if requested if f_range: freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) return freqs, spectrum