def _regenerate_model(self): """Regenerate model fit from parameters.""" self._ap_fit = gen_aperiodic(self.freqs, self.aperiodic_params_) self._peak_fit = gen_peaks(self.freqs, np.ndarray.flatten(self.gaussian_params_)) self.fooofed_spectrum_ = self._peak_fit + self._ap_fit
def refit_aperiodic(freqs, powers, peak_fit): """Refit the aperiodic component following the periodic refit. Parameters ---------- freqs : 1d array Frequency values for the power spectrum, in linear scale. powers : 1d array Power values, in log10 scale. peak_fit : 1d array Perodic refit, in log10 scale. Returns ------- ap_params : 1d array Exponent and offset values. ap_fit : 1d array Regenerated aperiodic fit. """ # Access simple ap fit method _fm = FOOOF() _fm.power_spectrum = powers _fm.freqs = freqs # Remove peaks spectrum_peak_rm = powers - peak_fit # Refit ap_params = _fm._simple_ap_fit(freqs, spectrum_peak_rm) ap_fit = gen_aperiodic(freqs, ap_params) return ap_params, ap_fit
def _robust_ap_fit(self, freqs, power_spectrum): """Fit the aperiodic component of the power spectrum robustly, ignoring outliers. Parameters ---------- freqs : 1d array Frequency values for the power spectrum, in linear scale. power_spectrum : 1d array Power values, in log10 scale. Returns ------- aperiodic_params : 1d array Parameter estimates for aperiodic fit. """ # Do a quick, initial aperiodic fit popt = self._simple_ap_fit(freqs, power_spectrum) initial_fit = gen_aperiodic(freqs, popt) # Flatten power_spectrum based on initial aperiodic fit flatspec = power_spectrum - initial_fit # Flatten outliers - any points that drop below 0 flatspec[flatspec < 0] = 0 # 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 with warnings.catch_warnings(): warnings.simplefilter("ignore") aperiodic_params, _ = curve_fit(get_ap_func(self.aperiodic_mode), freqs_ignore, spectrum_ignore, p0=popt, maxfev=5000, bounds=self._ap_bounds) return aperiodic_params
def plot_aperiodic_fits(aps, freq_range, control_offset=False, log_freqs=False, colors=None, labels=None, ax=None, **plot_kwargs): """Plot reconstructions of model aperiodic fits. Parameters ---------- aps : 2d array Aperiodic parameters. Each row is a parameter set, as [Off, Exp] or [Off, Knee, Exp]. freq_range : list of [float, float] The frequency range to plot the peak fits across, as [f_min, f_max]. control_offset : boolean, optional, default: False Whether to control for the offset, by setting it to zero. log_freqs : boolean, optional, default: False Whether to plot the x-axis in log space. colors : str or list of str, optional Color(s) to plot data. labels : list of str, optional Label(s) for plotted data, to be added in a legend. ax : matplotlib.Axes, optional Figure axes upon which to plot. **plot_kwargs Keyword arguments to pass into the ``style_plot``. """ ax = check_ax(ax, plot_kwargs.pop('figsize', PLT_FIGSIZES['params'])) if isinstance(aps, list): if not colors: colors = cycle(plt.rcParams['axes.prop_cycle'].by_key()['color']) recursive_plot(aps, plot_aperiodic_fits, ax=ax, freq_range=tuple(freq_range), control_offset=control_offset, log_freqs=log_freqs, colors=colors, labels=labels, **plot_kwargs) else: freqs = gen_freqs(freq_range, 0.1) plt_freqs = np.log10(freqs) if log_freqs else freqs colors = colors[0] if isinstance(colors, list) else colors avg_vals = np.zeros(shape=[len(freqs)]) for ap_params in aps: if control_offset: # Copy the object to not overwrite any data ap_params = ap_params.copy() ap_params[0] = 0 # Recreate & plot the aperiodic component from parameters ap_vals = gen_aperiodic(freqs, ap_params) ax.plot(plt_freqs, ap_vals, color=colors, alpha=0.35, linewidth=1.25) # Collect a running average across components avg_vals = np.nansum(np.vstack([avg_vals, ap_vals]), axis=0) # Plot the average component avg = avg_vals / aps.shape[0] avg_color = 'black' if not colors else colors ax.plot(plt_freqs, avg, linewidth=3.75, color=avg_color, label=labels) # Add axis labels ax.set_xlabel('log(Frequency)' if log_freqs else 'Frequency') ax.set_ylabel('log(Power)') # Set plot limit ax.set_xlim(np.log10(freq_range) if log_freqs else freq_range) style_param_plot(ax)
################################################################################################### # # The FOOOF object stores most of the intermediate steps internally. # # For this notebook, we will first fit the full model, as normal, but then step through, # and visualize each step the algorithm takes to come to that final fit. # # Fit the FOOOF model fm.fit(freqs, spectrum, [3, 40]) ################################################################################################### # Do an initial aperiodic signal fit - a robust fit, that excludes outliers # This recreates an initial fit that isn't ultimately stored in the FOOOF object) init_ap_fit = gen_aperiodic(fm.freqs, fm._robust_ap_fit(fm.freqs, fm.power_spectrum)) # Plot the initial aperiodic fit _, ax = plt.subplots(figsize=(12, 10)) plot_spectrum(fm.freqs, fm.power_spectrum, plt_log, label='Original Power Spectrum', ax=ax) plot_spectrum(fm.freqs, init_ap_fit, plt_log, label='Initial Aperiodic Fit', ax=ax) ###################################################################################################
def plot_annotated_peak_search(fm, plot_style=style_spectrum_plot): """Plot a series of plots illustrating the peak search from a flattened spectrum. Parameters ---------- fm : FOOOF FOOOF object, with model fit, data and settings available. plot_style : callable, optional, default: style_spectrum_plot A function to call to apply styling & aesthetics to the plots. """ # Recalculate the initial aperiodic fit and flattened spectrum that # is the same as the one that is used in the peak fitting procedure flatspec = fm.power_spectrum - \ gen_aperiodic(fm.freqs, fm._robust_ap_fit(fm.freqs, fm.power_spectrum)) # Calculate ylims of the plot that are scaled to the range of the data ylims = [ min(flatspec) - 0.1 * np.abs(min(flatspec)), max(flatspec) + 0.1 * max(flatspec) ] # Loop through the iterative search for each peak for ind in range(fm.n_peaks_ + 1): # This forces the creation of a new plotting axes per iteration ax = check_ax(None, PLT_FIGSIZES['spectral']) plot_spectrum(fm.freqs, flatspec, ax=ax, plot_style=None, label='Flattened Spectrum', color=PLT_COLORS['data'], linewidth=2.5) plot_spectrum(fm.freqs, [fm.peak_threshold * np.std(flatspec)] * len(fm.freqs), ax=ax, plot_style=None, label='Relative Threshold', color='orange', linewidth=2.5, linestyle='dashed') plot_spectrum(fm.freqs, [fm.min_peak_height] * len(fm.freqs), ax=ax, plot_style=None, label='Absolute Threshold', color='red', linewidth=2.5, linestyle='dashed') maxi = np.argmax(flatspec) ax.plot(fm.freqs[maxi], flatspec[maxi], '.', color=PLT_COLORS['periodic'], alpha=0.75, markersize=30) ax.set_ylim(ylims) ax.set_title('Iteration #' + str(ind + 1), fontsize=16) if ind < fm.n_peaks_: gauss = gaussian_function(fm.freqs, *fm.gaussian_params_[ind, :]) plot_spectrum(fm.freqs, gauss, ax=ax, plot_style=None, label='Gaussian Fit', color=PLT_COLORS['periodic'], linestyle=':', linewidth=3.0) flatspec = flatspec - gauss check_n_style(plot_style, ax, False, True)
def fit(self, freqs=None, power_spectrum=None, freq_range=None): """Fit the full power spectrum as a combination of periodic and aperiodic components. Parameters ---------- freqs : 1d array, optional Frequency values for the power spectrum, in linear space. power_spectrum : 1d array, optional Power values, which must be input in linear space. freq_range : list of [float, float], optional Frequency range to restrict power spectrum to. If not provided, keeps the entire range. Notes ----- Data is optional if data has been already been added to FOOOF object. """ # If freqs & power_spectrum provided together, add data to object. if freqs is not None and power_spectrum is not None: self.add_data(freqs, power_spectrum, freq_range) # If power spectrum provided alone, add to object, and use existing frequency data # Note: be careful passing in power_spectrum data like this: # It assumes the power_spectrum is already logged, with correct freq_range. elif isinstance(power_spectrum, np.ndarray): self.power_spectrum = power_spectrum # Check that data is available if self.freqs is None or self.power_spectrum is None: raise ValueError('No data available to fit - can not proceed.') # Check and warn about width limits (if in verbose mode) if self.verbose: self._check_width_limits() # In rare cases, the model fails to fit. Therefore it's in a try/except # Cause of failure: RuntimeError, failure to find parameters in curve_fit try: # Fit the aperiodic component self.aperiodic_params_ = self._robust_ap_fit( self.freqs, self.power_spectrum) self._ap_fit = gen_aperiodic(self.freqs, self.aperiodic_params_) # Flatten the power_spectrum using fit aperiodic fit self._spectrum_flat = self.power_spectrum - self._ap_fit # Find peaks, and fit them with gaussians self.gaussian_params_ = self._fit_peaks( np.copy(self._spectrum_flat)) # Calculate the peak fit # Note: if no peaks are found, this creates a flat (all zero) peak fit. self._peak_fit = gen_peaks( self.freqs, np.ndarray.flatten(self.gaussian_params_)) # Create peak-removed (but not flattened) power spectrum. self._spectrum_peak_rm = self.power_spectrum - self._peak_fit # Run final aperiodic fit on peak-removed power spectrum # Note: This overwrites previous aperiodic fit self.aperiodic_params_ = self._simple_ap_fit( self.freqs, self._spectrum_peak_rm) self._ap_fit = gen_aperiodic(self.freqs, self.aperiodic_params_) # Create full power_spectrum model fit self.fooofed_spectrum_ = self._peak_fit + self._ap_fit # Convert gaussian definitions to peak parameters self.peak_params_ = self._create_peak_params(self.gaussian_params_) # Calculate R^2 and error of the model fit. self._calc_r_squared() self._calc_rmse_error() # Catch failure, stemming from curve_fit process except RuntimeError: # Clear any interim model results that may have run # Partial model results shouldn't be interpreted in light of overall failure self._reset_data_results(clear_freqs=False, clear_spectrum=False, clear_results=True) # Print out status if self.verbose: print('Model fitting was unsuccessful.')