From a71dc97b0b602bc90beb177791d0ab0d72ba1b20 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 00:48:34 +0000 Subject: [PATCH 01/12] add algo example: --- examples/customize/plot_custom_algorithms.py | 301 +++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 examples/customize/plot_custom_algorithms.py diff --git a/examples/customize/plot_custom_algorithms.py b/examples/customize/plot_custom_algorithms.py new file mode 100644 index 00000000..5c10bbd1 --- /dev/null +++ b/examples/customize/plot_custom_algorithms.py @@ -0,0 +1,301 @@ +""" +Custom Algorithms +================= + +This example covers defining and using custom fit algorithms. +""" + +from specparam import SpectralModel + +# Import function to simulate a power spectrum +from specparam.sim import sim_power_spectrum + +# Import elements to define custom fit algorithms +from specparam.algorithms.settings import SettingsDefinition +from specparam.algorithms.algorithm import Algorithm + +################################################################################################### +# Defining Custom Fit Algorithms +# ------------------------------ +# +# The specparam module includes a standard fitting algorithm that is used to for fitting the +# selected fit modes to the data. However, you do not have to use this particular algorithm, +# you can tweak how it works, and/or define your own custom algorithm and plug this in to +# the model object. +# +# In this tutorial, we will explore how you can also define your own custom fit algorithms. +# +# To do so, we will start by simulating an example power spectrum to use for this example. +# + +################################################################################################### + +# Define simulation parameters +ap_params = [0, 1] +gauss_params = [10, 0.5, 2] +nlv = 0.025 + +# Simulate an example power spectrum +freqs, powers = sim_power_spectrum(\ + [3, 50], {'fixed' : ap_params}, {'gaussian' : gauss_params}, nlv) + +################################################################################################### +# Example: Custom Algorithm Object +# -------------------------------- +# +# In our first example, we will introduce how to create a custom fit algorithm. +# +# For simplicity, we will start with a 'dummy' algorithm - one that functions code wise, but +# doesn't actually implement a detailed fitting algorithm, so that we can start with the +# organization of the code, and build up from there. +# + +################################################################################################### +# Algorithm Settings +# ~~~~~~~~~~~~~~~~~~ +# +# A fitting algorithm typically has some settings that you want to define and describe so that +# the user can check their description and provide values for the settings. +# +# For fitting algorithms, these setting descriptions are managed by the +# :class:`~specparam.algorithms.settings.SettingsDefinition` object. +# +# For our dummy algorithm, we will initialize a settings definition object, with a +# placeholder label and description. +# + +################################################################################################### + +# Create a settings definition for our dummy algorithm +DUMMY_ALGO_SETTINGS = SettingsDefinition({'fit_setting' : 'Setting description'}) + +################################################################################################### +# Algorithm Object +# ~~~~~~~~~~~~~~~~ +# +# Now we can define our custom fitting algorithm. To do so, we will create a custom object +# that inherits from the specparam :class:`~specparam.algorithms.algorithm.Algorithm` object. +# +# Implementing a custom fit object requires following several standards for specparam +# to be able to use it: +# +# - the class should inherit from the specparam Algorithm object +# - the object needs to accept `modes`, `data`, `results`, and `debug` input arguments +# - at initialization, the object should initialize the Algorithm object ('super()'), +# including providing a name and description, passing in the algorithm settings +# object (from above), and passing in the 'modes', 'data', 'results', and 'debug' inputs +# - the object needs to define a `_fit` function that serves as the main fit function +# +# In the following code, we initialize a custom object following the above to create a fit +# algorithm object. Note that as a dummy algorithm, the 'fit' aspect doesn't actually +# implement a step-by-step fitting procedure, but simply instantiates a pre-specified +# model (to mimic the outputs of a fit algorithm). +# + +################################################################################################### + +import numpy as np + +class DummyAlgorithm(Algorithm): + """Dummy object to mimic a fit algorithm.""" + + def __init__(self, modes=None, data=None, results=None, debug=False): + """Initialize DummyAlgorithm instance.""" + + # Initialize base algorithm object with algorithm metadata + super().__init__( + name='dummy_fit_algo', + description='Dummy fit algorithm.', + public_settings=DUMMY_ALGO_SETTINGS, + modes=modes, data=data, results=results, debug=debug) + + def _fit(self): + """Define the full fitting algorithm.""" + + self.results.params.aperiodic.add_params('fit', np.array([0, 1])) + self.results.params.periodic.add_params('fit', np.array([10, 0.5, 2], ndmin=2)) + self.results._regenerate_model(self.data.freqs) + +################################################################################################### +# Expected outcomes of algorithm fitting +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# In order for a custom fitting algorithm to work properly when embedded within a model object, +# there are some expectations for what the fitting process should do. +# +# The following elements are expected to computed through the fitting procedure: +# +# - Parameter results should be added for each parameter +# - `model.results.params.{component}.add_params(...)` +# +# - The model should be computed and added to the object +# - `model.results.model.modeled_spectrum` should be populated, as well as model +# components (`model.results.model._ap_fit` & `model.results.model._peak_fit`) +# +# If the above you do the above, the model object can be used as normal, and you can do +# (fit / print_results / plot / report / as well as save and load results). +# +# There are also some additional procedures / outputs that a custom fit process may do: +# +# - Update fit parameters to also have converted versions +# + +################################################################################################### + +# Initialize a model object, passing in our custom dummy algorithm +fm = SpectralModel(algorithm=DummyAlgorithm) + +################################################################################################### + +# Fit and report model, using our custom algorithm +fm.report(freqs, powers) + +################################################################################################### +# +# In this case, with our dummy algorithm, we cheated a bit - the model was pre-specified to +# initialize a model that happened to match the simulated data, and no real fitting took place. +# +# The point of this example is to show the outline of how a custom fit algorithm can be developed, +# since the `_fit` method can implement any arbitrarily defined procedure to fit a model, +# + +################################################################################################### +# Example with Custom Fitting +# --------------------------- +# +# Having sketched out the basic outline with the dummy algorithm above, lets now define a custom +# fit algorithm that actually does some fitting. +# +# For simplicity, this algorithm will be a simple fit that starts with an aperiodic fit, and +# then fits a single peak to the flattened (aperiodic removed) spectrum. To do so, it will +# take in an algorithm setting that defines a guess center-frequency for this peak. +# + +################################################################################################### + +# Define the algorithm settings for our custom fit +CUSTOM_ALGO_SETTINGS = SettingsDefinition(\ + {'guess_cf' : 'Initial guess center frequency for peak.'}) + +################################################################################################### +# +# Now we need to define our fit approach! To do so, we will mimic the approach we used above +# to define a custom algorithm object, this time making the `_fit` method implement an actual +# fitting procedure. Note that while the `_fit` function should be the main method that runs +# the fitting process, it can also call additional methods. In this implementation, we define +# additional fit methods to fit each component. +# +# To fit the data components, we will use the `curve_fit` function from scipy. +# + +################################################################################################### + +from scipy.optimize import curve_fit + +class CustomAlgorithm(Algorithm): + """Custom fitting algorithm.""" + + def __init__(self, guess_cf, modes=None, data=None, results=None, debug=False): + """Initialize DummyAlgorithm instance.""" + + # Initialize base algorithm object with algorithm metadata + super().__init__( + name='custom_fit_algo', + description='Example custom algorithm.', + public_settings=CUSTOM_ALGO_SETTINGS, + modes=modes, data=data, results=results, debug=debug) + + ## Public settings + self.settings.guess_cf = guess_cf + + def _fit(self): + """Define the full fitting algorithm.""" + + # Fit each individual component + self._fit_aperiodic() + self._fit_peak() + + # Create full model from the individual components + self.results.model.modeled_spectrum = \ + self.results.model._peak_fit + self.results.model._ap_fit + + def _fit_aperiodic(self): + """Fit aperiodic - direct fit to full spectrum.""" + + # Fit aperiodic component directly to data & collect parameter results + ap_params, _ = curve_fit(\ + self.modes.aperiodic.func, self.data.freqs, self.data.power_spectrum, + p0=np.array([0] * self.modes.aperiodic.n_params)) + self.results.params.aperiodic.add_params('fit', ap_params) + + # Construct & collect aperiodic component + self.results.model._ap_fit = self.modes.aperiodic.func(freqs, *ap_params) + + def _fit_peak(self): + """Fit peak - single peak, with initial guess CF, to flattened spectrum.""" + + # Fit peak + self.results.model._spectrum_flat = self.data.power_spectrum - self.results.model._ap_fit + pe_params, _ = curve_fit(\ + self.modes.periodic.func, self.data.freqs, self.results.model._spectrum_flat, + p0=np.array([self.settings.guess_cf] + [1] * (self.modes.periodic.n_params - 1))) + self.results.params.periodic.add_params('fit', np.atleast_2d(pe_params)) + + # Construct periodic component + self.results.model._peak_fit = self.modes.periodic.func(freqs, *pe_params) + +################################################################################################### + +# Initialize a model object, passing in a custom fit algorithm and settings for this algorithm +fm = SpectralModel(algorithm=CustomAlgorithm, guess_cf=10) + +################################################################################################### + +# Fit model with custom algorithm and report results +fm.report(freqs, powers) + +################################################################################################### +# +# In the above we fit a model with our custom fit algorithm, and can see the results. +# + +################################################################################################### +# Notes on Defining Custom Algorithms +# ----------------------------------- +# +# In these examples, we have made quite simple algorithms. This may be a desired use case - +# creating bespoke fit approaches for specific kinds of data. +# +# In cases where generalizability is more desired, the fit algorithm is likely going to need to +# be significantly more detailed to address +# +# To see, for example, the details of the original / default fit algorithm, check the +# definition of the `spectral_fit` algorithm in the codebase, which is also defined in the same +# way as here. +# +# Additional notes to consider when creating custom algorithms: +# +# - In the above, we didn't consider different fit modes, and used the defaults. Depending on +# your use case, the fit algorithm may or not want to make assumptions about the fit modes. +# To make it generalize, the algorithm needs to be written in a way that is flexible for +# applying different fit functions that may have different numbers of parameters +# - As well as the public settings we defined here, you may want to additional specify +# a set of private settings (additional settings that are defined for the algorithm, which +# are not expected to be changed in most use cases, but which can be accessed) +# + +################################################################################################### +# Algorithms that use curve_fit +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# A common approach for fitting functions to data is to use the scipy `curve_fit` +# function to estimate parameters to fit a specified function to some data, as we did in +# an above example. +# +# When doing so, you may also want to manage and allow inputs for settings that to the +# curve_fit function to manage the fitting process. As a shortcut for this case, you can use the +# :class:`~specparam.algorithms.algorithm.AlgorithmCF` object which pre-initializes a set +# of curve_fit settings. +# +# In addition, when using `curve_fit` you are likely going to want to +# From 30c892677245b8488a465b505daf783e90536545 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 01:11:46 +0000 Subject: [PATCH 02/12] make print settings note algo --- specparam/reports/strings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 4af46ff1..cc93cc14 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -114,7 +114,7 @@ def gen_version_str(concise=False): # Header '=', '', - 'specparam - VERSION', + 'specparam - CODE VERSION', '', # Version information @@ -164,7 +164,7 @@ def gen_modes_str(model, description=False, concise=False): # Header '=', '', - 'specparam - MODES', + 'FIT MODES', '', # Settings - include descriptions if requested @@ -205,7 +205,8 @@ def gen_settings_str(model, description=False, concise=False): str_lst = [ '=', '', - 'specparam - SETTINGS', + 'ALGORITHM SETTINGS', + '(algorithm: {})'.format(model.algorithm.name), '', ] @@ -248,7 +249,7 @@ def gen_freq_range_str(model, concise=False): # Header '=', '', - 'specparam - FIT RANGE', + 'FIT RANGE', '', # Frequency range information information @@ -284,7 +285,7 @@ def gen_methods_report_str(concise=False): # Header '=', '', - 'specparam - REPORTING', + 'REPORTING', '', # Methods report information From 02427b07c69219ca930175de025cab889eca5ae2 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 01:18:35 +0000 Subject: [PATCH 03/12] update modes printing --- specparam/models/base.py | 4 ++-- specparam/modes/modes.py | 15 +++++++++++++++ specparam/reports/strings.py | 10 +++++----- specparam/tests/reports/test_strings.py | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/specparam/models/base.py b/specparam/models/base.py index 07497f15..326307b7 100644 --- a/specparam/models/base.py +++ b/specparam/models/base.py @@ -6,7 +6,7 @@ from specparam.utils.checks import check_array_dim from specparam.modes.modes import Modes from specparam.modutils.errors import NoDataError -from specparam.reports.strings import gen_modes_str, gen_settings_str, gen_issue_str +from specparam.reports.strings import gen_settings_str, gen_issue_str ################################################################################################### ################################################################################################### @@ -124,7 +124,7 @@ def print_modes(self, description=False, concise=False): Whether to print the report in a concise mode, or not. """ - print(gen_modes_str(self, description, concise)) + self.modes.print() def print_settings(self, description=False, concise=False): diff --git a/specparam/modes/modes.py b/specparam/modes/modes.py index e107984f..bb714459 100644 --- a/specparam/modes/modes.py +++ b/specparam/modes/modes.py @@ -3,6 +3,7 @@ from specparam.data import ModelModes from specparam.modes.mode import VALID_COMPONENTS from specparam.modes.definitions import check_mode_definition, AP_MODES, PE_MODES +from specparam.reports.strings import gen_modes_str ################################################################################################### ################################################################################################### @@ -49,3 +50,17 @@ def get_modes(self): return ModelModes(aperiodic_mode=self.aperiodic.name if self.aperiodic else None, periodic_mode=self.periodic.name if self.periodic else None) + + + def print(self, description=False, concise=False): + """Print out the current fit modes. + + Parameters + ---------- + description : bool, optional, default: False + Whether to print out a description with current fit modes. + concise : bool, optional, default: False + Whether to print the report in a concise mode, or not. + """ + + print(gen_modes_str(self, description, concise)) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index cc93cc14..9ee3c0f6 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -131,13 +131,13 @@ def gen_version_str(concise=False): return output -def gen_modes_str(model, description=False, concise=False): +def gen_modes_str(modes, description=False, concise=False): """Generate a string representation of fit modes. Parameters ---------- - model : SpectralModel or Spectral*Model or ModelModes - Object to access fit modes from. + modes : Modes + Modes definition. description : bool, optional, default: False Whether to also print out a description of the fit modes. concise : bool, optional, default: False @@ -168,9 +168,9 @@ def gen_modes_str(model, description=False, concise=False): '', # Settings - include descriptions if requested - *[el for el in ['Periodic Mode : {}'.format(model.modes.periodic.name), + *[el for el in ['Periodic Mode : {}'.format(modes.periodic.name), '{}'.format(desc['aperiodic_mode']), - 'Aperiodic Mode : {}'.format(model.modes.aperiodic.name), + 'Aperiodic Mode : {}'.format(modes.aperiodic.name), '{}'.format(desc['aperiodic_mode'])] if el != ''], # Footer diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index e1496cea..befb61fe 100644 --- a/specparam/tests/reports/test_strings.py +++ b/specparam/tests/reports/test_strings.py @@ -16,7 +16,7 @@ def test_gen_version_str(): def test_gen_modes_str(tfm): - assert gen_modes_str(tfm) + assert gen_modes_str(tfm.modes) def test_gen_settings_str(tfm): From 1321b5536e01d6d039db54b59a492218470b369d Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 11:11:56 +0000 Subject: [PATCH 04/12] update pring for algorithm --- specparam/algorithms/algorithm.py | 15 +++++++++++++++ specparam/models/base.py | 4 ++-- specparam/reports/methods.py | 2 +- specparam/reports/save.py | 8 ++++---- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/specparam/algorithms/algorithm.py b/specparam/algorithms/algorithm.py index 16d6e302..e2e42a48 100644 --- a/specparam/algorithms/algorithm.py +++ b/specparam/algorithms/algorithm.py @@ -5,6 +5,7 @@ from specparam.utils.checks import check_input_options from specparam.algorithms.settings import SettingsDefinition, SettingsValues from specparam.modutils.docs import docs_get_section, replace_docstring_sections +from specparam.reports.strings import gen_settings_str ################################################################################################### ################################################################################################### @@ -118,6 +119,20 @@ def set_debug(self, debug): self._debug = debug + def print(self, description=False, concise=False): + """Print out the algorithm name and fit settings. + + Parameters + ---------- + description : bool, optional, default: False + Whether to print out a description with current settings. + concise : bool, optional, default: False + Whether to print the report in a concise mode, or not. + """ + + print(gen_settings_str(self, description, concise)) + + def _reset_subobjects(self, modes=None, data=None, results=None): """Reset links to sub-objects (mode / data / results). diff --git a/specparam/models/base.py b/specparam/models/base.py index 326307b7..a78b2699 100644 --- a/specparam/models/base.py +++ b/specparam/models/base.py @@ -6,7 +6,7 @@ from specparam.utils.checks import check_array_dim from specparam.modes.modes import Modes from specparam.modutils.errors import NoDataError -from specparam.reports.strings import gen_settings_str, gen_issue_str +from specparam.reports.strings import gen_issue_str ################################################################################################### ################################################################################################### @@ -138,7 +138,7 @@ def print_settings(self, description=False, concise=False): Whether to print the report in a concise mode, or not. """ - print(gen_settings_str(self, description, concise)) + self.algorithm.print() @staticmethod diff --git a/specparam/reports/methods.py b/specparam/reports/methods.py index 82b4381c..03fb1565 100644 --- a/specparam/reports/methods.py +++ b/specparam/reports/methods.py @@ -26,7 +26,7 @@ def methods_report_info(model_obj=None, concise=False): if model_obj: print(gen_version_str(concise)) - print(gen_settings_str(model_obj, concise=concise)) + print(gen_settings_str(model_obj.algorithm, concise=concise)) print(gen_freq_range_str(model_obj, concise=concise)) diff --git a/specparam/reports/save.py b/specparam/reports/save.py index a382796a..41a919b7 100644 --- a/specparam/reports/save.py +++ b/specparam/reports/save.py @@ -57,7 +57,7 @@ def save_model_report(model, file_name, file_path=None, add_settings=True, **plo # Third - model settings if add_settings: - plot_text(gen_settings_str(model, False), 0.5, 0.1, ax=plt.subplot(grid[2])) + plot_text(gen_settings_str(model.algorithm, False), 0.5, 0.1, ax=plt.subplot(grid[2])) # Save out the report plt.savefig(create_file_path(file_name, file_path, SAVE_FORMAT)) @@ -107,7 +107,7 @@ def save_group_report(group, file_name, file_path=None, add_settings=True): # Third - Model settings if add_settings: - plot_text(gen_settings_str(group, False), 0.5, 0.1, ax=plt.subplot(grid[3, :])) + plot_text(gen_settings_str(group.algorithm, False), 0.5, 0.1, ax=plt.subplot(grid[3, :])) # Save out the report plt.savefig(create_file_path(file_name, file_path, SAVE_FORMAT)) @@ -146,7 +146,7 @@ def save_time_report(time, file_name, file_path=None, add_settings=True): # Third - Model settings if add_settings: - plot_text(gen_settings_str(time, False), 0.5, 0.1, ax=axes[-1]) + plot_text(gen_settings_str(time.algorithm, False), 0.5, 0.1, ax=axes[-1]) # Save out the report plt.savefig(create_file_path(file_name, file_path, SAVE_FORMAT)) @@ -187,7 +187,7 @@ def save_event_report(event, file_name, file_path=None, add_settings=True): # Third - Model settings if add_settings: - plot_text(gen_settings_str(event, False), 0.5, 0.1, ax=axes[-1]) + plot_text(gen_settings_str(event.algorithm, False), 0.5, 0.1, ax=axes[-1]) # Save out the report plt.savefig(create_file_path(file_name, file_path, SAVE_FORMAT)) From e895265a5349b0af570d46eb129598a785456443 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 11:14:08 +0000 Subject: [PATCH 05/12] add print_metrics --- specparam/metrics/metrics.py | 15 +++++++ specparam/reports/strings.py | 55 +++++++++++++++++++++---- specparam/tests/reports/test_strings.py | 6 ++- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/specparam/metrics/metrics.py b/specparam/metrics/metrics.py index 14c43b3d..8c2c0f73 100644 --- a/specparam/metrics/metrics.py +++ b/specparam/metrics/metrics.py @@ -6,6 +6,7 @@ from specparam.metrics.metric import Metric from specparam.metrics.definitions import METRICS, check_metric_definition +from specparam.reports.strings import gen_metrics_str ################################################################################################### ################################################################################################### @@ -177,6 +178,20 @@ def add_results(self, results): self[key].result = value + def print(self, description=False, concise=False): + """Print out the current metrics. + + Parameters + ---------- + description : bool, optional, default: False + Whether to print out a description with current metrics. + concise : bool, optional, default: False + Whether to print the report in a concise mode, or not. + """ + + print(gen_metrics_str(self, description, concise)) + + def reset(self): """Reset all metric results.""" diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 9ee3c0f6..811e202c 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -183,13 +183,13 @@ def gen_modes_str(modes, description=False, concise=False): return output -def gen_settings_str(model, description=False, concise=False): - """Generate a string representation of current fit settings. +def gen_settings_str(algorithm, description=False, concise=False): + """Generate a string representation of algorithm and fit settings. Parameters ---------- - model : SpectralModel or Spectral*Model or ModelSettings - Object to access settings from. + algorithm : Algorithm + Algorithm object. description : bool, optional, default: False Whether to also print out a description of the settings. concise : bool, optional, default: False @@ -206,15 +206,15 @@ def gen_settings_str(model, description=False, concise=False): '=', '', 'ALGORITHM SETTINGS', - '(algorithm: {})'.format(model.algorithm.name), + '(algorithm: {})'.format(algorithm.name), '', ] # Loop through algorithm settings, and add information - for name in model.algorithm.settings.names: - str_lst.append(name + ' : ' + str(getattr(model.algorithm.settings, name))) + for name in algorithm.settings.names: + str_lst.append(name + ' : ' + str(getattr(algorithm.settings, name))) if description: - str_lst.append(model.algorithm.public_settings.descriptions[name].split('\n ')[0]) + str_lst.append(algorithm.public_settings.descriptions[name].split('\n ')[0]) # Add footer to string str_lst.extend([ @@ -227,6 +227,45 @@ def gen_settings_str(model, description=False, concise=False): return output +def gen_metrics_str(metrics, description=False, concise=False): + """Generate a string representation of a set of metrics. + + Parameters + ---------- + metrics : Metrics + Metrics object. + description : bool, optional, default: False + Whether to also print out a description of the settings. + concise : bool, optional, default: False + Whether to print the report in concise mode. + + Returns + ------- + output : str + Formatted string of metrics. + """ + + if description: + prints = [(metric.label, metric.description) for metric in fm.results.metrics.metrics] + prints = list(chain(*prints)) + else: + prints = [metric.label for metric in metrics.metrics] + + str_lst = [ + '=', + '', + 'CURRENT METRICS', + '', + *[el for el in prints], + '', + '=' + ] + + output = _format(str_lst, concise) + + return output + + def gen_freq_range_str(model, concise=False): """Generate a string representation of the fit range that was used for the model. diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index befb61fe..ae98ea20 100644 --- a/specparam/tests/reports/test_strings.py +++ b/specparam/tests/reports/test_strings.py @@ -20,7 +20,11 @@ def test_gen_modes_str(tfm): def test_gen_settings_str(tfm): - assert gen_settings_str(tfm) + assert gen_settings_str(tfm.algorithm) + +def test_gen_metrics_str(tfm): + + assert gen_metrics_str(tfm.results.metrics) def test_gen_freq_range_str(tfm): From f9fbe244ee77f9d9b1fc15aa09dd51098e325961 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Tue, 11 Nov 2025 11:19:12 +0000 Subject: [PATCH 06/12] fix print metrics desc --- specparam/reports/strings.py | 4 +++- specparam/tests/reports/test_strings.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 811e202c..f941b1da 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -1,5 +1,7 @@ """Formatted strings for printing out model and fit related information.""" +from itertools import chain + import numpy as np from specparam.utils.array import compute_arr_desc @@ -246,7 +248,7 @@ def gen_metrics_str(metrics, description=False, concise=False): """ if description: - prints = [(metric.label, metric.description) for metric in fm.results.metrics.metrics] + prints = [(metric.label, metric.description) for metric in metrics.metrics] prints = list(chain(*prints)) else: prints = [metric.label for metric in metrics.metrics] diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index ae98ea20..81765443 100644 --- a/specparam/tests/reports/test_strings.py +++ b/specparam/tests/reports/test_strings.py @@ -17,14 +17,17 @@ def test_gen_version_str(): def test_gen_modes_str(tfm): assert gen_modes_str(tfm.modes) + assert gen_modes_str(tfm.modes, True) def test_gen_settings_str(tfm): assert gen_settings_str(tfm.algorithm) + assert gen_settings_str(tfm.algorithm, True) def test_gen_metrics_str(tfm): assert gen_metrics_str(tfm.results.metrics) + assert gen_metrics_str(tfm.results.metrics, True) def test_gen_freq_range_str(tfm): From 10edb671555e642112a5a4dc405a5c16e894f273 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 00:13:12 +0000 Subject: [PATCH 07/12] add note to examples about other model objects --- examples/customize/plot_custom_algorithms.py | 9 +++++++++ examples/customize/plot_custom_metrics.py | 12 ++++++++++++ examples/customize/plot_custom_modes.py | 10 +++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/examples/customize/plot_custom_algorithms.py b/examples/customize/plot_custom_algorithms.py index 5c10bbd1..2ad588ac 100644 --- a/examples/customize/plot_custom_algorithms.py +++ b/examples/customize/plot_custom_algorithms.py @@ -140,6 +140,15 @@ def _fit(self): # - Update fit parameters to also have converted versions # +################################################################################################### +# +# Now that our custom fit algorithm is defined, we can use it by passing it into a model object. +# +# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our +# example, but you can also take the same approach to define custom fit modes with other +# model object (e.g. for groups of spectra or across time). +# + ################################################################################################### # Initialize a model object, passing in our custom dummy algorithm diff --git a/examples/customize/plot_custom_metrics.py b/examples/customize/plot_custom_metrics.py index 1535d29e..0f753a04 100644 --- a/examples/customize/plot_custom_metrics.py +++ b/examples/customize/plot_custom_metrics.py @@ -92,6 +92,18 @@ def compute_total_error(power_spectrum, modeled_spectrum): func=compute_total_error, ) +################################################################################################### +# +# Our custom metric is now defined! +# +# The use this metric, we can initialize a model object and pass in the custom metric +# to use for fitting. +# +# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our +# example, but you can also take the same approach to define custom fit modes with other +# model object (e.g. for groups of spectra or across time). +# + ################################################################################################### # Initialize a spectral model, passing in our custom metric definition diff --git a/examples/customize/plot_custom_modes.py b/examples/customize/plot_custom_modes.py index 5d1204cd..3da89032 100644 --- a/examples/customize/plot_custom_modes.py +++ b/examples/customize/plot_custom_modes.py @@ -156,10 +156,14 @@ def expo_only_function(xs, *params): ################################################################################################### # -# Our custom fit mode if now defined! +# Our custom fit mode is now defined! # -# The use this fit mode, we initialize a model object, passing the fit mode in as the specified -# component mode. +# The use this fit mode, we can initialize a model object and pass in the custom fit mode +# to use for fitting. +# +# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our +# example, but you can also take the same approach to define custom fit modes with other +# model object (e.g. for groups of spectra or across time). # ################################################################################################### From 13a60ca1b4a631d81a280762e5bb404be012889c Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 00:39:06 +0000 Subject: [PATCH 08/12] tweak algo print --- specparam/reports/strings.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index f941b1da..260fc44f 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -116,7 +116,7 @@ def gen_version_str(concise=False): # Header '=', '', - 'specparam - CODE VERSION', + 'CODE VERSION', '', # Version information @@ -206,11 +206,18 @@ def gen_settings_str(algorithm, description=False, concise=False): # Create output string - header str_lst = [ '=', + '', + 'ALGORITHM: {}'.format(algorithm.name), + ] + + if description: + str_lst.append(algorithm.description) + + str_lst.extend([ '', 'ALGORITHM SETTINGS', - '(algorithm: {})'.format(algorithm.name), '', - ] + ]) # Loop through algorithm settings, and add information for name in algorithm.settings.names: @@ -334,7 +341,7 @@ def gen_methods_report_str(concise=False): '', '- the code version that was used', '- the fit modes that were used', - '- the algorithm settings that were used', + '- the algorithm & settings that were used', '- the frequency range that was fit', # Footer From b2780615021f965baa2bd7c7dd651a03cb5ef8cd Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 00:43:27 +0000 Subject: [PATCH 09/12] remove print_modes from base object --- specparam/models/base.py | 14 -------------- specparam/tests/models/test_model.py | 3 +-- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/specparam/models/base.py b/specparam/models/base.py index a78b2699..d81ca4ad 100644 --- a/specparam/models/base.py +++ b/specparam/models/base.py @@ -113,20 +113,6 @@ def get_data(self, component='full', space='log'): return output - def print_modes(self, description=False, concise=False): - """Print out the current fit modes. - - Parameters - ---------- - description : bool, optional, default: False - Whether to print out a description with current fit modes. - concise : bool, optional, default: False - Whether to print the report in a concise mode, or not. - """ - - self.modes.print() - - def print_settings(self, description=False, concise=False): """Print out the current settings. diff --git a/specparam/tests/models/test_model.py b/specparam/tests/models/test_model.py index 97cf6d68..ea0bb5ae 100644 --- a/specparam/tests/models/test_model.py +++ b/specparam/tests/models/test_model.py @@ -286,10 +286,9 @@ def test_get_component(tfm): def test_prints(tfm): """Test methods that print (alias and pass through methods). - Checks: print_modes, print_settings, print_results, print_report_issue. + Checks: print_settings, print_results, print_report_issue. """ - tfm.print_modes() tfm.print_settings() tfm.print_results() tfm.print_report_issue() From e07a4d095672b159927d73ac58bacbedefe429fb Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 00:43:53 +0000 Subject: [PATCH 10/12] add print outs of custom defs --- examples/customize/plot_custom_algorithms.py | 11 +++++-- examples/customize/plot_custom_metrics.py | 32 +++++++++++++++++--- examples/customize/plot_custom_modes.py | 17 +++++++++-- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/examples/customize/plot_custom_algorithms.py b/examples/customize/plot_custom_algorithms.py index 2ad588ac..2276d96f 100644 --- a/examples/customize/plot_custom_algorithms.py +++ b/examples/customize/plot_custom_algorithms.py @@ -144,9 +144,8 @@ def _fit(self): # # Now that our custom fit algorithm is defined, we can use it by passing it into a model object. # -# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our -# example, but you can also take the same approach to define custom fit modes with other -# model object (e.g. for groups of spectra or across time). +# Note that in this example, we will use :class:`~specparam.SpectralModel` for our example, but +# you can also take the same approach to define custom fit algorithms with other model objects. # ################################################################################################### @@ -154,6 +153,9 @@ def _fit(self): # Initialize a model object, passing in our custom dummy algorithm fm = SpectralModel(algorithm=DummyAlgorithm) +# Check the defined fit algorithm +fm.algorithm.print() + ################################################################################################### # Fit and report model, using our custom algorithm @@ -258,6 +260,9 @@ def _fit_peak(self): # Initialize a model object, passing in a custom fit algorithm and settings for this algorithm fm = SpectralModel(algorithm=CustomAlgorithm, guess_cf=10) +# Check the defined fit algorithm +fm.algorithm.print() + ################################################################################################### # Fit model with custom algorithm and report results diff --git a/examples/customize/plot_custom_metrics.py b/examples/customize/plot_custom_metrics.py index 0f753a04..09a9e314 100644 --- a/examples/customize/plot_custom_metrics.py +++ b/examples/customize/plot_custom_metrics.py @@ -87,7 +87,7 @@ def compute_total_error(power_spectrum, modeled_spectrum): # Define Metric for the total error total_error_metric = Metric( category='error', - measure='total_error', + measure='total', description='Total absolute error.', func=compute_total_error, ) @@ -99,9 +99,8 @@ def compute_total_error(power_spectrum, modeled_spectrum): # The use this metric, we can initialize a model object and pass in the custom metric # to use for fitting. # -# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our -# example, but you can also take the same approach to define custom fit modes with other -# model object (e.g. for groups of spectra or across time). +# Note that in this example, we will use :class:`~specparam.SpectralModel` for our example, +# but you can also take the same approach to define custom metrics with other model objects. # ################################################################################################### @@ -109,6 +108,11 @@ def compute_total_error(power_spectrum, modeled_spectrum): # Initialize a spectral model, passing in our custom metric definition fm = SpectralModel(min_peak_height=0.25, metrics=[total_error_metric]) +# Check the defined metrics +fm.results.metrics.print() + +################################################################################################### + # Fit the model and print a report fm.report(freqs, powers) @@ -132,7 +136,7 @@ def compute_total_error(power_spectrum, modeled_spectrum): # Define the information for a custom metric, in a dictionary custom_metric_dict = { 'category' : 'error', - 'measure' : 'total_error', + 'measure' : 'total', 'description' : 'Total absolute error.', 'func' : compute_total_error, } @@ -142,6 +146,9 @@ def compute_total_error(power_spectrum, modeled_spectrum): # Initialize a model object, passing in the custom metric, defined as a dictionary fm = SpectralModel(min_peak_height=0.25, metrics=[custom_metric_dict]) +# Check the defined metrics +fm.results.metrics.print() + ################################################################################################### # # When using custom metrics, you can also access the results using the @@ -170,6 +177,11 @@ def compute_total_error(power_spectrum, modeled_spectrum): # Initialize a spectral model, passing in multiple metrics (both internal and custom) fm = SpectralModel(min_peak_height=0.25, metrics=[total_error_metric, 'gof_rsquared']) +# Check the defined metrics +fm.results.metrics.print() + +################################################################################################### + # Fit the model and print a report fm.report(freqs, powers) @@ -257,6 +269,11 @@ def compute_lowfreq_error(power_spectrum, modeled_spectrum, freqs): # Initialize a spectral model, passing in custom metric with additional arguments fm = SpectralModel(metrics=[lowfreq_error]) +# Check the defined metrics +fm.results.metrics.print() + +################################################################################################### + # Fit the model and print a report fm.report(freqs, powers) @@ -318,6 +335,11 @@ def custom_measure(power_spectrum, modeled_spectrum, freqs, n_params): # Initialize a spectral model, passing in our new custom measure fm = SpectralModel(metrics=[custom_measure]) +# Check the defined metrics of the object +fm.results.metrics.print() + +################################################################################################### + # Fit the model and print a report fm.report(freqs, powers) diff --git a/examples/customize/plot_custom_modes.py b/examples/customize/plot_custom_modes.py index 3da89032..84d96b96 100644 --- a/examples/customize/plot_custom_modes.py +++ b/examples/customize/plot_custom_modes.py @@ -161,9 +161,8 @@ def expo_only_function(xs, *params): # The use this fit mode, we can initialize a model object and pass in the custom fit mode # to use for fitting. # -# Note that in this example, we will use the :class:`~specparam.SpectralModel` object for our -# example, but you can also take the same approach to define custom fit modes with other -# model object (e.g. for groups of spectra or across time). +# Note that in this example, we will use :class:`~specparam.SpectralModel` for our example, +# but you can also take the same approach to define custom fit modes with other model objects. # ################################################################################################### @@ -171,6 +170,9 @@ def expo_only_function(xs, *params): # Initialize model object, passing in custom aperiodic mode definition fm = SpectralModel(aperiodic_mode=expo_only_mode) +# Check the defined modes of the object +fm.modes.print() + ################################################################################################### # # Now we can use the model object to fit some data. @@ -249,6 +251,7 @@ def rectangular_function(xs, *params): ################################################################################################### +# Define the parameter definition for a new periodic fit mode rectangular_params = ParamDefinition(OrderedDict({ 'cf' : 'Center frequency of the rectangle.', 'pw' : 'Power of the rectangle, over and above the aperiodic component.', @@ -286,6 +289,9 @@ def rectangular_function(xs, *params): # Initialize model object, passing in custom periodic mode definition fm = SpectralModel(periodic_mode=rectangular_mode, max_n_peaks=2) +# Check the defined modes of the object +fm.modes.print() + ################################################################################################### # Fit model and report results @@ -324,6 +330,11 @@ def rectangular_function(xs, *params): # Initialize model object, passing in custom aperiodic & periodic mode definitions fm = SpectralModel(aperiodic_mode=expo_only_mode, periodic_mode=rectangular_mode, max_n_peaks=2) +# Check the defined modes of the object +fm.modes.print() + +################################################################################################### + # Fit model and report results fm.report(freqs, powers) From 4bef651efecbde5ab5967d4dcff5eb279e1747de Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 00:44:03 +0000 Subject: [PATCH 11/12] use print funcs in tutorials --- tutorials/plot_03-Algorithm.py | 3 +++ tutorials/plot_04-PeriodicFitting.py | 21 ++++++++++++++++++--- tutorials/plot_05-AperiodicFitting.py | 10 ++++++++++ tutorials/plot_06-Metrics.py | 25 +++++++++++++++++++++++-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tutorials/plot_03-Algorithm.py b/tutorials/plot_03-Algorithm.py index a15ec41e..39916723 100644 --- a/tutorials/plot_03-Algorithm.py +++ b/tutorials/plot_03-Algorithm.py @@ -64,6 +64,9 @@ # These settings will be more fully described later in the tutorials fm = SpectralModel(peak_width_limits=[1, 8], max_n_peaks=6, min_peak_height=0.15) +# Check the defined algorithm & settings +fm.algorithm.print() + ################################################################################################### # # Note that data can be added to a SpectralModel object independent of fitting the model, using diff --git a/tutorials/plot_04-PeriodicFitting.py b/tutorials/plot_04-PeriodicFitting.py index 79220d2a..b55c5dcd 100644 --- a/tutorials/plot_04-PeriodicFitting.py +++ b/tutorials/plot_04-PeriodicFitting.py @@ -68,7 +68,12 @@ # Initialize a model object, explicitly specifying periodic fit to 'gaussian' fm1 = SpectralModel(periodic_mode='gaussian') -# Fit the model +# Check the defined fit modes of the model object +fm1.modes.print() + +################################################################################################### + +# Fit the model and report results fm1.report(freqs, spectrum) ################################################################################################### @@ -160,7 +165,12 @@ # Initialize a model object, explicitly specifying periodic fit to 'gaussian' fm2 = SpectralModel(periodic_mode='skewed_gaussian') -# Fit the model +# Check the defined fit modes of the model object +fm2.modes.print() + +################################################################################################### + +# Fit the model and report results fm2.report(freqs, spectrum) ################################################################################################### @@ -182,7 +192,12 @@ # Initialize a model object, explicitly specifying periodic fit to 'gaussian' fm3 = SpectralModel(periodic_mode='cauchy') -# Fit the model +# Check the defined fit modes of the model object +fm3.modes.print() + +################################################################################################### + +# Fit the model and report results fm3.report(freqs, spectrum) ################################################################################################### diff --git a/tutorials/plot_05-AperiodicFitting.py b/tutorials/plot_05-AperiodicFitting.py index 8cd663a7..fb11e52a 100644 --- a/tutorials/plot_05-AperiodicFitting.py +++ b/tutorials/plot_05-AperiodicFitting.py @@ -48,8 +48,12 @@ ################################################################################################### +# Initialize a model object specifying a specific aperiodic fit mode fm1 = SpectralModel(aperiodic_mode='fixed') +# Check the defined fit modes of the model object +fm1.modes.print() + ################################################################################################### # Relating Exponents to Power Spectrum Slope # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -103,6 +107,9 @@ # Initialize a new spectral model with the knee aperiodic mode fm2 = SpectralModel(aperiodic_mode='knee') +# Check the defined fit modes of the model object +fm2.modes.print() + ################################################################################################### # Mathematical Description of the Aperiodic Component # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -155,6 +162,9 @@ # Initialize a model object, setting the aperiodic mode to use a 'knee' fit fm = SpectralModel(peak_width_limits=[2, 8], aperiodic_mode='knee') +# Check the defined fit modes of the model object +fm.modes.print() + ################################################################################################### # Fit a power spectrum model diff --git a/tutorials/plot_06-Metrics.py b/tutorials/plot_06-Metrics.py index e937f6cc..7d317389 100644 --- a/tutorials/plot_06-Metrics.py +++ b/tutorials/plot_06-Metrics.py @@ -68,8 +68,15 @@ # Define a set of metrics to use metrics1 = ['error_mae', 'gof_rsquared'] -# Initialize model with metric specification & fit model +# Initialize model with metric specification fm1 = SpectralModel(metrics=metrics1) + +# Check the defined metrics from the model object +fm1.results.metrics.print() + +################################################################################################### + +# Fit the model and report results fm1.report(freqs, spectrum) ################################################################################################### @@ -118,8 +125,15 @@ # Define a new set of metrics to use metrics2 = ['error_mse', 'gof_adjrsquared'] -# Initialize model with metric specification & fit model +# Initialize model with metric specification fm2 = SpectralModel(metrics=metrics2) + +# Check the defined metrics from the model object +fm2.results.metrics.print() + +################################################################################################### + +# Fit the model and report results fm2.report(freqs, spectrum) ################################################################################################### @@ -137,6 +151,13 @@ # Initialize model with metric specification & fit model fm3 = SpectralModel(metrics=metrics3) + +# Check the defined metrics from the model object +fm3.results.metrics.print() + +################################################################################################### + +# Fit the model and report results fm3.report(freqs, spectrum) ################################################################################################### From 8257002c9c41b5fb27970a59b6a285bcd51f427f Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Wed, 12 Nov 2025 01:08:21 +0000 Subject: [PATCH 12/12] update report str settings --- specparam/reports/settings.py | 12 +++++ specparam/reports/strings.py | 92 ++++++++++------------------------- 2 files changed, 37 insertions(+), 67 deletions(-) create mode 100644 specparam/reports/settings.py diff --git a/specparam/reports/settings.py b/specparam/reports/settings.py new file mode 100644 index 00000000..7e7b4c4a --- /dev/null +++ b/specparam/reports/settings.py @@ -0,0 +1,12 @@ +"""Report related settings.""" + +################################################################################################### +################################################################################################### + +# Centering values, with long & short options +# Note: Long CV of 98 is so that the max line length plays nice with notebook rendering +LCV = 98 +SCV = 70 + +# Divider +DIVIDER = '=' diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 260fc44f..08af86cf 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -7,15 +7,7 @@ from specparam.utils.array import compute_arr_desc from specparam.measures.properties import compute_presence from specparam.version import __version__ as MODULE_VERSION - -################################################################################################### -################################################################################################### - -## Settings & Globals -# Centering Value - Long & Short options -# Note: Long CV of 98 is so that the max line length plays nice with notebook rendering -LCV = 98 -SCV = 70 +from specparam.reports.settings import LCV, SCV, DIVIDER ################################################################################################### ################################################################################################### @@ -36,8 +28,7 @@ def gen_issue_str(concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'specparam - ISSUE REPORTING', '', @@ -58,8 +49,7 @@ def gen_issue_str(concise=False): 'You can attach the generated files to a Github issue.', '', - # Footer - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -113,18 +103,13 @@ def gen_version_str(concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'CODE VERSION', '', - - # Version information '{}'.format(MODULE_VERSION), - - # Footer '', - '=' + DIVIDER, ] @@ -163,21 +148,17 @@ def gen_modes_str(modes, description=False, concise=False): # Create output string str_lst = [ - # Header - '=', + DIVIDER, '', 'FIT MODES', '', - # Settings - include descriptions if requested *[el for el in ['Periodic Mode : {}'.format(modes.periodic.name), '{}'.format(desc['aperiodic_mode']), 'Aperiodic Mode : {}'.format(modes.aperiodic.name), '{}'.format(desc['aperiodic_mode'])] if el != ''], - - # Footer '', - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -205,7 +186,7 @@ def gen_settings_str(algorithm, description=False, concise=False): # Create output string - header str_lst = [ - '=', + DIVIDER, '', 'ALGORITHM: {}'.format(algorithm.name), ] @@ -225,10 +206,9 @@ def gen_settings_str(algorithm, description=False, concise=False): if description: str_lst.append(algorithm.public_settings.descriptions[name].split('\n ')[0]) - # Add footer to string str_lst.extend([ '', - '=' + DIVIDER, ]) output = _format(str_lst, concise) @@ -261,13 +241,13 @@ def gen_metrics_str(metrics, description=False, concise=False): prints = [metric.label for metric in metrics.metrics] str_lst = [ - '=', + DIVIDER, '', 'CURRENT METRICS', '', *[el for el in prints], '', - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -294,19 +274,13 @@ def gen_freq_range_str(model, concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'FIT RANGE', '', - - # Frequency range information information 'The model was fit from {} to {} Hz.'.format(*freq_range), - - # Footer '', - '=' - + DIVIDER, ] output = _format(str_lst, concise) @@ -330,23 +304,18 @@ def gen_methods_report_str(concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'REPORTING', '', - - # Methods report information 'Reports using spectral parameterization should include (at minimum):', '', '- the code version that was used', '- the fit modes that were used', '- the algorithm & settings that were used', '- the frequency range that was fit', - - # Footer '', - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -422,8 +391,7 @@ def gen_model_results_str(model, concise=False): # Create the formatted strings for printing str_lst = [ - # Header - '=', + DIVIDER, '', 'POWER SPECTRUM MODEL', '', @@ -452,8 +420,7 @@ def gen_model_results_str(model, concise=False): for key, res in model.results.metrics.results.items()], '', - # Footer - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -482,8 +449,7 @@ def gen_group_results_str(group, concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'GROUP SPECTRAL MODEL RESULTS ({} spectra)'.format(len(group.results.group_results)), *_report_str_n_null(group), @@ -514,9 +480,7 @@ def gen_group_results_str(group, concise=False): *compute_arr_desc(group.results.get_metrics(label))) \ for label in group.results.metrics.labels], '', - - # Footer - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -550,8 +514,7 @@ def gen_time_results_str(time, concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'TIME SPECTRAL MODEL RESULTS ({} time windows)'.format(time.data.n_time_windows), *_report_str_n_null(time), @@ -588,8 +551,7 @@ def gen_time_results_str(time, concise=False): for key in time.results.metrics.results], '', - # Footer - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -623,8 +585,7 @@ def gen_event_results_str(event, concise=False): str_lst = [ - # Header - '=', + DIVIDER, '', 'EVENT SPECTRAL MODEL RESULTS ({} events with {} time windows)'.format(\ event.data.n_events, event.data.n_time_windows), @@ -662,10 +623,7 @@ def gen_event_results_str(event, concise=False): *compute_arr_desc(np.mean(event.results.event_time_results[key], 1))) \ for key in event.results.metrics.results], '', - - - # Footer - '=' + DIVIDER, ] output = _format(str_lst, concise) @@ -715,11 +673,11 @@ def _no_model_str(concise=False): """ str_lst = [ - '=', + DIVIDER, '', 'Model fit has not been run, or fitting was unsuccessful.', '', - '=' + DIVIDER, ] output = _format(str_lst, concise)