Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: replace mutable empty list by None in Filter class constructor #214

Merged
merged 12 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/214.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: replace mutable empty list by None in Filter class constructor
59 changes: 34 additions & 25 deletions src/ansys/sound/core/signal_processing/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,34 @@


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
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.
"""

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,
Expand All @@ -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.
Expand All @@ -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 "
Expand All @@ -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)

Expand Down Expand Up @@ -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()
Expand Down
12 changes: 6 additions & 6 deletions tests/tests_signal_processing/test_signal_processing_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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\\(\\)."
),
):
Expand All @@ -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\\(\\)."
),
):
Expand Down
Loading