Esempio n. 1
0
def generate_Deflection_Patterns(npats):
    """
    This function creates a set of *PatternAutomata* patterns, each one
    responsible for obtaining the i-th relevant deflection in a given
    scope. It allows to overcome the limitation of having a single hypothesis
    for a pattern with the same base evidence (in the case of this pattern,
    this base evidence is None, since the only transition of the pattern
    automata does not include any observable, only general constraints).

    Parameters
    ----------
    npats:
        Integer specifying the number of the generated patterns.

    Returns:
    out:
        Ordered list with *n* abstraction patterns, each one responsible for
        the deduction of the *i-th* interesting energy interval in a specific
        area delimited by the hypothesis.
    """
    pats = []
    for i in range(npats):
        pat = PatternAutomata()
        pat.name = "Deflection"
        pat.Hypothesis = o.Deflection
        pat.add_transition(0, 1, tconst=_def_tconst, gconst=get_gconst(i))
        pat.final_states.add(1)
        pat.freeze()
        pats.append(pat)
    return pats
Esempio n. 2
0
    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)


#############################
### Observation procedure ###
#############################
def _rhythmblock_obs_proc(pattern):
    """Observation procedure executed once the rhythm pattern has finished"""
    # We asign the endpoint of the hypothesis.
    pattern.hypothesis.end.value = pattern.evidence[o.QRS][-1].time.value


RHYTHMBLOCK_PATTERN = PatternAutomata()
RHYTHMBLOCK_PATTERN.name = 'Rhythm Block'
RHYTHMBLOCK_PATTERN.Hypothesis = o.RhythmBlock
RHYTHMBLOCK_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                                   _prev_rhythm_tconst, _prev_rhythm_gconst)
RHYTHMBLOCK_PATTERN.add_transition(0, 2, o.Cardiac_Rhythm, ENVIRONMENT,
                                   _prev_rhythm_tconst, _prev_asyst_gconst)
RHYTHMBLOCK_PATTERN.add_transition(1, 2, o.QRS, ENVIRONMENT, _qrs1_tconst)
RHYTHMBLOCK_PATTERN.add_transition(2, 3, o.QRS, ENVIRONMENT, _qrs2_tconst)
RHYTHMBLOCK_PATTERN.add_transition(3, 4, o.QRS, ABSTRACTED, _qrs3_tconst,
                                   _qrs_gconst)
RHYTHMBLOCK_PATTERN.add_transition(4, 5, o.PWave, ABSTRACTED, _p_qrs_tconst)
RHYTHMBLOCK_PATTERN.add_transition(4, 6, o.TWave, ABSTRACTED, _t_qrs_tconst)
RHYTHMBLOCK_PATTERN.add_transition(4, 6)
RHYTHMBLOCK_PATTERN.add_transition(5, 6, o.TWave, ABSTRACTED, _t_qrs_tconst)
RHYTHMBLOCK_PATTERN.add_transition(6, 4, o.QRS, ABSTRACTED, _qrsn_tconst,
Esempio n. 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))


EXTRASYSTOLE_PATTERN = PatternAutomata()
EXTRASYSTOLE_PATTERN.name = 'Extrasystole'
EXTRASYSTOLE_PATTERN.Hypothesis = o.Extrasystole
EXTRASYSTOLE_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                                    _prev_rhythm_tconst, _prev_rhythm_gconst)
EXTRASYSTOLE_PATTERN.add_transition(1, 2, o.QRS, ENVIRONMENT, _qrs_rref_tconst)
EXTRASYSTOLE_PATTERN.add_transition(0, 2, o.Cardiac_Rhythm, ENVIRONMENT,
                                    _prev_rhythm_tconst,
                                    _prev_rhythm_nreg_gconst)
EXTRASYSTOLE_PATTERN.add_transition(2, 3, o.QRS, ENVIRONMENT, _qrs_env_tconst)
# Now there are two ways in the automata, one in which we look for the
# compensatory pause, and another in which a ventricular extrasystole may not
# involve a compensatory pause.
##First way: Compensatory pause.
EXTRASYSTOLE_PATTERN.add_transition(3, 4, o.QRS, ABSTRACTED,
                                    _qrs_ext_tconst(False), _qrs_ext_gconst)
    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))


BIGEMINY_PATTERN = PatternAutomata()
BIGEMINY_PATTERN.name = "Bigeminy"
BIGEMINY_PATTERN.Hypothesis = o.Bigeminy
BIGEMINY_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENV,
                                _prev_rhythm_tconst, _prev_rhythm_gconst)
# Necessary evidence are QRS complexes.
# N
BIGEMINY_PATTERN.add_transition(1, 2, o.QRS, ENV, _reg_qrs_tconst)
# V
BIGEMINY_PATTERN.add_transition(2, 3, o.QRS, ABS, _ect_qrs_tconst,
                                _ect0_gconst)
# N
BIGEMINY_PATTERN.add_transition(3, 4, o.QRS, ABS, _reg_qrs_tconst,
                                _cycle_finished_gconst)
# V
BIGEMINY_PATTERN.add_transition(4, 5, o.QRS, ABS, _ect_qrs_tconst)
Esempio n. 5
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))


TRIGEMINY_PATTERN = PatternAutomata()
TRIGEMINY_PATTERN.name = "Trigeminy"
TRIGEMINY_PATTERN.Hypothesis = o.Trigeminy
TRIGEMINY_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENV,
                                 _prev_rhythm_tconst)
# N
TRIGEMINY_PATTERN.add_transition(1, 2, o.QRS, ENV, _env_qrs_tconst)
# V
TRIGEMINY_PATTERN.add_transition(2, 3, o.QRS, ABS, _ect_qrs_tconst,
                                 _ect0_gconst)
# N
TRIGEMINY_PATTERN.add_transition(3, 4, o.QRS, ABS, _reg_ae_tconst,
                                 _cycle_finished_gconst)
# N
TRIGEMINY_PATTERN.add_transition(4, 5, o.QRS, ABS, _reg_nae_tconst)
# V
Esempio n. 6
0
def _tv0_tconst(pattern, twave):
    """
    Temporal constraints for the T Wave of the first extrasystole, that is
    only observed after the second extrasystole QRS has been properly observed.
    """
    BASIC_TCONST(pattern, twave)
    tnet = pattern.last_tnet
    beats = pattern.evidence[o.QRS]
    # The T wave must be in the hole between the two QRS complexes, and must
    # finish at least 1mm before the next QRS starts.
    tnet.set_before(beats[1].end, twave.start)
    tnet.set_before(twave.end, beats[2].start)
    tnet.add_constraint(twave.end, beats[2].start, Iv(C.TMARGIN, np.Inf))


COUPLET_PATTERN = PatternAutomata()
COUPLET_PATTERN.name = "Couplet"
COUPLET_PATTERN.Hypothesis = o.Couplet
COUPLET_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENV,
                               _prev_rhythm_tconst)
# N
COUPLET_PATTERN.add_transition(1, 2, o.QRS, ENV, _n0_tconst)
# V
COUPLET_PATTERN.add_transition(2, 3, o.QRS, ABS, _v0_tconst, _v0_gconst)
# V
COUPLET_PATTERN.add_transition(3, 4, o.QRS, ABS, _v1_tconst)
# The T wave of the first extrasystole is only observed after the second
# extrasystole QRS complex.
COUPLET_PATTERN.add_transition(4, 5, o.TWave, ABS, _tv0_tconst)
COUPLET_PATTERN.add_transition(4, 6, o.TWave, ABS, _t_tconst)
# N
def create_regular_rhythm(name, hypothesis, rr_bounds):
    """
    Creates a new abstraction pattern automata with the properties of a regular
    rhythm, but allows to parameterize the RR limits, the hypothesis observable
    type and the name of the pattern.
    """
    # The hypothesis must be a cardiac rhythm.
    assert issubclass(hypothesis, o.Cardiac_Rhythm)

    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))

    _qrs_tconst = _get_qrs_tconst(rr_bounds)
    # Automata definition
    automata = PatternAutomata()
    automata.name = name
    automata.Hypothesis = hypothesis
    automata.add_transition(0, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                            _prev_rhythm_tconst, _prev_rhythm_gconst)
    automata.add_transition(1, 2, o.QRS, ENVIRONMENT, _qrs_tconst)
    automata.add_transition(2, 3, o.QRS, ABSTRACTED, _qrs_tconst)
    automata.add_transition(2, 9, o.QRS, ABSTRACTED, _qrs_tconst, _pair_gconst)
    automata.add_transition(3, 4, o.PWave, ABSTRACTED, _p_qrs_tconst)
    automata.add_transition(3, 5, o.TWave, ABSTRACTED, _t_qrs_tconst)
    automata.add_transition(3, 8, o.QRS, ABSTRACTED, _qrs_tconst,
                            _cycle_finished_gconst)
    automata.add_transition(4, 5, o.TWave, ABSTRACTED, _t_qrs_tconst)
    automata.add_transition(5, 6, o.QRS, ABSTRACTED, _qrs_tconst)
    automata.add_transition(5, 8, o.QRS, ABSTRACTED, _qrs_tconst,
                            _cycle_finished_gconst)
    automata.add_transition(6, 7, o.PWave, ABSTRACTED, _p_qrs_tconst)
    automata.add_transition(6, 8, o.TWave, ABSTRACTED, _t_qrs_tconst,
                            _cycle_finished_gconst)
    automata.add_transition(7, 8, o.TWave, ABSTRACTED, _t_qrs_tconst,
                            _cycle_finished_gconst)
    automata.add_transition(8, 6, o.QRS, ABSTRACTED, _qrs_tconst)
    automata.add_transition(8, 8, o.QRS, ABSTRACTED, _qrs_tconst,
                            _cycle_finished_gconst)
    automata.add_transition(9, 10, o.PWave, ABSTRACTED, _p_qrs_tconst)
    automata.add_transition(9, 11, o.TWave, ABSTRACTED, _t_qrs_tconst)
    automata.add_transition(9, 11)
    automata.add_transition(10, 11, o.TWave, ABSTRACTED, _t_qrs_tconst)
    automata.final_states.add(8)
    automata.final_states.add(11)
    automata.abstractions[o.QRS] = (automata.transitions[2],
                                    automata.transitions[3])
    automata.obs_proc = _rhythm_obs_proc
    automata.freeze()
    return automata
Esempio n. 8
0
    hyp.end.value = Iv(beg + end, beg + end)
    ###################################################################
    # Amplitude conditions (between 0.5mV and 6.5 mV in at least one
    # lead or an identified pattern in most leads).
    ###################################################################
    verify(
        len(hyp.shape) > len(sig_buf.get_available_leads()) / 2.0
        or ph2dg(0.5) <= max(s.amplitude for s in hyp.shape.itervalues()) <= ph2dg(6.5)
    )
    hyp.freeze()


#########################
## Automata definition ##
#########################

QRS_PATTERN = PatternAutomata()
QRS_PATTERN.name = 'QRS'
QRS_PATTERN.Hypothesis = o.QRS
QRS_PATTERN.add_transition(0, 1, o.RDeflection, ABSTRACTED, _qrs_tconst, _qrs_gconst)
QRS_PATTERN.final_states.add(1)
QRS_PATTERN.abstractions[o.RDeflection] = (QRS_PATTERN.transitions[0],)
QRS_PATTERN.freeze()


if __name__ == "__main__":
    SHAPES = set(QRS_SHAPES.iterkeys())
    for S, V in QRS_SHAPES.iteritems():
        assert S in V
        assert V.issubset(SHAPES)
Esempio n. 9
0
    the global list for an appropriate annotation.
    """
    if ANNOTS is None:
        _load_annots()
    leads = IN.SIG.get_available_leads()
    # We find all the annotations in the given interval.
    rdef = pattern.hypothesis
    beg = min(int(rdef.earlystart), IN.get_acquisition_point()) + IN._OFFSET
    end = min(int(rdef.lateend), IN.get_acquisition_point()) + IN._OFFSET
    dummy = MITAnnotation()
    dummy.time = beg
    bidx = ANNOTS.bisect_left(dummy)
    dummy.time = end
    eidx = ANNOTS.bisect_right(dummy)
    verify(eidx > bidx)
    selected = max(ANNOTS[bidx:eidx], key=operator.attrgetter('num'))
    time = selected.time - IN._OFFSET
    rdef.time.value = Iv(time, time)
    rdef.start.value = Iv(time, time)
    rdef.end.value = Iv(time, time)
    rdef.level = {lead: 127 for lead in leads}
    rdef.level[leads[selected.chan]] = 127 - selected.num


RDEFLECTION_PATTERN = PatternAutomata()
RDEFLECTION_PATTERN.name = 'R-Deflection'
RDEFLECTION_PATTERN.Hypothesis = o.RDeflection
RDEFLECTION_PATTERN.add_transition(0, 1, gconst=_rdef_gconst)
RDEFLECTION_PATTERN.final_states.add(1)
RDEFLECTION_PATTERN.freeze()
Esempio n. 10
0
        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)


AFIB_PATTERN = PatternAutomata()
AFIB_PATTERN.name = "Atrial Fibrillation"
AFIB_PATTERN.Hypothesis = o.Atrial_Fibrillation
AFIB_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                            _prev_afib_tconst, _prev_afib_gconst)
AFIB_PATTERN.add_transition(1, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                            _prev_multrhythm_tconst, _prev_rhythm_gconst)
AFIB_PATTERN.add_transition(1, 2, o.Cardiac_Rhythm, ENVIRONMENT,
                            _prev_rhythm_tconst, _prev_rhythm_gconst)
AFIB_PATTERN.add_transition(0, 2, o.Cardiac_Rhythm, ENVIRONMENT,
                            _prev_rhythm_tconst, _prev_rhythm_gconst)
AFIB_PATTERN.add_transition(2, 3, o.QRS, ENVIRONMENT, _qrs0_tconst)
AFIB_PATTERN.add_transition(3, 4, o.QRS, ABSTRACTED, _qrs_tconst, _qrs_gconst)
AFIB_PATTERN.add_transition(4, 5, o.QRS, ABSTRACTED, _qrs_tconst, _qrs_gconst)
# We have two different ways to get a sufficient set of evidence. If there is
# a previous Afib episode, then we only need 3 QRS to have a firm hypothesis of
    verify(not _contains_qrs(pattern), 'QRS detected during flutter')
    lpos = 0.0
    ltot = 0.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))


VFLUTTER_PATTERN = PatternAutomata()
VFLUTTER_PATTERN.name = "Ventricular Flutter"
VFLUTTER_PATTERN.Hypothesis = o.Ventricular_Flutter
VFLUTTER_PATTERN.add_transition(0, 1, o.Cardiac_Rhythm, ENVIRONMENT,
                                _prev_rhythm_tconst)
VFLUTTER_PATTERN.add_transition(1, 2, o.QRS, ENVIRONMENT, _qrs0_tconst)
VFLUTTER_PATTERN.add_transition(2, 3, o.Deflection, ABSTRACTED, _def0_tconst,
                                _vflut_gconst)
VFLUTTER_PATTERN.add_transition(3, 3, o.Deflection, ABSTRACTED,
                                _deflection_tconst, _vflut_gconst)
VFLUTTER_PATTERN.add_transition(3, 4, o.QRS, ABSTRACTED, _qrs_tconst,
                                _vflut_gconst)
VFLUTTER_PATTERN.final_states.add(4)
VFLUTTER_PATTERN.abstractions[o.Deflection] = (
    VFLUTTER_PATTERN.transitions[2], )
VFLUTTER_PATTERN.freeze()
Esempio n. 12
0
    """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))


def _asystole_gconst(pattern, _):
    """General constraints of the asystole pattern."""
    # The rhythm information is copied from the precedent rhythm.
    if pattern.evidence[o.Cardiac_Rhythm]:
        rhythm = pattern.evidence[o.Cardiac_Rhythm][0]
        pattern.hypothesis.meas = copy.copy(rhythm.meas)


RHYTHMSTART_PATTERN = PatternAutomata()
RHYTHMSTART_PATTERN.name = "Rhythm Start"
RHYTHMSTART_PATTERN.Hypothesis = o.RhythmStart
RHYTHMSTART_PATTERN.add_transition(0, 1, o.QRS, ABSTRACTED, _rstart_tconst,
                                   _rhythmstart_gconst)
RHYTHMSTART_PATTERN.add_transition(1, 2, o.PWave, ABSTRACTED, _p_qrs_tconst)
RHYTHMSTART_PATTERN.add_transition(2, 3, o.TWave, ABSTRACTED, _t_qrs_tconst)
RHYTHMSTART_PATTERN.add_transition(1, 3, o.TWave, ABSTRACTED, _t_qrs_tconst)
RHYTHMSTART_PATTERN.add_transition(1, 3)
RHYTHMSTART_PATTERN.final_states.add(3)
RHYTHMSTART_PATTERN.abstractions[o.QRS] = (
    RHYTHMSTART_PATTERN.transitions[0], )
RHYTHMSTART_PATTERN.freeze()

ASYSTOLE_PATTERN = PatternAutomata()
ASYSTOLE_PATTERN.name = "Asystole"
Esempio n. 13
0
    # least the half of the duration of the energy interval.
    verify(
        Iv(twave.earlystart, twave.lateend).intersection(Iv(defl.earlystart, defl.lateend)).length
        >= (defl.lateend - defl.earlystart) / 2.0
    )
    # If the Deflection is a R-Deflection, we require a margin before
    # the end of the twave.
    if isinstance(defl, o.RDeflection):
        verify(twave.lateend - defl.time.end > C.TW_RDEF_MIN_DIST)


###########################
### Automata definition ###
###########################

TWAVE_PATTERN = PatternAutomata()
TWAVE_PATTERN.name = 'T Wave'
TWAVE_PATTERN.Hypothesis = o.TWave
TWAVE_PATTERN.add_transition(0, 1, o.QRS, ENVIRONMENT, _t_qrs_tconst)
TWAVE_PATTERN.add_transition(1, 2, o.Deflection, ABSTRACTED, _t_defl_tconst, _t_gconst)
TWAVE_PATTERN.final_states.add(2)
TWAVE_PATTERN.freeze()

##################################################
### Statistical knowledge stored as histograms ###
##################################################


def _check_histogram(hist, value):
    """
    Obtains a score of a value according to an histogram, between 0.0 and 1.0
Esempio n. 14
0
        # 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))


FIRST_BEAT_PATTERN = PatternAutomata()
FIRST_BEAT_PATTERN.name = "First Beat"
FIRST_BEAT_PATTERN.Hypothesis = o.FirstBeat
FIRST_BEAT_PATTERN.add_transition(0, 1, o.QRS, ABSTRACTED, _btime_tconst)
FIRST_BEAT_PATTERN.add_transition(1, 2, o.PWave, ABSTRACTED, _p_qrs_tconst)
FIRST_BEAT_PATTERN.add_transition(2, 3, o.TWave, ABSTRACTED, _t_qrs_tconst,
                                  _firstbeat_gconst)
FIRST_BEAT_PATTERN.add_transition(1, 3, o.TWave, ABSTRACTED, _t_qrs_tconst,
                                  _firstbeat_gconst)
FIRST_BEAT_PATTERN.add_transition(1,
                                  3,
                                  tconst=_qrs_time_tconst,
                                  gconst=_firstbeat_gconst)
FIRST_BEAT_PATTERN.final_states.add(3)
FIRST_BEAT_PATTERN.abstractions[o.QRS] = (FIRST_BEAT_PATTERN.transitions[0], )
FIRST_BEAT_PATTERN.freeze()