Exemplo n.º 1
0
def _t_gconst(pattern, defl):
    """
    T Wave abstraction pattern general constraints, checked when all the
    evidence has been observed.
    """
    twave = pattern.hypothesis
    if defl.earlystart != defl.latestart or not pattern.evidence[o.QRS]:
        return
    qrs = pattern.evidence[o.QRS][0]
    # Wave limits
    beg = int(twave.earlystart)
    end = int(twave.lateend)
    ls_lim = int(twave.latestart - beg)
    ee_lim = int(twave.earlyend - beg)
    # Start and end estimation.
    endpoints = {}
    for lead in sorted(qrs.shape, key=lambda l: qrs.shape[l].amplitude, reverse=True):
        baseline, _ = characterize_baseline(lead, beg, end)
        sig = sig_buf.get_signal_fragment(beg, end, lead=lead)[0]
        verify(len(sig) == end - beg + 1)
        ep = _delimit_t(sig, baseline, ls_lim, ee_lim, qrs.shape[lead])
        if ep is not None:
            endpoints[lead] = ep
    verify(endpoints)
    limits = max(endpoints.iteritems(), key=lambda ep: ep[1][1])[1][0]
    # We verify that in all leads the maximum slope of the T wave fragment does
    # not exceed the threshold.
    for lead in endpoints:
        sig = sig_buf.get_signal_fragment(beg + limits.start, beg + limits.end, lead=lead)[0]
        verify(np.max(np.abs(np.diff(sig))) <= qrs.shape[lead].maxslope * C.TQRS_MAX_DIFFR)
        # Amplitude measure
        if lead in endpoints:
            mx, mn = np.amax(sig), np.amin(sig)
            pol = 1.0 if max(mx - sig[0], mx - sig[-1]) >= -min(mn - sig[0], mn - sig[1]) else -1.0
            twave.amplitude[lead] = pol * np.ptp(sig)
    twave.start.value = Iv(beg + limits.start, beg + limits.start)
    twave.end.value = Iv(beg + limits.end, beg + limits.end)
    # The duration of the T Wave must be greater than the QRS
    # (with a security margin)
    verify(twave.earlyend - twave.latestart > qrs.earlyend - qrs.latestart - C.TMARGIN)
    # The overlapping between the energy interval and the T Wave must be at
    # 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)
Exemplo n.º 2
0
def _characterize_signal(beg, end):
    """
    Characterizes the available signal in a specific time interval.

    Parameters
    ----------
    beg:
        Starting time point of the interval.
    end:
        Last time point of the interval.

    Returns
    -------
    out:
        sortedlist with one entry by lead. Each entry is a 5-size tuple with
        the lead, the signal samples, the relevant points to represent the
        samples, the baseline level estimation for the fragment, and the
        quality of the fragment in that lead.
    """
    siginfo = sortedcontainers.SortedList(key=lambda v: -v[4])
    for lead in sig_buf.get_available_leads():
        baseline, quality = characterize_baseline(lead, beg, end)
        sig = sig_buf.get_signal_fragment(beg, end, lead=lead)[0]
        if len(sig) == 0:
            return None
        # We build a signal simplification taking at most 9 points, and with
        # a minimum relevant deviation of 50 uV.
        points = DP.arrayRDP(sig, ph2dg(0.05), 9)
        siginfo.add((lead, sig, points, baseline, quality))
    return siginfo
Exemplo n.º 3
0
def characterize_baseline(lead, beg, end):
    """
    Obtains the baseline estimation for a fragment delimited by two time
    points in a specific lead. It also obtains a quality estimator for the
    fragment.

    Parameters
    ----------
    lead:
        Selected lead to obtain the baseline estimator.
    beg:
        Starting sample of the interval.
    end:
        Ending sample of the interval.

    Returns
    ------
    out: (baseline, quality)
        Tuple with (baseline, quality) estimators. At the moment, the quality
        estimator is not yet numerically characterized, but we have strong
        evidence that the higher this value is, the higher the signal quality
        of the fragment where the baseline has been estimated.
    """
    assert beg >= 0 and end >= beg
    # We need at least 1 second of signal to estimate the baseline and the
    # quality.
    MIN_LENGTH = ms2sp(1000)
    if end - beg < MIN_LENGTH:
        center = beg + (end - beg) / 2.0
        beg = max(0, int(center - MIN_LENGTH / 2))
        end = int(center + MIN_LENGTH / 2)
    signal = sig_buf.get_signal_fragment(beg, end, lead=lead)[0]
    return (sig_meas.mode(signal), sig_meas.kurtosis(signal))
Exemplo n.º 4
0
def _vflut_gconst(pattern, _):
    """
    General constraints of the pattern, checked every time a new observation
    is added to the evidence. These constraints simply state that the majority
    of the leads must show a positive detection of a ventricular flutter.
    """
    if not pattern.evidence[o.Cardiac_Rhythm]:
        return
    hyp = pattern.hypothesis
    ##################
    beg = int(hyp.earlystart)
    if beg < 0:
        beg = 0
    end = int(hyp.earlyend)
    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))
Exemplo n.º 5
0
def _contains_qrs(pattern):
    """
    Checks if inside the flutter fragment there is a waveform "identical" to
    the first environment QRS complex.
    """
    qrs = pattern.evidence[o.QRS][0]
    # We limit the duration of the QRS to check this condition.
    if qrs.lateend - qrs.earlystart not in C.NQRS_DUR:
        return False
    defls = pattern.evidence[o.Deflection]
    if len(defls) > 1:
        limit = defls[-3].lateend if len(defls) > 2 else qrs.lateend
        sig = {}
        # We take the signal fragment with maximum correlation with the QRS
        # signal in each lead, and we check if the two fragments can be
        # clustered as equal QRS complexes.
        qshape = {}
        corr = -np.Inf
        delay = 0
        leads = sig_buf.get_available_leads()
        for lead in leads:
            qshape[lead] = o.QRSShape()
            sigfr = sig_buf.get_signal_fragment(qrs.earlystart,
                                                qrs.lateend + 1,
                                                lead=lead)[0]
            qshape[lead].sig = sigfr - sigfr[0]
            qshape[lead].amplitude = np.ptp(qshape[lead].sig)
            sig[lead] = sig_buf.get_signal_fragment(limit,
                                                    defls[-1].earlystart,
                                                    lead=lead)[0]
            if len(sig[lead]) > 0 and len(qshape[lead].sig) > 0:
                lcorr, ldelay = xcorr_valid(sig[lead], qshape[lead].sig)
                if lcorr > corr:
                    corr, delay = lcorr, ldelay
        if 0 <= delay < len(sig[lead]):
            sshape = {}
            for lead in leads:
                sshape[lead] = o.QRSShape()
                sshape[lead].sig = sig[lead][
                    delay:delay + len(qshape[lead].sig)] - sig[lead][delay]
                sshape[lead].amplitude = np.ptp(sshape[lead].sig)
            return not signal_unmatch(sshape, qshape)
    return False
Exemplo n.º 6
0
def _verify_atrial_activity(pattern):
    """
    Checks if the atrial activity is consistent with the definition of atrial
    fibrillation (that is, absence of constant P Waves or flutter-like
    baseline activity.)
    """
    beats = pattern.evidence[o.QRS][-5:]
    obseq = pattern.obs_seq
    atr_sig = {lead: [] for lead in sig_buf.get_available_leads()}
    pw_lims = []
    idx = pattern.get_step(beats[0])
    # First we get all the signal fragments between ventricular observations,
    # which are the only recognized by this pattern. In these fragments is where
    # atrial activity may be recognized.
    for i in range(idx + 1, len(obseq)):
        if isinstance(obseq[i], o.QRS):
            beg = next(obs for obs in reversed(obseq[:i])
                       if obs is not None).lateend
            end = obseq[i].earlystart
            if end - beg > ms2sp(200):
                beg = end - ms2sp(200)
            pw_lims.append((beg, end))
    for i in range(len(beats) - 1):
        beg, end = beats[i].lateend, beats[i + 1].earlystart
        for lead in atr_sig:
            atr_sig[lead].append(
                sig_buf.get_signal_fragment(beg, end, lead=lead)[0] -
                characterize_baseline(lead, beg, end)[0])
    # Flutter check (only for atrial activity)
    aflut = set()
    for lead in atr_sig:
        sigfr = np.concatenate(atr_sig[lead])
        if len(sigfr) > 15 and _is_VF(sigfr):
            aflut.add(lead)
    # FIXME improve flutter check, now is quite poor.
    # aflut = frozenset()
    # P waveform check (only for leads where flutters were not found.)
    pwaves = []
    for beg, end in pw_lims:
        pwsig = _get_pwave_sig(beg, end)
        if pwsig is not None:
            for lead in aflut:
                pwsig.pop(lead, None)
            if not pwsig:
                continue
            for wave in pwaves:
                verify(
                    abs(wave.values()[0].pr - pwsig.values()[0].pr) > C.TMARGIN
                    or not signal_match(wave, pwsig))
            pwaves.append(pwsig)
Exemplo n.º 7
0
def _get_pwave_sig(beg, end):
    """
    Checks if before a QRS complex there is a waveform similar to a P Wave. In
    an atrial fibrillation context, there cannot be any recognizable atrial
    activity.

    Parameters:
    ----------
    beg:
        Earliest point for the starting of the P Wave. This limit may be
        further constrained if the distance between the two parameters is
        excessive.
    end:
        Latest point for the ending of the P Wave. **It is assumed to be the
        starting point of the QRS complex associated to the P Wave**.

    Returns
    -------
    out:
        Dictionary with a tuple for each lead in which a P-Wave can be
        recognized. The tuple contains the distance in samples from *end* to
        the beginning of the P-Wave, and the signal fragment containing the
        P-Wave.
    """
    # If the result is cached, we use it
    result = PCACHE.get((beg, end), None)
    if result is not None:
        return result.copy()
    est = end - ms2sp(250) if end - beg > ms2sp(250) else beg
    lst = end - ms2sp(80)
    eend = est + ms2sp(40)
    ltnd = end - ms2sp(20)
    if est > lst or eend > end or eend > ltnd:
        # Inconsistency
        return None
    pwave = o.PWave()
    limits = delineate_pwave(est, lst, eend, ltnd, pwave)
    if limits is None:
        return None
    result = {}
    for lead in pwave.amplitude:
        sig = sig_buf.get_signal_fragment(est + limits.start,
                                          est + limits.end + 1,
                                          lead=lead)[0]
        result[lead] = PW_SIG(end - (est + limits.start), sig)
    # Result is cached
    PCACHE[(beg, end)] = result
    return result.copy()
Exemplo n.º 8
0
def _check_missed_beats(pattern):
    """
    Checks if a rhythm pattern has missed a QRS complex in the identification,
    by looking for a waveform "identical" to the last observed in the interval
    between the last two observations.
    """
    qrs = pattern.evidence[o.QRS][-1]
    obseq = pattern.obs_seq
    idx = obseq.index(qrs)
    if idx > 0:
        prevobs = next(
            (obs for obs in reversed(obseq[:idx]) if obs is not None), None)
        if prevobs is not None:
            if isinstance(prevobs, o.QRS):
                limit = max(prevobs.lateend,
                            prevobs.earlystart + qrs.lateend - qrs.earlystart)
            else:
                limit = prevobs.lateend
        else:
            limit = pattern.hypothesis.earlystart
        ulimit = qrs.earlystart - C.TACHY_RR.start
        if limit >= ulimit:
            return
        sig = {}
        # We take the signal fragment with maximum correlation with the QRS
        # signal in each lead, and we check if the two fragments can be
        # clustered as equal QRS complexes.
        qshape = {}
        corr = -np.Inf
        delay = 0
        leads = sig_buf.get_available_leads()
        for lead in leads:
            qshape[lead] = o.QRSShape()
            sigfr = sig_buf.get_signal_fragment(qrs.earlystart,
                                                qrs.lateend + 1,
                                                lead=lead)[0]
            qshape[lead].sig = sigfr - sigfr[0]
            qshape[lead].amplitude = np.ptp(qshape[lead].sig)
            sig[lead] = sig_buf.get_signal_fragment(limit, ulimit,
                                                    lead=lead)[0]
            lcorr, ldelay = xcorr_valid(sig[lead], qshape[lead].sig)
            if lcorr > corr:
                corr, delay = lcorr, ldelay
        if 0 <= delay < len(sig[lead]):
            sshape = {}
            for lead in leads:
                sshape[lead] = o.QRSShape()
                sshape[lead].sig = sig[lead][
                    delay:delay + len(qshape[lead].sig)] - sig[lead][delay]
                sshape[lead].amplitude = np.ptp(sshape[lead].sig)
            if isinstance(pattern.hypothesis, o.RegularCardiacRhythm):
                qref = pattern.evidence[o.QRS][-2]
                rr = float(qrs.earlystart - qref.earlystart)
                loc = (limit + delay - qref.earlystart) / rr
                # Check for one and two missed beats in regular positions
                if 0.45 <= loc <= 0.55:
                    verify(signal_unmatch(sshape, qshape), 'Missed beat')
                elif 0.28 <= loc <= 0.38 and not signal_unmatch(
                        sshape, qshape):
                    corr = -np.Inf
                    delay = 0
                    for lead in leads:
                        sig[lead] = sig_buf.get_signal_fragment(
                            int(qref.earlystart + 0.61 * rr),
                            min(
                                int(qref.earlystart + 0.71 * rr) +
                                len(qshape[lead].sig), int(qrs.earlystart)),
                            lead=lead,
                        )[0]
                        lcorr, ldelay = xcorr_valid(sig[lead],
                                                    qshape[lead].sig)
                        if lcorr > corr:
                            corr, delay = lcorr, ldelay
                    sshape = {}
                    for lead in leads:
                        sshape[lead] = o.QRSShape()
                        sshape[lead].sig = (
                            sig[lead][delay:delay + len(qshape[lead].sig)] -
                            sig[lead][delay])
                        sshape[lead].amplitude = np.ptp(sshape[lead].sig)
                    verify(signal_unmatch(sshape, qshape), 'Two missed beats')
                elif 0.61 <= loc <= 0.71 and not signal_unmatch(
                        sshape, qshape):
                    corr = -np.Inf
                    delay = 0
                    for lead in leads:
                        sig[lead] = sig_buf.get_signal_fragment(
                            int(qref.earlystart + 0.28 * rr),
                            min(
                                int(qref.earlystart + 0.38 * rr) +
                                len(qshape[lead].sig), int(qrs.earlystart)),
                            lead=lead,
                        )[0]
                        lcorr, ldelay = xcorr_valid(sig[lead],
                                                    qshape[lead].sig)
                        if lcorr > corr:
                            corr, delay = lcorr, ldelay
                    sshape = {}
                    for lead in leads:
                        sshape[lead] = o.QRSShape()
                        sshape[lead].sig = (
                            sig[lead][delay:delay + len(qshape[lead].sig)] -
                            sig[lead][delay])
                        sshape[lead].amplitude = np.ptp(sshape[lead].sig)
                    verify(signal_unmatch(sshape, qshape), 'Two missed beats')
            else:
                verify(signal_unmatch(sshape, qshape), 'Missed beat')
Exemplo n.º 9
0
def _guided_qrs_observation(hyp):
    """
    Performs the delineation and checking of the general constraints of the
    QRS abstraction pattern when a reference shape for seaching is set as the
    hypothesis shape. The modification is done in-place. modifying the
    hypothesis shape.

    Parameters
    ----------
    hyp:
        QRS observation that is the hypothesis of the pattern.
    """
    if hyp.shape:
        # We perform the alignment in the lead with highest energy.
        rlead, rshape = max(hyp.shape.iteritems(), key=lambda s: s[1].energy)
        ref = rshape.sig
        newshape = {}
        start = np.inf
        beg, end = (int(hyp.earlystart), min(int(hyp.latestart) + len(ref), int(hyp.lateend)))
        if beg < 0:
            beg = 0
        try:
            sig = sig_buf.get_signal_fragment(beg, end, lead=rlead)[0]
            verify(len(sig) == end - beg + 1)
            sig = sig - sig[0]
            _, idx = xcorr_full(sig, ref)
            verify(idx >= 0)
            sig = sig[idx : idx + len(ref)] - sig[idx]
            verify(len(sig) == len(ref))
            bref = rshape.waves[0].l
            rshape.move(-bref)
            shape = _get_guided_qrs_shape(sig, rshape)
            rshape.move(bref)
            shape.move(bref)
            # We admit a 25% variation in the energy of the new signal.
            verify(0.75 <= shape.energy / rshape.energy <= 1.25)
            # Absolute reference for QRS start
            start = idx - shape.waves[0].l
            verify(start >= 0)
            newshape[rlead] = shape
            for lead in hyp.shape:
                if lead is not rlead:
                    rshape = hyp.shape[lead]
                    bref = rshape.waves[0].l
                    sig = sig_buf.get_signal_fragment(
                        beg + start + bref, beg + start + rshape.waves[-1].r + 1, lead=lead
                    )[0]
                    sig = sig - sig[0]
                    rshape.move(-bref)
                    shape = _get_guided_qrs_shape(sig, rshape)
                    rshape.move(bref)
                    shape.move(bref)
                    newshape[lead] = shape
            verify(signal_match(hyp.shape, newshape))
            hyp.shape = newshape
            # The detected shapes may constrain the delineation area.
            llim = min(hyp.shape[lead].waves[0].l for lead in hyp.shape)
            if llim > 0:
                start = start + llim
                for lead in hyp.shape:
                    hyp.shape[lead].move(-llim)
            end = start + max(s.waves[-1].r for s in hyp.shape.itervalues())
            peak = start + min(s.waves[_reference_wave(s)].m for s in hyp.shape.itervalues())
            hyp.start.value = Iv(beg + start, beg + start)
            hyp.time.value = Iv(beg + peak, beg + peak)
            hyp.end.value = Iv(beg + end, beg + end)
            hyp.clustered = True
        except InconsistencyError:
            hyp.shape = {}
            hyp.paced = False