Source code for fooof.bands.bands
-"""A data oject for managing band definitions."""
+"""A data object for managing band definitions."""
from collections import OrderedDict
@@ -119,7 +121,7 @@ Source code for fooof.bands.bands
###################################################################################################
[docs]class Bands():
- """Frequency band definitions.
+ """Frequency band definitions.
Attributes
----------
@@ -134,7 +136,7 @@ Source code for fooof.bands.bands
"""
[docs] def __init__(self, input_bands={}):
- """Initialize the Bands object.
+ """Initialize the Bands object.
Parameters
----------
@@ -148,59 +150,59 @@ Source code for fooof.bands.bands
self.add_band(label, band_def)
def __getitem__(self, label):
- """Define indexing as returning the definition of a requested band label."""
+ """Define indexing as returning the definition of a requested band label."""
try:
return self.bands[label]
except KeyError:
message = "The label '{}' was not found in the defined bands.".format(label)
- raise ValueError(message) from None
+ raise ValueError(message) from None
def __getattr__(self, label):
- """Define getting an attribute as returning the definition of a requested band label."""
+ """Define getting an attribute as returning the definition of a requested band label."""
return self.__getitem__(label)
def __repr__(self):
- """Define the string representation as a printout of the band information."""
+ """Define the string representation as a printout of the band information."""
return '\n'.join(['{:8} : {:2} - {:2} Hz'.format(key, *val) \
for key, val in self.bands.items()])
def __len__(self):
- """Define length as the number of bands it contains."""
+ """Define length as the number of bands it contains."""
return self.n_bands
def __iter__(self):
- """Define iteratation as stepping across each band."""
+ """Define iteration as stepping across each band."""
for label, band_definition in self.bands.items():
yield (label, band_definition)
@property
def labels(self):
- """Labels for all the bands defined in the object."""
+ """Labels for all the bands defined in the object."""
return list(self.bands.keys())
@property
def definitions(self):
- """Frequency definitions for all the bands defined in the object."""
+ """Frequency definitions for all the bands defined in the object."""
return list(self.bands.values())
@property
def n_bands(self):
- """The number of bands defined in the object."""
+ """The number of bands defined in the object."""
return len(self.bands)
[docs] def add_band(self, label, band_definition):
- """Add a new oscillation band definition.
+ """Add a new oscillation band definition.
Parameters
----------
@@ -215,7 +217,7 @@ Source code for fooof.bands.bands
[docs] def remove_band(self, label):
- """Remove a previously defined oscillation band.
+ """Remove a previously defined oscillation band.
Parameters
----------
@@ -228,7 +230,7 @@ Source code for fooof.bands.bands
@staticmethod
def _check_band(label, band_definition):
- """Check that a proposed band definition is valid.
+ """Check that a proposed band definition is valid.
Parameters
----------
@@ -267,8 +269,8 @@ Source code for fooof.bands.bands
- © Copyright 2018-2021, VoytekLab.
- Created using Sphinx 3.1.2.
+ © Copyright 2018-2023, VoytekLab.
+ Created using Sphinx 5.3.0.
diff --git a/_modules/fooof/data/data.html b/_modules/fooof/data/data.html
index 1f30c31d..b6514deb 100644
--- a/_modules/fooof/data/data.html
+++ b/_modules/fooof/data/data.html
@@ -1,24 +1,25 @@
-
+
-
- fooof.data.data — fooof 1.0.0 documentation
-
-
+
+ fooof.data.data — fooof 1.1.0 documentation
+
+
-
-
-
-
+
+
+
+
+
+
-
+
-
@@ -26,10 +27,10 @@
-
-
-
-
+
+
+
+
@@ -56,7 +57,7 @@
fooof
- 1.0.0
+ 1.1.0
@@ -68,6 +69,7 @@
Motivations
Tutorials
Examples
+ Visualizers
Reference
GitHub
@@ -129,7 +131,7 @@ Source code for fooof.data.data
[docs]class FOOOFSettings(namedtuple('FOOOFSettings', ['peak_width_limits', 'max_n_peaks',
'min_peak_height', 'peak_threshold',
'aperiodic_mode'])):
- """User defined settings for the fitting algorithm.
+ """User defined settings for the fitting algorithm.
Parameters
----------
@@ -151,8 +153,27 @@ Source code for fooof.data.data
__slots__ = ()
+[docs]class FOOOFRunModes(namedtuple('FOOOFRunModes', ['debug', 'check_freqs', 'check_data'])):
+ """Checks performed and errors raised by the model.
+
+ Parameters
+ ----------
+ debug : bool
+ Whether to run in debug mode.
+ check_freqs : bool
+ Whether to run in check freqs mode.
+ check_data : bool
+ Whether to run in check data mode.
+
+ Notes
+ -----
+ This object is a data object, based on a NamedTuple, with immutable data attributes.
+ """
+ __slots__ = ()
+
+
[docs]class FOOOFMetaData(namedtuple('FOOOFMetaData', ['freq_range', 'freq_res'])):
- """Metadata information about a power spectrum.
+ """Metadata information about a power spectrum.
Parameters
----------
@@ -170,7 +191,7 @@ Source code for fooof.data.data
[docs]class FOOOFResults(namedtuple('FOOOFResults', ['aperiodic_params', 'peak_params',
'r_squared', 'error', 'gaussian_params'])):
- """Model results from parameterizing a power spectrum.
+ """Model results from parameterizing a power spectrum.
Parameters
----------
@@ -195,7 +216,7 @@ Source code for fooof.data.data
[docs]class SimParams(namedtuple('SimParams', ['aperiodic_params', 'periodic_params', 'nlv'])):
- """Parameters that define a simulated power spectrum.
+ """Parameters that define a simulated power spectrum.
Parameters
----------
@@ -224,8 +245,8 @@ Source code for fooof.data.data
- © Copyright 2018-2021, VoytekLab.
- Created using Sphinx 3.1.2.
+ © Copyright 2018-2023, VoytekLab.
+ Created using Sphinx 5.3.0.
diff --git a/_modules/fooof/objs/fit.html b/_modules/fooof/objs/fit.html
index 5930ccbc..9b819145 100644
--- a/_modules/fooof/objs/fit.html
+++ b/_modules/fooof/objs/fit.html
@@ -1,24 +1,25 @@
-
+
-
- fooof.objs.fit — fooof 1.0.0 documentation
-
-
+
+ fooof.objs.fit — fooof 1.1.0 documentation
+
+
-
-
-
-
+
+
+
+
+
+
-
+
-
@@ -26,10 +27,10 @@
-
-
-
-
+
+
+
+
@@ -56,7 +57,7 @@
fooof
- 1.0.0
+ 1.1.0
@@ -68,6 +69,7 @@
Motivations
Tutorials
Examples
+ Visualizers
Reference
GitHub
@@ -152,9 +154,17 @@ Source code for fooof.objs.fit
The maximum number of calls to the curve fitting function.
_error_metric : str
The error metric to use for post-hoc measures of model fit error.
+
+Run Modes
+---------
_debug : bool
Whether the object is set in debug mode.
This should be controlled by using the `set_debug_mode` method.
+_check_data, _check_freqs : bool
+ Whether to check added inputs for incorrect inputs, failing if present.
+ Frequency data is checked for linear spacing.
+ Power values are checked for data for NaN or Inf values.
+ These modes default to True, and can be controlled with the `set_check_modes` method.
Code Notes
----------
@@ -168,6 +178,7 @@ Source code for fooof.objs.fit
from numpy.linalg import LinAlgError
from scipy.optimize import curve_fit
+from fooof.core.utils import unlog
from fooof.core.items import OBJ_DESC
from fooof.core.info import get_indices
from fooof.core.io import save_fm, load_json
@@ -175,23 +186,24 @@ Source code for fooof.objs.fit
from fooof.core.modutils import copy_doc_func_to_method
from fooof.core.utils import group_three, check_array_dim
from fooof.core.funcs import gaussian_function, get_ap_func, infer_ap_func
+from fooof.core.jacobians import jacobian_gauss
from fooof.core.errors import (FitError, NoModelError, DataError,
NoDataError, InconsistentDataError)
from fooof.core.strings import (gen_settings_str, gen_results_fm_str,
gen_issue_str, gen_width_warning_str)
from fooof.plts.fm import plot_fm
-from fooof.plts.style import style_spectrum_plot
from fooof.utils.data import trim_spectrum
from fooof.utils.params import compute_gauss_std
-from fooof.data import FOOOFResults, FOOOFSettings, FOOOFMetaData
+from fooof.data import FOOOFSettings, FOOOFRunModes, FOOOFMetaData, FOOOFResults
+from fooof.data.conversions import model_to_dataframe
from fooof.sim.gen import gen_freqs, gen_aperiodic, gen_periodic, gen_model
###################################################################################################
###################################################################################################
[docs]class FOOOF():
- """Model a physiological power spectrum as a combination of aperiodic and periodic components.
+ """Model a physiological power spectrum as a combination of aperiodic and periodic components.
WARNING: FOOOF expects frequency and power values in linear space.
@@ -205,9 +217,11 @@ Source code for fooof.objs.fit
max_n_peaks : int, optional, default: inf
Maximum number of peaks to fit.
min_peak_height : float, optional, default: 0
- Absolute threshold for detecting peaks, in units of the input data.
+ Absolute threshold for detecting peaks.
+ This threshold is defined in absolute units of the power spectrum (log power).
peak_threshold : float, optional, default: 2.0
- Relative threshold for detecting peaks, in units of standard deviation of the input data.
+ Relative threshold for detecting peaks.
+ This threshold is defined in relative units of the power spectrum (standard deviation).
aperiodic_mode : {'fixed', 'knee'}
Which approach to take for fitting the aperiodic component.
verbose : bool, optional, default: True
@@ -263,7 +277,7 @@ Source code for fooof.objs.fit
[docs] 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 object with desired settings."""
+ """Initialize object with desired settings."""
# Set input settings
self.peak_width_limits = peak_width_limits
@@ -294,29 +308,41 @@ Source code for fooof.objs.fit
self._gauss_overlap_thresh = 0.75
# Parameter bounds for center frequency when fitting gaussians, in terms of +/- std dev
self._cf_bound = 1.5
- # The maximum number of calls to the curve fitting function
- self._maxfev = 5000
# The error metric to calculate, post model fitting. See `_calc_error` for options
- # Note: this is used to check error post-hoc, not an objective function for fitting models
+ # Note: this is for checking error post fitting, not an objective function for fitting
self._error_metric = 'MAE'
- # Set whether in debug mode, in which an error is raised if a model fit fails
+
+ ## PRIVATE CURVE_FIT SETTINGS
+ # The maximum number of calls to the curve fitting function
+ self._maxfev = 5000
+ # The tolerance setting for curve fitting (see scipy.curve_fit - ftol / xtol / gtol)
+ # Here reduce tolerance to speed fitting. Set value to 1e-8 to match curve_fit default
+ self._tol = 0.00001
+
+ ## RUN MODES
+ # Set default debug mode - controls if an error is raised if model fitting is unsuccessful
self._debug = False
+ # Set default data checking modes - controls which checks get run on input data
+ # check_freqs: checks the frequency values, and raises an error for uneven spacing
+ self._check_freqs = True
+ # check_data: checks the power values and raises an error for any NaN / Inf values
+ self._check_data = True
- # Set internal settings, based on inputs, & initialize data & results attributes
+ # Set internal settings, based on inputs, and initialize data & results attributes
self._reset_internal_settings()
self._reset_data_results(True, True, True)
@property
def has_data(self):
- """Indicator for if the object contains data."""
+ """Indicator for if the object contains data."""
return True if np.any(self.power_spectrum) else False
@property
def has_model(self):
- """Indicator for if the object contains a model fit.
+ """Indicator for if the object contains a model fit.
Notes
-----
@@ -331,13 +357,13 @@ Source code for fooof.objs.fit
@property
def n_peaks_(self):
- """How many peaks were fit in the model."""
+ """How many peaks were fit in the model."""
return self.peak_params_.shape[0] if self.has_model else None
def _reset_internal_settings(self):
- """Set, or reset, internal settings, based on what is provided in init.
+ """Set, or reset, internal settings, based on what is provided in init.
Notes
-----
@@ -350,19 +376,15 @@ Source code for fooof.objs.fit
# Bandwidth limits are given in 2-sided peak bandwidth
# Convert to gaussian std parameter limits
- self._gauss_std_limits = tuple([bwl / 2 for bwl in self.peak_width_limits])
- # Bounds for aperiodic fitting. Drops bounds on knee parameter if not set to fit knee
- self._ap_bounds = self._ap_bounds if self.aperiodic_mode == 'knee' \
- else tuple(bound[0::2] for bound in self._ap_bounds)
+ self._gauss_std_limits = tuple(bwl / 2 for bwl in self.peak_width_limits)
# Otherwise, assume settings are unknown (have been cleared) and set to None
else:
self._gauss_std_limits = None
- self._ap_bounds = None
def _reset_data_results(self, clear_freqs=False, clear_spectrum=False, clear_results=False):
- """Set, or reset, data & results attributes to empty.
+ """Set, or reset, data & results attributes to empty.
Parameters
----------
@@ -399,8 +421,8 @@ Source code for fooof.objs.fit
self._peak_fit = None
-[docs] def add_data(self, freqs, power_spectrum, freq_range=None):
- """Add data (frequencies, and power spectrum values) to the current object.
+[docs] def add_data(self, freqs, power_spectrum, freq_range=None, clear_results=True):
+ """Add data (frequencies, and power spectrum values) to the current object.
Parameters
----------
@@ -411,6 +433,9 @@ Source code for fooof.objs.fit
freq_range : list of [float, float], optional
Frequency range to restrict power spectrum to.
If not provided, keeps the entire range.
+ clear_results : bool, optional, default: True
+ Whether to clear prior results, if any are present in the object.
+ This should only be set to False if data for the current results are being re-added.
Notes
-----
@@ -418,17 +443,19 @@ Source code for fooof.objs.fit
they will be cleared by this method call.
"""
- # If any data is already present, then clear data & results
+ # If any data is already present, then clear previous data
+ # Also clear results, if present, unless indicated not to
# This is to ensure object consistency of all data & results
- if np.any(self.freqs):
- self._reset_data_results(True, True, True)
+ self._reset_data_results(clear_freqs=self.has_data,
+ clear_spectrum=self.has_data,
+ clear_results=self.has_model and clear_results)
self.freqs, self.power_spectrum, self.freq_range, self.freq_res = \
- self._prepare_data(freqs, power_spectrum, freq_range, 1, self.verbose)
+ self._prepare_data(freqs, power_spectrum, freq_range, 1)
[docs] def add_settings(self, fooof_settings):
- """Add settings into object from a FOOOFSettings object.
+ """Add settings into object from a FOOOFSettings object.
Parameters
----------
@@ -443,7 +470,7 @@ Source code for fooof.objs.fit
[docs] def add_meta_data(self, fooof_meta_data):
- """Add data information into object from a FOOOFMetaData object.
+ """Add data information into object from a FOOOFMetaData object.
Parameters
----------
@@ -458,7 +485,7 @@ Source code for fooof.objs.fit
[docs] def add_results(self, fooof_result):
- """Add results data into object from a FOOOFResults object.
+ """Add results data into object from a FOOOFResults object.
Parameters
----------
@@ -475,8 +502,9 @@ Source code for fooof.objs.fit
self._check_loaded_results(fooof_result._asdict())
-[docs] def report(self, freqs=None, power_spectrum=None, freq_range=None, plt_log=False):
- """Run model fit, and display a report, which includes a plot, and printed results.
+[docs] def report(self, freqs=None, power_spectrum=None, freq_range=None,
+ plt_log=False, plot_full_range=False, **plot_kwargs):
+ """Run model fit, and display a report, which includes a plot, and printed results.
Parameters
----------
@@ -489,6 +517,13 @@ Source code for fooof.objs.fit
If not provided, fits across the entire given range.
plt_log : bool, optional, default: False
Whether or not to plot the frequency axis in log space.
+ plot_full_range : bool, default: False
+ If True, plots the full range of the given power spectrum.
+ Only relevant / effective if `freqs` and `power_spectrum` passed in in this call.
+ **plot_kwargs
+ Keyword arguments to pass into the plot method.
+ Plot options with a name conflict be passed by pre-pending `plot_`.
+ e.g. `freqs`, `power_spectrum` and `freq_range`.
Notes
-----
@@ -496,12 +531,17 @@ Source code for fooof.objs.fit
"""
self.fit(freqs, power_spectrum, freq_range)
- self.plot(plt_log=plt_log)
+ self.plot(plt_log=plt_log,
+ freqs=freqs if plot_full_range else plot_kwargs.pop('plot_freqs', None),
+ power_spectrum=power_spectrum if \
+ plot_full_range else plot_kwargs.pop('plot_power_spectrum', None),
+ freq_range=plot_kwargs.pop('plot_freq_range', None),
+ **plot_kwargs)
self.print_results(concise=False)
[docs] def fit(self, freqs=None, power_spectrum=None, freq_range=None):
- """Fit the full power spectrum as a combination of periodic and aperiodic components.
+ """Fit the full power spectrum as a combination of periodic and aperiodic components.
Parameters
----------
@@ -544,6 +584,14 @@ Source code for fooof.objs.fit
# In rare cases, the model fails to fit, and so uses try / except
try:
+ # If not set to fail on NaN or Inf data at add time, check data here
+ # This serves as a catch all for curve_fits which will fail given NaN or Inf
+ # Because FitError's are by default caught, this allows fitting to continue
+ if not self._check_data:
+ if np.any(np.isinf(self.power_spectrum)) or np.any(np.isnan(self.power_spectrum)):
+ raise FitError("Model fitting was skipped because there are NaN or Inf "
+ "values in the data, which preclude model fitting.")
+
# 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_)
@@ -593,7 +641,7 @@ Source code for fooof.objs.fit
[docs] def print_settings(self, description=False, concise=False):
- """Print out the current settings.
+ """Print out the current settings.
Parameters
----------
@@ -607,7 +655,7 @@ Source code for fooof.objs.fit
[docs] def print_results(self, concise=False):
- """Print out model fitting results.
+ """Print out model fitting results.
Parameters
----------
@@ -620,7 +668,7 @@ Source code for fooof.objs.fit
[docs] @staticmethod
def print_report_issue(concise=False):
- """Prints instructions on how to report bugs and/or problematic fits.
+ """Prints instructions on how to report bugs and/or problematic fits.
Parameters
----------
@@ -632,7 +680,7 @@ Source code for fooof.objs.fit
[docs] def get_settings(self):
- """Return user defined settings of the current object.
+ """Return user defined settings of the current object.
Returns
-------
@@ -644,8 +692,21 @@ Source code for fooof.objs.fit
for key in OBJ_DESC['settings']})
+[docs] def get_run_modes(self):
+ """Return run modes of the current object.
+
+ Returns
+ -------
+ FOOOFRunModes
+ Object containing the run modes from the current object.
+ """
+
+ return FOOOFRunModes(**{key.strip('_') : getattr(self, key) \
+ for key in OBJ_DESC['run_modes']})
+
+
[docs] def get_meta_data(self):
- """Return data information from the current object.
+ """Return data information from the current object.
Returns
-------
@@ -657,8 +718,101 @@ Source code for fooof.objs.fit
for key in OBJ_DESC['meta_data']})
+[docs] def get_data(self, component='full', space='log'):
+ """Get a data component.
+
+ Parameters
+ ----------
+ component : {'full', 'aperiodic', 'peak'}
+ Which data component to return.
+ 'full' - full power spectrum
+ 'aperiodic' - isolated aperiodic data component
+ 'peak' - isolated peak data component
+ space : {'log', 'linear'}
+ Which space to return the data component in.
+ 'log' - returns in log10 space.
+ 'linear' - returns in linear space.
+
+ Returns
+ -------
+ output : 1d array
+ Specified data component, in specified spacing.
+
+ Notes
+ -----
+ The 'space' parameter doesn't just define the spacing of the data component
+ values, but rather defines the space of the additive data definition such that
+ `power_spectrum = aperiodic_component + peak_component`.
+ With space set as 'log', this combination holds in log space.
+ With space set as 'linear', this combination holds in linear space.
+ """
+
+ if not self.has_data:
+ raise NoDataError("No data available to fit, can not proceed.")
+ assert space in ['linear', 'log'], "Input for 'space' invalid."
+
+ if component == 'full':
+ output = self.power_spectrum if space == 'log' else unlog(self.power_spectrum)
+ elif component == 'aperiodic':
+ output = self._spectrum_peak_rm if space == 'log' else \
+ unlog(self.power_spectrum) / unlog(self._peak_fit)
+ elif component == 'peak':
+ output = self._spectrum_flat if space == 'log' else \
+ unlog(self.power_spectrum) - unlog(self._ap_fit)
+ else:
+ raise ValueError('Input for component invalid.')
+
+ return output
+
+
+[docs] def get_model(self, component='full', space='log'):
+ """Get a model component.
+
+ Parameters
+ ----------
+ component : {'full', 'aperiodic', 'peak'}
+ Which model component to return.
+ 'full' - full model
+ 'aperiodic' - isolated aperiodic model component
+ 'peak' - isolated peak model component
+ space : {'log', 'linear'}
+ Which space to return the model component in.
+ 'log' - returns in log10 space.
+ 'linear' - returns in linear space.
+
+ Returns
+ -------
+ output : 1d array
+ Specified model component, in specified spacing.
+
+ Notes
+ -----
+ The 'space' parameter doesn't just define the spacing of the model component
+ values, but rather defines the space of the additive model such that
+ `model = aperiodic_component + peak_component`.
+ With space set as 'log', this combination holds in log space.
+ With space set as 'linear', this combination holds in linear space.
+ """
+
+ if not self.has_model:
+ raise NoModelError("No model fit results are available, can not proceed.")
+ assert space in ['linear', 'log'], "Input for 'space' invalid."
+
+ if component == 'full':
+ output = self.fooofed_spectrum_ if space == 'log' else unlog(self.fooofed_spectrum_)
+ elif component == 'aperiodic':
+ output = self._ap_fit if space == 'log' else unlog(self._ap_fit)
+ elif component == 'peak':
+ output = self._peak_fit if space == 'log' else \
+ unlog(self.fooofed_spectrum_) - unlog(self._ap_fit)
+ else:
+ raise ValueError('Input for component invalid.')
+
+ return output
+
+
[docs] def get_params(self, name, col=None):
- """Return model fit parameters for specified feature(s).
+ """Return model fit parameters for specified feature(s).
Parameters
----------
@@ -680,9 +834,7 @@ Source code for fooof.objs.fit
Notes
-----
- For further description of the data you can extract, check the FOOOFResults documentation.
-
- If there is no data on periodic features, this method will return NaN.
+ If there are no fit peak (no peak parameters), this method will return NaN.
"""
if not self.has_model:
@@ -714,7 +866,7 @@ Source code for fooof.objs.fit
[docs] def get_results(self):
- """Return model fit parameters and goodness of fit metrics.
+ """Return model fit parameters and goodness of fit metrics.
Returns
-------
@@ -727,20 +879,23 @@ Source code for fooof.objs.fit
[docs] @copy_doc_func_to_method(plot_fm)
- def plot(self, plot_peaks=None, plot_aperiodic=True, plt_log=False,
- add_legend=True, save_fig=False, file_name=None, file_path=None,
- ax=None, plot_style=style_spectrum_plot,
- data_kwargs=None, model_kwargs=None, aperiodic_kwargs=None, peak_kwargs=None):
+ def plot(self, plot_peaks=None, plot_aperiodic=True, freqs=None, power_spectrum=None,
+ freq_range=None, plt_log=False, add_legend=True, save_fig=False, file_name=None,
+ file_path=None, ax=None, data_kwargs=None, model_kwargs=None,
+ aperiodic_kwargs=None, peak_kwargs=None, **plot_kwargs):
- plot_fm(self, plot_peaks, plot_aperiodic, plt_log, add_legend,
- save_fig, file_name, file_path, ax, plot_style,
- data_kwargs, model_kwargs, aperiodic_kwargs, peak_kwargs)
+ plot_fm(self, plot_peaks=plot_peaks, plot_aperiodic=plot_aperiodic, freqs=freqs,
+ power_spectrum=power_spectrum, freq_range=freq_range, plt_log=plt_log,
+ add_legend=add_legend, save_fig=save_fig, file_name=file_name,
+ file_path=file_path, ax=ax, data_kwargs=data_kwargs, model_kwargs=model_kwargs,
+ aperiodic_kwargs=aperiodic_kwargs, peak_kwargs=peak_kwargs, **plot_kwargs)
[docs] @copy_doc_func_to_method(save_report_fm)
- def save_report(self, file_name, file_path=None, plt_log=False):
+ def save_report(self, file_name, file_path=None, plt_log=False,
+ add_settings=True, **plot_kwargs):
- save_report_fm(self, file_name, file_path, plt_log)
+ save_report_fm(self, file_name, file_path, plt_log, add_settings, **plot_kwargs)
[docs] @copy_doc_func_to_method(save_fm)
@@ -751,13 +906,13 @@ Source code for fooof.objs.fit
[docs] def load(self, file_name, file_path=None, regenerate=True):
- """Load in a FOOOF formatted JSON file to the current object.
+ """Load in a FOOOF formatted JSON file to the current object.
Parameters
----------
file_name : str or FileObject
File to load data from.
- file_path : str or None, optional
+ file_path : Path or str, optional
Path to directory to load from. If None, loads from current directory.
regenerate : bool, optional, default: True
Whether to regenerate the model fit from the loaded data, if data is available.
@@ -781,13 +936,13 @@ Source code for fooof.objs.fit
[docs] def copy(self):
- """Return a copy of the current object."""
+ """Return a copy of the current object."""
return deepcopy(self)
[docs] def set_debug_mode(self, debug):
- """Set whether debug mode, wherein an error is raised if fitting is unsuccessful.
+ """Set debug mode, which controls if an error is raised if model fitting is unsuccessful.
Parameters
----------
@@ -798,8 +953,74 @@ Source code for fooof.objs.fit
self._debug = debug
+[docs] def set_check_modes(self, check_freqs=None, check_data=None):
+ """Set check modes, which controls if an error is raised based on check on the inputs.
+
+ Parameters
+ ----------
+ check_freqs : bool, optional
+ Whether to run in check freqs mode, which checks the frequency data.
+ check_data : bool, optional
+ Whether to run in check data mode, which checks the power spectrum values data.
+ """
+
+ if check_freqs is not None:
+ self._check_freqs = check_freqs
+ if check_data is not None:
+ self._check_data = check_data
+
+
+ # This kept for backwards compatibility, but to be removed in 2.0 in favor of `set_check_modes`
+[docs] def set_check_data_mode(self, check_data):
+ """Set check data mode, which controls if an error is raised if NaN or Inf data are added.
+
+ Parameters
+ ----------
+ check_data : bool
+ Whether to run in check data mode.
+ """
+
+ self.set_check_modes(check_data=check_data)
+
+
+[docs] def set_run_modes(self, debug, check_freqs, check_data):
+ """Simultaneously set all run modes.
+
+ Parameters
+ ----------
+ debug : bool
+ Whether to run in debug mode.
+ check_freqs : bool
+ Whether to run in check freqs mode.
+ check_data : bool
+ Whether to run in check data mode.
+ """
+
+ self.set_debug_mode(debug)
+ self.set_check_modes(check_freqs, check_data)
+
+
+[docs] def to_df(self, peak_org):
+ """Convert and extract the model results as a pandas object.
+
+ Parameters
+ ----------
+ peak_org : int or Bands
+ How to organize peaks.
+ If int, extracts the first n peaks.
+ If Bands, extracts peaks based on band definitions.
+
+ Returns
+ -------
+ pd.Series
+ Model results organized into a pandas object.
+ """
+
+ return model_to_dataframe(self.get_results(), peak_org)
+
+
def _check_width_limits(self):
- """Check and warn about peak width limits / frequency resolution interaction."""
+ """Check and warn about peak width limits / frequency resolution interaction."""
# Check peak width limits against frequency resolution and warn if too close
if 1.5 * self.freq_res >= self.peak_width_limits[0]:
@@ -807,7 +1028,7 @@ Source code for fooof.objs.fit
def _simple_ap_fit(self, freqs, power_spectrum):
- """Fit the aperiodic component of the power spectrum.
+ """Fit the aperiodic component of the power spectrum.
Parameters
----------
@@ -826,12 +1047,16 @@ Source code for fooof.objs.fit
# Note that these are collected as lists, to concatenate with or without knee later
off_guess = [power_spectrum[0] if not self._ap_guess[0] else self._ap_guess[0]]
kne_guess = [self._ap_guess[1]] if self.aperiodic_mode == 'knee' else []
- exp_guess = [np.abs(self.power_spectrum[-1] - self.power_spectrum[0] /
- np.log10(self.freqs[-1]) - np.log10(self.freqs[0]))
+ exp_guess = [np.abs((self.power_spectrum[-1] - self.power_spectrum[0]) /
+ (np.log10(self.freqs[-1]) - np.log10(self.freqs[0])))
if not self._ap_guess[2] else self._ap_guess[2]]
+ # Get bounds for aperiodic fitting, dropping knee bound if not set to fit knee
+ ap_bounds = self._ap_bounds if self.aperiodic_mode == 'knee' \
+ else tuple(bound[0::2] for bound in self._ap_bounds)
+
# Collect together guess parameters
- guess = np.array([off_guess + kne_guess + exp_guess])
+ guess = np.array(off_guess + kne_guess + exp_guess)
# Ignore warnings that are raised in curve_fit
# A runtime warning can occur while exploring parameters in curve fitting
@@ -842,16 +1067,19 @@ Source code for fooof.objs.fit
warnings.simplefilter("ignore")
aperiodic_params, _ = curve_fit(get_ap_func(self.aperiodic_mode),
freqs, power_spectrum, p0=guess,
- maxfev=self._maxfev, bounds=self._ap_bounds)
- except RuntimeError:
- raise FitError("Model fitting failed due to not finding parameters in "
- "the simple aperiodic component fit.")
+ maxfev=self._maxfev, bounds=ap_bounds,
+ ftol=self._tol, xtol=self._tol, gtol=self._tol,
+ check_finite=False)
+ except RuntimeError as excp:
+ error_msg = ("Model fitting failed due to not finding parameters in "
+ "the simple aperiodic component fit.")
+ raise FitError(error_msg) from excp
return aperiodic_params
def _robust_ap_fit(self, freqs, power_spectrum):
- """Fit the aperiodic component of the power spectrum robustly, ignoring outliers.
+ """Fit the aperiodic component of the power spectrum robustly, ignoring outliers.
Parameters
----------
@@ -887,6 +1115,10 @@ Source code for fooof.objs.fit
freqs_ignore = freqs[perc_mask]
spectrum_ignore = power_spectrum[perc_mask]
+ # Get bounds for aperiodic fitting, dropping knee bound if not set to fit knee
+ ap_bounds = self._ap_bounds if self.aperiodic_mode == 'knee' \
+ else tuple(bound[0::2] for bound in self._ap_bounds)
+
# Second aperiodic fit - using results of first fit as guess parameters
# See note in _simple_ap_fit about warnings
try:
@@ -894,18 +1126,23 @@ Source code for fooof.objs.fit
warnings.simplefilter("ignore")
aperiodic_params, _ = curve_fit(get_ap_func(self.aperiodic_mode),
freqs_ignore, spectrum_ignore, p0=popt,
- maxfev=self._maxfev, bounds=self._ap_bounds)
- except RuntimeError:
- raise FitError("Model fitting failed due to not finding "
- "parameters in the robust aperiodic fit.")
- except TypeError:
- raise FitError("Model fitting failed due to sub-sampling in the robust aperiodic fit.")
+ maxfev=self._maxfev, bounds=ap_bounds,
+ ftol=self._tol, xtol=self._tol, gtol=self._tol,
+ check_finite=False)
+ except RuntimeError as excp:
+ error_msg = ("Model fitting failed due to not finding "
+ "parameters in the robust aperiodic fit.")
+ raise FitError(error_msg) from excp
+ except TypeError as excp:
+ error_msg = ("Model fitting failed due to sub-sampling "
+ "in the robust aperiodic fit.")
+ raise FitError(error_msg) from excp
return aperiodic_params
def _fit_peaks(self, flat_iter):
- """Iteratively fit peaks to flattened spectrum.
+ """Iteratively fit peaks to flattened spectrum.
Parameters
----------
@@ -964,7 +1201,7 @@ Source code for fooof.objs.fit
guess_std = compute_gauss_std(fwhm)
except ValueError:
- # This procedure can fail (extremely rarely), if both le & ri ind's end up as None
+ # This procedure can fail (very rarely), if both left & right inds end up as None
# In this case, default the guess to the average of the peak width limits
guess_std = np.mean(self.peak_width_limits)
@@ -995,7 +1232,7 @@ Source code for fooof.objs.fit
def _fit_peak_guess(self, guess):
- """Fits a group of peak guesses with a fit function.
+ """Fits a group of peak guesses with a fit function.
Parameters
----------
@@ -1028,8 +1265,8 @@ Source code for fooof.objs.fit
# Unpacks the embedded lists into flat tuples
# This 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]))
+ 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)
@@ -1037,14 +1274,18 @@ Source code for fooof.objs.fit
# Fit the peaks
try:
gaussian_params, _ = curve_fit(gaussian_function, self.freqs, self._spectrum_flat,
- p0=guess, maxfev=self._maxfev, bounds=gaus_param_bounds)
- except RuntimeError:
- raise FitError("Model fitting failed due to not finding "
- "parameters in the peak component fit.")
- except LinAlgError:
- raise FitError("Model fitting failed due to a LinAlgError during peak fitting. "
- "This can happen with settings that are too liberal, leading, "
- "to a large number of guess peaks that cannot be fit together.")
+ p0=guess, maxfev=self._maxfev, bounds=gaus_param_bounds,
+ ftol=self._tol, xtol=self._tol, gtol=self._tol,
+ check_finite=False, jac=jacobian_gauss)
+ except RuntimeError as excp:
+ error_msg = ("Model fitting failed due to not finding "
+ "parameters in the peak component fit.")
+ raise FitError(error_msg) from excp
+ except LinAlgError as excp:
+ error_msg = ("Model fitting failed due to a LinAlgError during peak fitting. "
+ "This can happen with settings that are too liberal, leading, "
+ "to a large number of guess peaks that cannot be fit together.")
+ raise FitError(error_msg) from excp
# Re-organize params into 2d matrix
gaussian_params = np.array(group_three(gaussian_params))
@@ -1053,7 +1294,7 @@ Source code for fooof.objs.fit
def _create_peak_params(self, gaus_params):
- """Copies over the gaussian params to peak outputs, updating as appropriate.
+ """Copies over the gaussian params to peak outputs, updating as appropriate.
Parameters
----------
@@ -1080,24 +1321,22 @@ Source code for fooof.objs.fit
with `freqs`, `fooofed_spectrum_` and `_ap_fit` all required to be available.
"""
- peak_params = np.empty([0, 3])
+ peak_params = np.empty((len(gaus_params), 3))
for ii, peak in enumerate(gaus_params):
# Gets the index of the power_spectrum at the frequency closest to the CF of the peak
- ind = min(range(len(self.freqs)), key=lambda ii: abs(self.freqs[ii] - peak[0]))
+ ind = np.argmin(np.abs(self.freqs - peak[0]))
# Collect peak parameter data
- peak_params = np.vstack((peak_params,
- [peak[0],
- self.fooofed_spectrum_[ind] - self._ap_fit[ind],
- peak[2] * 2]))
+ peak_params[ii] = [peak[0], self.fooofed_spectrum_[ind] - self._ap_fit[ind],
+ peak[2] * 2]
return peak_params
def _drop_peak_cf(self, guess):
- """Check whether to drop peaks based on center's proximity to the edge of the spectrum.
+ """Check whether to drop peaks based on center's proximity to the edge of the spectrum.
Parameters
----------
@@ -1110,8 +1349,8 @@ Source code for fooof.objs.fit
Guess parameters for gaussian peak fits. Shape: [n_peaks, 3].
"""
- cf_params = [item[0] for item in guess]
- bw_params = [item[2] * self._bw_std_edge for item in guess]
+ cf_params = guess[:, 0]
+ bw_params = guess[:, 2] * self._bw_std_edge
# Check if peaks within drop threshold from the edge of the frequency range
keep_peak = \
@@ -1125,7 +1364,7 @@ Source code for fooof.objs.fit
def _drop_peak_overlap(self, guess):
- """Checks whether to drop gaussians based on amount of overlap.
+ """Checks whether to drop gaussians based on amount of overlap.
Parameters
----------
@@ -1140,21 +1379,21 @@ Source code for fooof.objs.fit
Notes
-----
For any gaussians with an overlap that crosses the threshold,
- the lowest height guess guassian is dropped.
+ the lowest height guess Gaussian is dropped.
"""
- # Sort the peak guesses by increasing frequency, so adjacenent peaks can
- # be compared from right to left.
+ # Sort the peak guesses by increasing frequency
+ # This is so adjacent peaks can be compared from right to left
guess = sorted(guess, key=lambda x: float(x[0]))
# Calculate standard deviation bounds for checking amount of overlap
- # The bounds are the gaussian frequncy +/- gaussian standard deviation
+ # The bounds are the gaussian frequency +/- gaussian standard deviation
bounds = [[peak[0] - peak[2] * self._gauss_overlap_thresh,
peak[0] + peak[2] * self._gauss_overlap_thresh] for peak in guess]
# Loop through peak bounds, comparing current bound to that of next peak
# If the left peak's upper bound extends pass the right peaks lower bound,
- # Then drop the guassian with the lower height.
+ # then drop the Gaussian with the lower height
drop_inds = []
for ind, b_0 in enumerate(bounds[:-1]):
b_1 = bounds[ind + 1]
@@ -1173,19 +1412,22 @@ Source code for fooof.objs.fit
def _calc_r_squared(self):
- """Calculate the r-squared goodness of fit of the model, compared to the original data."""
+ """Calculate the r-squared goodness of fit of the model, compared to the original data."""
r_val = np.corrcoef(self.power_spectrum, self.fooofed_spectrum_)
self.r_squared_ = r_val[0][1] ** 2
def _calc_error(self, metric=None):
- """Calculate the overall error of the model fit, compared to the original data.
+ """Calculate the overall error of the model fit, compared to the original data.
Parameters
----------
metric : {'MAE', 'MSE', 'RMSE'}, optional
- Which error measure to calculate.
+ Which error measure to calculate:
+ * 'MAE' : mean absolute error
+ * 'MSE' : mean squared error
+ * 'RMSE' : root mean squared error
Raises
------
@@ -1210,13 +1452,12 @@ Source code for fooof.objs.fit
self.error_ = np.sqrt(((self.power_spectrum - self.fooofed_spectrum_) ** 2).mean())
else:
- msg = "Error metric '{}' not understood or not implemented.".format(metric)
- raise ValueError(msg)
+ error_msg = "Error metric '{}' not understood or not implemented.".format(metric)
+ raise ValueError(error_msg)
- @staticmethod
- def _prepare_data(freqs, power_spectrum, freq_range, spectra_dim=1, verbose=True):
- """Prepare input data for adding to current object.
+ def _prepare_data(self, freqs, power_spectrum, freq_range, spectra_dim=1):
+ """Prepare input data for adding to current object.
Parameters
----------
@@ -1229,8 +1470,6 @@ Source code for fooof.objs.fit
Frequency range to restrict power spectrum to. If None, keeps the entire range.
spectra_dim : int, optional, default: 1
Dimensionality that the power spectra should have.
- verbose : bool, optional
- Whether to be verbose in printing out warnings.
Returns
-------
@@ -1285,7 +1524,7 @@ Source code for fooof.objs.fit
# Aperiodic fit gets an inf if freq of 0 is included, which leads to an error
if freqs[0] == 0.0:
freqs, power_spectrum = trim_spectrum(freqs, power_spectrum, [freqs[1], freqs.max()])
- if verbose:
+ if self.verbose:
print("\nFOOOF WARNING: Skipping frequency == 0, "
"as this causes a problem with fitting.")
@@ -1296,18 +1535,28 @@ Source code for fooof.objs.fit
# Log power values
power_spectrum = np.log10(power_spectrum)
- # Check if there are any infs / nans, and raise an error if so
- if np.any(np.isinf(power_spectrum)) or np.any(np.isnan(power_spectrum)):
- raise DataError("The input power spectra data, after logging, contains NaNs or Infs. "
- "This will cause the fitting to fail. "
- "One reason this can happen is if inputs are already logged. "
- "Inputs data should be in linear spacing, not log.")
+ ## Data checks - run checks on inputs based on check modes
+
+ if self._check_freqs:
+ # Check if the frequency data is unevenly spaced, and raise an error if so
+ freq_diffs = np.diff(freqs)
+ if not np.all(np.isclose(freq_diffs, freq_res)):
+ raise DataError("The input frequency values are not evenly spaced. "
+ "The model expects equidistant frequency values in linear space.")
+ if self._check_data:
+ # Check if there are any infs / nans, and raise an error if so
+ if np.any(np.isinf(power_spectrum)) or np.any(np.isnan(power_spectrum)):
+ error_msg = ("The input power spectra data, after logging, contains NaNs or Infs. "
+ "This will cause the fitting to fail. "
+ "One reason this can happen is if inputs are already logged. "
+ "Inputs data should be in linear spacing, not log.")
+ raise DataError(error_msg)
return freqs, power_spectrum, freq_range, freq_res
def _add_from_dict(self, data):
- """Add data to object from a dictionary.
+ """Add data to object from a dictionary.
Parameters
----------
@@ -1321,7 +1570,7 @@ Source code for fooof.objs.fit
def _check_loaded_results(self, data):
- """Check if results have been added and check data.
+ """Check if results have been added and check data.
Parameters
----------
@@ -1337,7 +1586,7 @@ Source code for fooof.objs.fit
def _check_loaded_settings(self, data):
- """Check if settings added, and update the object as needed.
+ """Check if settings added, and update the object as needed.
Parameters
----------
@@ -1363,13 +1612,13 @@ Source code for fooof.objs.fit
def _regenerate_freqs(self):
- """Regenerate the frequency vector, given the object metadata."""
+ """Regenerate the frequency vector, given the object metadata."""
self.freqs = gen_freqs(self.freq_range, self.freq_res)
def _regenerate_model(self):
- """Regenerate model fit from parameters."""
+ """Regenerate model fit from parameters."""
self.fooofed_spectrum_, self._peak_fit, self._ap_fit = gen_model(
self.freqs, self.aperiodic_params_, self.gaussian_params_, return_components=True)
@@ -1386,8 +1635,8 @@ Source code for fooof.objs.fit
- © Copyright 2018-2021, VoytekLab.
- Created using Sphinx 3.1.2.
+ © Copyright 2018-2023, VoytekLab.
+ Created using Sphinx 5.3.0.
diff --git a/_modules/fooof/objs/group.html b/_modules/fooof/objs/group.html
index da1e9a2d..299de45a 100644
--- a/_modules/fooof/objs/group.html
+++ b/_modules/fooof/objs/group.html
@@ -1,24 +1,25 @@
-
+
-
- fooof.objs.group — fooof 1.0.0 documentation
-
-
+
+ fooof.objs.group — fooof 1.1.0 documentation
+
+
-
-
-
-
+
+
+
+
+
+
-
+
-
@@ -26,10 +27,10 @@
-
-
-
-
+
+
+
+
@@ -56,7 +57,7 @@
fooof
- 1.0.0
+ 1.1.0
@@ -68,6 +69,7 @@
Motivations
Tutorials
Examples
+ Visualizers
Reference
GitHub
@@ -133,13 +135,17 @@ Source code for fooof.objs.group
from fooof.core.reports import save_report_fg
from fooof.core.strings import gen_results_fg_str
from fooof.core.io import save_fg, load_jsonlines
-from fooof.core.modutils import copy_doc_func_to_method, safe_import
+from fooof.core.modutils import (copy_doc_func_to_method, safe_import,
+ docs_get_section, replace_docstring_sections)
+from fooof.data.conversions import group_to_dataframe
###################################################################################################
###################################################################################################
-[docs]class FOOOFGroup(FOOOF):
- """Model a group of power spectra as a combination of aperiodic and periodic components.
+[docs]@replace_docstring_sections([docs_get_section(FOOOF.__doc__, 'Parameters'),
+ docs_get_section(FOOOF.__doc__, 'Notes')])
+class FOOOFGroup(FOOOF):
+ """Model a group of power spectra as a combination of aperiodic and periodic components.
WARNING: FOOOF expects frequency and power values in linear space.
@@ -148,18 +154,7 @@ Source code for fooof.objs.group
Parameters
----------
- peak_width_limits : tuple of (float, float), optional, default: (0.5, 12.0)
- 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_height : float, optional, default: 0
- Absolute threshold for detecting peaks, in units of the input data.
- peak_threshold : float, optional, default: 2.0
- 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 : bool, optional, default: True
- Verbosity mode. If True, prints out warnings and general status updates.
+ %copied in from FOOOF object
Attributes
----------
@@ -180,25 +175,14 @@ Source code for fooof.objs.group
Whether model results are available in the object.
n_peaks_ : int
The number of peaks fit in the model.
- n_failed_fits_ : int
- The number of models that failed to fit.
- failed_fit_inds_ : list of int
- The indices of any models that failed to fit.
+ n_null_ : int
+ The number of models that failed to fit and/or that are marked as null.
+ null_inds_ : list of int
+ The indices of any models that are null.
Notes
-----
- - Commonly used abbreviations used in this module include:
- 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.
- For example, raw FFT inputs are not appropriate. Where possible and appropriate, use
- longer time segments for power spectrum calculation to get smoother power spectra,
- as this will give better model fits.
- - The gaussian params are those that define the gaussian of the fit, where as the peak
- params are a modified version, in which the CF of the peak is the mean of the gaussian,
- the PW of the peak is the height of the gaussian over and above the aperiodic component,
- and the BW of the peak, is 2*std of the gaussian (as 'two sided' bandwidth).
+ %copied in from FOOOF object
- The FOOOFGroup object inherits from the FOOOF object. As such it also has data
attributes (`power_spectrum` & `fooofed_spectrum_`), and parameter attributes
(`aperiodic_params_`, `peak_params_`, `gaussian_params_`, `r_squared_`, `error_`)
@@ -211,7 +195,7 @@ Source code for fooof.objs.group
# pylint: disable=attribute-defined-outside-init, arguments-differ
[docs] def __init__(self, *args, **kwargs):
- """Initialize object with desired settings."""
+ """Initialize object with desired settings."""
FOOOF.__init__(self, *args, **kwargs)
@@ -221,48 +205,48 @@ Source code for fooof.objs.group
def __len__(self):
- """Define the length of the object as the number of model fit results available."""
+ """Define the length of the object as the number of model fit results available."""
return len(self.group_results)
def __iter__(self):
- """Allow for iterating across the object by stepping across model fit results."""
+ """Allow for iterating across the object by stepping across model fit results."""
for result in self.group_results:
yield result
def __getitem__(self, index):
- """Allow for indexing into the object to select model fit results."""
+ """Allow for indexing into the object to select model fit results."""
return self.group_results[index]
@property
def has_data(self):
- """Indicator for if the object contains data."""
+ """Indicator for if the object contains data."""
return True if np.any(self.power_spectra) else False
@property
def has_model(self):
- """Indicator for if the object contains model fits."""
+ """Indicator for if the object contains model fits."""
return True if self.group_results else False
@property
def n_peaks_(self):
- """How many peaks were fit for each model."""
+ """How many peaks were fit for each model."""
return [f_res.peak_params.shape[0] for f_res in self] if self.has_model else None
@property
def n_null_(self):
- """How many model fits are null."""
+ """How many model fits are null."""
return sum([1 for f_res in self.group_results if np.isnan(f_res.aperiodic_params[0])]) \
if self.has_model else None
@@ -270,7 +254,7 @@ Source code for fooof.objs.group
@property
def null_inds_(self):
- """The indices for model fits that are null."""
+ """The indices for model fits that are null."""
return [ind for ind, f_res in enumerate(self.group_results) \
if np.isnan(f_res.aperiodic_params[0])] \
@@ -279,7 +263,7 @@ Source code for fooof.objs.group
def _reset_data_results(self, clear_freqs=False, clear_spectrum=False,
clear_results=False, clear_spectra=False):
- """Set, or reset, data & results attributes to empty.
+ """Set, or reset, data & results attributes to empty.
Parameters
----------
@@ -299,7 +283,7 @@ Source code for fooof.objs.group
def _reset_group_results(self, length=0):
- """Set, or reset, results to be empty.
+ """Set, or reset, results to be empty.
Parameters
----------
@@ -311,7 +295,7 @@ Source code for fooof.objs.group
[docs] def add_data(self, freqs, power_spectra, freq_range=None):
- """Add data (frequencies and power spectrum values) to the current object.
+ """Add data (frequencies and power spectrum values) to the current object.
Parameters
----------
@@ -335,11 +319,11 @@ Source code for fooof.objs.group
self._reset_group_results()
self.freqs, self.power_spectra, self.freq_range, self.freq_res = \
- self._prepare_data(freqs, power_spectra, freq_range, 2, self.verbose)
+ self._prepare_data(freqs, power_spectra, freq_range, 2)
[docs] def report(self, freqs=None, power_spectra=None, freq_range=None, n_jobs=1, progress=None):
- """Fit a group of power spectra and display a report, with a plot and printed results.
+ """Fit a group of power spectra and display a report, with a plot and printed results.
Parameters
----------
@@ -366,7 +350,7 @@ Source code for fooof.objs.group
[docs] def fit(self, freqs=None, power_spectra=None, freq_range=None, n_jobs=1, progress=None):
- """Fit a group of power spectra.
+ """Fit a group of power spectra.
Parameters
----------
@@ -417,7 +401,7 @@ Source code for fooof.objs.group
[docs] def drop(self, inds):
- """Drop one or more model fit results from the object.
+ """Drop one or more model fit results from the object.
Parameters
----------
@@ -437,13 +421,13 @@ Source code for fooof.objs.group
[docs] def get_results(self):
- """Return the results run across a group of power spectra."""
+ """Return the results run across a group of power spectra."""
return self.group_results
[docs] def get_params(self, name, col=None):
- """Return model fit parameters for specified feature(s).
+ """Return model fit parameters for specified feature(s).
Parameters
----------
@@ -467,7 +451,8 @@ Source code for fooof.objs.group
Notes
-----
- For further description of the data you can extract, check the FOOOFResults documentation.
+ When extracting peak information ('peak_params' or 'gaussian_params'), an additional column
+ is appended to the returned array, indicating the index of the model that the peak came from.
"""
if not self.has_model:
@@ -488,8 +473,11 @@ Source code for fooof.objs.group
# As a special case, peak_params are pulled out in a way that appends
# an extra column, indicating which FOOOF run each peak comes from
if name in ('peak_params', 'gaussian_params'):
- out = np.array([np.insert(getattr(data, name), 3, index, axis=1)
- for index, data in enumerate(self.group_results)])
+
+ # Collect peak data, appending the index of the model it comes from
+ out = np.vstack([np.insert(getattr(data, name), 3, index, axis=1)
+ for index, data in enumerate(self.group_results)])
+
# This updates index to grab selected column, and the last column
# This last column is the 'index' column (FOOOF object source)
if col is not None:
@@ -497,12 +485,6 @@ Source code for fooof.objs.group
else:
out = np.array([getattr(data, name) for data in self.group_results])
- # Some data can end up as a list of separate arrays
- # If so, concatenate it all into one 2d array
- if isinstance(out[0], np.ndarray):
- out = np.concatenate([arr.reshape(1, len(arr)) \
- if arr.ndim == 1 else arr for arr in out], 0)
-
# Select out a specific column, if requested
if col is not None:
out = out[:, col]
@@ -511,15 +493,15 @@ Source code for fooof.objs.group
[docs] @copy_doc_func_to_method(plot_fg)
- def plot(self, save_fig=False, file_name=None, file_path=None):
+ def plot(self, save_fig=False, file_name=None, file_path=None, **plot_kwargs):
- plot_fg(self, save_fig, file_name, file_path)
+ plot_fg(self, save_fig=save_fig, file_name=file_name, file_path=file_path, **plot_kwargs)
[docs] @copy_doc_func_to_method(save_report_fg)
- def save_report(self, file_name, file_path=None):
+ def save_report(self, file_name, file_path=None, add_settings=True):
- save_report_fg(self, file_name, file_path)
+ save_report_fg(self, file_name, file_path, add_settings)
[docs] @copy_doc_func_to_method(save_fg)
@@ -530,13 +512,13 @@ Source code for fooof.objs.group
[docs] def load(self, file_name, file_path=None):
- """Load FOOOFGroup data from file.
+ """Load FOOOFGroup data from file.
Parameters
----------
file_name : str
File to load data from.
- file_path : str, optional
+ file_path : Path or str, optional
Path to directory to load from. If None, loads from current directory.
"""
@@ -574,7 +556,7 @@ Source code for fooof.objs.group
[docs] def get_fooof(self, ind, regenerate=True):
- """Get a FOOOF object for a specified model fit.
+ """Get a FOOOF object for a specified model fit.
Parameters
----------
@@ -589,8 +571,9 @@ Source code for fooof.objs.group
The FOOOFResults data loaded into a FOOOF object.
"""
- # Initialize a FOOOF object, with same settings as current FOOOFGroup
+ # Initialize a FOOOF object, with same settings & run modes as current FOOOFGroup
fm = FOOOF(*self.get_settings(), verbose=self.verbose)
+ fm.set_run_modes(*self.get_run_modes())
# Add data for specified single power spectrum, if available
# The power spectrum is inverted back to linear, as it is re-logged when added to FOOOF
@@ -609,7 +592,7 @@ Source code for fooof.objs.group
[docs] def get_group(self, inds):
- """Get a FOOOFGroup object with the specified sub-selection of model fits.
+ """Get a FOOOFGroup object with the specified sub-selection of model fits.
Parameters
----------
@@ -626,8 +609,9 @@ Source code for fooof.objs.group
# Check and convert indices encoding to list of int
inds = check_inds(inds)
- # Initialize a new FOOOFGroup object, with same settings as current FOOOFGroup
+ # Initialize a new FOOOFGroup object, with same settings and run modes as current FOOOFGroup
fg = FOOOFGroup(*self.get_settings(), verbose=self.verbose)
+ fg.set_run_modes(*self.get_run_modes())
# Add data for specified power spectra, if available
# The power spectra are inverted back to linear, as they are re-logged when added to FOOOF
@@ -644,7 +628,7 @@ Source code for fooof.objs.group
[docs] def print_results(self, concise=False):
- """Print out FOOOFGroup results.
+ """Print out FOOOFGroup results.
Parameters
----------
@@ -655,22 +639,65 @@ Source code for fooof.objs.group
print(gen_results_fg_str(self, concise))
+[docs] def save_model_report(self, index, file_name, file_path=None, plt_log=False,
+ add_settings=True, **plot_kwargs):
+ """"Save out an individual model report for a specified model fit.
+
+ Parameters
+ ----------
+ index : int
+ Index of the model fit to save out.
+ file_name : str
+ Name to give the saved out file.
+ file_path : Path or str, optional
+ Path to directory to save to. If None, saves to current directory.
+ plt_log : bool, optional, default: False
+ Whether or not to plot the frequency axis in log space.
+ add_settings : bool, optional, default: True
+ Whether to add a print out of the model settings to the end of the report.
+ plot_kwargs : keyword arguments
+ Keyword arguments to pass into the plot method.
+ """
+
+ self.get_fooof(ind=index, regenerate=True).save_report(\
+ file_name, file_path, plt_log, add_settings, **plot_kwargs)
+
+
+[docs] def to_df(self, peak_org):
+ """Convert and extract the model results as a pandas object.
+
+ Parameters
+ ----------
+ peak_org : int or Bands
+ How to organize peaks.
+ If int, extracts the first n peaks.
+ If Bands, extracts peaks based on band definitions.
+
+ Returns
+ -------
+ pd.DataFrame
+ Model results organized into a pandas object.
+ """
+
+ return group_to_dataframe(self.get_results(), peak_org)
+
+
def _fit(self, *args, **kwargs):
- """Create an alias to FOOOF.fit for FOOOFGroup object, for internal use."""
+ """Create an alias to FOOOF.fit for FOOOFGroup object, for internal use."""
super().fit(*args, **kwargs)
def _get_results(self):
- """Create an alias to FOOOF.get_results for FOOOFGroup object, for internal use."""
+ """Create an alias to FOOOF.get_results for FOOOFGroup object, for internal use."""
return super().get_results()
def _check_width_limits(self):
- """Check and warn about bandwidth limits / frequency resolution interaction."""
+ """Check and warn about bandwidth limits / frequency resolution interaction."""
# Only check & warn on first power spectrum
- # This is to avoid spamming stdout for every spectrum in the group
+ # This is to avoid spamming standard output for every spectrum in the group
if self.power_spectra[0, 0] == self.power_spectrum[0]:
super()._check_width_limits()
@@ -678,7 +705,7 @@ Source code for fooof.objs.group
###################################################################################################
def _par_fit(power_spectrum, fg):
- """Helper function for running in parallel."""
+ """Helper function for running in parallel."""
fg._fit(power_spectrum=power_spectrum)
@@ -686,7 +713,7 @@ Source code for fooof.objs.group
def _progress(iterable, progress, n_to_run):
- """Add a progress bar to an iterable to be processed.
+ """Add a progress bar to an iterable to be processed.
Parameters
----------
@@ -758,8 +785,8 @@ Source code for fooof.objs.group
- © Copyright 2018-2021, VoytekLab.
- Created using Sphinx 3.1.2.
+ © Copyright 2018-2023, VoytekLab.
+ Created using Sphinx 5.3.0.
diff --git a/_modules/fooof/objs/utils.html b/_modules/fooof/objs/utils.html
index 08646094..0dcbbe56 100644
--- a/_modules/fooof/objs/utils.html
+++ b/_modules/fooof/objs/utils.html
@@ -1,24 +1,25 @@
-
+
-
- fooof.objs.utils — fooof 1.0.0 documentation
-
-
+
+ fooof.objs.utils — fooof 1.1.0 documentation
+
+
-
-
-
-
+
+
+
+
+
+
-
+
-
@@ -26,10 +27,10 @@
-
-
-
-
+
+
+
+
@@ -56,7 +57,7 @@
fooof
- 1.0.0
+ 1.1.0
@@ -68,6 +69,7 @@
Motivations
Tutorials
Examples
+ Visualizers
Reference
GitHub
@@ -125,7 +127,7 @@ Source code for fooof.objs.utils
###################################################################################################
[docs]def compare_info(fooof_lst, aspect):
- """Compare a specified aspect of FOOOF objects across instances.
+ """Compare a specified aspect of FOOOF objects across instances.
Parameters
----------
@@ -152,7 +154,7 @@ Source code for fooof.objs.utils
[docs]def average_fg(fg, bands, avg_method='mean', regenerate=True):
- """Average across model fits in a FOOOFGroup object.
+ """Average across model fits in a FOOOFGroup object.
Parameters
----------
@@ -178,18 +180,15 @@ Source code for fooof.objs.utils
If there are no model fit results available to average across.
"""
- if avg_method not in ['mean', 'median']:
- raise ValueError("Requested average method not understood.")
if not fg.has_model:
raise NoModelError("No model fit results are available, can not proceed.")
- if avg_method == 'mean':
- avg_func = np.nanmean
- elif avg_method == 'median':
- avg_func = np.nanmedian
+ avg_funcs = {'mean' : np.nanmean, 'median' : np.nanmedian}
+ if avg_method not in avg_funcs.keys():
+ raise ValueError("Requested average method not understood.")
# Aperiodic parameters: extract & average
- ap_params = avg_func(fg.get_params('aperiodic_params'), 0)
+ ap_params = avg_funcs[avg_method](fg.get_params('aperiodic_params'), 0)
# Periodic parameters: extract & average
peak_params = []
@@ -203,15 +202,15 @@ Source code for fooof.objs.utils
# Check if there are any extracted peaks - if not, don't add
# Note that we only check peaks, but gauss should be the same
if not np.all(np.isnan(peaks)):
- peak_params.append(avg_func(peaks, 0))
- gauss_params.append(avg_func(gauss, 0))
+ peak_params.append(avg_funcs[avg_method](peaks, 0))
+ gauss_params.append(avg_funcs[avg_method](gauss, 0))
peak_params = np.array(peak_params)
gauss_params = np.array(gauss_params)
# Goodness of fit measures: extract & average
- r2 = avg_func(fg.get_params('r_squared'))
- error = avg_func(fg.get_params('error'))
+ r2 = avg_funcs[avg_method](fg.get_params('r_squared'))
+ error = avg_funcs[avg_method](fg.get_params('error'))
# Collect all results together, to be added to FOOOF object
results = FOOOFResults(ap_params, peak_params, r2, error, gauss_params)
@@ -229,8 +228,43 @@ Source code for fooof.objs.utils
return fm
+[docs]def average_reconstructions(fg, avg_method='mean'):
+ """Average across model reconstructions for a group of power spectra.
+
+ Parameters
+ ----------
+ fg : FOOOFGroup
+ Object with model fit results to average across.
+ avg : {'mean', 'median'}
+ Averaging function to use.
+
+ Returns
+ -------
+ freqs : 1d array
+ Frequency values for the average model reconstruction.
+ avg_model : 1d array
+ Power values for the average model reconstruction.
+ Note that power values are in log10 space.
+ """
+
+ if not fg.has_model:
+ raise NoModelError("No model fit results are available, can not proceed.")
+
+ avg_funcs = {'mean' : np.nanmean, 'median' : np.nanmedian}
+ if avg_method not in avg_funcs.keys():
+ raise ValueError("Requested average method not understood.")
+
+ models = np.zeros(shape=fg.power_spectra.shape)
+ for ind in range(len(fg)):
+ models[ind, :] = fg.get_fooof(ind, regenerate=True).fooofed_spectrum_
+
+ avg_model = avg_funcs[avg_method](models, 0)
+
+ return fg.freqs, avg_model
+
+
[docs]def combine_fooofs(fooofs):
- """Combine a group of FOOOF and/or FOOOFGroup objects into a single FOOOFGroup object.
+ """Combine a group of FOOOF and/or FOOOFGroup objects into a single FOOOFGroup object.
Parameters
----------
@@ -251,7 +285,7 @@ Source code for fooof.objs.utils
--------
Combine FOOOF objects together (where `fm1`, `fm2` & `fm3` are assumed to be defined and fit):
- >>> fg = combine_fooofs([fm1, fm2, f3]) # doctest:+SKIP
+ >>> fg = combine_fooofs([fm1, fm2, fm3]) # doctest:+SKIP
Combine FOOOFGroup objects together (where `fg1` & `fg2` are assumed to be defined and fit):
@@ -291,6 +325,9 @@ Source code for fooof.objs.utils
if len(fg) == temp_power_spectra.shape[0]:
fg.power_spectra = temp_power_spectra
+ # Set the check data mode, as True if any of the inputs have it on, False otherwise
+ fg.set_check_data_mode(any(getattr(f_obj, '_check_data') for f_obj in fooofs))
+
# Add data information information
fg.add_meta_data(fooofs[0].get_meta_data())
@@ -298,7 +335,7 @@ Source code for fooof.objs.utils
[docs]def fit_fooof_3d(fg, freqs, power_spectra, freq_range=None, n_jobs=1):
- """Fit FOOOF models across a 3d array of power spectra.
+ """Fit FOOOF models across a 3d array of power spectra.
Parameters
----------
@@ -329,10 +366,15 @@ Source code for fooof.objs.utils
>>> fgs = fit_fooof_3d(fg, freqs, power_spectra, freq_range=[3, 30]) # doctest:+SKIP
"""
- fgs = []
- for cond_spectra in power_spectra:
- fg.fit(freqs, cond_spectra, freq_range, n_jobs)
- fgs.append(fg.copy())
+ # Reshape 3d data to 2d and fit, in order to fit with a single group model object
+ shape = np.shape(power_spectra)
+ powers_2d = np.reshape(power_spectra, (shape[0] * shape[1], shape[2]))
+
+ fg.fit(freqs, powers_2d, freq_range, n_jobs)
+
+ # Reorganize 2d results into a list of model group objects, to reflect original shape
+ fgs = [fg.get_group(range(dim_a * shape[1], (dim_a + 1) * shape[1])) \
+ for dim_a in range(shape[0])]
return fgs
@@ -348,8 +390,8 @@ Source code for fooof.objs.utils
- © Copyright 2018-2021, VoytekLab.
- Created using Sphinx 3.1.2.
+ © Copyright 2018-2023, VoytekLab.
+ Created using Sphinx 5.3.0.
diff --git a/_modules/fooof/plts/annotate.html b/_modules/fooof/plts/annotate.html
index d081c356..8cb5e173 100644
--- a/_modules/fooof/plts/annotate.html
+++ b/_modules/fooof/plts/annotate.html
@@ -1,24 +1,25 @@
-
+
-
- fooof.plts.annotate — fooof 1.0.0 documentation
-
-
+
+ fooof.plts.annotate — fooof 1.1.0 documentation
+
+
-
-
-
-
+
+
+
+
+
+
-
+
-
@@ -26,10 +27,10 @@
-
-
-
-
+
+
+
+
@@ -56,7 +57,7 @@
fooof
- 1.0.0
+ 1.1.0
@@ -68,6 +69,7 @@
Motivations
Tutorials
Examples
+ Visualizers
Reference
GitHub
@@ -119,30 +121,31 @@ Source code for fooof.plts.annotate
from fooof.core.errors import NoModelError
from fooof.core.funcs import gaussian_function
from fooof.core.modutils import safe_import, check_dependency
+
from fooof.sim.gen import gen_aperiodic
-from fooof.plts.utils import check_ax
-from fooof.plts.spectra import plot_spectrum
-from fooof.plts.settings import PLT_FIGSIZES, PLT_COLORS
-from fooof.plts.style import check_n_style, style_spectrum_plot
from fooof.analysis.periodic import get_band_peak_fm
from fooof.utils.params import compute_knee_frequency, compute_fwhm
+from fooof.plts.spectra import plot_spectra
+from fooof.plts.utils import check_ax, savefig
+from fooof.plts.settings import PLT_FIGSIZES, PLT_COLORS
+from fooof.plts.style import style_spectrum_plot
+
plt = safe_import('.pyplot', 'matplotlib')
mpatches = safe_import('.patches', 'matplotlib')
###################################################################################################
###################################################################################################
-[docs]@check_dependency(plt, 'matplotlib')
-def plot_annotated_peak_search(fm, plot_style=style_spectrum_plot):
- """Plot a series of plots illustrating the peak search from a flattened spectrum.
+[docs]@savefig
+@check_dependency(plt, 'matplotlib')
+def plot_annotated_peak_search(fm):
+ """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
@@ -153,20 +156,21 @@ Source code for fooof.plts.annotate