diff --git a/README.md b/README.md index 9816d28d8..4e4de8038 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ FOOOF conceives of a model of the power spectrum as a combination of two distinc This model driven approach can be used to measure periodic and aperiodic properties of electrophysiological data, including EEG, MEG, ECoG and LFP data. -The benefit of using FOOOF for measuring putative oscillations, is that peaks in the power spectrum are characterized in terms of their specific center frequency, amplitude and bandwidth without requiring predefining specific bands of interest and controlling for the aperiodic component. FOOOF also gives you a measure of this aperiodic components of the signal, allowing for measuring and comparison of 1/f like components of the signal within and between subjects. +The benefit of using FOOOF for measuring putative oscillations, is that peaks in the power spectrum are characterized in terms of their specific center frequency, power and bandwidth without requiring predefining specific bands of interest and controlling for the aperiodic component. FOOOF also gives you a measure of this aperiodic components of the signal, allowing for measuring and comparison of 1/f like components of the signal within and between subjects. ## Documentation @@ -114,13 +114,13 @@ FOOOF.report() fits the model, plots the original power spectrum with the associ FOOOF also accepts parameters for fine-tuning the fit. For example: ```python -fm = FOOOF(peak_width_limits=[1.0, 8.0], max_n_peaks=6, min_peak_amplitude=0.1, peak_threshold=2.0) +fm = FOOOF(peak_width_limits=[1.0, 8.0], max_n_peaks=6, min_peak_height=0.1, peak_threshold=2.0) ``` * `peak_width_limits` sets the possible lower- and upper-bounds for the fitted peak widths. * `max_n_peaks` sets the maximum number of peaks to fit. -* `min_peak_amp` sets an absolute limit on the minimum amplitude (above aperiodic) for any extracted peak. -* `peak_threshold`, also sets a threshold above which a peak amplitude must cross to be included in the model. This parameter is in terms of standard deviation above the noise of the flattened spectrum. +* `min_peak_height` sets an absolute limit on the minimum height (above aperiodic) for any extracted peak. +* `peak_threshold`, also sets a threshold above which a peak height must cross to be included in the model. This parameter is in terms of standard deviation above the noise of the flattened spectrum. FOOOF also has convenience methods for running the FOOOF model across matrices of multiple power spectra, as well as functionality for saving and loading results, creating reports from FOOOF outputs, and utilities to further analize FOOOF results. diff --git a/doc/api.rst b/doc/api.rst index 9f9815574..609f75240 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -52,7 +52,7 @@ Analysis Functions get_band_peak get_band_peak_group - get_highest_amp_peak + get_highest_peak Synth Code ---------- diff --git a/doc/faq.rst b/doc/faq.rst index fe23b18d6..4c95381b2 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -129,7 +129,7 @@ symmetric function (gaussians) to what can be an asymetric peak power spectrum. Because of this, it is often useful to focus on the dominant (highest power) peak within a given frequency band from a FOOOF analysis, as this peak will offer the best estimate of -the putative oscillations center frequency and amplitude. If analyzing bandwidth of extracted peaks, +the putative oscillations center frequency and power. If analyzing bandwidth of extracted peaks, than overlapping peaks should always be considered. FOOOF is not currently optimized for inferring whether multiple peaks within a frequency band likely reflect distinct oscillations or not. diff --git a/examples/plot_synthetic_power_spectra.py b/examples/plot_synthetic_power_spectra.py index 3957d4ab6..3d942cdda 100644 --- a/examples/plot_synthetic_power_spectra.py +++ b/examples/plot_synthetic_power_spectra.py @@ -57,7 +57,7 @@ # the example below, this is interpreted as [offset, knee, exponent] for a 'knee' spectrum. # # Power spectra can also be simulated with any number of peaks. Peaks can be listed in a flat -# list with [center frequency, amplitude, bandwidth] listed for as many peaks as you would +# list with [center frequency, height, bandwidth] listed for as many peaks as you would # like to add, or as a list of lists containing the same information. # # The following example shows simulating a different power spectrum with some different diff --git a/fooof/analysis.py b/fooof/analysis.py index 2c78f8222..a6f5c6a3d 100644 --- a/fooof/analysis.py +++ b/fooof/analysis.py @@ -65,7 +65,7 @@ def get_band_peaks_group(peak_params, band, n_fits): Returns ------- band_peaks : 2d array - Peak data. Each row is a peak, as [CF, Amp, BW]. + Peak data. Each row is a peak, as [CF, PW, BW]. Notes ----- @@ -104,12 +104,12 @@ def get_band_peak(peak_params, band, ret_one=True): Defines the band of interest, as (lower_frequency_bound, upper_frequency_bound). ret_one : bool, optional, default: True Whether to return single peak (if True) or all peaks within the range found (if False). - If True, returns the highest power peak within the search range. + If True, returns the highest peak within the search range. Returns ------- band_peaks : 1d or 2d array - Peak data. Each row is a peak, as [CF, Amp, BW] + Peak data. Each row is a peak, as [CF, PW, BW] """ # Return nan array if empty input @@ -127,17 +127,17 @@ def get_band_peak(peak_params, band, ret_one=True): band_peaks = peak_params[peak_inds, :] - # If results > 1 and ret_one, then we return the highest power peak + # If results > 1 and ret_one, then we return the highest peak # Call a sub-function to select highest power peak in band if n_peaks > 1 and ret_one: - band_peaks = get_highest_amp_peak(band_peaks) + band_peaks = get_highest_peak(band_peaks) # If results == 1, return single peak return np.squeeze(band_peaks) -def get_highest_amp_peak(band_peaks): - """Searches for the highest amplitude peak. +def get_highest_peak(band_peaks): + """Searches for the highest peak. Parameters ---------- @@ -147,7 +147,7 @@ def get_highest_amp_peak(band_peaks): Returns ------- 1d array - Seleced peak data. Row is a peak, as [CF, Amp, BW]. + Peak data. Each row is a peak, as [CF, PW, BW]. """ # Catch & return NaN if empty diff --git a/fooof/core/funcs.py b/fooof/core/funcs.py index 6c76b3acf..4cc3c15e5 100644 --- a/fooof/core/funcs.py +++ b/fooof/core/funcs.py @@ -32,9 +32,9 @@ def gaussian_function(xs, *params): for ii in range(0, len(params), 3): - ctr, amp, wid = params[ii:ii+3] + ctr, hgt, wid = params[ii:ii+3] - ys = ys + amp * np.exp(-(xs-ctr)**2 / (2*wid**2)) + ys = ys + hgt * np.exp(-(xs-ctr)**2 / (2*wid**2)) return ys diff --git a/fooof/core/info.py b/fooof/core/info.py index e41443400..5aea7fe97 100644 --- a/fooof/core/info.py +++ b/fooof/core/info.py @@ -13,7 +13,7 @@ def get_obj_desc(): 'r_squared_', 'error_', '_gaussian_params'], 'settings' : ['peak_width_limits', 'max_n_peaks', - 'min_peak_amplitude', 'peak_threshold', + 'min_peak_height', 'peak_threshold', 'aperiodic_mode'], 'data' : ['power_spectrum', 'freq_range', 'freq_res'], 'data_info' : ['freq_range', 'freq_res'], @@ -40,12 +40,12 @@ def get_data_indices(aperiodic_mode): """ indices = { - 'CF' : 0, - 'Amp' : 1, - 'BW' : 2, + 'CF' : 0, + 'PW' : 1, + 'BW' : 2, 'offset' : 0, - 'knee' : 1 if aperiodic_mode == 'knee' else None, - 'exponent' : 1 if aperiodic_mode == 'fixed' else 2 + 'knee' : 1 if aperiodic_mode == 'knee' else None, + 'exponent' : 1 if aperiodic_mode == 'fixed' else 2 } return indices diff --git a/fooof/core/strings.py b/fooof/core/strings.py index bb1d621e7..0d151dfc9 100644 --- a/fooof/core/strings.py +++ b/fooof/core/strings.py @@ -59,7 +59,7 @@ def gen_settings_str(f_obj, description=False, concise=False): # Parameter descriptions to print out, if requested desc = {'peak_width_limits' : 'Enforced limits for peak widths, in Hz.', 'max_n_peaks' : 'The maximum number of peaks that can be extracted.', - 'min_peak_amplitude' : 'Minimum absolute amplitude of a peak, above aperiodic component.', + 'min_peak_height' : 'Minimum absolute height of a peak, above the aperiodic component.', 'peak_threshold' : 'Threshold at which to stop searching for peaks.', 'aperiodic_mode' : 'The aproach taken to fitting the aperiodic component.'} @@ -81,9 +81,9 @@ def gen_settings_str(f_obj, description=False, concise=False): '{}'.format(desc['peak_width_limits']), 'Max Number of Peaks : {}'.format(f_obj.max_n_peaks), '{}'.format(desc['max_n_peaks']), - 'Minimum Amplitude : {}'.format(f_obj.min_peak_amplitude), - '{}'.format(desc['min_peak_amplitude']), - 'Amplitude Threshold: {}'.format(f_obj.peak_threshold), + 'Minimum Peak Height : {}'.format(f_obj.min_peak_height), + '{}'.format(desc['min_peak_height']), + 'Peak Threshold: {}'.format(f_obj.peak_threshold), '{}'.format(desc['peak_threshold']), 'Aperiodic Mode : {}'.format(f_obj.aperiodic_mode), '{}'.format(desc['aperiodic_mode'])] if el != ''], @@ -143,7 +143,7 @@ def gen_results_str_fm(fm, concise=False): # Peak parameters '{} peaks were found:'.format( len(fm.peak_params_)), - *['CF: {:6.2f}, Amp: {:6.3f}, BW: {:5.2f}'.format(op[0], op[1], op[2]) \ + *['CF: {:6.2f}, PW: {:6.3f}, BW: {:5.2f}'.format(op[0], op[1], op[2]) \ for op in fm.peak_params_], '', diff --git a/fooof/data.py b/fooof/data.py index e9373758c..80a258f0c 100644 --- a/fooof/data.py +++ b/fooof/data.py @@ -6,7 +6,7 @@ ################################################################################################### FOOOFSettings = namedtuple('FOOOFSettings', ['peak_width_limits', 'max_n_peaks', - 'min_peak_amplitude', 'peak_threshold', + 'min_peak_height', 'peak_threshold', 'aperiodic_mode']) FOOOFSettings.__doc__ = """\ The user defined settings for a FOOOF object. @@ -17,10 +17,10 @@ Limits on possible peak width, as (lower_bound, upper_bound). max_n_peaks : int, optional, default: inf Maximum number of gaussians to be fit in a single spectrum. -min_peak_amplitude : float, optional, default: 0 - Minimum amplitude threshold for a peak to be modeled. +min_peak_height : float, optional, default: 0 + Absolute threshold for detecting peaks, in units of the input data. peak_threshold : float, optional, default: 2.0 - Threshold for detecting peaks, units of standard deviation. + Relative threshold for detecting peaks, in units of standard deviation of the input data. aperiodic_mode : {'fixed', 'knee'} Which approach to take for fitting the aperiodic component. """ @@ -51,13 +51,14 @@ Parameters that define the aperiodic fit. As [Offset, (Knee), Exponent]. The knee parameter is only included if aperiodic is fit with knee. peak_params : 2d array - Fitted parameter values for the peaks. Each row is a peak, as [CF, Amp, BW]. + Fitted parameter values for the peaks. Each row is a peak, as [CF, PW, BW]. r_squared : float R-squared of the fit between the input power spectrum and the full model fit. error : float Root mean squared error of the full model fit. gaussian_params : 2d array - Parameters that define the gaussian fit(s). Each row is a gaussian, as [mean, amp, std]. + Parameters that define the gaussian fit(s). + Each row is a gaussian, as [mean, height, standard deviation]. """ @@ -70,9 +71,10 @@ ---------- aperiodic_params : list, len 2 or 3 Parameters that define the aperiodic fit. As [Offset, (Knee), Exponent]. - The knee parameter is only included if aperiodic is fit with knee. Otherwise, length is 2. + The knee parameter is only included if aperiodic is fit with knee. Otherwise, length is 2. gaussian_params : list or list of lists - Fitted parameter values for the peaks. Each list is a peak, as [CF, Amp, BW]. + Fitted parameter values for the peaks. + Each list is a peak, with a gaussian definition of [mean, height, standard deviation]. nlv : float Noise level added to the generated power spectrum. """ diff --git a/fooof/fit.py b/fooof/fit.py index 1effa50f4..cf0a35a69 100644 --- a/fooof/fit.py +++ b/fooof/fit.py @@ -12,13 +12,14 @@ _spectrum_peak_rm : 1d array Power spectrum with peaks removed (not flattened). _gaussian_params : 2d array - Parameters that define the gaussian fit(s). Each row is a gaussian, as [mean, amp, std]. + Parameters that define the gaussian fit(s). + Each row is a gaussian, as [mean, height, standard deviation]. _ap_fit : 1d array Values of the aperiodic fit. _peak_fit : 1d array Values of the peak fit (flattened). -_ap_amp_thresh : float - Noise threshold for finding peaks above the aperiodic component. +_ap_percentile_thresh : float + Percentile threshold for finding peaks above the aperiodic component. _ap_guess : list of [float, float, float] Guess parameters for fitting the aperiodic component. _ap_bounds : tuple of tuple of float @@ -67,10 +68,10 @@ class FOOOF(object): Limits on possible peak width, as (lower_bound, upper_bound). max_n_peaks : int, optional, default: inf Maximum number of gaussians to be fit in a single spectrum. - min_peak_amplitude : float, optional, default: 0 - Minimum amplitude threshold for a peak to be modeled. + min_peak_height : float, optional, default: 0 + Absolute threshold for detecting peaks, in units of the input data. peak_threshold : float, optional, default: 2.0 - Threshold for detecting peaks, units of standard deviation. + Relative threshold for detecting peaks, in units of standard deviation of the input data. aperiodic_mode : {'fixed', 'knee'} Which approach to take for fitting the aperiodic component. verbose : boolean, optional, default: True @@ -92,7 +93,7 @@ class FOOOF(object): Parameters that define the aperiodic fit. As [Offset, (Knee), Exponent]. The knee parameter is only included if aperiodic component is fit with a knee. peak_params_ : 2d array - Fitted parameter values for the peaks. Each row is a peak, as [CF, Amp, BW]. + Fitted parameter values for the peaks. Each row is a peak, as [CF, PW, BW]. r_squared_ : float R-squared of the fit between the input power spectrum and the full model fit. error_ : float @@ -101,7 +102,7 @@ class FOOOF(object): Notes ----- - Commonly used abbreviations used in FOOOF include - CF: center frequency, Amp: amplitude, BW: Bandwidth, ap: aperiodic + CF: center frequency, PW: power, BW: Bandwidth, ap: aperiodic - Input power spectra must be provided in linear scale. Internally they are stored in log10 scale, as this is what the model operates upon. - Input power spectra should be smooth, as overly noisy power spectra may lead to bad fits. @@ -111,7 +112,7 @@ class FOOOF(object): get smoother power spectra, as this will give better FOOOF fits. """ - def __init__(self, peak_width_limits=(0.5, 12.0), max_n_peaks=np.inf, min_peak_amplitude=0.0, + def __init__(self, peak_width_limits=(0.5, 12.0), max_n_peaks=np.inf, min_peak_height=0.0, peak_threshold=2.0, aperiodic_mode='fixed', verbose=True): """Initialize FOOOF object with run parameters.""" @@ -124,15 +125,15 @@ def __init__(self, peak_width_limits=(0.5, 12.0), max_n_peaks=np.inf, min_peak_a # Set input parameters self.peak_width_limits = peak_width_limits self.max_n_peaks = max_n_peaks - self.min_peak_amplitude = min_peak_amplitude + self.min_peak_height = min_peak_height self.peak_threshold = peak_threshold self.aperiodic_mode = aperiodic_mode self.verbose = verbose ## SETTINGS - these are updateable by the user if required. - # Noise threshold, as a percentage of the lowest amplitude values in the total data to fit. - # Defines the minimum amplitude, above residuals, to be considered a peak. - self._ap_amp_thresh = 0.025 + # Noise threshold, as a percentage of the lowest magnitude values in the total data to fit. + # Defines the minimum height, above residuals, to be considered a peak. + self._ap_percentile_thresh = 0.025 # Guess parameters for aperiodic fitting, [offset, knee, exponent] # If offset guess is None, the first value of the power spectrum is used as offset guess self._ap_guess = (None, 0, 2) @@ -593,11 +594,11 @@ def _robust_ap_fit(self, freqs, power_spectrum): # Flatten outliers - any points that drop below 0 flatspec[flatspec < 0] = 0 - # Amplitude threshold - in terms of # of points - perc_thresh = np.percentile(flatspec, self._ap_amp_thresh) - amp_mask = flatspec <= perc_thresh - freqs_ignore = freqs[amp_mask] - spectrum_ignore = power_spectrum[amp_mask] + # Use percential threshold, in terms of # of points, to extract and re-fit + perc_thresh = np.percentile(flatspec, self._ap_percentile_thresh) + perc_mask = flatspec <= perc_thresh + freqs_ignore = freqs[perc_mask] + spectrum_ignore = power_spectrum[perc_mask] # Second aperiodic fit - using results of first fit as guess parameters # See note in _simple_ap_fit about warnings @@ -621,38 +622,39 @@ def _fit_peaks(self, flat_iter): Returns ------- gaussian_params : 2d array - Parameters that define the gaussian fit(s). Each row is a gaussian, as [mean, amp, std]. + Parameters that define the gaussian fit(s). + Each row is a gaussian, as [mean, height, standard deviation]. """ # Initialize matrix of guess parameters for gaussian fitting. guess = np.empty([0, 3]) # Find peak: Loop through, finding a candidate peak, and fitting with a guass gaussian. - # Stopping procedure based on either # of peaks, or the threshold/amplitude limits. + # Stopping procedure based on either # of peaks, or the relative or absolute height thresholds. while len(guess) < self.max_n_peaks: # Find candidate peak - the maximum point of the flattened spectrum. max_ind = np.argmax(flat_iter) - max_amp = flat_iter[max_ind] + max_height = flat_iter[max_ind] - # Stop searching for peaks peaks once drops below amplitude threshold. - if max_amp <= self.peak_threshold * np.std(flat_iter): + # Stop searching for peaks peaks once drops below height threshold. + if max_height <= self.peak_threshold * np.std(flat_iter): break - # Set the guess parameters for gaussian fitting - CF and amp. + # Set the guess parameters for gaussian fitting - mean and height. guess_freq = self.freqs[max_ind] - guess_amp = max_amp + guess_height = max_height - # Halt fitting process if candidate peak drops below minimum amp size. - if not guess_amp > self.min_peak_amplitude: + # Halt fitting process if candidate peak drops below minimum height. + if not guess_height > self.min_peak_height: break - # Data-driven first guess BW - # Find half-amp index on each side of the center frequency. - half_amp = 0.5 * max_amp - le_ind = next((x for x in range(max_ind - 1, 0, -1) if flat_iter[x] <= half_amp), None) + # Data-driven first guess at standard deviation + # Find half height index on each side of the center frequency. + half_height = 0.5 * max_height + le_ind = next((x for x in range(max_ind - 1, 0, -1) if flat_iter[x] <= half_height), None) ri_ind = next((x for x in range(max_ind + 1, len(flat_iter), 1) - if flat_iter[x] <= half_amp), None) + if flat_iter[x] <= half_height), None) # Keep bandwidth estimation from the shortest side. # We grab shortest to avoid estimating very large std from overalapping peaks. @@ -672,10 +674,10 @@ def _fit_peaks(self, flat_iter): guess_std = self._gauss_std_limits[1] # Collect guess parameters. - guess = np.vstack((guess, (guess_freq, guess_amp, guess_std))) + guess = np.vstack((guess, (guess_freq, guess_height, guess_std))) # Subtract best-guess gaussian. - peak_gauss = gaussian_function(self.freqs, guess_freq, guess_amp, guess_std) + peak_gauss = gaussian_function(self.freqs, guess_freq, guess_height, guess_std) flat_iter = flat_iter - peak_gauss # Check peaks based on edges, and on overlap @@ -699,19 +701,19 @@ def _fit_peak_guess(self, guess): Parameters ---------- guess : 2d array, shape=[n_peaks, 3] - Guess parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Guess parameters for gaussian fits to peaks, as gaussian parameters. Returns ------- gaussian_params : 2d array, shape=[n_peaks, 3] - Parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Parameters for gaussian fits to peaks, as gaussian parameters. """ - # Set the bounds for center frequency, enforce positive amp value, and set bandwidth limits. - # Note that 'guess' is in terms of gaussian std, so +/- BW is 2 * the guess_gauss_std. + # Set the bounds for center frequency, enforce positive height value, and set bandwidth limits. + # Note that 'guess' is in terms of gaussian standard deviation, so +/- BW is 2 * the guess_gauss_std. # This set of list comprehensions is a way to end up with bounds in the form: - # ((cf_low_bound_peak1, amp_low_bound_peak1, bw_low_bound_peak1, *repeated for n_peak*), - # (cf_high_bound_peak1, amp_high_bound_peak1, bw_high_bound_peak, *repeated for n_peak*)) + # ((cf_low_bound_peak1, height_low_bound_peak1, bw_low_bound_peak1, *repeated for n_peak*), + # (cf_high_bound_peak1, height_high_bound_peak1, bw_high_bound_peak, *repeated for n_peak*)) lo_bound = [[peak[0] - 2 * self._cf_bound * peak[2], 0, self._gauss_std_limits[0]] for peak in guess] hi_bound = [[peak[0] + 2 * self._cf_bound * peak[2], np.inf, self._gauss_std_limits[1]] @@ -744,21 +746,23 @@ def _create_peak_params(self, gaus_params): Parameters ---------- gaus_params : 2d array - Parameters that define the gaussian fit(s), with each row as [mean, amp, std]. + Parameters that define the gaussian fit(s), as gaussian parameters. Returns ------- peak_params : 2d array - Fitted parameter values for the peaks, with each row as [CF, Amp, BW]. + Fitted parameter values for the peaks, with each row as [CF, PW, BW]. Notes ----- - Amplitude is updated to the amplitude of peak above the aperiodic fit. - This is returned instead of the gaussian amplitude, as Gaussian amplitude - is harder to interpret, due to peak overlaps. + The gaussian center is unchanged as the peak center frequency. - Bandwidth is updated to be 'both-sided', as opposed to the gaussian - standard deviation parameter, which is 1-sided. + The gaussian height is updated to reflect the height of the peak above + the aperiodic fit. This is returned instead of the gaussian height, as + the gaussian height is harder to interpret, due to peak overlaps. + + The gaussian standard deviation is updated to be 'both-sided', to reflect the + 'bandwidth' of the peak, as opposed to the gaussian parameter, which is 1-sided. Performing this conversion requires that the model has been run, with `freqs`, `fooofed_spectrum_` and `ap_fit` all required to be available. @@ -781,17 +785,17 @@ def _create_peak_params(self, gaus_params): def _drop_peak_cf(self, guess): - """Check whether to drop peaks based CF proximity to edge. + """Check whether to drop peaks based on center's proximity to the edge of the spectrum. Parameters ---------- guess : 2d array, shape=[n_peaks, 3] - Guess parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Guess parameters for gaussian fits to peaks, as gaussian parameters. Returns ------- guess : 2d array, shape=[n_peaks, 3] - Guess parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Guess parameters for gaussian fits to peaks, as gaussian parameters. """ cf_params = [item[0] for item in guess] @@ -802,29 +806,29 @@ def _drop_peak_cf(self, guess): (np.abs(np.subtract(cf_params, self.freq_range[0])) > bw_params) & \ (np.abs(np.subtract(cf_params, self.freq_range[1])) > bw_params) - # Drop peaks that fail CF edge criterion + # Drop peaks that fail the center frequency edge criterion guess = np.array([d for (d, keep) in zip(guess, keep_peak) if keep]) return guess def _drop_peak_overlap(self, guess): - """Checks whether to drop peaks based on overlap. + """Checks whether to drop gaussians based on amount of overlap. Parameters ---------- guess : 2d array, shape=[n_peaks, 3] - Guess parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Guess parameters for gaussian fits to peaks, as gaussian parameters. Returns ------- guess : 2d array, shape=[n_peaks, 3] - Guess parameters for gaussian fits to peaks, with each row as: [CF, amp, BW]. + Guess parameters for gaussian fits to peaks, as gaussian parameters. Notes ----- - For any peak guesses with an overlap that crosses the threshold, - the lower amplitude guess is dropped. + For any gaussians with an overlap that crosses the threshold, + the lowest height guess guassian is dropped. """ # Sort the peak guesses, so can check overlap of adjacent peaks @@ -843,7 +847,7 @@ def _drop_peak_overlap(self, guess): # Check if bound of current peak extends into next peak if b_0[1] > b_1[0]: - # If so, get the index of the lowest amplitude peak (to drop) + # If so, get the index of the gaussian with the lowest height (to drop) drop_inds.append([ind, ind + 1][np.argmin([guess[ind][1], guess[ind + 1][1]])]) # Drop any peaks guesses that overlap too much, based on threshold. diff --git a/fooof/group.py b/fooof/group.py index 30aa57f72..5cd139bdf 100644 --- a/fooof/group.py +++ b/fooof/group.py @@ -200,7 +200,7 @@ def get_all_data(self, name, col=None): ---------- name : {'aperiodic_params', 'peak_params', 'error', 'r_squared', 'gaussian_params'} Name of the data field to extract across the group. - col : {'CF', 'Amp', 'BW', 'offset', 'knee', 'exponent'} or int, optional + col : {'CF', 'PW', 'BW', 'offset', 'knee', 'exponent'} or int, optional Column name / index to extract from selected data, if requested. Only used for name of {'aperiodic_params', 'peak_params', 'gaussian_params'}. diff --git a/fooof/plts/fm.py b/fooof/plts/fm.py index 75642591c..320dd084b 100644 --- a/fooof/plts/fm.py +++ b/fooof/plts/fm.py @@ -86,7 +86,7 @@ def plot_peak_iter(fm): plot_spectrum(fm.freqs, flatspec, linewidth=2.0, label='Flattened Spectrum', ax=ax) plot_spectrum(fm.freqs, [fm.peak_threshold * np.std(flatspec)]*len(fm.freqs), color='orange', linestyle='dashed', label='Relative Threshold', ax=ax) - plot_spectrum(fm.freqs, [fm.min_peak_amplitude]*len(fm.freqs), + plot_spectrum(fm.freqs, [fm.min_peak_height]*len(fm.freqs), color='red', linestyle='dashed', label='Absolute Threshold', ax=ax) maxi = np.argmax(flatspec) diff --git a/fooof/synth/gen.py b/fooof/synth/gen.py index e5ed5f6a1..414d7a723 100644 --- a/fooof/synth/gen.py +++ b/fooof/synth/gen.py @@ -67,7 +67,7 @@ def gen_power_spectrum(freq_range, aperiodic_params, gaussian_params, nlv=0.005, - Each gaussian description is a set of three values: - * Mean (Center Frequency), amplitude (Amplitude), and std (Bandwidth). + * mean (Center Frequency), height (Power), and standard deviation (Bandwidth). * Make sure any center frequencies you request are within the simulated frequency range - The total number of parameters that need to be specified is number of peaks * 3 @@ -141,7 +141,7 @@ def gen_group_power_spectra(n_spectra, freq_range, aperiodic_params, - Each gaussian description is a set of three values: - * mean (Center Frequency), amplitude (Amplitude), and std (Bandwidth). + * mean (Center Frequency), height (Power), and standard deviation (Bandwidth). * Make sure any center frequencies you request are within the simulated frequency range. Examples diff --git a/fooof/tests/test_analysis.py b/fooof/tests/test_analysis.py index 7307aff1e..4d9e28ca2 100644 --- a/fooof/tests/test_analysis.py +++ b/fooof/tests/test_analysis.py @@ -43,18 +43,18 @@ def test_get_band_peak(): # Test multiple results - return one assert np.array_equal(get_band_peak(dat, [10, 15], ret_one=True), [14, 2, 4]) -def test_get_highest_amp_osc(): +def test_get_highest_peak(): dat = np.array([[10, 1, 1.8], [14, 2, 4], [12, 3, 2]]) - assert np.array_equal(get_highest_amp_peak(dat), [12, 3, 2]) + assert np.array_equal(get_highest_peak(dat), [12, 3, 2]) def test_empty_inputs(): dat = np.empty(shape=[0, 3]) assert np.all(get_band_peak(dat, [8, 12])) - assert np.all(get_highest_amp_peak(dat)) + assert np.all(get_highest_peak(dat)) dat = np.empty(shape=[0, 4]) diff --git a/fooof/tests/test_core_funcs.py b/fooof/tests/test_core_funcs.py index e3a51ab60..2eb343631 100644 --- a/fooof/tests/test_core_funcs.py +++ b/fooof/tests/test_core_funcs.py @@ -12,16 +12,16 @@ def test_gaussian_function(): - ctr, amp, wid = 50, 5, 10 + ctr, hgt, wid = 50, 5, 10 xs = np.arange(1, 100) - ys = gaussian_function(xs, ctr, amp, wid) + ys = gaussian_function(xs, ctr, hgt, wid) assert np.all(ys) # Check distribution matches generated gaussian from scipy - # Generated gaussian is normalized for this comparison, amp tested separately - assert max(ys) == amp + # Generated gaussian is normalized for this comparison, height tested separately + assert max(ys) == hgt assert np.allclose([i/sum(ys) for i in ys], norm.pdf(xs, ctr, wid)) def test_expo_function(): diff --git a/fooof/tests/test_group.py b/fooof/tests/test_group.py index 7cb367967..f63b2ebf7 100644 --- a/fooof/tests/test_group.py +++ b/fooof/tests/test_group.py @@ -91,7 +91,7 @@ def test_get_all_data(tfg): assert np.any(tfg.get_all_data(dname, dtype)) if dname == 'peak_params': - for dtype in ['CF', 'Amp', 'BW']: + for dtype in ['CF', 'PW', 'BW']: assert np.any(tfg.get_all_data(dname, dtype)) @plot_test diff --git a/tutorials/plot_01-ModelDescription.py b/tutorials/plot_01-ModelDescription.py index 1a403f2ba..7b70b5586 100644 --- a/tutorials/plot_01-ModelDescription.py +++ b/tutorials/plot_01-ModelDescription.py @@ -75,7 +75,7 @@ # # Each peak is defined in terms of parameters `a`, `c` and `w`, where: # -# - `a` is the amplitude of the peak, over and above the aperiodic signal +# - `a` is the height of the peak, over and above the aperiodic signal # - `c` is the center frequency of the peak # - `w` is the width of the peak # - `F` is the vector of input frequencies diff --git a/tutorials/plot_02-FOOOF.py b/tutorials/plot_02-FOOOF.py index 3c717a878..77f0d17c0 100644 --- a/tutorials/plot_02-FOOOF.py +++ b/tutorials/plot_02-FOOOF.py @@ -112,20 +112,27 @@ # Notes on Interpreting Peak Parameters # ------------------------------------- # +# Peak parameters are labelled as: +# +# - CF: center frequency of the extracted peak +# - PW: power of the peak, over and above the aperiodic background +# - BW: bandwidth of the extracted peak +# # Note that the peak parameters that are returned are not exactly the same as the # parameters of the Gaussians used internally to fit the peaks. # # Specifically: # -# - CF is the mean parameter of the Gaussian (same as the Gaussian) -# - Amp is the amplitude of the model fit above the aperiodic signal fit [1], -# which is not necessarily the same as the Gaussian amplitude +# - CF is the exact same as mean parameter of the Gaussian +# - PW is the height of the model fit above the aperiodic signal fit [1], +# which is not necessarily the same as the Gaussian height # - BW is 2 * the standard deviation of the Gaussian [2] # # [1] Since the Gaussians are fit together, if any Gaussians overlap, # than the actual height of the fit at a given point can only be assessed -# when considering all Gaussians. To be better able to interpret amplitudes -# for single peak fits, we re-define the peak amplitude as above. +# when considering all Gaussians. To be better able to interpret heights +# for single peak fits, we re-define the peak height as above, and label +# it as 'power', as the units of the input data as expected to be power. # # [2] Standard deviation is '1 sided', where as the returned BW is '2 sided'. # diff --git a/tutorials/plot_03-FOOOFAlgorithm.py b/tutorials/plot_03-FOOOFAlgorithm.py index e3078467d..25ea77c25 100644 --- a/tutorials/plot_03-FOOOFAlgorithm.py +++ b/tutorials/plot_03-FOOOFAlgorithm.py @@ -54,7 +54,7 @@ ################################################################################################### # Initialize a FOOOF object, with some settings -fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6 , min_peak_amplitude=0.15) +fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6 , min_peak_height=0.15) ################################################################################################### # @@ -113,7 +113,7 @@ # # - The maximum point of the flattened spectrum is found. # -# - If this point fails to pass the relative or absolute amplitude threshold, +# - If this point fails to pass the relative or absolute height threshold, # the procedure halts. # - A Gaussian is fit around this maximum point # - This 'guess' Gaussian is then subtracted from the flatted spectrum diff --git a/tutorials/plot_04-MoreFOOOF.py b/tutorials/plot_04-MoreFOOOF.py index f7433b2d8..3c85572df 100644 --- a/tutorials/plot_04-MoreFOOOF.py +++ b/tutorials/plot_04-MoreFOOOF.py @@ -58,14 +58,14 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # An iterative procedures searches for candidate peaks in the flattened spectrum. Candidate -# peaks are extracted in order of decreasing amplitude, until some stopping criterion is met, +# peaks are extracted in order of decreasing height, until some stopping criterion is met, # which is controlled by the following parameters: # # **max_n_peaks (int)** default: infinite # # The maximum number of peaks that can be extracted from a given power spectrum. FOOOF will # halt searching for new peaks when this number is reached. Note that FOOOF extracts peaks -# iteratively by amplitude (over and above the aperiodic signal), and so this approach will +# iteratively by height (over and above the aperiodic signal), and so this approach will # extract (up to) the *n* largest peaks. # # **peak_threshold (in units of standard deviation)** default: 2.0 @@ -75,15 +75,15 @@ # Once a candidate peak drops below this threshold, the peak search is halted (without # including the most recent candidate). # -# **min_peak_amplitude (units of power - same as the input spectrum)** default: 0 +# **min_peak_height (units of power - same as the input spectrum)** default: 0 # -# The minimum amplitude, above the aperiodic fit, that a peak must have to be extracted +# The minimum height, above the aperiodic fit, that a peak must have to be extracted # in the initial fit stage. Once a candidate peak drops below this threshold, the peak # search is halted (without including the most recent candidate). Note that because # this constraint is enforced during peak search, and prior to final peak fit, returned -# peaks are not guaranteed to surpass this value in amplitude. +# peaks are not guaranteed to surpass this value in height. # -# Note: there are two different amplitude-related halting conditions for the peak searching. +# Note: there are two different height-related halting conditions for the peak searching. # By default, the relative (standard-deviation based) threshold is defined, whereas the # absolute threshold is set to zero (this default is because there is no general way to # set this value without knowing the scale of the data). If both are defined, both are @@ -182,7 +182,7 @@ ################################################################################################### # Initialize FOOOF model, with some specified settings -fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_amplitude=0.15) +fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_height=0.15) # Fit FOOOF fm.report(freqs, spectrum, freq_range) diff --git a/tutorials/plot_06-FOOOFGroup.py b/tutorials/plot_06-FOOOFGroup.py index d072de7dc..ace4ab503 100644 --- a/tutorials/plot_06-FOOOFGroup.py +++ b/tutorials/plot_06-FOOOFGroup.py @@ -87,7 +87,7 @@ ################################################################################################### # Initialize a FOOOFGroup object - it accepts all the same settings as FOOOF -fg = FOOOFGroup(peak_width_limits=[1, 8], min_peak_amplitude=0.05, max_n_peaks=6) +fg = FOOOFGroup(peak_width_limits=[1, 8], min_peak_height=0.05, max_n_peaks=6) ################################################################################################### diff --git a/tutorials/plot_07-TroubleShooting.py b/tutorials/plot_07-TroubleShooting.py index a482bf942..96ba95301 100644 --- a/tutorials/plot_07-TroubleShooting.py +++ b/tutorials/plot_07-TroubleShooting.py @@ -118,7 +118,7 @@ # - Setting a maximum number of peaks that the algorithm may fit: `max_n_peaks` # # - If set, the algorithm will fit (up to) the `max_n_peaks` highest power peaks. -# - Setting a minimum absolute amplitude for peaks: `min_peak_amplitude` +# - Setting a minimum absolute peak height: `min_peak_height` # ################################################################################################### @@ -157,7 +157,7 @@ ################################################################################################### # Update settings to fit a more constrained FOOOF model, to reduce overfitting -fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_amplitude=0.4) +fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_height=0.4) fm.report(freqs, spectrum) ################################################################################################### @@ -182,10 +182,10 @@ # # A known case in which FOOOF can overfit is in power spectra in which no peaks # are present. In this case, the standard deviation can be very low, and so the -# relative amplitude check (`min_peak_threshold`) is very liberal at keeping gaussian fits. +# relative peak height check (`min_peak_threshold`) is very liberal at keeping gaussian fits. # # If you expect, or know, you have power spectra without peaks in your data, -# we therefore recommend making sure you set some value for `min_peak_amplitude`, +# we therefore recommend making sure you set some value for `min_peak_height`, # as otherwise FOOOF is unlikely to appropriately fit power spectra as having # no peaks. Setting this value requires checking the scale of your power spectra, # allowing you to define an absolute threshold for extracting peaks. @@ -197,7 +197,7 @@ # # If you are finding that FOOOF is underfitting: # -# - First check and perhaps loosen any restrictions from `max_n_peaks` and `min_peak_amplitude` +# - First check and perhaps loosen any restrictions from `max_n_peaks` and `min_peak_height` # - Try updating `peak_threshold` to a lower value # - Bad fits may come from issues with aperiodic signal fitting # @@ -219,7 +219,7 @@ freqs, spectrum = gen_power_spectrum([1, 50], ap_params, gauss_params, nlv=nlv) # Update settings to make sure they are sensitive to smaller peaks in smoother power spectra -fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_amplitude=0.2) +fm = FOOOF(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_height=0.2) fm.report(freqs, spectrum) ################################################################################################### diff --git a/tutorials/plot_08-FurtherAnalysis.py b/tutorials/plot_08-FurtherAnalysis.py index e7975a4c2..3bfd9e411 100644 --- a/tutorials/plot_08-FurtherAnalysis.py +++ b/tutorials/plot_08-FurtherAnalysis.py @@ -68,7 +68,7 @@ ################################################################################################### # Set up indexes for accessing data, for convenience -cf_ind, amp_ind, bw_ind = 0, 1, 2 +cf_ind, pw_ind, bw_ind = 0, 1, 2 # Define frequency bands of interest theta_band = [4, 8] @@ -113,7 +113,7 @@ ################################################################################################### # Fit FOOOF models across the group of synthesized power spectra -fg = FOOOFGroup(peak_width_limits=[1, 8], min_peak_amplitude=0.05, max_n_peaks=6, verbose=False) +fg = FOOOFGroup(peak_width_limits=[1, 8], min_peak_height=0.05, max_n_peaks=6, verbose=False) fg.fit(freqs, spectra) ################################################################################################### @@ -144,7 +144,7 @@ # Check descriptive statistics of oscillation data print('Alpha CF : ', np.nanmean(alphas[:, cf_ind])) -print('Alpha Amp: ', np.nanmean(alphas[:, amp_ind])) +print('Alpha PW : ', np.nanmean(alphas[:, pw_ind])) print('Alpha BW : ', np.nanmean(alphas[:, bw_ind])) ###################################################################################################