diff --git a/efel/api.py b/efel/api.py index 74d77a52..b4063d67 100644 --- a/efel/api.py +++ b/efel/api.py @@ -36,59 +36,35 @@ import efel.pyfeatures as pyfeatures from efel.pyfeatures.pyfeatures import get_cpp_feature - _settings = efel.Settings() -_int_settings = {} -_double_settings = {} -_string_settings = {} -def reset(): - """Resets the efel to its initial state +def set_setting(setting_name: str, new_value: int | float | str) -> None: + """Set a certain setting to a new value. - The user can set certain values in the efel, like the spike threshold. - These values are persisten. This function will reset these value to their - original state. + Args: + setting_name: Name of the setting to change. + new_value: New value for the setting. """ - global _settings, _int_settings, _double_settings, _string_settings - _settings = efel.Settings() - _int_settings = {} - _double_settings = {} - _string_settings = {} - - set_double_setting('spike_skipf', 0.1) - set_int_setting('max_spike_skip', 2) - set_double_setting('Threshold', _settings.threshold) - set_double_setting('DerivativeThreshold', _settings.derivative_threshold) - set_double_setting( - 'DownDerivativeThreshold', - _settings.down_derivative_threshold) - set_double_setting('interp_step', 0.1) - set_double_setting('burst_factor', 1.5) - set_double_setting('strict_burst_factor', 2.0) - set_double_setting('voltage_base_start_perc', 0.9) - set_double_setting('voltage_base_end_perc', 1.0) - set_double_setting('current_base_start_perc', 0.9) - set_double_setting('current_base_end_perc', 1.0) - set_double_setting('rise_start_perc', 0.0) - set_double_setting('rise_end_perc', 1.0) - set_double_setting("initial_perc", 0.1) - set_double_setting("min_spike_height", 20.0) - set_int_setting("strict_stiminterval", 0) - set_double_setting("initburst_freq_threshold", 50) - set_double_setting("initburst_sahp_start", 5) - set_double_setting("initburst_sahp_end", 100) - set_int_setting("DerivativeWindow", 3) - set_str_setting("voltage_base_mode", "mean") - set_str_setting("current_base_mode", "mean") - set_double_setting("precision_threshold", 1e-10) - set_double_setting("sahp_start", 5.0) - set_int_setting("ignore_first_ISI", 1) - set_double_setting("impedance_max_freq", 50.0) + _settings.set_setting(setting_name, new_value) + +def get_settings() -> efel.Settings: + """Returns the current settings of eFEL.""" + return _settings + + +def reset(): + """Resets the efel settings to their default values. + see :py:func:`efel.Settings` + """ + global _settings + _settings = efel.Settings() + _settings.reset_to_default() _initialise() +@deprecated("Use `set_setting('dependencyfile_path', location)` instead") def set_dependency_file_location(location: str | Path) -> None: """Sets the location of the Dependency file. @@ -103,10 +79,7 @@ def set_dependency_file_location(location: str | Path) -> None: Raises: FileNotFoundError: If the path to the dependency file doesn't exist. """ - location = Path(location) - if not location.exists(): - raise FileNotFoundError(f"Path to dependency file {location} doesn't exist") - _settings.dependencyfile_path = str(location) + set_setting('dependencyfile_path', str(location)) def get_dependency_file_location() -> str: @@ -118,6 +91,7 @@ def get_dependency_file_location() -> str: return _settings.dependencyfile_path +@deprecated("Use `set_setting('Threshold', new_threshold)` instead") def set_threshold(new_threshold: float) -> None: """Set the spike detection threshold in the eFEL, default -20.0 @@ -125,10 +99,11 @@ def set_threshold(new_threshold: float) -> None: new_threshold: The new spike detection threshold value (in the same units as the traces, e.g. mV). """ - _settings.threshold = new_threshold - set_double_setting('Threshold', _settings.threshold) + set_setting('Threshold', new_threshold) +@deprecated("Use `set_setting('DerivativeThreshold', " + "new_derivative_threshold)` instead") def set_derivative_threshold(new_derivative_threshold: float) -> None: """Set the threshold for the derivative for detecting the spike onset. @@ -139,8 +114,7 @@ def set_derivative_threshold(new_derivative_threshold: float) -> None: new_derivative_threshold: The new derivative threshold value (in the same units as the traces, e.g. mV/ms). """ - _settings.derivative_threshold = new_derivative_threshold - set_double_setting('DerivativeThreshold', _settings.derivative_threshold) + set_setting('DerivativeThreshold', new_derivative_threshold) def get_feature_names() -> list[str]: @@ -239,34 +213,38 @@ def _initialise() -> None: # flush the GErrorString from previous runs by calling getgError() cppcore.getgError() - # First set some settings that are used by the feature extraction - - for setting_name, int_setting in list(_int_settings.items()): - cppcore.setFeatureInt(setting_name, [int_setting]) - - for setting_name, double_setting in list(_double_settings.items()): - if isinstance(double_setting, list): - cppcore.setFeatureDouble(setting_name, double_setting) - else: - cppcore.setFeatureDouble(setting_name, [double_setting]) - - for setting_name, str_setting in list(_string_settings.items()): - cppcore.setFeatureString(setting_name, str_setting) + # Set the settings in the cppcore + settings_attrs = vars(_settings) + for setting_name, setting_value in settings_attrs.items(): + if isinstance(setting_value, bool): + setting_value = int(setting_value) + if isinstance(setting_value, int): + cppcore.setFeatureInt(setting_name, [setting_value]) + elif isinstance(setting_value, float): + if isinstance(setting_value, list): + cppcore.setFeatureDouble(setting_name, setting_value) + else: + cppcore.setFeatureDouble(setting_name, [setting_value]) + elif isinstance(setting_value, str): + cppcore.setFeatureString(setting_name, setting_value) +@deprecated("Use `set_setting()` instead") def set_int_setting(setting_name: str, new_value: int) -> None: - """Set a certain integer setting to a new value""" - _int_settings[setting_name] = new_value + """Set a certain integer setting to a new value.""" + set_setting(setting_name, new_value) +@deprecated("Use `set_setting()` instead") def set_double_setting(setting_name: str, new_value: float) -> None: - """Set a certain double setting to a new value""" - _double_settings[setting_name] = new_value + """Set a certain double setting to a new value.""" + set_setting(setting_name, new_value) +@deprecated("Use `set_setting()` instead") def set_str_setting(setting_name: str, new_value: str) -> None: - """Set a certain string setting to a new value""" - _string_settings[setting_name] = new_value + """Set a certain string setting to a new value.""" + set_setting(setting_name, new_value) @overload diff --git a/efel/settings.py b/efel/settings.py index d5e0512e..6220a9d5 100644 --- a/efel/settings.py +++ b/efel/settings.py @@ -19,16 +19,124 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ +from dataclasses import dataclass, fields, asdict from pathlib import Path +from typing import Union +from typing_extensions import deprecated +import logging +logger = logging.getLogger(__name__) + +@dataclass class Settings: - """FEL settings class""" - - def __init__(self): - self.threshold = -20.0 - self.derivative_threshold = 10.0 - self.down_derivative_threshold = -12.0 - self.dependencyfile_path = str( - Path(__file__).parent.absolute() / "DependencyV5.txt" - ) + """eFEL settings class. + + Attributes: + Threshold (float): Spike detection threshold (default: -20.0). + DerivativeThreshold (float): Threshold value for derivative calculations + (default: 10.0). + DownDerivativeThreshold (float): Threshold value for downward derivative + calculations (default: -12.0). + dependencyfile_path (str): Path to the dependency file + (default: 'DependencyV5.txt'). + spike_skipf (float): Fraction of spikes to skip (default: 0.1). + max_spike_skip (int): Maximum number of spikes to skip (default: 2). + interp_step (float): Interpolation step (default: 0.1). + burst_factor (float): Burst factor (default: 1.5). + strict_burst_factor (float): Strict burst factor (default: 2.0). + voltage_base_start_perc (float): Voltage base start percentage (default: 0.9). + voltage_base_end_perc (float): Voltage base end percentage (default: 1.0). + current_base_start_perc (float): Current base start percentage (default: 0.9). + current_base_end_perc (float): Current base end percentage (default: 1.0). + rise_start_perc (float): Rise start percentage (default: 0.0). + rise_end_perc (float): Rise end percentage (default: 1.0). + initial_perc (float): Initial percentage (default: 0.1). + min_spike_height (float): Minimum spike height (default: 20.0). + strict_stiminterval (bool): Strict stimulus interval (default: False). + initburst_freq_threshold (int): Initial burst frequency threshold + (default: 50) + initburst_sahp_start (int): Initial burst SAHP start (default: 5). + initburst_sahp_end (int): Initial burst SAHP end (default: 100). + DerivativeWindow (int): Derivative window (default: 3). + voltage_base_mode (str): Voltage base mode (default: "mean"). + current_base_mode (str): Current base mode (default: "mean"). + precision_threshold (float): Precision threshold (default: 1e-10). + sahp_start (float): SAHP start (default: 5.0). + ignore_first_ISI (bool): Ignore first ISI (default: True). + impedance_max_freq (float): Impedance maximum frequency (default: 50.0). + """ + + Threshold: float = -20.0 + DerivativeThreshold: float = 10.0 + DownDerivativeThreshold: float = -12.0 + dependencyfile_path: str = str( + Path(__file__).parent.absolute() / "DependencyV5.txt" + ) + + spike_skipf: float = 0.1 + max_spike_skip: int = 2 + interp_step: float = 0.1 + burst_factor: float = 1.5 + strict_burst_factor: float = 2.0 + voltage_base_start_perc: float = 0.9 + voltage_base_end_perc: float = 1.0 + current_base_start_perc: float = 0.9 + current_base_end_perc: float = 1.0 + rise_start_perc: float = 0.0 + rise_end_perc: float = 1.0 + initial_perc: float = 0.1 + min_spike_height: float = 20.0 + strict_stiminterval: bool = False + initburst_freq_threshold: int = 50 + initburst_sahp_start: int = 5 + initburst_sahp_end: int = 100 + DerivativeWindow: int = 3 + voltage_base_mode: str = "mean" + current_base_mode: str = "mean" + precision_threshold: float = 1e-10 + sahp_start: float = 5.0 + ignore_first_ISI: bool = True + impedance_max_freq: float = 50.0 + + def set_setting(self, + setting_name: str, + new_value: Union[int, float, str, bool]) -> None: + """Set a certain setting to a new value. + + Args: + setting_name (str): Name of the setting to be modified. + new_value (Union[int, float, str, bool]): New value for the setting. + + Raises: + ValueError: If the value is of the wrong type. + FileNotFoundError: If the path to the dependency file does not exist + (for 'dependencyfile_path' setting). + """ + if hasattr(self, setting_name): + expected_types = {f.name: f.type for f in fields(self)} + expected_type = expected_types.get(setting_name) + if expected_type and not isinstance(new_value, expected_type): + raise ValueError(f"Invalid value for setting '{setting_name}'. " + f"Expected type: {expected_type.__name__}.") + else: + logger.warning("Setting '%s' not found in settings. " + "Adding it as a new setting.", setting_name) + + if setting_name == "dependencyfile_path": + path = Path(str(new_value)) + if not path.exists(): + raise FileNotFoundError(f"Path to dependency file {new_value}" + "doesn't exist") + + setattr(self, setting_name, new_value) + + def reset_to_default(self): + """Reset settings to their default values""" + default_settings = Settings() + for field in default_settings.__dataclass_fields__: + setattr(self, field, getattr(default_settings, field)) + + def __str__(self): + attributes = asdict(self) + return '\n'.join([f"{key}: {value}" for key, value in attributes.items()]) diff --git a/examples/neo/load_nwb.ipynb b/examples/neo/load_nwb.ipynb index ae998456..c83e15b2 100644 --- a/examples/neo/load_nwb.ipynb +++ b/examples/neo/load_nwb.ipynb @@ -152,8 +152,8 @@ } ], "source": [ - "efel.set_int_setting(\"ignore_first_ISI\", 0) # Don't ignore the first spike\n", - "efel.set_double_setting(\"strict_burst_factor\", 4.0) # The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0.\n", + "efel.set_setting(\"ignore_first_ISI\", False) # Don't ignore the first spike\n", + "efel.set_setting(\"strict_burst_factor\", 4.0) # The burst detection can be fine-tuned by changing the setting strict_burst_factor. Default value is 2.0.\n", "feature_values = efel.get_feature_values([trace], ['spikes_per_burst', 'strict_burst_number', 'strict_burst_mean_freq', 'peak_time', 'AP_height', 'peak_indices', 'burst_begin_indices', 'burst_end_indices'])[0]\n", "feature_values = {feature_name: list(values) for feature_name, values in feature_values.items()}\n", "feature_values" diff --git a/tests/test_basic.py b/tests/test_basic.py index 2948c0b6..d7e158e6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -38,10 +38,7 @@ from efel.io import load_ascii_input from efel.api import get_feature_values -from efel.api import set_double_setting -from efel.api import set_int_setting -from efel.api import set_derivative_threshold -from efel.api import set_str_setting +from efel.api import set_setting from efel.api import get_distance @@ -111,25 +108,6 @@ def test_version(): assert efel.__version__ is not None -def test_setDependencyFileLocation_wrongpath(): - """basic: Test if setDependencyFileLocation fails if path doesn't exist.""" - import efel - efel.reset() - pytest.raises( - FileNotFoundError, - efel.setDependencyFileLocation, "thisfiledoesntexist") - - -def test_setDependencyFileLocation(): - """basic: Test if setDependencyFileLocation works.""" - import efel - efel.reset() - dep_file = str(Path(__file__).parent / 'DependencyV5_LibV5peakindices.txt') - efel.setDependencyFileLocation(dep_file) - result = efel.getDependencyFileLocation() - assert result == dep_file - - def test_nonexisting_feature(): """basic: Test nonexisting feature.""" import efel @@ -434,7 +412,7 @@ def test_setDerivativeThreshold(): features) AP_begin_voltage_orig = feature_values[0]['AP_begin_voltage'][1] - set_derivative_threshold(5) + set_setting('DerivativeThreshold', 5.0) feature_values = \ get_feature_values( [trace], @@ -564,7 +542,7 @@ def test_min_AHP_indices_strict(): for strict, n_of_ahp in [(False, 17), (True, 16)]: efel.reset() - set_int_setting('strict_stiminterval', strict) + set_setting('strict_stiminterval', strict) stim_start = 700.0 stim_end = 2700.0 @@ -621,7 +599,7 @@ def test_strict_stiminterval(): for strict, n_of_spikes in [(False, 5), (True, 3)]: efel.reset() - set_int_setting("strict_stiminterval", strict) + set_setting("strict_stiminterval", strict) stim_start = 600.0 stim_end = 750.0 @@ -702,7 +680,7 @@ def test_ISI_values_noIgnore(): features = ['ISI_values'] - set_int_setting("ignore_first_ISI", 0) + set_setting("ignore_first_ISI", False) feature_values = \ get_feature_values( @@ -711,7 +689,7 @@ def test_ISI_values_noIgnore(): isi_values_no_ignore = feature_values[0]['ISI_values'] efel.reset() - set_int_setting("ignore_first_ISI", 1) + set_setting("ignore_first_ISI", True) feature_values = \ get_feature_values( @@ -848,7 +826,7 @@ def test_AP_end_indices(): efel.reset() - set_double_setting("DownDerivativeThreshold", -24) + set_setting("DownDerivativeThreshold", -24.0) feature_values = \ get_feature_values( [trace], @@ -994,7 +972,7 @@ def test_voltagebase_median(): import efel efel.reset() - set_str_setting("voltage_base_mode", "median") + set_setting("voltage_base_mode", "median") trace, time, voltage, stim_start, stim_end = load_data( 'mean_frequency1', interp=True) @@ -1044,7 +1022,7 @@ def test_currentbase_median(): """basic: Test currentbase with median""" import efel efel.reset() - set_str_setting("current_base_mode", "median") + set_setting("current_base_mode", "median") data = numpy.loadtxt(testdata_dir / 'basic' / 'current.txt') current = data[:, 1] @@ -1258,7 +1236,7 @@ def test_derivwindow1(): numpy.testing.assert_allclose(AP_begin_voltage, -45.03627393790836) efel.reset() - set_double_setting('interp_step', 0.01) + set_setting('interp_step', 0.01) feature_values = \ get_feature_values( [trace], @@ -1268,8 +1246,8 @@ def test_derivwindow1(): numpy.testing.assert_allclose(AP_begin_voltage, -45.5055215) efel.reset() - set_double_setting('interp_step', 0.01) - set_int_setting('DerivativeWindow', 30) + set_setting('interp_step', 0.01) + set_setting('DerivativeWindow', 30) feature_values = \ get_feature_values( [trace], @@ -1369,7 +1347,7 @@ def test_ohmic_inputresistance(): features = ['ohmic_input_resistance', 'voltage_deflection'] stimulus_current = 10.0 - set_double_setting('stimulus_current', stimulus_current) + set_setting('stimulus_current', stimulus_current) feature_values = \ get_feature_values( [trace], @@ -1393,7 +1371,7 @@ def test_ohmic_input_resistance_zero_stimulus_current(): features = ['ohmic_input_resistance'] stimulus_current = 0.0 - set_double_setting('stimulus_current', stimulus_current) + set_setting('stimulus_current', stimulus_current) feature_values = get_feature_values([trace], features) ohmic_input_resistance = feature_values[0]['ohmic_input_resistance'] @@ -1597,7 +1575,7 @@ def test_ohmic_input_resistance_vb_ssse(): features = ['ohmic_input_resistance_vb_ssse', 'voltage_deflection_vb_ssse'] stimulus_current = 10.0 - set_double_setting('stimulus_current', stimulus_current) + set_setting('stimulus_current', stimulus_current) feature_values = \ get_feature_values( [trace], @@ -1622,7 +1600,7 @@ def test_ohmic_input_resistance_vb_ssse_zero_stimulus_current(): trace = {'T': time, 'V': voltage, 'stim_start': [500.0], 'stim_end': [900.0]} stimulus_current = 0.0 - set_double_setting('stimulus_current', stimulus_current) + set_setting('stimulus_current', stimulus_current) feature_values = get_feature_values([trace], ['ohmic_input_resistance_vb_ssse']) ohmic_input_resistance = feature_values[0]['ohmic_input_resistance_vb_ssse'] assert ohmic_input_resistance is None @@ -1901,8 +1879,8 @@ def test_multiple_decay_time_constant_after_stim(): features = ['multiple_decay_time_constant_after_stim'] - set_double_setting("multi_stim_start", [stim_start]) - set_double_setting("multi_stim_end", [stim_end]) + set_setting("multi_stim_start", stim_start) + set_setting("multi_stim_end", stim_end) feature_values = get_feature_values([trace], features)[0] @@ -2059,7 +2037,7 @@ def test_unfinished_peak(): """basic: Test if unfinished peak doesn't break spike_count""" import efel - set_int_setting('strict_stiminterval', True) + set_setting('strict_stiminterval', True) dt = 0.1 v = numpy.zeros(int(100 / dt)) - 70.0 @@ -2347,8 +2325,8 @@ def test_AP_width_between_threshold(): import efel efel.reset() - threshold = -48 - set_double_setting("Threshold", threshold) + threshold = -48.0 + set_setting("Threshold", threshold) stim_start = 200.0 stim_end = 1200.0 @@ -2389,10 +2367,10 @@ def test_AP_width_between_threshold_strict(): import efel efel.reset() - set_int_setting('strict_stiminterval', True) + set_setting('strict_stiminterval', True) - threshold = -48 - set_double_setting("Threshold", threshold) + threshold = -48.0 + set_setting("Threshold", threshold) stim_start = 200.0 stim_end = 1200.0 @@ -3106,7 +3084,7 @@ def test_burst_indices(): """basic: Test burst_begin_indices and burst_end_indices""" import efel efel.reset() - set_int_setting('ignore_first_ISI', 0) + set_setting('ignore_first_ISI', False) time, voltage = load_ascii_input(burst1_url) time, voltage = interpolate(time, voltage, 0.1) @@ -3142,7 +3120,7 @@ def test_strict_burst_mean_freq(): """basic: Test strict_burst_mean_freq""" import efel efel.reset() - set_int_setting('ignore_first_ISI', 0) + set_setting('ignore_first_ISI', False) time, voltage = load_ascii_input(burst1_url) time, voltage = interpolate(time, voltage, 0.1) @@ -3189,7 +3167,7 @@ def test_strict_burst_number(): """basic: Test strict_burst_number""" import efel efel.reset() - set_int_setting('ignore_first_ISI', 0) + set_setting('ignore_first_ISI', False) time, voltage = load_ascii_input(burst1_url) time, voltage = interpolate(time, voltage, 0.1) @@ -3237,7 +3215,7 @@ def test_strict_interburst_voltage(): """basic: Test strict_interburst_voltage""" import efel efel.reset() - set_int_setting('ignore_first_ISI', 0) + set_setting('ignore_first_ISI', False) time, voltage = load_ascii_input(burst1_url) time, voltage = interpolate(time, voltage, 0.1) @@ -3299,7 +3277,7 @@ def test_AP_width_spike_before_stim_start(): assert len(ap_width) == 15 - set_int_setting('strict_stiminterval', 1) + set_setting('strict_stiminterval', True) feature_values = \ get_feature_values( [trace], @@ -3337,7 +3315,7 @@ def test_ADP_peak_amplitude(): """basic: Test ADP_peak_amplitude""" import efel efel.reset() - set_int_setting('strict_stiminterval', True) + set_setting('strict_stiminterval', True) stim_start = 250.0 stim_end = 1600.0 @@ -3562,7 +3540,7 @@ def test_postburst_min_values(): import efel efel.reset() # use this to have all spikes in burst for burst3_url case - set_double_setting('strict_burst_factor', 4.0) + set_setting('strict_burst_factor', 4.0) time, voltage = load_ascii_input(url) diff --git a/tests/test_isi.py b/tests/test_isi.py index f3261bed..dffbfa86 100644 --- a/tests/test_isi.py +++ b/tests/test_isi.py @@ -99,7 +99,7 @@ def test_ISI_log_slope_skip(self): def test_ISI_log_slope_skip_ValueError(self): with pytest.raises(ValueError): - efel.set_double_setting("spike_skipf", 1.0) + efel.set_setting("spike_skipf", 1.0) get_feature_values( [self.trace], ["ISI_log_slope_skip"], raise_warnings=False)[0] @@ -142,7 +142,7 @@ def test_single_burst_ratio(self): assert self.feature_values["single_burst_ratio"] is None # set ignore_first_ISI=False - efel.set_int_setting("ignore_first_ISI", 0) + efel.set_setting("ignore_first_ISI", False) self.feature_values = get_feature_values( [self.trace], self.features, raise_warnings=False)[0] diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 00000000..ffe2545d --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,111 @@ +# pylint: disable=W0611, W0612, F0401, R0914, C0302 + +"""Settings tests of eFEL""" + + +""" +Copyright (c) 2015, Blue Brain Project/EPFL + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import pytest + +from efel.settings import Settings +from efel.api import set_setting, get_settings + + +def test_set_setting(): + """Test that the set_setting method correctly updates a setting.""" + settings = Settings() + settings.set_setting("Threshold", -30.0) + assert settings.Threshold == -30.0 + + +def test_set_setting_invalid_type(): + """Test that the set_setting method raises a ValueError + when given an invalid type.""" + settings = Settings() + with pytest.raises(ValueError): + settings.set_setting("Threshold", "-30.0") + + +def test_set_setting_dependencyfile_path_not_found(): + """Test that the set_setting method raises a FileNotFoundError + when given a nonexistent file.""" + settings = Settings() + with pytest.raises(FileNotFoundError): + settings.set_setting("dependencyfile_path", "nonexistent_file.txt") + + +def test_reset_to_default(): + """Test that the reset_to_default method correctly resets a setting to + its default value.""" + settings = Settings() + settings.Threshold = -30.0 + settings.reset_to_default() + assert settings.Threshold == -20.0 + + +def test_get_settings(): + """Test that the get_settings method returns an instance of efel.Settings.""" + settings = get_settings() + assert isinstance(settings, Settings) + + +def test_str_method(): + """Test that the __str__ method returns the correct string representation.""" + settings = Settings() + expected_output = ( + "Threshold: -20.0\n" + "DerivativeThreshold: 10.0\n" + "DownDerivativeThreshold: -12.0\n" + f"dependencyfile_path: {settings.dependencyfile_path}\n" + "spike_skipf: 0.1\n" + "max_spike_skip: 2\n" + "interp_step: 0.1\n" + "burst_factor: 1.5\n" + "strict_burst_factor: 2.0\n" + "voltage_base_start_perc: 0.9\n" + "voltage_base_end_perc: 1.0\n" + "current_base_start_perc: 0.9\n" + "current_base_end_perc: 1.0\n" + "rise_start_perc: 0.0\n" + "rise_end_perc: 1.0\n" + "initial_perc: 0.1\n" + "min_spike_height: 20.0\n" + "strict_stiminterval: False\n" + "initburst_freq_threshold: 50\n" + "initburst_sahp_start: 5\n" + "initburst_sahp_end: 100\n" + "DerivativeWindow: 3\n" + "voltage_base_mode: mean\n" + "current_base_mode: mean\n" + "precision_threshold: 1e-10\n" + "sahp_start: 5.0\n" + "ignore_first_ISI: True\n" + "impedance_max_freq: 50.0" + ) + assert str(settings) == expected_output