def _qrs_gconst(pattern, _): """ General constraints to be added when a new cycle is observed, which currently coincides with the observation of the T waves or a QRS complex not followed by an observed T wave. """ #We check that there are no missed beat forms. _check_missed_beats(pattern) beats = pattern.evidence[o.QRS] #Morphology check. We require the rhythm morphology to be matched #by the new beat in the sequence. ref = pattern.hypothesis.morph #We initialize the morphology with the first beat. if not ref: ref = copy.deepcopy(beats[0].shape) pattern.hypothesis.morph = ref verify(signal_match(ref, beats[-1].shape)) #This comparison avoids positive matchings with extrasystoles, #but we only check it if the beat before the first block is advanced. if len(beats) == 3: refrr, stdrr = pattern.hypothesis.meas.rr if (beats[1].time.start - beats[0].time.start < min(0.9 * refrr, refrr-stdrr)): verify(beats[2].time.start - beats[0].time.start > refrr * C.COMPAUSE_MAX_F) verify(beats[2].time.start - beats[1].time.start > refrr + C.COMPAUSE_MIN_DUR) #We require a significant change in consecutive RR intervals. if len(beats) >= 3: rr = beats[-1].time.start - beats[-2].time.start prevrr = beats[-2].time.start - beats[-3].time.start verify(abs(prevrr-rr) >= C.RR_MAX_DIFF)
def _pair_gconst(pattern, _): """ General constraints to be satisfied when a regular rhythm consists of only two beats. """ if pattern.evidence[o.Cardiac_Rhythm]: _check_missed_beats(pattern) prhythm = pattern.evidence[o.Cardiac_Rhythm][0] rhythm = pattern.hypothesis #Previous rhythm cannot be a regular rhythm. verify(not isinstance(prhythm, o.RegularCardiacRhythm)) mrr, stdrr = prhythm.meas.rr beats = pattern.evidence[o.QRS] rr = beats[-1].time.start - beats[0].time.start verify(rr in rr_bounds) #Avoid duplicate hypotheses with overlapping rhythms. if pattern.automata is SINUS_PATTERN: verify(C.TACHY_RR.end < rr < C.BRADY_RR.start) maxvar = max(C.TMARGIN, min(C.RR_MAX_DIFF, 2.5 * stdrr)) verify(rr in Iv(mrr - maxvar, mrr + maxvar)) #Besides being in rhythm, the two beats must share the morphology. verify(signal_match(beats[0].shape, beats[1].shape)) #The amplitude difference is also constrained for lead in beats[0].shape: if lead in beats[1].shape: samp, qamp = (beats[0].shape[lead].amplitude, beats[1].shape[lead].amplitude) verify( min(samp, qamp) / max(samp, qamp) >= C.MISSED_QRS_MAX_DIFF) rhythm.meas = o.CycleMeasurements( (rr, stdrr), (prhythm.meas.rt[0], C.QT_ERR_STD), (prhythm.meas.pq[0], C.QT_ERR_STD))
def _extrasyst_gconst(pattern, _): """ General constraints of the pattern that are checked when the last T wave has been observed. """ beats = pattern.evidence[o.QRS] if pattern.istate == 0: #We ensure that there are no missed beats. _check_missed_beats(pattern) #We must ensure that the first two beats and the last one have the #same shape. verify((beats[-3].paced and beats[-1].paced) or signal_match(beats[-3].shape, beats[-1].shape))
def _verify_atrial_activity(pattern): """ Checks if the atrial activity is consistent with the definition of atrial fibrillation (that is, absence of constant P Waves or flutter-like baseline activity.) """ beats = pattern.evidence[o.QRS][-5:] obseq = pattern.obs_seq atr_sig = {lead: [] for lead in sig_buf.get_available_leads()} pw_lims = [] idx = pattern.get_step(beats[0]) #First we get all the signal fragments between ventricular observations, #which are the only recognized by this pattern. In these fragments is where #atrial activity may be recognized. for i in xrange(idx + 1, len(obseq)): if isinstance(obseq[i], o.QRS): beg = next(obs for obs in reversed(obseq[:i]) if obs is not None).lateend end = obseq[i].earlystart if end - beg > ms2sp(200): beg = end - ms2sp(200) pw_lims.append((beg, end)) for i in xrange(len(beats) - 1): beg, end = beats[i].lateend, beats[i + 1].earlystart for lead in atr_sig: atr_sig[lead].append( sig_buf.get_signal_fragment(beg, end, lead=lead)[0] - characterize_baseline(lead, beg, end)[0]) #Flutter check (only for atrial activity) aflut = set() for lead in atr_sig: sigfr = np.concatenate(atr_sig[lead]) if len(sigfr) > 15 and _is_VF(sigfr): aflut.add(lead) #FIXME improve flutter check, now is quite poor. #aflut = frozenset() #P waveform check (only for leads where flutters were not found.) pwaves = [] for beg, end in pw_lims: pwsig = _get_pwave_sig(beg, end) if pwsig is not None: for lead in aflut: pwsig.pop(lead, None) if not pwsig: continue for wave in pwaves: verify( abs(wave.values()[0].pr - pwsig.values()[0].pr) > C.TMARGIN or not signal_match(wave, pwsig)) pwaves.append(pwsig)
def _qrs_gconst(pattern, _): """ General constraints to be added when a new cycle is observed, which currently coincides with the observation of the T waves or a QRS complex not followed by an observed T wave. """ #We update the measurements of the rhythm. _update_measures(pattern) #And check that there are no missed beat forms. _check_missed_beats(pattern) beats = pattern.evidence[o.QRS] #Morphology check. We require the rhythm morphology to be matched #by the new beat in the sequence. ref = pattern.hypothesis.morph #We initialize the morphology with the first beat. if not ref: ref = copy.deepcopy(beats[0].shape) pattern.hypothesis.morph = ref verify(signal_match(ref, beats[-1].shape)) #TODO improve morphology updating. #_update_morphology(pattern) envrhythms = pattern.evidence[o.Cardiac_Rhythm] prevafib = envrhythms and isinstance(envrhythms[0], o.Atrial_Fibrillation) if len(beats) == 2 and envrhythms: refrr, stdrr = envrhythms[-1].meas.rr #The first RR cannot be within the mean +- 2*std of the previous RR. #There must be a rhythm change. verify(not refrr - 2*stdrr <= beats[1].time.start - beats[0].time.start <= refrr + 2*stdrr) if len(beats) >= 3: verify(not beats[-1].paced) rpks = np.array([b.time.start for b in beats]) rrs = np.diff(rpks) _verify_afib_rhythm(rrs) #With this check, we avoid false positives with bigeminies, checking #the RR constraints with even and odd rrs. if len(beats) >= 6: _verify_afib_rhythm(rrs[0::2]) _verify_afib_rhythm(rrs[1::2]) _verify_afib_rhythm(np.diff(rpks[0::2])) #Atrial activity is only checked at the beginning of the pattern and if #there are not previous atrial fibrillation episodes. if 1 < len(beats) < 32 and not prevafib: _verify_atrial_activity(pattern)
def _guided_qrs_observation(hyp): """ Performs the delineation and checking of the general constraints of the QRS abstraction pattern when a reference shape for seaching is set as the hypothesis shape. The modification is done in-place. modifying the hypothesis shape. Parameters ---------- hyp: QRS observation that is the hypothesis of the pattern. """ if hyp.shape: #We perform the alignment in the lead with highest energy. rlead, rshape = max(hyp.shape.iteritems(), key=lambda s: s[1].energy) ref = rshape.sig newshape = {} start = np.inf beg, end = (int(hyp.earlystart), min(int(hyp.latestart) + len(ref), int(hyp.lateend))) if beg < 0: beg = 0 try: sig = sig_buf.get_signal_fragment(beg, end, lead=rlead)[0] verify(len(sig) == end - beg + 1) sig = sig - sig[0] _, idx = xcorr_full(sig, ref) verify(idx >= 0) sig = sig[idx:idx + len(ref)] - sig[idx] verify(len(sig) == len(ref)) bref = rshape.waves[0].l rshape.move(-bref) shape = _get_guided_qrs_shape(sig, rshape) rshape.move(bref) shape.move(bref) #We admit a 25% variation in the energy of the new signal. verify(0.75 <= shape.energy / rshape.energy <= 1.25) #Absolute reference for QRS start start = idx - shape.waves[0].l verify(start >= 0) newshape[rlead] = shape for lead in hyp.shape: if lead is not rlead: rshape = hyp.shape[lead] bref = rshape.waves[0].l sig = sig_buf.get_signal_fragment(beg + start + bref, beg + start + rshape.waves[-1].r + 1, lead=lead)[0] sig = sig - sig[0] rshape.move(-bref) shape = _get_guided_qrs_shape(sig, rshape) rshape.move(bref) shape.move(bref) newshape[lead] = shape verify(signal_match(hyp.shape, newshape)) hyp.shape = newshape #The detected shapes may constrain the delineation area. llim = min(hyp.shape[lead].waves[0].l for lead in hyp.shape) if llim > 0: start = start + llim for lead in hyp.shape: hyp.shape[lead].move(-llim) end = start + max(s.waves[-1].r for s in hyp.shape.itervalues()) peak = start + min(s.waves[_reference_wave(s)].m for s in hyp.shape.itervalues()) hyp.start.value = Iv(beg + start, beg + start) hyp.time.value = Iv(beg + peak, beg + peak) hyp.end.value = Iv(beg + end, beg + end) hyp.clustered = True except InconsistencyError: hyp.shape = {} hyp.paced = False