def _tag_qrs(waves): """ Creates a new string tag for a QRS complex from a sequence of waves. This tag matches the name given by cardiologists to the different QRS waveforms. """ #This method consists in a concatenation of heuristic rules described with #more or less precision in "European Heart Journal: Recommendations for #measurement standards in quantitative electrocardiography. (1985)". result = '' waves = list(waves) while waves: wav = waves.pop(0) #If the first wave is negative... if not result and wav.sign == -1: if not waves: result = 'QS' if abs(wav.amp) > ph2dg(0.5) else 'Q' else: result = 'Q' if abs(wav.amp) > ph2dg(0.2) else 'q' else: newt = 'r' if wav.sign == 1 else 's' if abs(wav.amp) > ph2dg(0.5): newt = newt.upper() result += newt return result
def _paced_qrs_delineation(signal, points, peak, baseline): """ Checks if a sequence of waves is a paced heartbeat. The main criteria is the presence of a spike at the beginning of the beat, followed by at least one significant wave. """ try: #Gets the slope between two points. slope = lambda a, b : abs(dg2mm((signal[b]-signal[a])/sp2mm(b-a))) #First we search for the spike. spike = _find_spike(signal, points) verify(spike) if not spike[-1] in points: points = np.insert(points, bisect.bisect(points, spike[-1]), spike[-1]) #Now we get relevant points, checking some related constraints. bpts = points[points <= spike[0]] apts = points[points >= spike[-1]] verify(len(apts) >= 2) #Before and after the spike there must be a significant slope change. verify(slope(spike[0], spike[1]) > 2.0 * slope(bpts[-2], bpts[-1])) verify(slope(spike[1], spike[-1]) > 2.0 * slope(apts[0], apts[1])) #Now we look for the end of the QRS complex, by applying the same #clustering strategy than regular QRS, but only for the end. slopes = (signal[apts][1:]-signal[apts][:-1])/(apts[1:]-apts[:-1]) features = [] for i in xrange(len(slopes)): #The features are the slope in logarithmic scale and the distance to #the peak. features.append([math.log(abs(slopes[i])+1.0), abs(apts[i+1] - peak)]) features = whiten(features) #We initialize the centroids in the extremes (considering what is #interesting of each feature for us) fmin = np.min(features, 0) fmax = np.max(features, 0) valid = np.where(kmeans2(features, np.array([[fmin[0], fmax[1]], [fmax[0], fmin[1]]]), minit = 'matrix')[1])[0] verify(np.any(valid)) end = apts[valid[-1]+1] #The duration of the QRS complex after the spike must be more than 2 #times the duration of the spike. verify((end-apts[0]) > 2.0 * (spike[-1]-spike[0])) #The amplitude of the qrs complex must higher than 0.5 the amplitude #of the spike. sgspike = signal[spike[0]:spike[-1]+1] sgqrs = signal[apts[0]:end+1] verify(np.ptp(sgqrs) > ph2dg(0.5)) verify(np.ptp(sgqrs) > 0.5 * np.ptp(sgspike)) #There must be at least one peak in the QRS fragment. qrspt = signal[apts[apts <= end]] verify(len(qrspt) >= 3) verify(abs(signal[end] - signal[spike[0]]) <= ph2dg(0.3) or len(get_peaks(qrspt)) > 0) #The area of the rest of the QRS complex must be higher than the spike. verify(np.sum(np.abs(sgspike-sgspike[0])) < np.sum(np.abs(sgqrs-sgspike[0]))) #The distance between the beginning of the spike and the baseline #cannot be more than the 30% of the amplitude of the complex. verify(abs(signal[spike[0]]-baseline) < 0.3 * np.ptp(signal[spike[0]:end+1])) #At last, we have found the paced QRS limits. return Iv(spike[0], end) except InconsistencyError: return None