### 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, _qrs_gconst) RHYTHMBLOCK_PATTERN.final_states.add(6) RHYTHMBLOCK_PATTERN.abstractions[o.QRS] = ( RHYTHMBLOCK_PATTERN.transitions[4], ) RHYTHMBLOCK_PATTERN.obs_proc = _rhythmblock_obs_proc RHYTHMBLOCK_PATTERN.freeze()
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
TRIGEMINY_PATTERN.add_transition(11, 13, o.TWave, ABS, get_t_tconst(3)) TRIGEMINY_PATTERN.add_transition(11, 13) TRIGEMINY_PATTERN.add_transition(12, 13, o.TWave, ABS, get_t_tconst(-3)) # TRIGEMINY_PATTERN.add_transition(13, 14, o.PWave, ABS, get_p_tconst(-2)) TRIGEMINY_PATTERN.add_transition(13, 15, o.TWave, ABS, get_t_tconst(-2)) TRIGEMINY_PATTERN.add_transition(13, 15) TRIGEMINY_PATTERN.add_transition(14, 15, o.TWave, ABS, get_t_tconst(-2)) # TRIGEMINY_PATTERN.add_transition(15, 16, o.PWave, ABS, get_p_tconst(-1)) TRIGEMINY_PATTERN.add_transition(15, 17, o.TWave, ABS, get_t_tconst(-1)) TRIGEMINY_PATTERN.add_transition(15, 17) TRIGEMINY_PATTERN.add_transition(16, 17, o.TWave, ABS, get_t_tconst(-1)) # Each new cycle adds three more QRS complexes. # N TRIGEMINY_PATTERN.add_transition(17, 18, o.QRS, ABS, _reg_nae_tconst) # V TRIGEMINY_PATTERN.add_transition(18, 19, o.QRS, ABS, _ect_qrs_tconst) # N TRIGEMINY_PATTERN.add_transition(19, 20, o.QRS, ABS, _reg_ae_tconst, _cycle_finished_gconst) # And the corresponding P and T waves. TRIGEMINY_PATTERN.add_transition(20, 12, o.PWave, ABS, get_p_tconst(-3)) TRIGEMINY_PATTERN.add_transition(20, 13, o.TWave, ABS, get_t_tconst(-3)) TRIGEMINY_PATTERN.add_transition(20, 13) # TRIGEMINY_PATTERN.final_states.add(17) TRIGEMINY_PATTERN.abstractions[o.QRS] = (TRIGEMINY_PATTERN.transitions[2], ) TRIGEMINY_PATTERN.obs_proc = _rhythm_obs_proc TRIGEMINY_PATTERN.freeze()
# T waves are searched after the necessary QRS have been observed. ST = 9 + C.AFIB_MIN_NQRS - 4 for i in range(C.AFIB_MIN_NQRS - 2): AFIB_PATTERN.add_transition(ST, ST + 1, o.TWave, ABSTRACTED, get_t_tconst(i + 1)) AFIB_PATTERN.add_transition(ST, ST + 1) ST += 1 # T wave for the last observed QRS, and cycle QRS observation. AFIB_PATTERN.add_transition(ST, ST + 1, o.TWave, ABSTRACTED, get_t_tconst(-1)) AFIB_PATTERN.add_transition(ST, ST + 1) AFIB_PATTERN.add_transition(ST + 1, ST, o.QRS, ABSTRACTED, _qrs_tconst, _qrs_gconst) AFIB_PATTERN.final_states.add(8) AFIB_PATTERN.final_states.add(ST + 1) AFIB_PATTERN.abstractions[o.QRS] = (AFIB_PATTERN.transitions[5], ) AFIB_PATTERN.obs_proc = _rhythm_obs_proc AFIB_PATTERN.freeze() ######################### ### Helper structures ### ######################### # The following polynomial gives us the minimum number of Non-Empty Cells in # the RdR plot to positively detect atrial fibrillation, as described in # "Lian: A simple method to detect Atrial Fibrillation using RR intervals" _NEC = scipy.interpolate.interpolate.lagrange([32, 64, 128], [23, 40, 65]) if __name__ == "__main__": pass