From 51f344e2743491a5a3e6fe704f76a693fc242fea Mon Sep 17 00:00:00 2001 From: Antoine Minard Date: Mon, 20 Jan 2025 16:42:52 +0100 Subject: [PATCH 1/5] Changed default coefficient values to None + added details in class docstring --- .../sound/core/signal_processing/filter.py | 55 +++++++++++-------- .../test_signal_processing_filter.py | 12 ++-- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/ansys/sound/core/signal_processing/filter.py b/src/ansys/sound/core/signal_processing/filter.py index 6cad780a8..8428311b3 100644 --- a/src/ansys/sound/core/signal_processing/filter.py +++ b/src/ansys/sound/core/signal_processing/filter.py @@ -37,12 +37,21 @@ class Filter(SignalProcessingParent): - """Filter class. + r"""Filter class. This class allows designing, loading, and applying a digital filter to a signal. The filter - coefficients can be provided directly, or computed from a specific frequency response function - (FRF). In this latter case, the filter is designed as a minimum-phase FIR filter, and the - filter denominator (:attr:`a_coefficients`) is set to 1 as a consequence. + coefficients can be provided directly, using the attributes :attr:`b_coefficients` and + :attr:`a_coefficients`, or computed from a specific frequency response function (FRF), using + the methods :meth:`design_FIR_from_FRF` or :meth:`design_FIR_from_FRF_file`. In this latter + case, the filter is designed as a minimum-phase FIR filter, and the filter denominator + (:attr:`a_coefficients`) is set to 1 as a consequence. + + Filtering a signal consists in applying the filter coefficients :math:`b[k]` and :math:`a[k]` + in the following difference equation, with :math:`x[n]` the input signal, and :math:`y[n]` the + output signal: + + .. math:: + y[n] = \sum_{k=0}^{N} b[k] \cdot x[n-k] - \sum_{k=1}^{N} a[k] \cdot y[n-k] .. note:: Whether they are derived from the provided FRF or specified directly, the filter @@ -54,8 +63,8 @@ class Filter(SignalProcessingParent): def __init__( self, - b_coefficients: list[float] = [], - a_coefficients: list[float] = [], + b_coefficients: list[float] = None, + a_coefficients: list[float] = None, sampling_frequency: float = 44100.0, file: str = "", signal: Field = None, @@ -64,9 +73,9 @@ def __init__( Parameters ---------- - a_coefficients : list[float], default: [] + a_coefficients : list[float], default: None Denominator coefficients of the filter. - b_coefficients : list[float], default: [] + b_coefficients : list[float], default: None Numerator coefficients of the filter. sampling_frequency : float, default: 44100.0 Sampling frequency associated with the filter coefficients, in Hz. @@ -87,7 +96,7 @@ def __init__( self.__sampling_frequency = sampling_frequency if file != "": - if a_coefficients != [] or b_coefficients != []: + if a_coefficients is not None or b_coefficients is not None: warnings.warn( PyAnsysSoundWarning( "Specified parameters a_coefficients and b_coefficients are ignored " @@ -103,17 +112,17 @@ def __init__( def __str__(self) -> str: """Return the string representation of the object.""" - if len(self.a_coefficients) > 5: - str_a = str(self.a_coefficients[:5])[:-1] + ", ... ]" - elif len(self.a_coefficients) == 0: + if self.a_coefficients is None: str_a = "Not set" + elif len(self.a_coefficients) > 5: + str_a = str(self.a_coefficients[:5])[:-1] + ", ... ]" else: str_a = str(self.a_coefficients) - if len(self.b_coefficients) > 5: - str_b = str(self.b_coefficients[:5])[:-1] + ", ... ]" - elif len(self.b_coefficients) == 0: + if self.b_coefficients is None: str_b = "Not set" + elif len(self.b_coefficients) > 5: + str_b = str(self.b_coefficients[:5])[:-1] + ", ... ]" else: str_b = str(self.b_coefficients) @@ -245,26 +254,26 @@ def process(self): f"Input signal is not set. Use {__class__.__name__}.signal." ) - if self.a_coefficients == []: + if self.a_coefficients is None or len(self.a_coefficients) == 0: raise PyAnsysSoundException( - "Filter's denominator coefficients (a_coefficients) cannot be empty. Use " - f"{__class__.__name__}.a_coefficients, or the methods " + "Filter's denominator coefficients (a_coefficients) must be defined and cannot be " + f"empty. Use {__class__.__name__}.a_coefficients, or the methods " f"{__class__.__name__}.design_FIR_from_FRF() or " f"{__class__.__name__}.design_FIR_from_FRF_file()." ) - if self.b_coefficients == []: + if self.b_coefficients is None or len(self.b_coefficients) == 0: raise PyAnsysSoundException( - "Filter's numerator coefficients (b_coefficients) cannot be empty. Use " - f"{__class__.__name__}.b_coefficients, or the methods " + "Filter's numerator coefficients (b_coefficients) must be defined and cannot be " + f"empty. Use {__class__.__name__}.b_coefficients, or the methods " f"{__class__.__name__}.design_FIR_from_FRF() or " f"{__class__.__name__}.design_FIR_from_FRF_file()." ) # Set operator inputs. self.__operator_filter.connect(0, self.signal) - self.__operator_filter.connect(1, self.b_coefficients) - self.__operator_filter.connect(2, self.a_coefficients) + self.__operator_filter.connect(1, list(self.b_coefficients)) + self.__operator_filter.connect(2, list(self.a_coefficients)) # Run the operator. self.__operator_filter.run() diff --git a/tests/tests_signal_processing/test_signal_processing_filter.py b/tests/tests_signal_processing/test_signal_processing_filter.py index de2ddc783..d6b80a060 100644 --- a/tests/tests_signal_processing/test_signal_processing_filter.py +++ b/tests/tests_signal_processing/test_signal_processing_filter.py @@ -60,8 +60,8 @@ def test_filter_instantiation_no_arg(): """Test Filter instantiation without arguments.""" filter = Filter() assert isinstance(filter, Filter) - assert len(filter.a_coefficients) == 0 - assert len(filter.b_coefficients) == 0 + assert filter.a_coefficients is None + assert filter.b_coefficients is None assert filter.signal is None @@ -254,8 +254,8 @@ def test_filter_process_exceptions(): with pytest.raises( PyAnsysSoundException, match=( - "Filter's denominator coefficients \\(a_coefficients\\) cannot be empty. Use " - "Filter.a_coefficients, or the methods Filter.design_FIR_from_FRF\\(\\) or " + "Filter's denominator coefficients \\(a_coefficients\\) must be defined and cannot be " + "empty. Use Filter.a_coefficients, or the methods Filter.design_FIR_from_FRF\\(\\) or " "Filter.design_FIR_from_FRF_file\\(\\)." ), ): @@ -267,8 +267,8 @@ def test_filter_process_exceptions(): with pytest.raises( PyAnsysSoundException, match=( - "Filter's numerator coefficients \\(b_coefficients\\) cannot be empty. Use " - "Filter.b_coefficients, or the methods Filter.design_FIR_from_FRF\\(\\) or " + "Filter's numerator coefficients \\(b_coefficients\\) must be defined and cannot be " + "empty. Use Filter.b_coefficients, or the methods Filter.design_FIR_from_FRF\\(\\) or " "Filter.design_FIR_from_FRF_file\\(\\)." ), ): From 8a2ff315c3c611153c108d8c049a215dc046b365 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:26:07 +0000 Subject: [PATCH 2/5] chore: adding changelog file 214.miscellaneous.md [dependabot-skip] --- doc/changelog.d/214.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/214.miscellaneous.md diff --git a/doc/changelog.d/214.miscellaneous.md b/doc/changelog.d/214.miscellaneous.md new file mode 100644 index 000000000..d0437f49f --- /dev/null +++ b/doc/changelog.d/214.miscellaneous.md @@ -0,0 +1 @@ +fix: replace mutable empty list by None in Filter class constructor \ No newline at end of file From e4c53d674e2aaf5310d181cef5efc8e3058d52dc Mon Sep 17 00:00:00 2001 From: Antoine Minard Date: Mon, 20 Jan 2025 17:30:59 +0100 Subject: [PATCH 3/5] Removed cast to list --- src/ansys/sound/core/signal_processing/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/sound/core/signal_processing/filter.py b/src/ansys/sound/core/signal_processing/filter.py index 8428311b3..6c8cb9c6f 100644 --- a/src/ansys/sound/core/signal_processing/filter.py +++ b/src/ansys/sound/core/signal_processing/filter.py @@ -272,8 +272,8 @@ def process(self): # Set operator inputs. self.__operator_filter.connect(0, self.signal) - self.__operator_filter.connect(1, list(self.b_coefficients)) - self.__operator_filter.connect(2, list(self.a_coefficients)) + self.__operator_filter.connect(1, self.b_coefficients) + self.__operator_filter.connect(2, self.a_coefficients) # Run the operator. self.__operator_filter.run() From 5529a13fe413d64085a19b37fb1496c906750655 Mon Sep 17 00:00:00 2001 From: Antoine Minard Date: Tue, 21 Jan 2025 08:45:53 +0100 Subject: [PATCH 4/5] Added doc link --- src/ansys/sound/core/signal_processing/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/sound/core/signal_processing/filter.py b/src/ansys/sound/core/signal_processing/filter.py index 6c8cb9c6f..75938237a 100644 --- a/src/ansys/sound/core/signal_processing/filter.py +++ b/src/ansys/sound/core/signal_processing/filter.py @@ -55,8 +55,8 @@ class Filter(SignalProcessingParent): .. note:: Whether they are derived from the provided FRF or specified directly, the filter - coefficients are linked to the sampling frequency value that is given in the argument - ``sampling_frequency`` of the ``Filter`` class. As a consequence, the signal to filter + coefficients are linked to the sampling frequency value that is given in the attribute + :attr:`sampling_frequency` of the ``Filter`` class. As a consequence, the signal to filter :attr:`signal` must have the same sampling frequency. If necessary, use the :class:`.Resample` class to resample the signal prior to using the ``Filter`` class. """ From e4fb3034abd51553a9bf0994482d1923e0a5f28c Mon Sep 17 00:00:00 2001 From: Antoine Minard Date: Tue, 21 Jan 2025 15:54:37 +0100 Subject: [PATCH 5/5] Cast to list in case coefficients are passed as numpy arrays --- src/ansys/sound/core/signal_processing/filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/sound/core/signal_processing/filter.py b/src/ansys/sound/core/signal_processing/filter.py index 75938237a..561cfa09e 100644 --- a/src/ansys/sound/core/signal_processing/filter.py +++ b/src/ansys/sound/core/signal_processing/filter.py @@ -272,8 +272,8 @@ def process(self): # Set operator inputs. self.__operator_filter.connect(0, self.signal) - self.__operator_filter.connect(1, self.b_coefficients) - self.__operator_filter.connect(2, self.a_coefficients) + self.__operator_filter.connect(1, list(self.b_coefficients)) + self.__operator_filter.connect(2, list(self.a_coefficients)) # Run the operator. self.__operator_filter.run()