Exemple #1
0
    def test_set_before(self):
        v0, v1 = [Interval(-1, x) for x in range(2)]
        nw = ConstraintNetwork()
        nw.set_before(v0, v1)
        nw.minimize_network()
        assert v0 < v1
        assert v0.start == v1.start
        assert v0.start == -1

        nw.set_before(v1, v0)
        nw.minimize_network()
        assert v0 == v1
        assert v0 == v1
        assert v0.start == -1
Exemple #2
0
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
Exemple #3
0
    def test_equal(self):
        v0 = Interval(0, 10)
        v1 = Interval(7, 15)

        nw = ConstraintNetwork()
        nw.set_equal(v0, v1)
        nw.minimize_network()
        assert v0 == v1
Exemple #4
0
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)
Exemple #5
0
    def test_between(self):
        v0 = Interval(0, 10)
        v1 = Interval(7, 15)
        v2 = Interval(4, 10)

        nw = ConstraintNetwork()
        nw.set_between(v0, v1, v2)
        nw.minimize_network()
        assert v0 <= v1 <= v2

        v0 = Interval(0, 10)
        v1 = Interval(7, 15)
        v2 = Interval(4, 10)

        nw = ConstraintNetwork()
        nw.set_between(v2, v1, v0)
        nw.minimize_network()
        assert v2 <= v1 <= v0
Exemple #6
0
    def test_add_constraint(self):
        # Known example assertion (Detcher STP example in TCN paper)
        v0, v1, v2, v3, v4 = [Interval(-np.inf, np.inf) for _ in range(5)]
        v0.set(0, 0)
        nw = ConstraintNetwork()
        nw.add_constraint(v0, v1, Interval(10, 20))
        nw.add_constraint(v1, v2, Interval(30, 40))
        nw.add_constraint(v3, v2, Interval(10, 20))
        nw.add_constraint(v3, v4, Interval(40, 50))
        nw.add_constraint(v0, v4, Interval(60, 70))
        nw.minimize_network()
        assert v0 == Interval(0, 0)
        assert v1 == Interval(10, 20)
        assert v2 == Interval(40, 50)
        assert v3 == Interval(20, 30)
        assert v4 == Interval(60, 70)
        assert tuple(v0) == (0, 0)
        assert tuple(v1) == (10, 20)
        assert tuple(v2) == (40, 50)
        assert tuple(v3) == (20, 30)
        assert tuple(v4) == (60, 70)

        # Testing if a stricker constraint is applied
        v0, v1, v2, v3, v4 = [Interval(-np.inf, np.inf) for _ in range(5)]
        v0.set(0, 0)
        nw = ConstraintNetwork()
        nw.add_constraint(v0, v1, Interval(10, 20))
        nw.add_constraint(v1, v2, Interval(30, 40))
        nw.add_constraint(v3, v2, Interval(10, 20))
        nw.add_constraint(v3, v4, Interval(40, 50))
        nw.add_constraint(v0, v4, Interval(60, 70))

        nw.add_constraint(v0, v1, Interval(10, 15))
        nw.add_constraint(v1, v2, Interval(30, 35))
        nw.add_constraint(v3, v2, Interval(10, 15))
        nw.add_constraint(v3, v4, Interval(40, 45))
        nw.add_constraint(v0, v4, Interval(60, 65))
        nw.minimize_network()
        assert v0 == Interval(0, 0)
        assert v1 == Interval(10, 10)
        assert v2 == Interval(40, 40)
        assert v3 == Interval(25, 25)
        assert v4 == Interval(65, 65)
Exemple #7
0
    def test_add_constraint(self):
        # Known example assertion (Detcher STP example in TCN paper)
        v0, v1, v2, v3, v4 = [Variable() for _ in range(5)]
        v0.value = Interval(0, 0)
        nw = ConstraintNetwork()
        nw.add_constraint(v0, v1, Interval(10, 20))
        nw.add_constraint(v1, v2, Interval(30, 40))
        nw.add_constraint(v3, v2, Interval(10, 20))
        nw.add_constraint(v3, v4, Interval(40, 50))
        nw.add_constraint(v0, v4, Interval(60, 70))
        nw.minimize_network()
        assert v0.value == Interval(0, 0)
        assert v1.value == Interval(10, 20)
        assert v2.value == Interval(40, 50)
        assert v3.value == Interval(20, 30)
        assert v4.value == Interval(60, 70)

        # Testing if a stricker constraint is applied
        v0, v1, v2, v3, v4 = [Variable() for _ in range(5)]
        v0.value = Interval(0, 0)
        nw = ConstraintNetwork()
        nw.add_constraint(v0, v1, Interval(10, 20))
        nw.add_constraint(v1, v2, Interval(30, 40))
        nw.add_constraint(v3, v2, Interval(10, 20))
        nw.add_constraint(v3, v4, Interval(40, 50))
        nw.add_constraint(v0, v4, Interval(60, 70))

        nw.add_constraint(v0, v1, Interval(10, 15))
        nw.add_constraint(v1, v2, Interval(30, 35))
        nw.add_constraint(v3, v2, Interval(10, 15))
        nw.add_constraint(v3, v4, Interval(40, 45))
        nw.add_constraint(v0, v4, Interval(60, 65))
        nw.minimize_network()
        assert v0.value == Interval(0, 0)
        assert v1.value == Interval(10, 10)
        assert v2.value == Interval(40, 40)
        assert v3.value == Interval(25, 25)
        assert v4.value == Interval(65, 65)
Exemple #8
0
def _ect_qrs_tconst(pattern, qrs):
    """
    Temporal constraints for ectopic beats, which appear after every regular
    beat.
    """
    beats = pattern.evidence[o.QRS]
    idx = beats.index(qrs)
    tnet = pattern.last_tnet
    hyp = pattern.hypothesis
    if idx > 0:
        prev = beats[idx - 1]
        #After the second couplet, every ectopic beat introduces a new temporal
        #network in the pattern to make it easier the minimization.
        if idx > 3:
            tnet.remove_constraint(hyp.end, prev.time)
            #We create a new temporal network for the cyclic observations
            tnet = ConstraintNetwork()
            pattern.temporal_constraints.append(tnet)
            #The duration of each couplet should not have high instantaneous
            #variations.
            refrr = beats[idx - 2].time.end - beats[idx - 3].time.start
            tnet.add_constraint(
                prev.time, qrs.time,
                Iv(refrr - C.RR_MAX_DIFF, refrr + C.RR_MAX_DIFF))
            #We guide the morphology search to be similar to the previous
            #ectopic QRS complex.
            qrs.shape = beats[idx - 2].shape
        #The reference RR varies from an upper limit to the last measurement,
        #through the contextual previous rhythm.
        refrr = C.BRADY_RR.end
        stdrr = 0.1 * refrr
        if pattern.evidence[o.Cardiac_Rhythm] and idx == 1:
            mrr, srr = pattern.evidence[o.Cardiac_Rhythm][0].meas.rr
            if mrr > 0:
                refrr, stdrr = mrr, srr
        elif idx > 1:
            refrr, stdrr = hyp.meas.rr
        #Ectopic beats must be advanced wrt the reference RR
        tnet.add_constraint(
            prev.time, qrs.time,
            Iv(C.TACHY_RR.start, max(C.TACHY_RR.start, refrr - stdrr)))
        #Beats cannot overlap
        tnet.add_constraint(prev.end, qrs.start, Iv(C.TQ_INTERVAL_MIN, np.Inf))
    BASIC_TCONST(pattern, qrs)
    tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR)
    tnet.set_before(qrs.time, hyp.end)
    #Constraints with the precedent T Wave
    _qrs_after_twave(pattern, qrs)
Exemple #9
0
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
Exemple #10
0
def set_knowledge_base(knowledge):
    """
    Sets the knowledge base to be used in the interpretation, and updates
    the necessary global variables.
    """
    global KNOWLEDGE, _OBSERVABLES, _ABDUCIBLES, _LMAP, _EXCLUSION
    KNOWLEDGE = knowledge
    #First, we check the consistency of every single abstraction pattern
    for p in KNOWLEDGE:
        #In the Environment and Abstracted sets no repeated types can happen.
        for qset in (p.abstracted, p.environment):
            for q in qset:
                #The only coincidence must be q
                assert len(set(q.mro()) & qset) == 1
        #There should be no subclass relations between hyp. and abstractions
        for q in p.abstracted:
            assert not p.Hypothesis in q.mro()
        assert not set(p.Hypothesis.mro()) & p.abstracted
        #The abstraction transitions must be properly set.
        for q in p.abstractions:
            assert q in p.abstracted
            for tr in p.abstractions[q]:
                assert tr.observable is q
                assert tr.abstracted is ABSTRACTED
                assert tr in p.transitions
    #Organization of all the observables in abstraction levels.
    _OBSERVABLES = set.union(*(({p.Hypothesis} | p.abstracted | p.environment)
                               for p in KNOWLEDGE))
    _ABDUCIBLES = tuple(set.union(*(p.abstracted for p in KNOWLEDGE)))
    #To perform the level assignment, we use a constraint network.
    _CNET = ConstraintNetwork()
    #Mapping from observable types to abstraction levels.
    _LMAP = {}
    for q in _OBSERVABLES:
        _LMAP[q] = Variable(value=Interval(0, numpy.inf))
    #We set the restrictions, and minimize the network
    #All subclasses must have the same level than the superclasses
    for q in _OBSERVABLES:
        for sup in (set(q.mro()) & _OBSERVABLES) - {q}:
            _CNET.set_equal(_LMAP[q], _LMAP[sup])
    #Abstractions force a level increasing
    for p in KNOWLEDGE:
        for qabs in p.abstracted:
            _CNET.add_constraint(_LMAP[qabs], _LMAP[p.Hypothesis],
                                 Interval(1, numpy.inf))
    #Network minimization
    _CNET.minimize_network()
    #Now we assign to each observable the minimum of the solutions interval.
    for q in _OBSERVABLES:
        _LMAP[q] = int(_LMAP[q].start)
    #Manual definition of the exclusion relation between observables.
    _EXCLUSION = {
        Deflection: (Deflection, ),
        QRS: (QRS, ),
        TWave: (TWave, ),
        PWave: (PWave, ),
        CardiacCycle: (CardiacCycle, ),
        Cardiac_Rhythm: (Cardiac_Rhythm, )
    }
    #Automatic expansion of the exclusion relation.
    for q, qexc in _EXCLUSION.iteritems():
        _EXCLUSION[q] = tuple(
            (q2 for q2 in _OBSERVABLES if issubclass(q2, qexc)))
    for q in sorted(_OBSERVABLES, key=_LMAP.get, reverse=True):
        _EXCLUSION[q] = tuple(
            set.union(*(set(_EXCLUSION[q2]) for q2 in _EXCLUSION
                        if issubclass(q, q2))))
Exemple #11
0
 def _qrs_tconst(pattern, qrs):
     """
     Temporal constraints to observe a new QRS complex.
     """
     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)
     #The environment complex sets the start of the rhythm observation.
     if pattern.get_evidence_type(qrs)[1] is ENVIRONMENT:
         tnet.set_equal(hyp.start, qrs.time)
     else:
         if idx > 0:
             prev = beats[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, qrs.time, rr_bounds)
             if rr_bounds is not C.TACHY_RR:
                 #Also bounding on begin and end, but with relaxed variation
                 #margin.
                 rlx_rrb = Iv(rr_bounds.start - C.TMARGIN,
                              rr_bounds.end + C.TMARGIN)
                 tnet.add_constraint(prev.start, qrs.start, rlx_rrb)
                 tnet.add_constraint(prev.end, qrs.end, rlx_rrb)
             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)
             ##RR evolution constraint. We combine the statistical limits
             #with a dynamic evolution.
             if idx > 1:
                 prev2 = beats[idx - 2]
                 rrev = prev.time.start - prev2.time.start
                 if hyp.meas.rr[0] > 0:
                     meanrr, stdrr = hyp.meas.rr
                     const = Iv(
                         min(0.8 * rrev, rrev - C.RR_MAX_DIFF,
                             meanrr - 2 * stdrr),
                         max(1.2 * rrev, rrev + C.RR_MAX_DIFF,
                             meanrr + 2 * stdrr))
                 else:
                     const = Iv(min(0.8 * rrev, rrev - C.RR_MAX_DIFF),
                                max(1.2 * rrev, rrev + C.RR_MAX_DIFF))
                 tnet.add_constraint(prev.time, qrs.time, const)
             pattern.temporal_constraints.append(tnet)
             #TODO improve
             if not qrs.frozen and hyp.morph:
                 nullsh = o.QRSShape()
                 refbeat = next((b for b in reversed(beats[:idx])
                                 if not b.clustered and all(
                                     b.shape.get(lead, nullsh).tag ==
                                     hyp.morph[lead].tag
                                     for lead in hyp.morph)), None)
                 if refbeat is not None:
                     qrs.shape = refbeat.shape
                     qrs.paced = refbeat.paced
     BASIC_TCONST(pattern, qrs)
     tnet.add_constraint(qrs.start, qrs.end, C.QRS_DUR)
     tnet.set_before(qrs.time, hyp.end)