def _fit_peak_guess(self, guess): """Fits a group of peak guesses with a fit function. Parameters ---------- guess : 2d array, shape=[n_peaks, 3] 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, as gaussian parameters. """ # 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, 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] ] for peak in guess] # Check that CF bounds are within frequency range, and, if not, updates them to be restricted to frequency range. lo_bound = [ bound if bound[0] > self.freq_range[0] else [self.freq_range[0], *bound[1:]] for bound in lo_bound ] hi_bound = [ bound if bound[0] < self.freq_range[1] else [self.freq_range[1], *bound[1:]] for bound in hi_bound ] # Unpacks the embedded lists into flat tuples, which is what the fit function requires as input. gaus_param_bounds = (tuple([ item for sublist in lo_bound for item in sublist ]), tuple([item for sublist in hi_bound for item in sublist])) # Flatten guess, for use with curve fit. guess = np.ndarray.flatten(guess) # Fit the peaks. gaussian_params, _ = curve_fit(gaussian_function, self.freqs, self._spectrum_flat, p0=guess, maxfev=5000, bounds=gaus_param_bounds) # Re-organize params into 2d matrix. gaussian_params = np.array(group_three(gaussian_params)) return gaussian_params
def test_fooof_fit_nk(): """Test FOOOF fit, no knee.""" bgp = [50, 2] gauss_params = [10, 0.5, 2, 20, 0.3, 4] xs, ys = gen_power_spectrum([3, 50], bgp, gauss_params) tfm = FOOOF() tfm.fit(xs, ys) # Check model results - background parameters assert np.all(np.isclose(bgp, tfm.background_params_, [0.5, 0.1])) # Check model results - gaussian parameters for ii, gauss in enumerate(group_three(gauss_params)): assert np.all(np.isclose(gauss, tfm._gaussian_params[ii], [1.5, 0.25, 0.5]))
def _fit_peak_guess(self, guess): """Fit a guess of peak gaussian fit(s). Parameters ---------- guess : 2d array Guess parameters for gaussian fits to peaks. [n_oscs, 3], row: [CF, amp, BW]. Returns ------- gaussian_params : 2d array Parameters for gaussian fits to peaks. [n_oscs, 3], row: [CF, amp, BW]. """ # Set the bounds for center frequency, enforce positive amp value, and set width limits. # Note that osc_guess is in terms of gaussian std, 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_osc1, amp_low_bound_osc1, bw_low_bound_osc1, *repeated for n oscs*), # (cf_high_bound_osc1, amp_high_bound_osc1, bw_high_bound_osc1, *repeated for n oscs*)) lo_bound = [[ osc[0] - 2 * self._cf_bound * osc[2], 0, self._gauss_std_limits[0] ] for osc in guess] hi_bound = [[ osc[0] + 2 * self._cf_bound * osc[2], np.inf, self._gauss_std_limits[1] ] for osc in guess] # The double for-loop here unpacks the embedded lists gaus_param_bounds = (tuple([ item for sublist in lo_bound for item in sublist ]), tuple([item for sublist in hi_bound for item in sublist])) # Flatten guess, for use with curve fit. guess = np.ndarray.flatten(guess) # Fit the peaks. gaussian_params, _ = curve_fit(gaussian_function, self.freqs, self._spectrum_flat, p0=guess, maxfev=5000, bounds=gaus_param_bounds) # Re-organize params into 2d matrix. gaussian_params = np.array(group_three(gaussian_params)) return gaussian_params
def test_fooof_fit_knee(): """Test FOOOF fit, with a knee.""" ap_params = [50, 10, 1] gauss_params = [10, 0.3, 2, 20, 0.1, 4, 60, 0.3, 1] nlv = 0.0025 xs, ys = gen_power_spectrum([1, 150], ap_params, gauss_params, nlv) tfm = FOOOF(aperiodic_mode='knee', verbose=False) tfm.fit(xs, ys) # Check model results - aperiodic parameters assert np.allclose(ap_params, tfm.aperiodic_params_, [1, 2, 0.2]) # Check model results - gaussian parameters for ii, gauss in enumerate(group_three(gauss_params)): assert np.allclose(gauss, tfm.gaussian_params_[ii], [2.0, 0.5, 1.0])
def test_fooof_fit_nk(): """Test FOOOF fit, no knee.""" ap_params = [50, 2] gauss_params = [10, 0.5, 2, 20, 0.3, 4] nlv = 0.0025 xs, ys = gen_power_spectrum([3, 50], ap_params, gauss_params, nlv) tfm = FOOOF(verbose=False) tfm.fit(xs, ys) # Check model results - aperiodic parameters assert np.allclose(ap_params, tfm.aperiodic_params_, [0.5, 0.1]) # Check model results - gaussian parameters for ii, gauss in enumerate(group_three(gauss_params)): assert np.allclose(gauss, tfm.gaussian_params_[ii], [2.0, 0.5, 1.0])
def collect_sim_params(aperiodic_params, periodic_params, nlv): """Collect simulation parameters into a SimParams object. Parameters ---------- aperiodic_params : list of float Parameters of the aperiodic component of the power spectrum. periodic_params : list of float or list of list of float Parameters of the periodic component of the power spectrum. nlv : float Noise level of the power spectrum. Returns ------- SimParams Object containing the simulation parameters. """ return SimParams(aperiodic_params.copy(), sorted(group_three(check_flat(periodic_params))), nlv)
def gen_group_power_spectra(n_spectra, freq_range, background_params, gauss_params, nlvs=0.005, freq_res=0.5): """Generate a group of synthetic power spectra. Parameters ---------- n_spectra : int The number of power spectra to generate in the matrix. freq_range : list of [float, float] Minimum and maximum values of the desired frequency vector. background_params : list of float or generator Parameter for the background of the power spectra. gauss_params : list of float or generator Parameters for the peaks of the power spectra. Length of n_peaks * 3. nlvs : float or list of float or generator, optional Noise level to add to generated power spectrum. default: 0.005 freq_res : float, optional Frequency resolution for the synthetic power spectra. default: 0.5 Returns ------- xs : 1d array Frequency values (linear). ys : 2d array Matrix of power values (linear). syn_params : list of SynParams Definitions of parameters used for each spectrum. Has length of n_spectra. Notes ----- Parameters options can be: - A single set of parameters - If so, these same parameters are used for all spectra. - A list of parameters whose length is n_spectra. - If so, each successive parameter set is such for each successive spectrum. - A generator object that returns parameters for a power spectrum. - If so, each spectrum has parameters pulled from the generator. Background Parameters: - The type of background process to use is inferred from the provided parameters. - If length of 2, 'fixed' background is used, if length of 3, 'knee' is used. Gaussian Parameters: - Each gaussian description is a set of three values: - mean (CF), amplitude (Amp), and std (BW) Examples -------- Generate 2 power spectra using the same parameters. >>> freqs, psds, _ = gen_group_power_spectra(2, [1, 50], [0, 2], [10, 1, 1]) Generate 10 power spectra, randomly sampling possible parameters >>> bg_opts = param_sampler([[0, 1.0], [0, 1.5], [0, 2]]) >>> gauss_opts = param_sampler([[], [10, 1, 1], [10, 1, 1, 20, 2, 1]]) >>> freqs, psds, syn_params = gen_group_power_spectra(10, [1, 50], bg_opts, gauss_opts) """ # Initialize things xs = gen_freqs(freq_range, freq_res) ys = np.zeros([n_spectra, len(xs)]) syn_params = [None] * n_spectra # Check if inputs are generators, if not, make them into repeat generators background_params = _check_iter(background_params, n_spectra) gauss_params = _check_iter(gauss_params, n_spectra) nlvs = _check_iter(nlvs, n_spectra) # Synthesize power spectra for ind, bgp, gp, nlv in zip(range(n_spectra), background_params, gauss_params, nlvs): syn_params[ind] = SynParams(bgp, sorted(group_three(gp)), nlv) ys[ind, :] = gen_power_vals(xs, bgp, gp, nlv) return xs, ys, syn_params
def gen_group_power_spectra(n_spectra, freq_range, aperiodic_params, gaussian_params, nlvs=0.005, freq_res=0.5): """Generate a group of simulated power spectra. Parameters ---------- n_spectra : int The number of power spectra to generate in the matrix. freq_range : list of [float, float] Minimum and maximum values of the desired frequency vector. aperiodic_params : list of float or generator Parameters for the aperiodic component of the power spectra. gaussian_params : list of float or generator Parameters for the peaks of the power spectra. Length of n_peaks * 3. nlvs : float or list of float or generator, optional, default: 0.005 Noise level to add to generated power spectrum. freq_res : float, optional, default: 0.5 Frequency resolution for the simulated power spectra. Returns ------- freqs : 1d array Frequency values (linear). powers : 2d array Matrix of power values (linear), as [n_power_spectra, n_freqs]. sim_params : list of SimParams Definitions of parameters used for each spectrum. Has length of n_spectra. Notes ----- Parameters options can be: - A single set of parameters. If so, these same parameters are used for all spectra. - A list of parameters whose length is n_spectra. If so, each successive parameter set is such for each successive spectrum. - A generator object that returns parameters for a power spectrum. If so, each spectrum has parameters pulled from the generator. Aperiodic Parameters: - The function for the aperiodic process to use is inferred from the provided parameters. - If length of 2, the 'fixed' aperiodic mode is used, if length of 3, 'knee' is used. Gaussian Parameters: - Each gaussian description is a set of three values: * mean (Center Frequency), height (Power), and standard deviation (Bandwidth). * Make sure any center frequencies you request are within the simulated frequency range. Examples -------- Generate 2 power spectra using the same parameters: >>> freqs, psds, _ = gen_group_power_spectra(2, [1, 50], [0, 2], [10, 1, 1]) Generate 10 power spectra, randomly sampling possible parameters: >>> ap_opts = param_sampler([[0, 1.0], [0, 1.5], [0, 2]]) >>> gauss_opts = param_sampler([[], [10, 1, 1], [10, 1, 1, 20, 2, 1]]) >>> freqs, psds, sim_params = gen_group_power_spectra(10, [1, 50], ap_opts, gauss_opts) """ # Initialize things freqs = gen_freqs(freq_range, freq_res) powers = np.zeros([n_spectra, len(freqs)]) sim_params = [None] * n_spectra # Check if inputs are generators, if not, make them into repeat generators aperiodic_params = check_iter(aperiodic_params, n_spectra) gaussian_params = check_iter(gaussian_params, n_spectra) nlvs = check_iter(nlvs, n_spectra) # Simulate power spectra for ind, bgp, gp, nlv in zip(range(n_spectra), aperiodic_params, gaussian_params, nlvs): sim_params[ind] = SimParams(bgp.copy(), sorted(group_three(gp)), nlv) powers[ind, :] = gen_power_vals(freqs, bgp, gp, nlv) return freqs, powers, sim_params
fm.report(freqs, spectrum) ################################################################################################### # # The synthetic definition is defined in terms of Gaussian parameters (these are # slightly different from the peak parameters - see the note in tutorial 02). # # We can compare how FOOOF, with our updated settings, compares to the # ground truth of the synthetic spectrum. # ################################################################################################### # Compare ground truth synthetic parameters to model fit results print('Ground Truth \t\t FOOOF Reconstructions') for sy, fi in zip(np.array(group_three(gauss_params)), fm._gaussian_params): print('{:5.2f} {:5.2f} {:5.2f} \t {:5.2f} {:5.2f} {:5.2f}'.format( *sy, *fi)) ################################################################################################### # Power Spectra with No Peaks # --------------------------- # # 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 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_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,