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/models/base.py b/specparam/models/base.py index 0981e2a2..dce3212a 100644 --- a/specparam/models/base.py +++ b/specparam/models/base.py @@ -114,31 +114,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 a4666e3c..c8e76c30 100644 --- a/specparam/models/model.py +++ b/specparam/models/model.py @@ -237,19 +237,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/reports/strings.py b/specparam/reports/strings.py index 7144f7d0..52ca007c 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 @@ -27,10 +28,7 @@ def gen_issue_str(concise=False): """ str_lst = [ - - DIVIDER, - '', - 'specparam - ISSUE REPORTING', + 'ISSUE REPORTING', '', # Reporting bugs @@ -47,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) @@ -102,15 +97,9 @@ def gen_version_str(concise=False): """ str_lst = [ - - DIVIDER, - '', 'CODE VERSION', '', '{}'.format(MODULE_VERSION), - '', - DIVIDER, - ] output = _format(str_lst, concise) @@ -135,10 +124,12 @@ def gen_data_str(data, concise=False): Formatted string of data summary. """ + str_lst = ['DATA INFORMATION', ''] + if not data.has_data: no_data_str = "No data currently loaded in the object." - str_lst = [DIVIDER,'', no_data_str, '', DIVIDER] + str_lst.append(no_data_str) else: @@ -153,17 +144,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.extend(str_lst_add) + output = _format(str_lst, concise) return output @@ -198,9 +186,6 @@ def gen_modes_str(modes, description=False, concise=False): # Create output string str_lst = [ - - DIVIDER, - '', 'FIT MODES', '', # Settings - include descriptions if requested @@ -208,8 +193,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) @@ -237,9 +220,8 @@ def gen_settings_str(algorithm, description=False, concise=False): # Create output string - header str_lst = [ - DIVIDER, - '', - 'ALGORITHM: {}'.format(algorithm.name), + 'ALGORITHM', + algorithm.name, ] if description: @@ -248,7 +230,6 @@ def gen_settings_str(algorithm, description=False, concise=False): str_lst.extend([ '', 'ALGORITHM SETTINGS', - '', ]) # Loop through algorithm settings, and add information @@ -257,10 +238,34 @@ 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([ + output = _format(str_lst, concise) + + 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', '', - DIVIDER, - ]) + ] + + for label, definition in bands.bands.items(): + str_lst.append('{}: {}'.format(label, definition)) output = _format(str_lst, concise) @@ -292,13 +297,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) @@ -324,14 +325,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) @@ -354,9 +350,6 @@ def gen_methods_report_str(concise=False): """ str_lst = [ - - DIVIDER, - '', 'REPORTING', '', 'Reports using spectral parameterization should include (at minimum):', @@ -365,8 +358,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) @@ -374,6 +365,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. @@ -452,9 +444,7 @@ def gen_model_results_str(model, concise=False): # Create the formatted strings for printing str_lst = [ - DIVIDER, - '', - 'POWER SPECTRUM MODEL', + 'SPECTRUM MODEL RESULTS', '', # Fit algorithm & data overview @@ -477,9 +467,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) @@ -508,8 +495,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), '', @@ -538,13 +523,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 @@ -576,8 +556,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), '', @@ -608,9 +586,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) @@ -644,8 +619,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), @@ -678,8 +651,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) @@ -729,11 +700,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) @@ -759,6 +726,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/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 9639e9b4..cac5dec0 100644 --- a/specparam/tests/models/test_model.py +++ b/specparam/tests/models/test_model.py @@ -304,14 +304,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): diff --git a/specparam/tests/reports/test_strings.py b/specparam/tests/reports/test_strings.py index 8833cf85..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) @@ -76,7 +80,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 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