From fafe5c7b973e81ce3c6caf9f14a4fbb2c0a08d51 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Sat, 22 Nov 2025 23:28:15 +0000 Subject: [PATCH 1/5] consolidate to 1 print function --- specparam/models/base.py | 31 ++++++++++++++-------------- specparam/models/event.py | 11 +++++++--- specparam/models/group.py | 13 ++++++++---- specparam/models/model.py | 13 ++++++++---- specparam/models/time.py | 25 ++++++++++++++-------- specparam/tests/models/test_event.py | 2 +- specparam/tests/models/test_group.py | 3 +-- specparam/tests/models/test_model.py | 10 +++------ specparam/tests/models/test_time.py | 2 +- 9 files changed, 64 insertions(+), 46 deletions(-) diff --git a/specparam/models/base.py b/specparam/models/base.py index d81ca4ad..88d9bc35 100644 --- a/specparam/models/base.py +++ b/specparam/models/base.py @@ -113,31 +113,32 @@ def get_data(self, component='full', space='log'): return output - def print_settings(self, description=False, concise=False): - """Print out the current settings. + def print(self, info, description=False, concise=False): + """Print out requested information. Parameters ---------- + info : {'algorithm', 'settings', 'data', 'modes', 'issue'} + Which information to print: + 'algorithm' or 'settings: print information on the fit algorithm & settings + 'data' : print a description of the data + 'modes' : print a description of the fit modes + 'issue' : print instructions on how to report bugs and/or problematic fits 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. + Whether to print the report in a concise mode. """ - self.algorithm.print() + # Special case - treat 'settings' as request to print algorithm info + if info == 'settings': + info = 'algorithm' + if info in ['algorithm', 'data', 'modes']: + getattr(self, info).print(concise=concise) - @staticmethod - def print_report_issue(concise=False): - """Prints instructions on how to report bugs and/or problematic fits. - - Parameters - ---------- - concise : bool, optional, default: False - Whether to print the report in a concise mode, or not. - """ - - print(gen_issue_str(concise)) + if info == 'issue': + print(gen_issue_str(concise)) def _add_from_dict(self, data): diff --git a/specparam/models/event.py b/specparam/models/event.py index 23a5d745..34d0b1aa 100644 --- a/specparam/models/event.py +++ b/specparam/models/event.py @@ -187,19 +187,24 @@ def report(self, freqs=None, spectrograms=None, freq_range=None, self.fit(freqs, spectrograms, freq_range, bands, n_jobs, progress) self.plot() - self.print_results() + self.print('results') - def print_results(self, concise=False): + def print(self, info='results', concise=False): """Print out SpectralTimeEventModel results. Parameters ---------- + info : {'results', ...} + XX concise : bool, optional, default: False Whether to print the report in a concise mode, or not. """ - print(gen_event_results_str(self, concise)) + if info == 'results': + print(gen_event_results_str(self, concise=concise)) + else: + super().print(info, concise=concise) @copy_doc_func_to_method(plot_event_model) diff --git a/specparam/models/group.py b/specparam/models/group.py index ab617622..bddac2d6 100644 --- a/specparam/models/group.py +++ b/specparam/models/group.py @@ -182,7 +182,7 @@ def report(self, freqs=None, power_spectra=None, freq_range=None, n_jobs=1, self.fit(freqs, power_spectra, freq_range, n_jobs=n_jobs, progress=progress) self.plot(**plot_kwargs) - self.print_results(False) + self.print('results') @copy_doc_func_to_method(plot_group_model) @@ -331,16 +331,21 @@ def save_report(self, file_name, file_path=None, add_settings=True): save_group_report(self, file_name, file_path, add_settings) - def print_results(self, concise=False): - """Print out the group results. + def print(self, info='results', concise=False): + """Print out requested information. Parameters ---------- + info : {'results', ...} + xx concise : bool, optional, default: False Whether to print the report in a concise mode, or not. """ - print(gen_group_results_str(self, concise)) + if info == 'results': + print(gen_group_results_str(self, concise=concise)) + else: + super().print(info, concise=concise) def save_model_report(self, index, file_name, file_path=None, diff --git a/specparam/models/model.py b/specparam/models/model.py index 78875310..4748beee 100644 --- a/specparam/models/model.py +++ b/specparam/models/model.py @@ -225,19 +225,24 @@ def report(self, freqs=None, power_spectrum=None, freq_range=None, plot_full_range else plot_kwargs.pop('plot_power_spectrum', None), freq_range=plot_kwargs.pop('plot_freq_range', None), **plot_kwargs) - self.print_results(concise=False) + self.print('results') - def print_results(self, concise=False): - """Print out model fitting results. + def print(self, info='results', concise=False): + """Print out requested information. Parameters ---------- + info : {'results', ...} + xx concise : bool, optional, default: False Whether to print the report in a concise mode, or not. """ - print(gen_model_results_str(self, concise)) + if info == 'results': + print(gen_model_results_str(self, concise)) + else: + super().print(info, concise=concise) @copy_doc_func_to_method(plot_model) diff --git a/specparam/models/time.py b/specparam/models/time.py index 3c7e30d6..20b839d0 100644 --- a/specparam/models/time.py +++ b/specparam/models/time.py @@ -119,6 +119,8 @@ def report(self, freqs=None, spectrogram=None, freq_range=None, bands : Bands or dict or int, optional How to organize peaks into bands. If Bands or dict, uses band definitions. If int, extracts the first 'n' peaks. + TODOXX:results_format : + xx n_jobs : int, optional, default: 1 Number of jobs to run in parallel. 1 is no parallelization. -1 uses all available cores. @@ -132,24 +134,29 @@ def report(self, freqs=None, spectrogram=None, freq_range=None, self.fit(freqs, spectrogram, freq_range, bands, n_jobs=n_jobs, progress=progress) self.plot(report_type) - self.print_results(report_type) + self.print('results', report_type) - def print_results(self, print_type='time', concise=False): - """Print out SpectralTimeModel results. + def print(self, info='results', report_type='time', concise=False): + """Print out requested information. Parameters ---------- - print_type : {'time', 'group'} - Which format to print results out in. + info : {'results', ...} + xxx + report_type : {'time', 'group'} + Which report type to print results in. Only used if 'info' is 'results'. concise : bool, optional, default: False Whether to print the report in a concise mode, or not. """ - if print_type == 'time': - print(gen_time_results_str(self, concise)) - if print_type == 'group': - super().print_results(concise) + if info == 'results': + if report_type == 'time': + print(gen_time_results_str(self, concise=concise)) + if report_type == 'group': + super().print('results', concise=concise) + else: + super().print(info, concise=concise) @copy_doc_func_to_method(plot_time_model) diff --git a/specparam/tests/models/test_event.py b/specparam/tests/models/test_event.py index e5583b69..c3f1bedf 100644 --- a/specparam/tests/models/test_event.py +++ b/specparam/tests/models/test_event.py @@ -78,7 +78,7 @@ def test_event_fit_par(): def test_event_print(tfe): - tfe.print_results() + tfe.print('results') @plot_test def test_event_plot(tfe, skip_if_no_mpl): diff --git a/specparam/tests/models/test_group.py b/specparam/tests/models/test_group.py index 2b72e875..06b0bce5 100644 --- a/specparam/tests/models/test_group.py +++ b/specparam/tests/models/test_group.py @@ -235,8 +235,7 @@ def test_fit_par(): def test_print(tfg): """Check print method (alias).""" - tfg.print_results() - assert True + tfg.print('results') def test_save_model_report(tfg, skip_if_no_mpl): diff --git a/specparam/tests/models/test_model.py b/specparam/tests/models/test_model.py index 82e39fd4..ba90f341 100644 --- a/specparam/tests/models/test_model.py +++ b/specparam/tests/models/test_model.py @@ -284,14 +284,10 @@ def test_get_component(tfm): assert isinstance(tfm.results.model.get_component(comp, space), np.ndarray) def test_prints(tfm): - """Test methods that print (alias and pass through methods). + """Test printing method.""" - Checks: print_settings, print_results, print_report_issue. - """ - - tfm.print_settings() - tfm.print_results() - tfm.print_report_issue() + for info in ['results', 'algorithm', 'data', 'modes', 'settings', 'issue']: + tfm.print(info) @plot_test def test_plot(tfm, skip_if_no_mpl): diff --git a/specparam/tests/models/test_time.py b/specparam/tests/models/test_time.py index 14d10bd7..bdb70742 100644 --- a/specparam/tests/models/test_time.py +++ b/specparam/tests/models/test_time.py @@ -62,7 +62,7 @@ def test_time_fit(): def test_time_print(tft): - tft.print_results() + tft.print('results') @plot_test def test_time_plot(tft, skip_if_no_mpl): From ade545f65845b17543f9134322014b913ebe7525 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Sun, 23 Nov 2025 00:27:18 +0000 Subject: [PATCH 2/5] add list_insert util --- specparam/tests/utils/test_select.py | 12 +++++++++++- specparam/utils/select.py | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/specparam/tests/utils/test_select.py b/specparam/tests/utils/test_select.py index 1b33a8d3..6380ee3e 100644 --- a/specparam/tests/utils/test_select.py +++ b/specparam/tests/utils/test_select.py @@ -58,4 +58,14 @@ def test_find_first_ind(): assert find_first_ind(l1, 'word') == 1 assert find_first_ind(l1, 'lion') == 2 assert find_first_ind(l1, 'again') == 3 - assert find_first_ind(l1, 'not') is None \ No newline at end of file + assert find_first_ind(l1, 'not') is None + +def test_list_insert(): + + lsta = [1, 3, 4] + outa = list_insert(lsta, 2, 1) + assert outa == [1, 2, 3, 4] + + lstb = [1, 4] + outb = list_insert(lstb, [2, 3], 1) + assert outb == [1, 2, 3, 4] diff --git a/specparam/utils/select.py b/specparam/utils/select.py index 2c54a774..4b0d8bf3 100644 --- a/specparam/utils/select.py +++ b/specparam/utils/select.py @@ -116,3 +116,30 @@ def find_first_ind(options, search): break return oind + + +def list_insert(lst, inserts, ind): + """Insert new elements to a specified index of a list. + + Parameters + ---------- + lst : list + List to insert elements into. + inserts : list + Additional elements to insert in lst. + ind : int + Starting index to add the new elements + + Returns + ------- + lst : list + Updated list, with inserted elements. + """ + + if isinstance(inserts, list): + for el in reversed(inserts): + lst.insert(ind, el) + else: + lst.insert(ind, inserts) + + return lst From 57c8c903dfb82aa57bf9ee6246a8c014fd078a7a Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Sun, 23 Nov 2025 00:31:08 +0000 Subject: [PATCH 3/5] tweak str formats --- specparam/reports/strings.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 6e1f5239..0c68e25d 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -4,6 +4,7 @@ import numpy as np +from specparam.utils.select import list_insert from specparam.utils.array import compute_arr_desc from specparam.measures.properties import compute_presence from specparam.version import __version__ as MODULE_VERSION @@ -30,7 +31,7 @@ def gen_issue_str(concise=False): DIVIDER, '', - 'specparam - ISSUE REPORTING', + 'ISSUE REPORTING', '', # Reporting bugs @@ -110,7 +111,6 @@ def gen_version_str(concise=False): '{}'.format(MODULE_VERSION), '', DIVIDER, - ] output = _format(str_lst, concise) @@ -135,10 +135,13 @@ def gen_data_str(data, concise=False): Formatted string of data summary. """ + str_lst = [DIVIDER, '', 'DATA INFORMATION', '', '', DIVIDER] + add_ind = 4 + if not data.has_data: no_data_str = "No data currently loaded in the object." - str_lst = [DIVIDER,'', no_data_str, '', DIVIDER] + str_lst = list_insert(str_lst, no_data_str, add_ind) else: @@ -152,17 +155,14 @@ def gen_data_str(data, concise=False): else: n_spectra_str = '1 power spectrum' - str_lst = [ - - DIVIDER, - '', + str_lst_add = [ 'The data object contains {}'.format(n_spectra_str), 'with a frequency range of {} Hz'.format(data.freq_range), 'and a frequency resolution of {} Hz.'.format(data.freq_res), - '', - DIVIDER, ] + str_lst = list_insert(str_lst, str_lst_add, add_ind) + output = _format(str_lst, concise) return output @@ -238,7 +238,8 @@ def gen_settings_str(algorithm, description=False, concise=False): str_lst = [ DIVIDER, '', - 'ALGORITHM: {}'.format(algorithm.name), + 'ALGORITHM', + algorithm.name, ] if description: @@ -247,7 +248,6 @@ def gen_settings_str(algorithm, description=False, concise=False): str_lst.extend([ '', 'ALGORITHM SETTINGS', - '', ]) # Loop through algorithm settings, and add information @@ -453,7 +453,7 @@ def gen_model_results_str(model, concise=False): DIVIDER, '', - 'POWER SPECTRUM MODEL', + 'SPECTRUM MODEL RESULTS', '', # Fit algorithm & data overview From 1c1390e893228c2ce279b9ef0b2ef6790416beb6 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Sun, 23 Nov 2025 00:52:06 +0000 Subject: [PATCH 4/5] update _format for strings --- specparam/reports/strings.py | 73 +++---------------------- specparam/tests/reports/test_strings.py | 2 +- 2 files changed, 8 insertions(+), 67 deletions(-) diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 0c68e25d..832b3ddb 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -28,9 +28,6 @@ def gen_issue_str(concise=False): """ str_lst = [ - - DIVIDER, - '', 'ISSUE REPORTING', '', @@ -48,9 +45,6 @@ def gen_issue_str(concise=False): "model.save('bad_fit_data', True, True, True)", '', 'You can attach the generated files to a Github issue.', - '', - - DIVIDER, ] output = _format(str_lst, concise) @@ -103,14 +97,9 @@ def gen_version_str(concise=False): """ str_lst = [ - - DIVIDER, - '', 'CODE VERSION', '', '{}'.format(MODULE_VERSION), - '', - DIVIDER, ] output = _format(str_lst, concise) @@ -135,13 +124,12 @@ def gen_data_str(data, concise=False): Formatted string of data summary. """ - str_lst = [DIVIDER, '', 'DATA INFORMATION', '', '', DIVIDER] - add_ind = 4 + str_lst = ['DATA INFORMATION', ''] if not data.has_data: no_data_str = "No data currently loaded in the object." - str_lst = list_insert(str_lst, no_data_str, add_ind) + str_lst.append(no_data_str) else: @@ -161,7 +149,7 @@ def gen_data_str(data, concise=False): 'and a frequency resolution of {} Hz.'.format(data.freq_res), ] - str_lst = list_insert(str_lst, str_lst_add, add_ind) + str_lst.extend(str_lst_add) output = _format(str_lst, concise) @@ -197,9 +185,6 @@ def gen_modes_str(modes, description=False, concise=False): # Create output string str_lst = [ - - DIVIDER, - '', 'FIT MODES', '', # Settings - include descriptions if requested @@ -207,8 +192,6 @@ def gen_modes_str(modes, description=False, concise=False): '{}'.format(desc['aperiodic_mode']), 'Aperiodic Mode : {}'.format(modes.aperiodic.name), '{}'.format(desc['aperiodic_mode'])] if el != ''], - '', - DIVIDER, ] output = _format(str_lst, concise) @@ -236,8 +219,6 @@ def gen_settings_str(algorithm, description=False, concise=False): # Create output string - header str_lst = [ - DIVIDER, - '', 'ALGORITHM', algorithm.name, ] @@ -256,11 +237,6 @@ def gen_settings_str(algorithm, description=False, concise=False): if description: str_lst.append(algorithm.public_settings.descriptions[name].split('\n ')[0]) - str_lst.extend([ - '', - DIVIDER, - ]) - output = _format(str_lst, concise) return output @@ -291,13 +267,9 @@ 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) @@ -323,14 +295,9 @@ def gen_freq_range_str(model, concise=False): freq_range = model.data.freq_range if model.data.has_data else ('XX', 'XX') str_lst = [ - - DIVIDER, - '', 'FIT RANGE', '', 'The model was fit from {} to {} Hz.'.format(*freq_range), - '', - DIVIDER, ] output = _format(str_lst, concise) @@ -353,9 +320,6 @@ def gen_methods_report_str(concise=False): """ str_lst = [ - - DIVIDER, - '', 'REPORTING', '', 'Reports using spectral parameterization should include (at minimum):', @@ -364,8 +328,6 @@ def gen_methods_report_str(concise=False): '- the fit modes that were used', '- the algorithm & settings that were used', '- the frequency range that was fit', - '', - DIVIDER, ] output = _format(str_lst, concise) @@ -373,6 +335,7 @@ def gen_methods_report_str(concise=False): return output +# NOTE: move this function? It's text - not a print report def gen_methods_text_str(model=None): """Generate a string representation of a template methods report. @@ -451,8 +414,6 @@ def gen_model_results_str(model, concise=False): # Create the formatted strings for printing str_lst = [ - DIVIDER, - '', 'SPECTRUM MODEL RESULTS', '', @@ -475,9 +436,6 @@ def gen_model_results_str(model, concise=False): 'Model metrics:', *['{:>18s} is {:1.4f} {:8s}'.format('{:s} ({:s})'.format(*key.split('_')), res, ' ') \ for key, res in model.results.metrics.results.items()], - '', - - DIVIDER, ] output = _format(str_lst, concise) @@ -506,8 +464,6 @@ def gen_group_results_str(group, concise=False): str_lst = [ - DIVIDER, - '', 'GROUP SPECTRAL MODEL RESULTS ({} spectra)'.format(len(group.results.group_results)), *_report_str_n_null(group), '', @@ -536,13 +492,8 @@ def gen_group_results_str(group, concise=False): '{:s} ({:s})'.format(*label.split('_')), *compute_arr_desc(group.results.get_metrics(label))) \ for label in group.results.metrics.labels], - '', ]) - str_lst.extend([ - DIVIDER, - ]) - output = _format(str_lst, concise) return output @@ -574,8 +525,6 @@ def gen_time_results_str(time, concise=False): str_lst = [ - DIVIDER, - '', 'TIME SPECTRAL MODEL RESULTS ({} time windows)'.format(time.data.n_time_windows), *_report_str_n_null(time), '', @@ -606,9 +555,6 @@ def gen_time_results_str(time, concise=False): '{:s} ({:s})'.format(*key.split('_')), *compute_arr_desc(time.results.time_results[key])) \ for key in time.results.metrics.results], - '', - - DIVIDER, ] output = _format(str_lst, concise) @@ -642,8 +588,6 @@ def gen_event_results_str(event, concise=False): str_lst = [ - DIVIDER, - '', 'EVENT SPECTRAL MODEL RESULTS ({} events with {} time windows)'.format(\ event.data.n_events, event.data.n_time_windows), *_report_str_n_null(event), @@ -676,8 +620,6 @@ def gen_event_results_str(event, concise=False): '{:s} ({:s})'.format(*key.split('_')), *compute_arr_desc(np.mean(event.results.event_time_results[key], 1))) \ for key in event.results.metrics.results], - '', - DIVIDER, ] output = _format(str_lst, concise) @@ -727,11 +669,7 @@ 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) @@ -757,6 +695,9 @@ def _format(str_lst, concise): Formatted string, ready for printing. """ + str_template = [DIVIDER, '', '', DIVIDER] + str_lst = list_insert(str_template, str_lst, 2) + # Set centering value - use a smaller value if in concise mode center_val = SCV if concise else LCV diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index 8833cf85..248f1eb4 100644 --- a/specparam/tests/reports/test_strings.py +++ b/specparam/tests/reports/test_strings.py @@ -76,7 +76,7 @@ def test_no_model_str(): def test_format(): - str_lst = ['=', '', 'a', '', 'b', '', '='] + str_lst = ['a', '', 'b'] str_out_1 = _format(str_lst, False) assert str_out_1 From 660973339ea7eca79045d31740a4d0039579ace7 Mon Sep 17 00:00:00 2001 From: Tom Donoghue Date: Sun, 23 Nov 2025 00:55:23 +0000 Subject: [PATCH 5/5] add print to Bands --- specparam/bands/bands.py | 14 ++++++++++++ specparam/reports/strings.py | 29 +++++++++++++++++++++++++ specparam/tests/reports/test_strings.py | 4 ++++ 3 files changed, 47 insertions(+) diff --git a/specparam/bands/bands.py b/specparam/bands/bands.py index 6e04c5f6..8cbafce9 100644 --- a/specparam/bands/bands.py +++ b/specparam/bands/bands.py @@ -2,6 +2,8 @@ from collections import OrderedDict +from specparam.reports.strings import gen_bands_str + ################################################################################################### ################################################################################################### @@ -126,6 +128,18 @@ def add_band(self, label, band_definition): self.bands[label] = tuple(band_definition) + def print(self, concise=False): + """Print out the current band definitions. + + Parameters + ---------- + concise : bool, optional, default: False + Whether to print the report in a concise mode, or not. + """ + + print(gen_bands_str(self, concise)) + + def remove_band(self, label): """Remove a previously defined oscillation band. diff --git a/specparam/reports/strings.py b/specparam/reports/strings.py index 832b3ddb..26f1273e 100644 --- a/specparam/reports/strings.py +++ b/specparam/reports/strings.py @@ -242,6 +242,35 @@ def gen_settings_str(algorithm, description=False, concise=False): return output +def gen_bands_str(bands, concise=False): + """Generate a string representation of a set of bands definitions. + + Parameters + ---------- + bands : Bands + Bands definition. + concise : bool, optional, default: False + Whether to create the string in concise mode. + + Returns + ------- + output : str + Formatted string of bands definition. + """ + + str_lst = [ + 'BANDS DEFINITION', + '', + ] + + for label, definition in bands.bands.items(): + str_lst.append('{}: {}'.format(label, definition)) + + output = _format(str_lst, concise) + + return output + + def gen_metrics_str(metrics, description=False, concise=False): """Generate a string representation of a set of metrics. diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index 248f1eb4..e0ade42d 100644 --- a/specparam/tests/reports/test_strings.py +++ b/specparam/tests/reports/test_strings.py @@ -31,6 +31,10 @@ def test_gen_settings_str(tfm): assert gen_settings_str(tfm.algorithm) assert gen_settings_str(tfm.algorithm, True) +def test_gen_bands_str(tbands): + + assert gen_bands_str(tbands) + def test_gen_metrics_str(tfm): assert gen_metrics_str(tfm.results.metrics)