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
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,
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)
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
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
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)
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()
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()
"""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"
# 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
# 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()