Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MNT] - Fix management of check modes #293

Merged
merged 17 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Objects to store settings, metadata and results for power spectrum models.
:template: data_object.rst

FOOOFSettings
FOOOFRunModes
FOOOFMetaData
FOOOFResults

Expand Down
2 changes: 2 additions & 0 deletions fooof/core/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def get_description():

- results : parameters for and measures of the model
- settings : model settings
- run_modes: checks performed and errors raised
- data : input data
- meta_data : meta data of the inputs
- arrays : data stored in arrays
Expand All @@ -29,6 +30,7 @@ def get_description():
'settings' : ['peak_width_limits', 'max_n_peaks',
'min_peak_height', 'peak_threshold',
'aperiodic_mode'],
'run_modes': ['_debug', '_check_freqs', '_check_data'],
'data' : ['power_spectrum', 'freq_range', 'freq_res'],
'meta_data' : ['freq_range', 'freq_res'],
'arrays' : ['freqs', 'power_spectrum', 'aperiodic_params_',
Expand Down
2 changes: 1 addition & 1 deletion fooof/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Data sub-module for FOOOF."""

from .data import FOOOFSettings, FOOOFMetaData, FOOOFResults, SimParams
from .data import FOOOFSettings, FOOOFRunModes, FOOOFMetaData, FOOOFResults, SimParams
19 changes: 19 additions & 0 deletions fooof/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ class FOOOFSettings(namedtuple('FOOOFSettings', ['peak_width_limits', 'max_n_pea
__slots__ = ()


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__ = ()


class FOOOFMetaData(namedtuple('FOOOFMetaData', ['freq_range', 'freq_res'])):
"""Metadata information about a power spectrum.

Expand Down
62 changes: 56 additions & 6 deletions fooof/objs/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@
_debug : bool
Whether the object is set in debug mode.
This should be controlled by using the `set_debug_mode` method.
_check_data : bool
Whether to check added data for NaN or Inf values, and fail out if present.
This should be controlled by using the `set_check_data_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
----------
Expand Down Expand Up @@ -76,7 +78,7 @@
from fooof.plts.fm import plot_fm
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

Expand Down Expand Up @@ -199,7 +201,7 @@ def __init__(self, peak_width_limits=(0.5, 12.0), max_n_peaks=np.inf, min_peak_h
# 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: check the frequency values, and raises an error for uneven spacing
# 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
Expand Down Expand Up @@ -568,6 +570,19 @@ def get_settings(self):
for key in OBJ_DESC['settings']})


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']})


def get_meta_data(self):
"""Return data information from the current object.

Expand Down Expand Up @@ -723,6 +738,24 @@ def set_debug_mode(self, debug):
self._debug = debug


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
ryanhammonds marked this conversation as resolved.
Show resolved Hide resolved
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`
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.

Expand All @@ -732,7 +765,24 @@ def set_check_data_mode(self, check_data):
Whether to run in check data mode.
"""

self._check_data = check_data
self.set_check_modes(check_data=check_data)


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)
ryanhammonds marked this conversation as resolved.
Show resolved Hide resolved


def to_df(self, peak_org):
Expand Down
7 changes: 4 additions & 3 deletions fooof/objs/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,9 @@ def get_fooof(self, ind, regenerate=True):
The FOOOFResults data loaded into a FOOOF object.
"""

# Initialize a FOOOF object, with same settings & check data mode 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_check_data_mode(self._check_data)
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
Expand Down Expand Up @@ -494,8 +494,9 @@ def get_group(self, inds):
# 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
Expand Down
13 changes: 8 additions & 5 deletions fooof/sim/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,16 @@ def _check_values(start, stop, step):
If the given values for defining the iteration range are inconsistent.
"""

if any(ii < 0 for ii in [start, stop, step]):
raise ValueError("Inputs 'start', 'stop', and 'step' should all be positive values.")
if any(ii < 0 for ii in [start, stop]):
raise ValueError("Inputs 'start' and 'stop' should be positive values.")

if not start < stop:
raise ValueError("Input 'start' should be less than 'stop'.")
if (stop - start) * step < 0:
raise ValueError("The sign of input 'step' does not align with 'start' / 'stop' values.")

if not step < (stop - start):
if start == stop:
raise ValueError("Input 'start' and 'stop' must be different values.")

if not abs(step) < abs(stop - start):
raise ValueError("Input 'step' is too large given values for 'start' and 'stop'.")


Expand Down
2 changes: 0 additions & 2 deletions fooof/tests/core/test_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def test_get_description(tfm):
for it in va:
assert it in objs


def test_get_peak_indices():

indices = get_peak_indices()
Expand All @@ -33,7 +32,6 @@ def test_get_ap_indices():
for ind, val in enumerate(['offset', 'exponent']):
assert indices_fixed[val] == ind


indices_knee = get_indices('knee')

assert indices_knee
Expand Down
8 changes: 8 additions & 0 deletions fooof/tests/data/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ def test_fooof_settings():
for field in OBJ_DESC['settings']:
assert getattr(settings, field)

def test_fooof_run_modes():

run_modes = FOOOFRunModes(True, True, True)
assert run_modes

for field in OBJ_DESC['run_modes']:
assert getattr(run_modes, field.strip('_'))

def test_fooof_meta_data():

meta_data = FOOOFMetaData([1, 50], 0.5)
Expand Down
31 changes: 25 additions & 6 deletions fooof/tests/objs/test_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,17 +414,24 @@ def test_fooof_debug():
with raises(FitError):
tfm.fit(*gen_power_spectrum([3, 50], [50, 2], [10, 0.5, 2, 20, 0.3, 4]))

def test_fooof_check_data():
"""Test FOOOF in with check data mode turned off, including with NaN data."""
def test_fooof_set_check_modes(tfm):
"""Test changing check_modes using set_check_modes, and that checks get turned off.
Note that testing for checks raising errors happens in test_fooof_checks.`"""

tfm = FOOOF(verbose=False)

tfm.set_check_data_mode(False)
tfm.set_check_modes(False, False)
assert tfm._check_freqs is False
assert tfm._check_data is False

# Add data, with check data turned off
# In check data mode, adding data with NaN should run
freqs = gen_freqs([3, 50], 0.5)
# Add bad frequency data, with check freqs turned off
freqs = np.array([1, 2, 4])
powers = np.array([1, 2, 3])
tfm.add_data(freqs, powers)
assert tfm.has_data

# Add bad power values data, with check data turned off
freqs = gen_freqs([3, 30], 1)
powers = np.ones_like(freqs) * np.nan
tfm.add_data(freqs, powers)
assert tfm.has_data
Expand All @@ -433,6 +440,18 @@ def test_fooof_check_data():
tfm.fit()
assert not tfm.has_model

# Reset check modes to true
tfm.set_check_modes(True, True)
assert tfm._check_freqs is True
assert tfm._check_data is True

def test_set_run_modes():

tfm = FOOOF(verbose=False)
tfm.set_run_modes(False, False, False)
for field in OBJ_DESC['run_modes']:
assert getattr(tfm, field) is False

def test_fooof_to_df(tfm, tbands, skip_if_no_pandas):

df1 = tfm.to_df(2)
Expand Down