From e4e0d4067cdd9dcbe83d5fa1aadf8cc1a8904858 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 18 Jul 2023 12:30:45 -0400 Subject: [PATCH 1/5] update docs_get_section to support explicit section end --- fooof/core/modutils.py | 10 ++++++++-- fooof/tests/core/test_modutils.py | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/fooof/core/modutils.py b/fooof/core/modutils.py index 6c208a529..2e7c1b299 100644 --- a/fooof/core/modutils.py +++ b/fooof/core/modutils.py @@ -164,7 +164,7 @@ def docs_append_to_section(docstring, section, add): for split in docstring.split('\n\n')]) -def docs_get_section(docstring, section, output='extract'): +def docs_get_section(docstring, section, output='extract', end=None): """Extract and/or remove a specified section from a docstring. Parameters @@ -177,6 +177,7 @@ def docs_get_section(docstring, section, output='extract'): Run mode, options: 'extract' - returns the extracted section from the docstring. 'remove' - returns the docstring after removing the specified section. + end : str, optional Returns ------- @@ -193,7 +194,12 @@ def docs_get_section(docstring, section, output='extract'): # Track whether in the desired section if section in line and '--' in docstring_split[ind + 1]: in_section = True - if in_section and line == '': + if end: + if in_section and ' ' + end == line: + in_section = False + # In this approach, an extra newline is caught - so pop it off + outs.pop() + elif in_section and line == '': in_section = False # Collect desired outputs based on whether extracting or removing section diff --git a/fooof/tests/core/test_modutils.py b/fooof/tests/core/test_modutils.py index 3398da9a5..39a1a8036 100644 --- a/fooof/tests/core/test_modutils.py +++ b/fooof/tests/core/test_modutils.py @@ -73,6 +73,11 @@ def test_docs_get_section(tdocstring): assert 'Parameters' not in out2 assert 'Returns' in out2 + # Test with end_selection + out3 = docs_get_section(tdocstring, 'Parameters', output='extract', end='Returns') + assert 'Parameters' in out3 + assert 'Returns' not in out3 + def test_docs_add_section(tdocstring): tdocstring = tdocstring + \ From e89719240db2430a337c5d539b3533716222add8 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 18 Jul 2023 12:33:23 -0400 Subject: [PATCH 2/5] add interpolate_spectra --- doc/api.rst | 1 + fooof/utils/data.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/doc/api.rst b/doc/api.rst index 23f06e5ee..5c2609739 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -341,6 +341,7 @@ Utilities for working with data. trim_spectrum interpolate_spectrum + interpolate_spectra subsample_spectra Parameter Utilities diff --git a/fooof/utils/data.py b/fooof/utils/data.py index 5ce8f9902..6f2e0b8df 100644 --- a/fooof/utils/data.py +++ b/fooof/utils/data.py @@ -1,9 +1,12 @@ """Utilities for working with data and models.""" from itertools import repeat +from functools import partial import numpy as np +from fooof.core.modutils import docs_get_section, replace_docstring_sections + ################################################################################################### ################################################################################################### @@ -138,6 +141,55 @@ def interpolate_spectrum(freqs, powers, interp_range, buffer=3): return freqs, powers +def wrap_interpolate_spectrum(powers, freqs, interp_range, buffer): + """Wraps interpolate function, organizing inputs & outputs to use `partial`.""" + return interpolate_spectrum(freqs, powers, interp_range, buffer)[1] + + +@replace_docstring_sections(docs_get_section(interpolate_spectrum.__doc__, 'Notes', end='Examples')) +def interpolate_spectra(freqs, powers, interp_range, buffer=3): + """Interpolate a frequency region across a group of power spectra. + + Parameters + ---------- + freqs : 1d array + Frequency values for the power spectrum. + powers : 2d array + Power values for the power spectra. + interp_range : list of float or list of list of float + Frequency range to interpolate, as [lowest_freq, highest_freq]. + If a list of lists, applies each as it's own interpolation range. + buffer : int or list of int + The number of samples to use on either side of the interpolation + range, that are then averaged and used to calculate the interpolation. + + Returns + ------- + freqs : 1d array + Frequency values for the power spectrum. + powers : 2d array + Power values, with interpolation, for the power spectra. + + Notes + ----- + % copied in from interpolate_spectrum + + Examples + -------- + Using simulated spectra, interpolate away line noise peaks: + + >>> from fooof.sim import gen_group_power_spectra + >>> freqs, powers = gen_power_spectrum(5, [1, 75], [1, 1], [[10, 0.5, 1.0], [60, 2, 0.1]]) + >>> freqs, powers = interpolate_spectra(freqs, powers, [58, 62]) + """ + + tfunc = partial(wrap_interpolate_spectrum, freqs=freqs, + interp_range=interp_range, buffer=buffer) + powers = np.apply_along_axis(tfunc, 1, powers) + + return freqs,powers + + def subsample_spectra(spectra, selection, return_inds=False): """Subsample a group of power spectra. From 0ed6d7d4c4c649ee35cbd1058817e68264e58b20 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 18 Jul 2023 12:40:19 -0400 Subject: [PATCH 3/5] add tests for interpolate_spectra --- fooof/tests/utils/test_data.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fooof/tests/utils/test_data.py b/fooof/tests/utils/test_data.py index 9d91f5a37..f27cd9bd6 100644 --- a/fooof/tests/utils/test_data.py +++ b/fooof/tests/utils/test_data.py @@ -50,6 +50,21 @@ def test_interpolate_spectrum(): mask = np.logical_and(freqs >= f_range[0], freqs <= f_range[1]) assert powers[mask].sum() > powers_out[mask].sum() +def test_interpolate_spectra(): + + freqs, powers = gen_group_power_spectra(\ + 5, [1, 150], [1, 100, 1], [[10, 0.5, 1.0], [60, 1, 0.1], [120, 0.5, 0.1]]) + + exclude = [[58, 62], [118, 122]] + freqs_out, powers_out = interpolate_spectra(freqs, powers, exclude) + assert np.array_equal(freqs, freqs_out) + assert np.all(powers) + assert powers.shape == powers_out.shape + + for f_range in exclude: + mask = np.logical_and(freqs >= f_range[0], freqs <= f_range[1]) + assert powers[:, mask].sum() > powers_out[:, mask].sum() + def test_subsample_spectra(): # Simulate spectra, each with unique osc peak (for checking) From d97f433faf602d846999c71e2221a780de2c1b98 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 18 Jul 2023 12:46:40 -0400 Subject: [PATCH 4/5] fix doctest' --- fooof/utils/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fooof/utils/data.py b/fooof/utils/data.py index 6f2e0b8df..d64cd09ac 100644 --- a/fooof/utils/data.py +++ b/fooof/utils/data.py @@ -179,7 +179,7 @@ def interpolate_spectra(freqs, powers, interp_range, buffer=3): Using simulated spectra, interpolate away line noise peaks: >>> from fooof.sim import gen_group_power_spectra - >>> freqs, powers = gen_power_spectrum(5, [1, 75], [1, 1], [[10, 0.5, 1.0], [60, 2, 0.1]]) + >>> freqs, powers = gen_group_power_spectra(5, [1, 75], [1, 1], [[10, 0.5, 1.0], [60, 2, 0.1]]) >>> freqs, powers = interpolate_spectra(freqs, powers, [58, 62]) """ From 6fe727d03fc5912dbf802ac7e8e5d113484fc686 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Fri, 21 Jul 2023 09:44:13 -0400 Subject: [PATCH 5/5] udpate docstring for end --- fooof/core/modutils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fooof/core/modutils.py b/fooof/core/modutils.py index 2e7c1b299..bab190694 100644 --- a/fooof/core/modutils.py +++ b/fooof/core/modutils.py @@ -178,6 +178,8 @@ def docs_get_section(docstring, section, output='extract', end=None): 'extract' - returns the extracted section from the docstring. 'remove' - returns the docstring after removing the specified section. end : str, optional + Indicates the contents of a line that signals the end of the section to select. + If not provided, the section is selected until a blank line. Returns -------