`find_local_peaks` ignores the sample at `i + radius`, so points on a rising slope are returned as peaks
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 84/100
Rechercherichtung
Beginne in wfdb/processing/peaks.py bei find_local_peaks und untersuche die Fensterslices in seinen Schleifen. Verwende die bereitgestellte Reproduktion, um zu überprüfen, dass die rechte Grenze für jeden Radius berücksichtigt wird, und prüfe anschließend die XQRS-Aufrufstellen in qrs.py auf Kompatibilität. Als erledigt gilt die Aufgabe, wenn die Ausgaben mit den Referenzfällen übereinstimmen, ohne das separate flat-top TODO zu ändern.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
The docstring of wfdb.processing.find_local_peaks says a sample is a local peak "if it is the largest value within the samples on its left and right". The window it actually checks leaves out the right side:
# wfdb/processing/peaks.py
while i < radius + 1:
if sig[i] == max(sig[: i + radius]): # right side stops at i + radius - 1
...
while i < len(sig):
if sig[i] == max(sig[i - radius : i + radius]): # sample i + radius is never looked at
Python slices exclude the end, so sig[i + radius] is never compared. With radius=1 the right neighbour is not checked at all, so every sample on a rising edge counts as a peak. With bigger radii a sample still wins if the only larger value is exactly radius samples to its right.
Reproduction
import numpy as np
from wfdb.processing import find_local_peaks
def reference(sig, radius):
# what the docstring describes
out = []
for i in range(len(sig)):
lo, hi = max(0, i - radius), min(len(sig), i + radius + 1)
if sig[i] == sig[lo:hi].max():
out.append(i)
return out
for sig, r in [
(np.array([0, 1, 2, 3, 2, 1, 0], dtype=float), 1),
(np.array([0, 1, 3, 2, 10, 5, 1, 0], dtype=float), 2),
]:
print(sig.astype(int).tolist(), "radius", r)
print(" find_local_peaks:", find_local_peaks(sig, r).tolist())
print(" expected :", reference(sig, r))
Output:
[0, 1, 2, 3, 2, 1, 0] radius 1
find_local_peaks: [0, 1, 2, 3]
expected : [3]
[0, 1, 3, 2, 10, 5, 1, 0] radius 2
find_local_peaks: [2, 4]
expected : [4]
In the first case indices 0, 1 and 2 are all on the way up to the real maximum at index 3. In the second case index 2 (value 3) is reported even though index 4 (value 10) is only 2 samples away.
Why it matters
XQRS uses this function to pick QRS candidates (qrs.py, lines 236 and 283), so a candidate can end up on the slope next to the real peak, not on the peak itself. With real ECG sampling rates the radius is large, so this probably shows up rarely there, but anyone calling find_local_peaks directly with a small radius gets wrong results.
Possible fix
Include the right edge in both slices (sig[: i + radius + 1] and sig[i - radius : i + radius + 1]). The third loop is unreachable (already reported in #474), so it can go away in the same change. I have not touched the flat-top case that the TODO comment mentions; that looks like a separate question.
Versions
- wfdb 4.3.1 from PyPI, and also current
main(f627b5f), same output - numpy 2.5.3, Python 3.12.13, macOS 26.4.1
- Vorherrschende Sprache
- Jupyter Notebook
- Sterne
- 853
- Forks
- 323
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Entwicklungsumgebung
Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus MIT-LCP/wfdb-python
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 45/100
MIT-LCP/wfdb-python#568 ·
-
Add validation of segments when writing a `MultiRecord`Evtl. vergeben Ein verknüpfter Pull Request ist offen oder bereits gemergt. Offen
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 48/100
MIT-LCP/wfdb-python#557 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 58/100
MIT-LCP/wfdb-python#554 ·
-
WFDB path ignoredOffen
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 35/100
MIT-LCP/wfdb-python#545 ·
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 30/100
MIT-LCP/wfdb-python#540 ·
Alle Issues in MIT-LCP/wfdb-python
Ähnliche Issues
-
bug difficulty:2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
MakazhanAlpamys/Soup#1856 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Add: MS NOWOffencheck:passed streams:add
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
Maintainer antworten meist innerhalb von 1 Tag
-
Boolean statistics (Covered, Confident, JaccardDistant) aggregate to 1.0 without float weightsOffen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 63/100
google-research/weatherbenchX#310 ·
Maintainer antworten meist innerhalb von 3 Tagen
-
request-theme
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 65/100
LizardByte/ThemerrDB#8924 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
automation missing-model model-sync provider:aimlapi
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
anomalyco/models.dev#9734 ·
Maintainer antworten meist innerhalb von 1 Tag