Example #1
0
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)
Example #2
0
 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))
Example #3
0
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))
Example #4
0
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)
Example #5
0
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)
Example #6
0
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