From da08f5b7d034a7dc4d6c62f43b2d0217bb5070ce Mon Sep 17 00:00:00 2001 From: Bharat Kaurav <117659673+TheNetherWatcher@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:52:06 +0530 Subject: [PATCH 1/2] Include the right edge of the search window in correct_peaks shift_peaks sliced sig[ind - r : min(ind + r, sig_len - 1)], which leaves out ind + r and the last sample of the signal. A peak exactly search_radius samples after the original index, or on the last sample, could not be found. --- docs/changes.rst | 3 +++ src/wfdb/processing/peaks.py | 2 +- tests/test_processing.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 8c63c074..03cbb1e8 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -38,6 +38,9 @@ Unreleased **Use Ruff for formatting and linting** The ``dev`` extra now includes ``ruff`` instead of ``black`` and ``pylint``, and the code is formatted with a line length of 88 (the default). +**Fix search window in correct_peaks** + ``correct_peaks`` now searches ``search_radius`` samples on both sides of each peak, including the last sample of the signal. It used to stop one sample short on the right, so a peak exactly ``search_radius`` samples after the original index, or on the last sample, could not be found. + **Fix resample_sig for signals starting with NaN** ``resample_sig`` (and the functions that use it, such as ``resample_singlechan`` and ``resample_multichan``) returned only NaN values if the signal started with NaN. Leading NaN values now take the first valid value before resampling, as trailing ones already took the last. diff --git a/src/wfdb/processing/peaks.py b/src/wfdb/processing/peaks.py index 7c618d91..6dcc9ed6 100644 --- a/src/wfdb/processing/peaks.py +++ b/src/wfdb/processing/peaks.py @@ -225,7 +225,7 @@ def shift_peaks(sig, peak_inds, search_radius, peak_up): for i in range(n_peaks): ind = peak_inds[i] local_sig = sig[ - max(0, ind - search_radius) : min(ind + search_radius, sig_len - 1) + max(0, ind - search_radius) : min(ind + search_radius + 1, sig_len) ] if peak_up: diff --git a/tests/test_processing.py b/tests/test_processing.py index 60bb7bd0..1e5943b1 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -195,6 +195,21 @@ def test_correct_peaks(self): assert np.array_equal(yz, expected_peaks) + def test_correct_peaks_window_edges(self): + """Peaks at the edge of the search radius or the signal are found.""" + sig = np.zeros(30) + sig[13] = 5 + sig[29] = 5 + + # Peak exactly search_radius samples after the guess + assert processing.correct_peaks( + sig, np.array([10]), search_radius=3, smooth_window_size=9, peak_dir="up" + ).tolist() == [13] + # Peak on the last sample of the signal + assert processing.correct_peaks( + sig, np.array([27]), search_radius=5, smooth_window_size=9, peak_dir="up" + ).tolist() == [29] + class TestComputeHr: """Tests for compute_hr.""" From c68ff135e97882ae42fb7a95bd66f0edce00ea32 Mon Sep 17 00:00:00 2001 From: Bharat Kaurav <117659673+TheNetherWatcher@users.noreply.github.com> Date: Sat, 31 Oct 2026 23:10:11 +0530 Subject: [PATCH 2/2] Fix correct_peaks for peaks near the start of the signal For peaks closer to the start than search_radius, the search window starts at 0, so argmax already gives the absolute index. The offset was subtracted instead of added, moving these peaks to the wrong place, often before the start of the signal. --- docs/changes.rst | 3 +++ src/wfdb/processing/peaks.py | 3 ++- tests/test_processing.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index 03cbb1e8..27c74f89 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -41,6 +41,9 @@ Unreleased **Fix search window in correct_peaks** ``correct_peaks`` now searches ``search_radius`` samples on both sides of each peak, including the last sample of the signal. It used to stop one sample short on the right, so a peak exactly ``search_radius`` samples after the original index, or on the last sample, could not be found. +**Fix correct_peaks for peaks near the start of the signal** + ``correct_peaks`` no longer moves peaks within ``search_radius`` samples of the start of the signal to the wrong place, often before the start (for example ``-6`` instead of ``0``). The offset for these peaks was subtracted instead of added. + **Fix resample_sig for signals starting with NaN** ``resample_sig`` (and the functions that use it, such as ``resample_singlechan`` and ``resample_multichan``) returned only NaN values if the signal started with NaN. Leading NaN values now take the first valid value before resampling, as trailing ones already took the last. diff --git a/src/wfdb/processing/peaks.py b/src/wfdb/processing/peaks.py index 6dcc9ed6..eab41894 100644 --- a/src/wfdb/processing/peaks.py +++ b/src/wfdb/processing/peaks.py @@ -238,7 +238,8 @@ def shift_peaks(sig, peak_inds, search_radius, peak_up): ind = peak_inds[i] if ind >= search_radius: break - shift_inds[i] -= search_radius - ind + # The window starts at 0 rather than ind - search_radius + shift_inds[i] += search_radius - ind shifted_peak_inds = peak_inds + shift_inds - search_radius diff --git a/tests/test_processing.py b/tests/test_processing.py index 1e5943b1..57a62c72 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -210,6 +210,17 @@ def test_correct_peaks_window_edges(self): sig, np.array([27]), search_radius=5, smooth_window_size=9, peak_dir="up" ).tolist() == [29] + @pytest.mark.parametrize("peak", [0, 1, 4, 6]) + @pytest.mark.parametrize("peak_dir", ["up", "down"]) + def test_correct_peaks_near_start(self, peak, peak_dir): + """Peaks closer to the start than search_radius aren't shifted below 0.""" + sig = np.zeros(30) + sig[peak] = 5 if peak_dir == "up" else -5 + + assert processing.correct_peaks( + sig, np.array([2]), search_radius=5, smooth_window_size=9, peak_dir=peak_dir + ).tolist() == [peak] + class TestComputeHr: """Tests for compute_hr."""