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