Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
140 changes: 140 additions & 0 deletions fooof/core/modutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ def wrapped_func(*args, **kwargs):
return wrap


DOCSTRING_SECTIONS = ['Parameters', 'Returns', 'Yields', 'Raises',
'Warns', 'Examples', 'References', 'Notes',
'Attributes', 'Methods']


def get_docs_indices(docstring, sections=DOCSTRING_SECTIONS):
"""Get the indices of each section within a docstring.

Parameters
----------
docstring : str
Docstring to check indices for.
sections : list of str, optional
List of sections to check and get indices for.
If not provided, uses the default set of

Returns
-------
inds : dict
Dictionary in which each key is a section label, and each value is the corresponding index.
"""

inds = {label : None for label in DOCSTRING_SECTIONS}

for ind, line in enumerate(docstring.split('\n')):
for key, val in inds.items():
if key in line:
inds[key] = ind

return inds


def docs_drop_param(docstring):
"""Drop the first parameter description for a string representation of a docstring.

Expand Down Expand Up @@ -132,6 +164,91 @@ def docs_append_to_section(docstring, section, add):
for split in docstring.split('\n\n')])


def docs_get_section(docstring, section, output='extract'):
"""Extract and/or remove a specified section from a docstring.

Parameters
----------
docstring : str
Docstring to extract / remove a section from.
section : str
Label of the section to extract / remove.
mode : {'extract', 'remove'}
Run mode, options:
'extract' - returns the extracted section from the docstring.
'remove' - returns the docstring after removing the specified section.

Returns
-------
out_docstring : str
Extracted / updated docstring.
"""

outs = []
in_section = False

docstring_split = docstring.split('\n')
for ind, line in enumerate(docstring_split):

# Track whether in the desired section
if section in line and '--' in docstring_split[ind + 1]:
in_section = True
if in_section and line == '':
in_section = False

# Collect desired outputs based on whether extracting or removing section
if output == 'extract' and in_section:
outs.append(line)
if output == 'remove' and not in_section:
outs.append(line)

# As a special case, when removing section, end section marker if there is a '%' line
if in_section and output == 'remove' and not line.isspace() and line.strip()[0] == '%':
in_section = False

out_docstring = '\n'.join(outs)

return out_docstring


def docs_add_section(docstring, section):
"""Add a section to a specified index of a docstring.

Parameters
----------
docstring : str
Docstring to add section to.
section : str
New section to add to docstring.

Returns
-------
out_docstring : str
Updated docstring, with the new section added.
"""

inds = get_docs_indices(docstring)

# Split the section, extract the label, and check it's a known docstring section
split_section = section.split('\n')
section_label = split_section[0].strip()
assert section_label in inds, 'Section label does not match expected list.'

# Remove the header section from the docstring (to replace it)
docstring = docs_get_section(docstring, section_label, 'remove')

# Check for and drop leading and trailing empty lines
split_section = split_section[1:] if split_section[0] == '' else split_section
split_section = split_section[:-1] if split_section[-1] == ' ' else split_section

# Insert the new section into the docstring and rejoin it together
split_docstring = docstring.split('\n')
split_docstring[inds[section_label]:inds[section_label]] = split_section
new_docstring = '\n'.join(split_docstring)

return new_docstring


def copy_doc_func_to_method(source):
"""Decorator that copies method docstring from function, dropping first parameter.

Expand Down Expand Up @@ -180,3 +297,26 @@ def wrapper(func):
return func

return wrapper


def replace_docstring_sections(replacements):
"""Decorator to drop in docstring sections

Parameters
----------
replacements : str or list of str
Section(s) to drop into the decorated function's docstring.
"""

def wrapper(func):

docstring = func.__doc__

for replacement in [replacements] if isinstance(replacements, str) else replacements:
docstring = docs_add_section(docstring, replacement)

func.__doc__ = docstring

return func

return wrapper
31 changes: 6 additions & 25 deletions fooof/objs/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
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

###################################################################################################
###################################################################################################

@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.

Expand All @@ -36,18 +39,7 @@ class FOOOFGroup(FOOOF):

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
----------
Expand Down Expand Up @@ -75,18 +67,7 @@ class FOOOFGroup(FOOOF):

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_`)
Expand Down
6 changes: 5 additions & 1 deletion fooof/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from fooof.core.modutils import safe_import

from fooof.tests.tutils import get_tfm, get_tfg, get_tbands, get_tresults
from fooof.tests.tutils import get_tfm, get_tfg, get_tbands, get_tresults, get_tdocstring
from fooof.tests.settings import (BASE_TEST_FILE_PATH, TEST_DATA_PATH,
TEST_REPORTS_PATH, TEST_PLOTS_PATH)

Expand Down Expand Up @@ -52,6 +52,10 @@ def tbands():
def tresults():
yield get_tresults()

@pytest.fixture(scope='function')
def tdocstring():
yield get_tdocstring()

@pytest.fixture(scope='session')
def skip_if_no_mpl():
if not safe_import('matplotlib'):
Expand Down
123 changes: 89 additions & 34 deletions fooof/tests/core/test_modutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,13 @@ def subfunc_bad():
with raises(ImportError):
subfunc_bad()

def test_docs_drop_param():
def test_docs_drop_param(tdocstring):

ds = """STUFF

Parameters
----------
first : thing
Words, words, words.
second : stuff
Words, words, words.

Returns
-------
out : yay
Words, words, words.
"""

out = docs_drop_param(ds)
out = docs_drop_param(tdocstring)
assert 'first' not in out
assert 'second' in out

def test_docs_append_to_section():

ds = """STUFF

Parameters
----------
first : thing
Words, words, words.
second : stuff
Words, words, words.

Returns
-------
out : yay
Words, words, words.
"""
def test_docs_append_to_section(tdocstring):

section = 'Parameters'
add = \
Expand All @@ -78,7 +48,92 @@ def test_docs_append_to_section():
Added description.
"""

new_ds = docs_append_to_section(ds, section, add)
new_ds = docs_append_to_section(tdocstring, section, add)

assert 'third' in new_ds
assert 'Added description' in new_ds

def test_get_docs_indices(tdocstring):

inds = get_docs_indices(tdocstring)

for el in DOCSTRING_SECTIONS:
assert el in inds.keys()

assert inds['Parameters'] == 2
assert inds['Returns'] == 9

def test_docs_get_section(tdocstring):

out1 = docs_get_section(tdocstring, 'Parameters', output='extract')
assert 'Parameters' in out1
assert 'Returns' not in out1

out2 = docs_get_section(tdocstring, 'Parameters', output='remove')
assert 'Parameters' not in out2
assert 'Returns' in out2

def test_docs_add_section(tdocstring):

tdocstring = tdocstring + \
"""\nNotes\n-----\n % copied in"""

new_section = \
"""Notes\n-----\n \nThis is a new note."""
new_docstring = docs_add_section(tdocstring, new_section)

assert 'Notes' in new_docstring
assert '%' not in new_docstring
assert 'new note' in new_docstring

def test_copy_doc_func_to_method(tdocstring):

def tfunc(): pass
tfunc.__doc__ = tdocstring

class tObj():

@copy_doc_func_to_method(tfunc)
def tmethod():
pass

assert tObj.tmethod.__doc__
assert 'first' not in tObj.tmethod.__doc__
assert 'second' in tObj.tmethod.__doc__


def test_copy_doc_class(tdocstring):

class tObj1():
pass
tObj1.__doc__ = tdocstring

new_section = \
"""
third : stuff
Words, words, words.
"""
@copy_doc_class(tObj1, 'Parameters', new_section)
class tObj2():
pass

assert 'third' in tObj2.__doc__
assert 'third' not in tObj1.__doc__

def test_replace_docstring_sections(tdocstring):

# Extract just the parameters section from general test docstring
new_parameters = '\n'.join(tdocstring.split('\n')[2:8])

@replace_docstring_sections(new_parameters)
def tfunc():
"""Test function docstring

Parameters
----------
% copied in
"""
pass

assert 'first' in tfunc.__doc__
assert 'second' in tfunc.__doc__
Loading