Пример #1
0
def _cycle_finished_gconst(pattern, _):
    """
    General constraints to be added when a trigeminy cycle is finished, this is,
    with the normal beat following an ectopy.
    """
    #We check that there are no missed beats.
    _check_missed_beats(pattern)
    #We update the measurements of the rhythm taking the measures of the
    #regular cycles.
    rrs, pqs, rts = _get_measures(pattern, False)
    if len(pqs) == 0:
        pqm, pqst = 0, 0
    elif len(pqs) == 1:
        #TODO use specific deviations for PQ rather than QT
        pqm, pqst = pqs[0], C.QT_ERR_STD
    else:
        pqm, pqst = np.mean(pqs), max(np.std(pqs), C.MIN_QT_STD)
    if len(rts) == 0:
        rtm, rtst = 0, 0
    elif len(rts) == 1:
        rtm, rtst = rts[0], C.QT_ERR_STD
    else:
        rtm, rtst = np.mean(rts), max(np.std(rts), C.MIN_QT_STD)
    pattern.hypothesis.meas = o.CycleMeasurements((np.mean(rrs), np.std(rrs)),
                                                  (rtm, rtst), (pqm, pqst))
Пример #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))
Пример #3
0
def _vflut_gconst(pattern, _):
    """
    General constraints of the pattern, checked every time a new observation
    is added to the evidence. These constraints simply state that the majority
    of the leads must show a positive detection of a ventricular flutter.
    """
    if not pattern.evidence[o.Cardiac_Rhythm]:
        return
    hyp = pattern.hypothesis
    ##################
    beg = int(hyp.earlystart)
    if beg < 0:
        beg = 0
    end = int(hyp.earlyend)
    verify(not _contains_qrs(pattern), 'QRS detected during flutter')
    lpos = 0.
    ltot = 0.
    for lead in sig_buf.get_available_leads():
        if _is_VF(sig_buf.get_signal_fragment(beg, end, lead=lead)[0]):
            lpos += 1
        ltot += 1
    verify(lpos/ltot > 0.5)
    defls = pattern.evidence[o.Deflection]
    if len(defls) > 1:
        rrs = np.diff([defl.earlystart for defl in defls])
        hyp.meas = o.CycleMeasurements((np.mean(rrs), np.std(rrs)),
                                                                (0, 0), (0, 0))
Пример #4
0
def _firstbeat_gconst(pattern, twave):
    """General constraints for the first beat, to obtain the first measure"""
    if twave is None:
        rt = 0.0
    else:
        rt = twave.earlyend - pattern.obs_seq[0].time.start
    pattern.hypothesis.meas = o.CycleMeasurements((0.0, 0.0), (rt, QT_ERR_STD),
                                                  (0.0, 0.0))
Пример #5
0
def get_features(interpretation):
    """
    Obtains the relevant classification features for every QRS in the
    interpretation.
    """
    result = collections.OrderedDict()
    rhythms = interpretation.get_observations(o.Cardiac_Rhythm)
    beats = sortedcontainers.SortedList(interpretation.get_observations(o.QRS))
    rrs = np.diff([b.time.start for b in beats])
    beatiter = iter(beats)
    obs = interpretation.observations
    qrs = None
    for rh in rhythms:
        qidx0 = bidx = 0
        if qrs is None:
            i = 0
            qrs = next(beatiter)
        else:
            i = 1
        while qrs.time.start <= rh.lateend:
            info = BeatInfo(qrs)
            info.rh = rh
            bidx = beats.index(qrs)
            qidx0 = qidx0 or bidx
            if bidx > 0:
                info.rr = rrs[bidx - 1]
            idx = obs.index(qrs)
            pw = None
            if idx > 0 and isinstance(obs[idx - 1], o.PWave):
                pw = obs[idx - 1]
            elif idx > 1 and isinstance(obs[idx - 2], o.PWave):
                pw = obs[idx - 2]
            info.pwave = pw.amplitude if pw is not None else {}
            if isinstance(rh, (o.Sinus_Rhythm, o.Bradycardia, o.Tachycardia)):
                info.pos = REGULAR
            elif isinstance(rh, o.Extrasystole):
                info.pos = ADVANCED if i == 1 else REGULAR
            elif isinstance(rh, o.Couplet):
                info.pos = ADVANCED if i in (1, 2) else REGULAR
            elif isinstance(rh, (o.RhythmBlock, o.Asystole)):
                info.pos = DELAYED
            elif isinstance(rh, o.Atrial_Fibrillation):
                info.pos = AFIB
            elif isinstance(rh, o.Bigeminy):
                info.pos = ADVANCED if i % 2 == 1 else REGULAR
            elif isinstance(rh, o.Trigeminy):
                info.pos = ADVANCED if i % 3 == 1 else REGULAR
            elif isinstance(rh, o.Ventricular_Flutter):
                info.pos = REGULAR
            result[qrs] = info
            qrs = next(beatiter, None)
            if qrs is None:
                break
            i += 1
        meanrr = np.mean(rrs[qidx0:bidx]) if qidx0 < bidx else rrs[bidx - 1]
        rh.meas = o.CycleMeasurements((meanrr, 0), (0, 0), (0, 0))
    return result
Пример #6
0
def _prev_rhythm_gconst(pattern, rhythm):
    """General constraints of a cardiac rhythm with the preceden one."""
    #We only accept the concatenation of the same rhythm for asystoles.
    verify(
        isinstance(pattern.hypothesis, o.Asystole)
        or type(pattern.hypothesis) != type(rhythm))
    #The rhythm measurements are initially taken from the previous rhythm.
    pattern.hypothesis.meas = o.CycleMeasurements(
        rhythm.meas.rr, (rhythm.meas.rt[0], C.QT_ERR_STD),
        (rhythm.meas.pq[0], C.QT_ERR_STD))
Пример #7
0
def _update_measures(pattern):
    """
    Updates the cycle time measures of the pattern.
    """
    #Maximum number of observations considered for the measures (to avoid
    #excessive influence of old observations)
    nobs = 30
    beats = pattern.evidence[o.QRS][-nobs:]
    obseq = pattern.obs_seq
    #RR
    rrs = np.diff([b.time.start for b in beats])
    #The RT (QT) measure is updated by a Kalman Filter strategy.
    #Belief values
    rtmean, rtstd = pattern.hypothesis.meas.rt
    #Current RR measure (bounded)
    qrs = beats[-1]
    rr = rrs[-1]
    rr = max(min(rr, C.QTC_RR_LIMITS.end), C.QTC_RR_LIMITS.start)
    #Kalman filter algorithm, as explained in "Probabilistic Robotics"
    sigma_tbar = rtstd**2 + C.KF_Q**2
    twave = obseq[-1]
    if isinstance(twave, o.TWave):
        #rt and corrected rt measure in the current iteration
        rt = twave.earlyend - qrs.time.start
        rtc = ms2sp(1000.0 * sp2sc(rt) / np.cbrt(sp2sc(rr)))
        meas_err = rtc - rtmean
        #Abnormally QT intervals have associated higher uncertainty
        qt = twave.earlyend - qrs.earlystart
        qt_lims = C.QT_FROM_RR(Iv(rr, rr))
        #Measure uncertainty, represented by the R matrix in the Kalman filter
        KF_R = meas_err if qt in qt_lims else ms2sp(120)
        k_t = sigma_tbar / (sigma_tbar + max(KF_R, C.MIN_QT_STD)**2)
    else:
        #No measure - 0 Kalman gain
        meas_err = 0
        k_t = 0
    if rtmean == 0:
        mu_t = meas_err
        sigma_t = C.QT_ERR_STD**2
    else:
        mu_t = rtmean + k_t * meas_err
        sigma_t = (1.0 - k_t) * sigma_tbar
    #PQ
    pqs = []
    for pwave in pattern.evidence[o.PWave][-nobs:]:
        i = pattern.get_step(pwave)
        qrs = obseq[i - 1]
        pqs.append(qrs.earlystart - pwave.earlystart)
    pattern.hypothesis.meas = o.CycleMeasurements((np.mean(rrs), np.std(rrs)),
                                                  (mu_t, np.sqrt(sigma_t)),
                                                  (np.mean(pqs), np.std(pqs)))
Пример #8
0
def _update_measures(pattern):
    """
    Updates the cycle time measures of the pattern.
    """
    #Maximum number of observations considered for the measures (to avoid
    #excessive influence of old observations)
    nobs = 30
    beats = pattern.evidence[o.QRS][-nobs:]
    #RR
    rrs = np.diff([b.time.start for b in beats])
    obseq = pattern.obs_seq
    #The RT (QT) measure is updated by a Kalman Filter strategy.
    #Belief values
    rtmean, rtstd = pattern.hypothesis.meas.rt
    if (len(obseq) > 1 and isinstance(obseq[-2], o.TWave)
            and obseq[-2] is not pattern.finding):
        twave = obseq[-2]
        #Current RR measure (bounded)
        qrs = next(
            (q for q in reversed(beats) if q.lateend <= twave.earlystart),
            None)
        rr = qrs.time.start - beats[beats.index(qrs) - 1].time.start
        rr = max(min(rr, C.QTC_RR_LIMITS.end), C.QTC_RR_LIMITS.start)
        #Kalman filter algorithm, as explained in "Probabilistic Robotics"
        sigma_tbar = rtstd**2 + C.KF_Q**2
        #rt and corrected rt measure in the current iteration
        rt = twave.earlyend - qrs.time.start
        rtc = ms2sp(1000.0 * sp2sc(rt) / np.cbrt(sp2sc(rr)))
        meas_err = rtc - rtmean
        #Abnormally QT intervals have associated higher uncertainty
        qt = twave.earlyend - qrs.earlystart
        qt_lims = C.QT_FROM_RR(Iv(rr, rr))
        #Measure uncertainty, represented by the R matrix in the Kalman filter
        KF_R = meas_err if qt in qt_lims else ms2sp(120)
        k_t = sigma_tbar / (sigma_tbar + max(KF_R, C.MIN_QT_STD)**2)
        if rtmean == 0:
            rtmean = meas_err
            rtstd = C.QT_ERR_STD
        else:
            rtmean = rtmean + k_t * meas_err
            rtstd = np.sqrt((1.0 - k_t) * sigma_tbar)
    pattern.hypothesis.meas = o.CycleMeasurements((np.mean(rrs), np.std(rrs)),
                                                  (rtmean, rtstd), (0.0, 0.0))
Пример #9
0
def _cycle_gconst(pattern, twave):
    """
    General constraints applied after all the evidence of a heartbeat has
    been observed. The Kalman filter update for the QT limits is performed in
    this function.
    """
    #Belief values
    rtmean, rtstd = pattern.obs_seq[0].meas.rt
    #Current RR measure (bounded)
    qrs = pattern.obs_seq[2]
    rr = qrs.time.start - pattern.obs_seq[1].time.start
    rr = max(min(rr, RR_LIMITS.end), RR_LIMITS.start)
    #Kalman filter algorithm, as explained in "Probabilistic Robotics"
    sigma_tbar = rtstd**2 + KF_Q**2
    if twave is not None:
        #rt and corrected rt measure in the current iteration
        rt = twave.earlyend - qrs.time.start
        rtc = msec2samples(1000.0 * sp2sg(rt) / np.cbrt(sp2sg(rr)))
        meas_err = rtc - rtmean
        #Abnormally QT intervals have associated higher uncertainty
        qt = twave.earlyend - qrs.earlystart
        qt_lims = C.QT_FROM_RR(Iv(rr, rr))
        #Measure uncertainty, represented by the R matrix in the Kalman filter
        KF_R = meas_err if qt in qt_lims else msec2samples(120)
        k_t = sigma_tbar / (sigma_tbar + max(KF_R, MIN_QT_STD)**2)
    else:
        #No measure - 0 Kalman gain
        meas_err = QT_ERR_STD
        k_t = 0
    mu_t = rtmean + k_t * meas_err
    sigma_t = (1.0 - k_t) * sigma_tbar
    #PR interval
    pr = 0.0
    if isinstance(pattern.obs_seq[3], o.PWave):
        pr = qrs.time.start - pattern.obs_seq[3].earlystart
        prmean = pattern.obs_seq[0].meas.pq[0]
        pr = (pr + prmean) / 2 if prmean > 0 else pr
    pattern.hypothesis.meas = o.CycleMeasurements(
        (rr, 0.0), (mu_t, np.sqrt(sigma_t)), (pr, 0.0))
Пример #10
0
def _rhythmstart_gconst(pattern, _):
    """General constraints of the rhythm start pattern."""
    #We assume an starting mean rhythm of 75ppm, but the range allows from 65
    #to 85bpm
    pattern.hypothesis.meas = o.CycleMeasurements((ms2sp(800), ms2sp(200)),
                                                  (0, 0), (0, 0))