def _regenerate_model(self): """Regenerate model fit from parameters.""" self._bg_fit = gen_background(self.freqs, self.background_params_) self._peak_fit = gen_peaks(self.freqs, np.ndarray.flatten(self._gaussian_params)) self.fooofed_spectrum_ = self._peak_fit + self._bg_fit
def _robust_bg_fit(self, freqs, power_spectrum): """Fit the 1/f background of power spectrum robustly, ignoring outliers. Parameters ---------- freqs : 1d array Frequency values for the power spectrum, in linear scale. power_spectrum : 1d array Power spectrum values, in log10 scale. Returns ------- background_params : 1d array Parameter estimates for background fit. """ # Do a quick, initial background fit. popt = self._simple_bg_fit(freqs, power_spectrum) initial_fit = gen_background(freqs, popt) # Flatten power_spectrum based on initial background fit. flatspec = power_spectrum - initial_fit # 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._bg_amp_thresh) amp_mask = flatspec <= perc_thresh freqs_ignore = freqs[amp_mask] spectrum_ignore = power_spectrum[amp_mask] # Second background fit - using results of first fit as guess parameters. # See note in _simple_bg_fit about warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") background_params, _ = curve_fit(get_bg_func(self.background_mode), freqs_ignore, spectrum_ignore, p0=popt, maxfev=5000, bounds=self._bg_bounds) return background_params
fm.plot(plt_log) ############################################################################### # # 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 background fit (excluding outliers) # This recreates an initial fit that isn't ultimately stored in the FOOOF object) init_bg_fit = gen_background(fm.freqs, fm._robust_bg_fit(fm.freqs, fm.power_spectrum)) # Plot the initial aperiodic 'background' 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_bg_fit, plt_log, label='Initial Background Fit', ax=ax) ###############################################################################
def fit(self, freqs=None, power_spectrum=None, freq_range=None): """Fit the full power spectrum as a combination of background and peaks. Parameters ---------- freqs : 1d array, optional Frequency values for the power spectrum, in linear space. power_spectrum : 1d array, optional Power spectrum values, 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 isinstance(freqs, np.ndarray) and isinstance( power_spectrum, np.ndarray): 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 not (np.all(self.freqs) and np.all(self.power_spectrum)): 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 background 1/f. self.background_params_ = self._robust_bg_fit( self.freqs, self.power_spectrum) self._bg_fit = gen_background(self.freqs, self.background_params_) # Flatten the power_spectrum using fit background. self._spectrum_flat = self.power_spectrum - self._bg_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 background fit on peak-removed power spectrum. # Note: This overwrites previous background fit. self.background_params_ = self._simple_bg_fit( self.freqs, self._spectrum_peak_rm) self._bg_fit = gen_background(self.freqs, self.background_params_) # Create full power_spectrum model fit. self.fooofed_spectrum_ = self._peak_fit + self._bg_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.')