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 _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 _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 not _is_ectopic(qidx): 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 _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 _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 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 _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 range(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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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(5 * C.TACHY_RR.start, np.inf))
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 _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 _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 _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 _qrs_fin_pause_tconst(pattern, qrs): """ Temporal constraints for the compensatory pause of the last QRS of the extrasystole. """ BASIC_TCONST(pattern, qrs) tnet = pattern.last_tnet tnet.set_equal(pattern.hypothesis.end, qrs.time) beats = pattern.evidence[o.QRS] idx = beats.index(qrs) # We need all the previous evidence. if pattern.istate == 0: step = pattern.get_step(qrs) if isinstance(pattern.trseq[step - 1][1], o.TWave): twave = pattern.trseq[step - 1][1] 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] # Compensatory pause tnet.add_constraint( beats[-3].time, qrs.time, Iv(min(minrr + C.COMPAUSE_MIN_DUR, minrr * C.COMPAUSE_MIN_F), maxrr * C.COMPAUSE_MAX_F), ) # Advanced beat RR minrr = beats[-2].time.start - beats[-3].time.end maxrr = beats[-2].time.end - beats[-3].time.start tnet.add_constraint( beats[-2].time, qrs.time, Iv( min(minrr * C.COMPAUSE_RREXT_MIN_F, minrr + C.COMPAUSE_RREXT_MIN), maxrr * C.COMPAUSE_RREXT_MAX_F, ), ) # The last QRS should have the same morphology than the one before the # extrasystole. if not qrs.frozen: qrs.shape = beats[-3].shape qrs.paced = beats[-3].paced elif idx > 0: # We constraint the previous beat location. tnet.add_constraint( beats[idx - 1].time, qrs.time, Iv(C.TACHY_RR.start * C.COMPAUSE_MIN_F, C.BRADY_RR.start * C.COMPAUSE_RREXT_MAX_F), )
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 _deflection_tconst(pattern, defl): """Temporal constraints of the posterior deflections""" defls = pattern.evidence[o.Deflection] idx = defls.index(defl) hyp = pattern.hypothesis tnet = pattern.last_tnet prev = defls[idx - 1] tnet.remove_constraint(hyp.end, prev.time) # We create a new temporal network for the cyclic observations tnet = ConstraintNetwork() tnet.add_constraint(prev.time, defl.time, C.VFLUT_WW_INTERVAL) pattern.temporal_constraints.append(tnet) BASIC_TCONST(pattern, defl) tnet.add_constraint(defl.start, defl.end, Iv(0, C.VFLUT_WW_INTERVAL.end)) tnet.set_before(defl.time, hyp.end)