Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

`find_local_peaks` ignores the sample at `i + radius`, so points on a rising slope are returned as peaks

Ouverte Adaptée aux débutants
#572 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
84/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
numpy, python
Domaine
data

Piste de recherche

Commencez dans wfdb/processing/peaks.py au niveau de find_local_peaks et examinez les tranches de fenêtre dans ses boucles. Utilisez la reproduction fournie pour vérifier que la limite droite est prise en compte pour chaque rayon, puis vérifiez la compatibilité des points d’appel de XQRS dans qrs.py. Le travail est terminé lorsque les sorties correspondent aux cas de référence sans modifier le TODO séparé de flat-top.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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
Langage dominant
Jupyter Notebook
Étoiles
853
Forks
322
Métriques de merge des PR
Aucune PR mergée en 30 j

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de MIT-LCP/wfdb-python

Toutes les issues de MIT-LCP/wfdb-python

Issues similaires

Plus d'issues Data Engineering

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.