def _prev_rhythm_tconst(pattern, rhythm): """Temporal constraints of a cardiac rhythm with the precedent one.""" BASIC_TCONST(pattern, rhythm) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, rhythm.end) tnet.add_constraint(pattern.hypothesis.start, pattern.hypothesis.end, Iv(2*C.TACHY_RR.start, 3*C.BRADY_RR.end))
def _qrs2_tconst(pattern, qrs): """Temporal constraints of the delayed QRS in the asystole.""" BASIC_TCONST(pattern, qrs) pattern.last_tnet.set_equal(qrs.time, pattern.hypothesis.end) if len(pattern.evidence[o.QRS]) > 1: prev = pattern.evidence[o.QRS][0] pattern.last_tnet.add_constraint(prev.time, qrs.time, ASYSTOLE_RR)
def _qrs3_tconst(pattern, qrs): """Temporal constraints of the third QRS complex""" BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_before(qrs.time, pattern.hypothesis.end) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR) beats = pattern.evidence[o.QRS] #If there is a previous QRS if beats.index(qrs) == 1: tnet.add_constraint(beats[0].time, qrs.time, Iv(C.TACHY_RR.start + C.RR_MAX_DIFF, C.BRADY_RR.end)) #If we have reached an initial state. if pattern.istate == 0: idx = beats.index(qrs) meanrr, stdrr = pattern.hypothesis.meas.rr minrr = (beats[1].time.start - beats[0].time.end if idx == 2 else meanrr-stdrr) tnet.add_constraint(beats[idx-1].time, qrs.time, Iv(min(C.ASYSTOLE_RR.start, minrr+C.RR_MAX_DIFF), C.ASYSTOLE_RR.start)) #The block time has to be higher than the mean RR plus the standard #deviation. if meanrr > 0: tnet.add_constraint(beats[idx-1].time, qrs.time, Iv(meanrr+stdrr, max(meanrr+stdrr, C.ASYSTOLE_RR.start)))
def _common_qrs_constraints(pattern, qrs): """Temporal constraints affecting all QRS complex.""" tnet = pattern.last_tnet hyp = pattern.hypothesis BASIC_TCONST(pattern, qrs) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR) tnet.set_before(qrs.time, hyp.end)
def _qrs_fin_npause_tconst(pattern, qrs): """ Temporal constraints of the fourth beat in an extrasystole without compensatory pause. """ BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.end, qrs.time) beats = pattern.evidence[o.QRS] #We need all previous evidence if pattern.istate == 0: step = pattern.get_step(qrs) twave = pattern.trseq[step-1][1] if isinstance(twave, o.TWave): tnet.set_before(twave.end, qrs.start) #Reference RR minrr = (beats[1].time.start - beats[0].time.end if len(beats) == 4 else pattern.hypothesis.meas.rr[0]) maxrr = (beats[1].time.end - beats[0].time.start if len(beats) == 4 else pattern.hypothesis.meas.rr[0]) tnet.add_constraint(beats[-3].time, qrs.time, Iv(minrr - C.RR_MAX_DIFF, maxrr + C.RR_MAX_DIFF)) #The last QRS should have the same morphology than the one before the #extrasystole. qrs.shape = beats[-3].shape
def _t_tconst(pattern, twave): """ Temporal constraints of the T Waves wrt the corresponding QRS complex. """ BASIC_TCONST(pattern, twave) obseq = pattern.obs_seq idx = pattern.get_step(twave) try: tnet = pattern.last_tnet #We find the qrs observation precedent to this T wave. qrs = next(obseq[i] for i in xrange(idx - 1, -1, -1) if isinstance(obseq[i], o.QRS)) #If we have more than one QRS, it is possible to constrain even more #the location of the T-Wave, based on rhythm information. qidx = pattern.evidence[o.QRS].index(qrs) if qidx > 1: refrr = ( (qrs.time.end - pattern.evidence[o.QRS][qidx - 2].time.start) / 2.0) tnet.add_constraint(qrs.time, twave.end, Iv(0, refrr - C.TQ_INTERVAL_MIN)) if idx > 0 and isinstance(obseq[idx - 1], o.PWave): pwave = obseq[idx - 1] tnet.add_constraint( pwave.end, twave.start, Iv(C.ST_INTERVAL.start, C.PQ_INTERVAL.end + C.QRS_DUR.end)) #ST interval tnet.add_constraint(qrs.end, twave.start, C.ST_INTERVAL) #QT duration tnet.add_constraint(qrs.start, twave.end, C.N_QT_INTERVAL) except StopIteration: pass
def _qrsn_tconst(pattern, qrs): """ Temporal constraints for the QRS complexes. """ beats = pattern.evidence[o.QRS] idx = beats.index(qrs) hyp = pattern.hypothesis tnet = pattern.last_tnet obseq = pattern.obs_seq oidx = pattern.get_step(qrs) prev = beats[idx-1] #In cyclic observations, we have to introduce more networks to simplify #the minimization operation. tnet.remove_constraint(hyp.end, prev.time) tnet = ConstraintNetwork() pattern.temporal_constraints.append(tnet) meanrr, stdrr = pattern.hypothesis.meas.rr rr_bounds = Iv(min(C.ASYSTOLE_RR.start, meanrr-stdrr+C.RR_MAX_DIFF), C.ASYSTOLE_RR.start) tnet.add_constraint(prev.time, qrs.time, rr_bounds) tnet.add_constraint(prev.start, qrs.start, rr_bounds) tnet.add_constraint(prev.end, qrs.end, rr_bounds) tnet.set_before(prev.end, qrs.start) #If there is a prior T Wave, it must finish before the start #of the QRS complex. if isinstance(obseq[oidx-1], o.TWave): prevt = obseq[oidx-1] tnet.set_before(prevt.end, qrs.start) BASIC_TCONST(pattern, qrs) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR) tnet.set_before(qrs.time, hyp.end) #We can introduce constraints on the morphology of the new QRS complex. if hyp.morph and not qrs.frozen: qrs.shape = hyp.morph
def _def0_tconst(pattern, defl): """Temporal constraints of the first deflection""" BASIC_TCONST(pattern, defl) tnet = pattern.last_tnet tnet.add_constraint(pattern.hypothesis.start, defl.time, C.VFLUT_LIM_INTERV) tnet.set_before(defl.time, pattern.hypothesis.end)
def _reg_nae_tconst(pattern, qrs): """ Temporal constraints for regular beats not coming after ectopic beats. """ beats = pattern.evidence[o.QRS] idx = beats.index(qrs) assert not _is_ectopic(idx) hyp = pattern.hypothesis tnet = pattern.last_tnet prev = beats[idx - 1] if idx > 3: #We create a new temporal network for the new trigeminy cycle. tnet.remove_constraint(hyp.end, prev.time) tnet = ConstraintNetwork() pattern.temporal_constraints.append(tnet) rrev = beats[idx - 3].time.start - beats[idx - 4].time.start ##RR evolution constraint. else: rrev = pattern.evidence[o.Cardiac_Rhythm][0].meas.rr[0] tnet.add_constraint(prev.time, qrs.time, Iv(rrev - C.RR_MAX_DIFF, rrev + C.RR_MAX_DIFF)) BASIC_TCONST(pattern, qrs) tnet.add_constraint(qrs.start, qrs.end, C.NQRS_DUR) tnet.set_before(qrs.time, hyp.end) #Constraints with the precedent T Wave _qrs_after_twave(pattern, qrs) #Morphology should be similar to the previous QRS, since both are normal qrs.shape = prev.shape qrs.paced = prev.paced
def tconst(pattern, qrs): """ Defines the temporal constraints function for the ectopic beat in an extrasystole, depending on its ventricular nature or not. """ BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_before(qrs.end, pattern.hypothesis.end) if ventricular: tnet.add_constraint(qrs.start, qrs.end, C.VQRS_DUR) #It must be the third beat. beats = pattern.evidence[o.QRS] idx = beats.index(qrs) #If there is a previous beat if idx > 0: tnet.add_constraint(beats[idx-1].time, qrs.time, Iv(C.TACHY_RR.start, 0.9*C.BRADY_RR.end)) #If all the previous evidence has been observed if pattern.istate == 0: #Anticipation of at least the 10% of the reference RR, or 1mm. if idx == 2: refrr = beats[1].time.end - beats[0].time.start elif pattern.evidence[o.Cardiac_Rhythm][0] is not pattern.finding: refrr = pattern.hypothesis.meas.rr[0] else: refrr = None if refrr is not None: short = min(0.1*refrr, C.TMARGIN) tnet.add_constraint(beats[idx-1].time, qrs.time, Iv(C.TACHY_RR.start, max(C.TACHY_RR.start, refrr-short)))
def _prev_rhythm_tconst(pattern, rhythm): """Temporal constraints of the flutter with the precedent rhythm""" BASIC_TCONST(pattern, rhythm) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, rhythm.end) tnet.add_constraint(pattern.hypothesis.start, pattern.hypothesis.end, Iv(C.VFLUT_MIN_DUR, np.inf))
def _asyst_prev_rhythm_tconst(pattern, rhythm): """Temporal constraints of an asystole with the precedent rhythm.""" BASIC_TCONST(pattern, rhythm) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, rhythm.end) tnet.add_constraint(pattern.hypothesis.start, pattern.hypothesis.end, ASYSTOLE_RR)
def _t_tconst(pattern, twave): """ Temporal constraints of the T wave. """ BASIC_TCONST(pattern, twave) beats = pattern.evidence[o.QRS] tnet = pattern.last_tnet qidx = qrsidx + len(beats) if qrsidx < 0 else qrsidx qrs = beats[qidx] if qidx < len(beats) - 1: tnet.set_before(twave.end, beats[qidx + 1].start) if qidx > 0: refrr = qrs.time.end - pattern.evidence[o.QRS][qidx - 1].time.start refrr = max(min(refrr, C.QTC_RR_LIMITS.end), C.QTC_RR_LIMITS.start) rtc, rtstd = pattern.hypothesis.meas.rt if rtc > 0: #Expected QT value from the QT corrected value rtmean = ms2sp(1000.0 * sp2sc(rtc) * np.cbrt(sp2sc(refrr))) tnet.add_constraint( qrs.time, twave.end, Iv(rtmean - 2.5 * rtstd, rtmean + 2.5 * rtstd)) try: tnet.add_constraint(qrs.time, twave.end, Iv(0, refrr - C.TQ_INTERVAL_MIN)) except ValueError: pass tnet.add_constraint(qrs.start, twave.end, C.N_QT_INTERVAL) #ST interval tnet.add_constraint(qrs.end, twave.start, C.ST_INTERVAL)
def _qrs1_tconst(pattern, qrs): """Temporal constraints of the first QRS complex""" BASIC_TCONST(pattern, qrs) pattern.last_tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR) if pattern.evidence[o.Cardiac_Rhythm]: pattern.last_tnet.set_before(qrs.end, pattern.evidence[o.Cardiac_Rhythm][0].end)
def _p_tconst(pattern, pwave): """P waves temporal constraints""" BASIC_TCONST(pattern, pwave) tnet = pattern.last_tnet tnet.add_constraint(pwave.start, pwave.end, C.PW_DURATION) #We find the associated QRS. beats = pattern.evidence[o.QRS] qidx = qrsidx + len(beats) if qrsidx < 0 else qrsidx qrs = beats[qidx] if qidx > 0: tnet.set_before(beats[qidx - 1].end, pwave.start) tnet.add_constraint(pwave.start, qrs.start, C.N_PR_INTERVAL) tnet.set_before(pwave.end, qrs.start) if len(pattern.evidence[o.PWave]) > 10: #The mean and standard deviation of the PQ measurements will #influence the following observations. if qidx % 2 == 0: pqmean, pqstd = pattern.hypothesis.meas.pq else: pqs = _get_measures(pattern, True)[2] pqmean, pqstd = np.mean(pqs), np.std(pqs) if not np.isnan(pqmean) and not np.isnan(pqstd): interv = Iv(int(pqmean - 2 * pqstd), int(pqmean + 2 * pqstd)) if interv.overlap(C.N_PR_INTERVAL): tnet.add_constraint(pwave.start, qrs.start, interv)
def _prev_rhythm_tconst(pattern, rhythm): """Temporal constraints of the fibrillation with the precedent rhythm""" BASIC_TCONST(pattern, rhythm) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, rhythm.end) #An atrial fibrillation needs at least 7 QRS complexes. tnet.add_constraint(pattern.hypothesis.start, pattern.hypothesis.end, Iv(7 * C.TACHY_RR.start, np.inf))
def _env_qrs_tconst(pattern, qrs): """Temporal constraints for the environment QRS observation, the first QRS of the pattern""" tnet = pattern.last_tnet BASIC_TCONST(pattern, qrs) tnet.set_equal(pattern.hypothesis.start, qrs.time) tnet.set_before(qrs.time, pattern.hypothesis.end) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR)
def _prev_afib_tconst(pattern, afib): """ Temporal constraints of the fibrillation wrt a previous atrial fibrillation that will helps us to reduce the necessary evidence """ BASIC_TCONST(pattern, afib) pattern.last_tnet.add_constraint(afib.end, pattern.hypothesis.start, Iv(0, C.AFIB_MAX_DELAY))
def _qrs_tconst(pattern, qrs): """Temporal constraints of the QRS complex that determines the end of the flutter""" BASIC_TCONST(pattern, qrs) defl = pattern.evidence[o.Deflection][-1] tnet = pattern.last_tnet tnet.add_constraint(defl.time, qrs.time, C.VFLUT_LIM_INTERV) tnet.set_equal(pattern.hypothesis.end, qrs.time) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR)
def _qrs_env_tconst(pattern, qrs): """Temporal constraints of the second environment QRS complex""" BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, qrs.time) if pattern.evidence[o.QRS].index(qrs) == 1: prev = pattern.evidence[o.QRS][0] tnet.add_constraint(prev.time, qrs.time, Iv(C.TACHY_RR.start, C.BRADY_RR.end))
def _envbeat_tconst(pattern, obs): """ Temporal constraints for the environment QRS and CardiacCycle observation """ BASIC_TCONST(pattern, obs) pattern.last_tnet.add_constraint(obs.end, pattern.hypothesis.time, Iv(msec2samples(20), np.inf)) if isinstance(obs, o.QRS) and isinstance(pattern.obs_seq[0], o.CardiacCycle): pattern.last_tnet.set_equal(pattern.obs_seq[0].time, obs.time)
def _qrs0_tconst(pattern, qrs): """ Temporal constraints of the QRS complex that must be at the beginning of the flutter. """ BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, qrs.time) tnet.add_constraint(pattern.hypothesis.start, pattern.hypothesis.end, Iv(C.VFLUT_MIN_DUR, np.inf))
def _t_tconst(pattern, twave): """ Temporal constraints of the T Waves wrt the corresponding QRS complex. """ BASIC_TCONST(pattern, twave) tnet = pattern.last_tnet obseq = pattern.obs_seq idx = pattern.get_step(twave) beats = pattern.evidence[o.QRS] qidx = qrsidx + len(beats) if qrsidx < 0 else qrsidx qrs = beats[qidx] if qidx > 1: refsq = beats[qidx - 1].earlystart - beats[qidx - 2].lateend tnet.add_constraint(qrs.time, twave.end, Iv(0, max(0, refsq - C.TQ_INTERVAL_MIN))) if idx > 0 and isinstance(obseq[idx - 1], o.PWave): pwave = obseq[idx - 1] tnet.add_constraint( pwave.end, twave.start, Iv(C.ST_INTERVAL.start, C.PQ_INTERVAL.end + C.QRS_DUR.end)) if qidx < len(beats) - 1: tnet.set_before(twave.end, beats[qidx + 1].start) #ST interval tnet.add_constraint(qrs.end, twave.start, C.ST_INTERVAL) #QT duration tnet.add_constraint(qrs.start, twave.end, C.N_QT_INTERVAL) #RT variation if qidx % 2 == 0: rtmean, rtstd = pattern.hypothesis.meas.rt #We also define a constraint on T wave end based on the last #distance between normal and ectopic QRS. if qidx > 0: tnet.add_constraint( qrs.end, twave.end, Iv(0, beats[qidx - 1].earlystart - beats[qidx - 2].lateend)) else: rts = _get_measures(pattern, 1)[2] rtmean, rtstd = np.mean(rts), np.std(rts) if rtmean > 0: #The mean and standard deviation of the PQ measurements will #influence the following observations. maxdiff = (C.QT_ERR_STD if len(pattern.evidence[o.TWave]) < 10 else rtstd) maxdiff = max(maxdiff, C.MIN_QT_STD) interv = Iv(int(rtmean - 2.5 * maxdiff), int(rtmean + 2.5 * maxdiff)) #We avoid possible inconsistencies with constraint introduced by #the rhythm information. try: existing = tnet.get_constraint(qrs.time, twave.end).constraint except KeyError: existing = Iv(-np.inf, np.inf) if interv.overlap(existing): tnet.add_constraint(qrs.time, twave.end, interv)
def _reg_qrs_tconst(pattern, qrs): """ Temporal constraints for regular beats, which appear after every ectopic beat. """ beats = pattern.evidence[o.QRS] idx = beats.index(qrs) tnet = pattern.last_tnet hyp = pattern.hypothesis BASIC_TCONST(pattern, qrs) tnet.add_constraint(qrs.start, qrs.end, C.NQRS_DUR) tnet.set_before(qrs.time, hyp.end) #Constraints with the precedent T Wave _qrs_after_twave(pattern, qrs) #The environment QRS complex determines the beginning of the bigeminy. if pattern.get_evidence_type(qrs)[1] is ENV: tnet.set_equal(hyp.start, qrs.time) else: #The first regular beat takes the reference RR from the previous rhythm #and the subsequent take the reference from the proper bigeminy. if idx == 2: refrr, stdrr = pattern.evidence[o.Cardiac_Rhythm][0].meas[0] max_var = max(2 * C.RR_MAX_DIFF, 4 * stdrr) tnet.add_constraint( beats[0].time, qrs.time, Iv(min(2 * refrr - max_var, refrr * C.COMPAUSE_MIN_F), max(2 * refrr + max_var, refrr * C.COMPAUSE_MAX_F))) else: ref2rr = beats[idx - 2].time.end - beats[idx - 4].time.start mrr, srr = hyp.meas.rr const = Iv(min(ref2rr - 2 * C.RR_MAX_DIFF, 2 * mrr - 4 * srr), max(ref2rr + 2 * C.RR_MAX_DIFF, 2 * mrr + 4 * srr)) tnet.add_constraint(beats[idx - 2].time, qrs.time, const) tnet.add_constraint(beats[idx - 2].start, qrs.start, const) tnet.add_constraint(beats[idx - 2].end, qrs.end, const) #We guide the morphology search to be similar to the previous regular #QRS complex. qrs.shape = beats[idx - 2].shape qrs.paced = beats[idx - 2].paced #Compensatory pause RR minrr = beats[idx - 1].time.start - beats[idx - 2].time.end maxrr = beats[idx - 1].time.end - beats[idx - 2].time.start refcompause = (beats[idx - 2].time.start - beats[idx - 3].time.start if idx > 2 else maxrr * C.COMPAUSE_RREXT_MAX_F) mincompause = max( C.COMPAUSE_MIN_DUR, maxrr, min(minrr * C.COMPAUSE_RREXT_MIN_F, refcompause - C.TMARGIN, minrr + C.COMPAUSE_RREXT_MIN)) tnet.add_constraint(beats[idx - 1].time, qrs.time, Iv(mincompause, maxrr * C.COMPAUSE_RREXT_MAX_F)) #Beats cannot overlap tnet.add_constraint(beats[idx - 1].end, qrs.start, Iv(C.TQ_INTERVAL_MIN, np.Inf))
def _t_qrs_tconst(pattern, qrs): """ Temporal constraints wrt the leading QRS complex. """ BASIC_TCONST(pattern, qrs) twave = pattern.hypothesis tc = pattern.last_tnet tc.add_constraint(qrs.end, twave.start, C.ST_INTERVAL) tc.add_constraint(qrs.start, twave.end, C.QT_INTERVAL) tc.add_constraint(qrs.end, twave.end, C.SQT_INTERVAL) tc.add_constraint(twave.start, twave.end, C.TW_DURATION)
def _prev_multrhythm_tconst(pattern, rhythm): """ Temporal constraints of the fibrillation with the cardiac rhythms between the last atrial fibrillation and the precedent one. """ BASIC_TCONST(pattern, rhythm) const = Iv(1, C.AFIB_MAX_DELAY - 1) tnet = pattern.last_tnet tnet.add_constraint(rhythm.start, rhythm.end, const) tnet.add_constraint(rhythm.start, pattern.hypothesis.start, const) tnet.add_constraint(rhythm.end, pattern.hypothesis.start, const)
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))
def _qrs2_tconst(pattern, qrs): """Temporal constraints of the second QRS complex""" BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.start, qrs.time) tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR) if pattern.evidence[o.QRS].index(qrs) == 1: prev = pattern.evidence[o.QRS][0] tnet.add_constraint(prev.time, qrs.time, Iv(C.TACHY_RR.start, C.BRADY_RR.end)) elif (pattern.evidence[o.Cardiac_Rhythm] and isinstance(pattern.evidence[o.Cardiac_Rhythm][0], o.Asystole)): pattern.last_tnet.set_equal(pattern.hypothesis.start, qrs.time)
def _p_tconst(pattern, pwave): """ Temporal constraints of the P Waves wrt the corresponding QRS complex """ BASIC_TCONST(pattern, pwave) tnet = pattern.last_tnet tnet.add_constraint(pwave.start, pwave.end, C.PW_DURATION) #We find the QRS observed just before that P wave. idx = pattern.get_step(pwave) if idx > 0 and isinstance(pattern.trseq[idx - 1][1], o.QRS): qrs = pattern.trseq[idx - 1][1] #PR interval tnet.add_constraint(pwave.start, qrs.start, C.N_PR_INTERVAL) tnet.set_before(pwave.end, qrs.start)
def _p_qrs_tconst(pattern, pwave): """ Temporal constraints of the P Wave wrt the corresponding QRS complex """ BASIC_TCONST(pattern, pwave) obseq = pattern.obs_seq idx = pattern.get_step(pwave) if idx == 0 or not isinstance(obseq[idx - 1], o.QRS): return qrs = obseq[idx - 1] tnet = pattern.last_tnet tnet.add_constraint(pwave.start, pwave.end, PW_DURATION) #PR interval tnet.add_constraint(pwave.start, qrs.start, N_PR_INTERVAL) tnet.set_before(pwave.end, qrs.start)