Repository navigation
Skip already-rejected peaks in XQRS backsearch - #596
Merged
Merged
Conversation
A backsearch that finds nothing leaves last_qrs_peak_num unchanged, so the next one rescans the same peaks. Long stretches without QRS complexes were rescanned at every peak, which is quadratic. Start each backsearch after the last peak already checked; the result is unchanged because those peaks can only pass after a QRS update, which moves last_qrs_peak_num past them.
tompollard
approved these changes
Oct 31, 2026
tompollard
left a comment
Member
There was a problem hiding this comment.
Thanks, nice find! I checked it locally. The new test fails on main (backsearch checks 856 peaks 366,768 times in the 2-minute gap) and passes here. A random comparison of old and new code on sample-data records with inserted noise, flat and low-amplitude gaps gave identical qrs_inds, backsearch_qrs_inds and running parameters. Timings were similar to yours: 10 min gap 2.3 s → 0.7 s, 40 min gap 28.2 s → 1.6 s.
The skip depends on two things about the current code that look like they could be changed later:
_update_noisedoesn't recalculateqrs_thr._update_qrssetslast_qrs_peak_num = self.peak_num(the current peak) rather than thepeak_numargument.
If someone changes either of those later, this could break without any test catching it. I've raised an issue to suggest we might want to add a comment about this: #597
tompollard
pushed a commit
that referenced
this pull request
Oct 31, 2026
The skip added in #596 is only correct while _update_noise leaves qrs_thr alone and _update_qrs moves last_qrs_peak_num to the current peak. Name both in the comment and add a test that fails if either changes.
tompollard
added a commit
that referenced
this pull request
Oct 31, 2026
Closes #597. The skip added in #596 is only correct while two things stay true: - `_update_noise` doesn't change `qrs_thr`. - `_update_qrs` sets `last_qrs_peak_num` to the current peak. This PR: - Uses the comment suggested in #597 above the skip in `_backsearch`. - Adds `test_xqrs_backsearch_skip_assumptions`, which runs XQRS on record 100 with a 2-minute low-amplitude gap followed by scaled-down beats (so backsearch actually finds QRS complexes) and checks both properties after every update. I first tried a test comparing the skip against a full rescan, but it still passed when I broke either property, because the data never hit the case where they matter. The direct check fails for both changes: - `_update_noise` also recalculating `qrs_thr` → fails - `_update_qrs` setting `last_qrs_peak_num = peak_num` → fails No change in behaviour, so no changelog entry. `ruff check` is clean and `pytest -m "not network"` passes (83 tests).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When backsearch runs and finds nothing,
last_qrs_peak_numstays put. On the next peak backsearch runs again over the same range plus one more peak, and so on. A long stretch with no QRS complexes (lead off, flat line, low-amplitude noise) is therefore rescanned at every peak, which is quadratic in the length of the stretch. I think this is what #352 is seeing: most records are fine, the ones with a long bad section take hours.Record 100 with a gap of low-amplitude noise inserted:
The fix remembers the last peak a backsearch checked and starts the next one after it. A peak's backsearch result only depends on
qrs_thr,last_qrs_indand the signal, which only change in_update_qrs, and_update_qrsalways moveslast_qrs_peak_numup to the current peak. So the skipped peaks would have been rejected again, and the detector ends up in exactly the same state.Testing:
hr_init,ref_period,t_inspect_period, with and without learning).qrs_inds,backsearch_qrs_indsand all running parameters were identical.Fixes the slowdown in #352.