Skip to content

Commit b9c61aa

Browse files
authored
Merge pull request #321 from hofaflo/no-sklearn
Remove dependency on scikit-learn
2 parents ae67d09 + c5960e2 commit b9c61aa

File tree

5 files changed

+53
-43
lines changed

5 files changed

+53
-43
lines changed

docs/conf.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ class Mock(MagicMock):
2929
def __getattr__(cls, name):
3030
return MagicMock()
3131

32-
MOCK_MODULES = ['numpy', 'matplotlib', 'matplotlib.pyplot', 'pandas', 'scipy',
33-
'sklearn', 'sklearn.preprocessing']
32+
MOCK_MODULES = ['numpy', 'matplotlib', 'matplotlib.pyplot', 'pandas', 'scipy']
3433
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
3534

3635
# -- General configuration ------------------------------------------------
@@ -177,6 +176,3 @@ def __getattr__(cls, name):
177176
author, 'wfdb', 'One line description of project.',
178177
'Miscellaneous'),
179178
]
180-
181-
182-

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ nose==1.3.7
33
numpy==1.18.5
44
pandas==1.0.3
55
requests==2.23.0
6-
scikit-learn==0.22.2.post1
76
scipy==1.4.1

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
'numpy>=1.10.1',
6161
'pandas>=0.17.0',
6262
'requests>=2.8.1',
63-
'scikit-learn>=0.18',
6463
'scipy>=0.17.0',
6564
],
6665

wfdb/processing/basic.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def get_filter_gain(b, a, f_gain, fs):
215215
The frequency at which to calculate the gain.
216216
fs : int, float, optional
217217
The sampling frequency of the system.
218-
218+
219219
Returns
220220
-------
221221
gain : int, float
@@ -230,3 +230,21 @@ def get_filter_gain(b, a, f_gain, fs):
230230
gain = abs(h[ind])
231231

232232
return gain
233+
234+
235+
def normalize(X):
236+
"""
237+
Scale input vector to unit norm (vector length).
238+
239+
Parameters
240+
----------
241+
X : ndarray
242+
The vector to normalize.
243+
244+
Returns
245+
-------
246+
ndarray
247+
The normalized vector.
248+
249+
"""
250+
return X / np.linalg.norm(X)

wfdb/processing/qrs.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33

44
import numpy as np
55
from scipy import signal
6-
from sklearn.preprocessing import normalize
76

8-
from wfdb.processing.basic import get_filter_gain
7+
from wfdb.processing.basic import get_filter_gain, normalize
98
from wfdb.processing.peaks import find_local_peaks
109
from wfdb.io.record import Record
1110

1211

1312
class XQRS(object):
1413
"""
15-
The QRS detector class for the XQRS algorithm. The `XQRS.Conf`
16-
class is the configuration class that stores initial parameters
14+
The QRS detector class for the XQRS algorithm. The `XQRS.Conf`
15+
class is the configuration class that stores initial parameters
1716
for the detection. The `XQRS.detect` method runs the detection algorithm.
1817
1918
The process works as follows:
@@ -85,7 +84,7 @@ class Conf(object):
8584
----------
8685
hr_init : int, float, optional
8786
Initial heart rate in beats per minute. Used for calculating
88-
recent R-R intervals.
87+
recent R-R intervals.
8988
hr_max : int, float, optional
9089
Hard maximum heart rate between two beats, in beats per
9190
minute. Used for refractory period.
@@ -104,13 +103,13 @@ class Conf(object):
104103
ref_period : int, float, optional
105104
The QRS refractory period.
106105
t_inspect_period : int, float, optional
107-
The period below which a potential QRS complex is
108-
inspected to see if it is a T-wave.
106+
The period below which a potential QRS complex is inspected to
107+
see if it is a T-wave. Leave as 0 for no T-wave inspection.
109108
110109
"""
111110
def __init__(self, hr_init=75, hr_max=200, hr_min=25, qrs_width=0.1,
112111
qrs_thr_init=0.13, qrs_thr_min=0, ref_period=0.2,
113-
t_inspect_period=0.36):
112+
t_inspect_period=0):
114113
if hr_min < 0:
115114
raise ValueError("'hr_min' must be >= 0")
116115

@@ -134,7 +133,7 @@ def __init__(self, hr_init=75, hr_max=200, hr_min=25, qrs_width=0.1,
134133
def _set_conf(self):
135134
"""
136135
Set configuration parameters from the Conf object into the detector
137-
object. Time values are converted to samples, and amplitude values
136+
object. Time values are converted to samples, and amplitude values
138137
are in mV.
139138
140139
Parameters
@@ -288,10 +287,10 @@ def _learn_init_params(self, n_calib_beats=8):
288287

289288
# Question: should the signal be squared? Case for inverse QRS
290289
# complexes
291-
sig_segment = normalize((self.sig_f[i - self.qrs_radius:
292-
i + self.qrs_radius]).reshape(-1, 1), axis=0)
290+
sig_segment = normalize(self.sig_f[i - self.qrs_radius:
291+
i + self.qrs_radius])
293292

294-
xcorr = np.correlate(sig_segment[:, 0], ricker_wavelet[:,0])
293+
xcorr = np.correlate(sig_segment, ricker_wavelet[:,0])
295294

296295
# Classify as QRS if xcorr is large enough
297296
if xcorr > 0.6 and i-last_qrs_ind > self.rr_min:
@@ -470,15 +469,15 @@ def _update_qrs(self, peak_num, backsearch=False):
470469
The peak number of the MWI signal where the QRS is detected.
471470
backsearch: bool, optional
472471
Whether the QRS was found via backsearch.
473-
472+
474473
Returns
475474
-------
476475
N/A
477476
478477
"""
479478
i = self.peak_inds_i[peak_num]
480479

481-
# Update recent R-R interval if the beat is consecutive (do this
480+
# Update recent R-R interval if the beat is consecutive (do this
482481
# before updating self.last_qrs_ind)
483482
rr_new = i - self.last_qrs_ind
484483
if rr_new < self.rr_max:
@@ -514,7 +513,7 @@ def _is_twave(self, peak_num):
514513
----------
515514
peak_num : int
516515
The peak number of the MWI signal where the QRS is detected.
517-
516+
518517
Returns
519518
-------
520519
bool
@@ -530,8 +529,7 @@ def _is_twave(self, peak_num):
530529

531530
# Get half the QRS width of the signal to the left.
532531
# Should this be squared?
533-
sig_segment = normalize((self.sig_f[i - self.qrs_radius:i]
534-
).reshape(-1, 1), axis=0)
532+
sig_segment = normalize(self.sig_f[i - self.qrs_radius:i])
535533
last_qrs_segment = self.sig_f[self.last_qrs_ind - self.qrs_radius:
536534
self.last_qrs_ind]
537535

@@ -901,7 +899,7 @@ def __init__(self, fs, adc_gain, hr=75,
901899
class Peak(object):
902900
"""
903901
Holds all of the peak information for the QRS object.
904-
902+
905903
Attributes
906904
----------
907905
peak_time : int, float
@@ -923,7 +921,7 @@ def __init__(self, peak_time, peak_amp, peak_type):
923921
class Annotation(object):
924922
"""
925923
Holds all of the annotation information for the QRS object.
926-
924+
927925
Attributes
928926
----------
929927
ann_time : int, float
@@ -1160,8 +1158,8 @@ def qfv_put(self, t, v):
11601158

11611159
def sm(self, at_t):
11621160
"""
1163-
Implements a trapezoidal low pass (smoothing) filter (with a gain
1164-
of 4*smdt) applied to input signal sig before the QRS matched
1161+
Implements a trapezoidal low pass (smoothing) filter (with a gain
1162+
of 4*smdt) applied to input signal sig before the QRS matched
11651163
filter qf(). Before attempting to 'rewind' by more than BUFLN-smdt
11661164
samples, reset smt and smt0.
11671165
@@ -1220,7 +1218,7 @@ def qf(self):
12201218
N/A
12211219
12221220
"""
1223-
# Do this first, to ensure that all of the other smoothed values
1221+
# Do this first, to ensure that all of the other smoothed values
12241222
# needed below are in the buffer
12251223
dv2 = self.sm(self.t + self.c.dt4)
12261224
dv2 -= self.smv_at(self.t - self.c.dt4)
@@ -1302,17 +1300,17 @@ def add_peak(peak_time, peak_amp, peak_type):
13021300

13031301
def peaktype(p):
13041302
"""
1305-
The neighborhood consists of all other peaks within rrmin.
1306-
Normally, "most prominent" is equivalent to "largest in
1307-
amplitude", but this is not always true. For example, consider
1308-
three consecutive peaks a, b, c such that a and b share a
1309-
neighborhood, b and c share a neighborhood, but a and c do not;
1310-
and suppose that amp(a) > amp(b) > amp(c). In this case, if
1303+
The neighborhood consists of all other peaks within rrmin.
1304+
Normally, "most prominent" is equivalent to "largest in
1305+
amplitude", but this is not always true. For example, consider
1306+
three consecutive peaks a, b, c such that a and b share a
1307+
neighborhood, b and c share a neighborhood, but a and c do not;
1308+
and suppose that amp(a) > amp(b) > amp(c). In this case, if
13111309
there are no other peaks, a is the most prominent peak in the (a, b)
1312-
neighborhood. Since b is thus identified as a non-prominent peak,
1313-
c becomes the most prominent peak in the (b, c) neighborhood.
1314-
This is necessary to permit detection of low-amplitude beats that
1315-
closely precede or follow beats with large secondary peaks (as,
1310+
neighborhood. Since b is thus identified as a non-prominent peak,
1311+
c becomes the most prominent peak in the (b, c) neighborhood.
1312+
This is necessary to permit detection of low-amplitude beats that
1313+
closely precede or follow beats with large secondary peaks (as,
13161314
for example, in R-on-T PVCs).
13171315
13181316
Parameters
@@ -1323,7 +1321,7 @@ def peaktype(p):
13231321
Returns
13241322
-------
13251323
int
1326-
Whether the input peak is the most prominent peak in its
1324+
Whether the input peak is the most prominent peak in its
13271325
neighborhood (1) or not (2).
13281326
13291327
"""
@@ -1534,8 +1532,8 @@ def gqrs_detect(sig=None, fs=None, d_sig=None, adc_gain=None, adc_zero=None,
15341532
"""
15351533
Detect QRS locations in a single channel ecg. Functionally, a direct port
15361534
of the GQRS algorithm from the original WFDB package. Accepts either a
1537-
physical signal, or a digital signal with known adc_gain and adc_zero. See
1538-
the notes below for a summary of the program. This algorithm is not being
1535+
physical signal, or a digital signal with known adc_gain and adc_zero. See
1536+
the notes below for a summary of the program. This algorithm is not being
15391537
developed/supported.
15401538
15411539
Parameters

0 commit comments

Comments
 (0)