From 40ce83e80fda7541166bb6942574e6ea946632d3 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Wed, 5 Aug 2026 05:12:32 -0700 Subject: [PATCH 1/3] Fix chebyshev band pass and band stop returning NaN The chebyshev band pass and band stop designs in DSPFilters derive from ChebyshevI::Design::TypeII, which takes five params in the order sample rate, order, center frequency, band width, ripple. The low pass and high pass designs derive from TypeI and take four, with the ripple in slot three. perform_bandpass and perform_bandstop wrote the ripple into slot three for every chebyshev filter type. For band pass and band stop that overwrote the band width and left the real ripple slot unwritten, so the design was built from a bogus band width and an uninitialized ripple and every output sample came back NaN. The exit code was still STATUS_OK. Write the ripple into slot four for the band designs and clear the params struct first, since Dsp::Params has no constructor. --- src/data_handler/data_handler.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/data_handler/data_handler.cpp b/src/data_handler/data_handler.cpp index 106c614dd..a52b0db3e 100644 --- a/src/data_handler/data_handler.cpp +++ b/src/data_handler/data_handler.cpp @@ -294,14 +294,17 @@ int perform_bandpass (double *data, int data_len, int sampling_rate, double star } Dsp::Params params; + params.clear (); params[0] = sampling_rate; // sample rate params[1] = order; // order params[2] = center_freq; // center freq - params[3] = band_width; + params[3] = band_width; // band width if ((filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1) || (filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1_ZERO_PHASE)) { - params[3] = ripple; // ripple + // band pass and band stop chebyshev designs take 5 params and expect the ripple after + // the band width, unlike the low pass and high pass designs which take 4 + params[4] = ripple; // ripple } f->setParams (params); f->process (data_len, filter_data); @@ -362,14 +365,17 @@ int perform_bandstop (double *data, int data_len, int sampling_rate, double star } Dsp::Params params; + params.clear (); params[0] = sampling_rate; // sample rate params[1] = order; // order params[2] = center_freq; // center freq - params[3] = band_width; + params[3] = band_width; // band width if ((filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1) || (filter_type == (int)FilterTypes::CHEBYSHEV_TYPE_1_ZERO_PHASE)) { - params[3] = ripple; // ripple + // band pass and band stop chebyshev designs take 5 params and expect the ripple after + // the band width, unlike the low pass and high pass designs which take 4 + params[4] = ripple; // ripple } f->setParams (params); f->process (data_len, filter_data); From 5e99344f4931dcc86b051a278e18e76137917598 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Thu, 6 Aug 2026 20:32:52 -0700 Subject: [PATCH 2/3] Add chebyshev band pass and band stop regression test Drives both filters with an in-band and an out-of-band tone, asserts every output sample is finite, and checks the kept tone survives above 0.8 while the rejected one falls below 0.05. --- .../examples/tests/chebyshev_band_filters.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 python_package/examples/tests/chebyshev_band_filters.py diff --git a/python_package/examples/tests/chebyshev_band_filters.py b/python_package/examples/tests/chebyshev_band_filters.py new file mode 100644 index 000000000..777071bc9 --- /dev/null +++ b/python_package/examples/tests/chebyshev_band_filters.py @@ -0,0 +1,56 @@ +import numpy as np + +from brainflow.data_filter import DataFilter, FilterTypes + +SAMPLING_RATE = 256 +PASS_FREQ = 10.0 +STOP_FREQ = 50.0 +CENTER_LOW = 5.0 +CENTER_HIGH = 15.0 +ORDER = 4 +RIPPLE = 1.0 + + +def amplitude_at(signal, freq): + spectrum = np.abs(np.fft.rfft(signal)) * 2 / len(signal) + return spectrum[int(round(freq * len(signal) / SAMPLING_RATE))] + + +def main(): + samples = SAMPLING_RATE * 4 + t = np.arange(samples) / SAMPLING_RATE + # One tone inside the 5 to 15 Hz band and one well outside it. + signal = np.sin(2 * np.pi * PASS_FREQ * t) + np.sin(2 * np.pi * STOP_FREQ * t) + + assert np.isclose(amplitude_at(signal, PASS_FREQ), 1.0, atol=0.01) + assert np.isclose(amplitude_at(signal, STOP_FREQ), 1.0, atol=0.01) + + # Chebyshev type 1 band pass and band stop take five design parameters and expect the + # ripple after the band width. Writing it into slot 3 overwrote the band width, which + # produced an unusable design and an all-NaN output. + for name, apply_filter, kept, removed in ( + ('bandpass', DataFilter.perform_bandpass, PASS_FREQ, STOP_FREQ), + ('bandstop', DataFilter.perform_bandstop, STOP_FREQ, PASS_FREQ), + ): + filtered = np.copy(signal) + apply_filter( + filtered, + SAMPLING_RATE, + CENTER_LOW, + CENTER_HIGH, + ORDER, + FilterTypes.CHEBYSHEV_TYPE_1.value, + RIPPLE, + ) + + assert np.all(np.isfinite(filtered)), '%s produced non-finite samples' % name + # The tone the filter is meant to keep survives close to full amplitude. + assert amplitude_at(filtered, kept) > 0.8, (name, amplitude_at(filtered, kept)) + # The tone it is meant to reject is pushed far down. + assert amplitude_at(filtered, removed) < 0.05, (name, amplitude_at(filtered, removed)) + + print('chebyshev band filter regression passed') + + +if __name__ == '__main__': + main() From 1d533670e2f5a6a33d318af0699ba3e3a80d5d2f Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Thu, 6 Aug 2026 20:37:26 -0700 Subject: [PATCH 3/3] Run the new regression test in unix CI --- .github/workflows/run_unix.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run_unix.yml b/.github/workflows/run_unix.yml index 557001dae..c003901d9 100644 --- a/.github/workflows/run_unix.yml +++ b/.github/workflows/run_unix.yml @@ -354,6 +354,8 @@ jobs: run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/transforms.py - name: Downsampling Python run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/downsampling.py + - name: Chebyshev Band Filters Python + run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/chebyshev_band_filters.py - name: ICA Python run: sudo -H python3 $GITHUB_WORKSPACE/python_package/examples/tests/ica.py - name: CSP Python