def challenge(record, alarm_type):
    """Evaluates the presence of a given alarm in a given record"""
    assert alarm_type in (ASYST, BRAD, TACH, VFLUT,
                          VTACH), ('Unknown alarm type {0}'.format(alarm_type))
    annots = MIT.read_annotations(INTERP_DIR + str(record) + '.igqrs')
    rec = None
    if alarm_type == VTACH:
        rec = MIT.load_MIT_record(DB_DIR + str(record))
        rec.leads = [VALID_LEAD_NAMES[l] for l in rec.leads]
    return EVAL_ALARM_F[alarm_type](annots, rec)
def eval_asyst(annotations, _):
    """Evaluates the asystole presence"""
    def check_vf(start, end):
        """Obtains the flutter waves present in a given interval"""
        return [
            a for a in annotations
            if start < a.time < end and a.code is ECGCodes.FLWAV
        ]

    lth, uth, dth = ms2sp(
        (4 * 60 + 45) * 1000), ms2sp(5 * 60 * 1000), ms2sp(3500)
    beats = np.array([
        b.time for b in annotations
        if MIT.is_qrs_annotation(b) and lth <= b.time <= uth
    ])
    if len(beats) < 2:
        return not check_vf(lth, uth)
    if uth - beats[-1] > dth:
        return not check_vf(beats[-1], uth)
    rrs = np.diff(beats)
    for i in range(len(rrs)):
        if rrs[i] > dth:
            if not check_vf(beats[i], beats[i + 1]):
                return True
    return False
def eval_vtach(anns, rec):
    """Evaluates the ventricular tachycardia presence"""
    lth, uth = ms2sp((4 * 60 + 45) * 1000), ms2sp(5 * 60 * 1000)
    #First we perform clustering on all beats
    qrsdur = {}
    clusters = []
    for ann in anns:
        if MIT.is_qrs_annotation(ann):
            delin = json.loads(ann.aux)
            qrs = {}
            for lead in delin:
                sidx = rec.leads.index(lead)
                qon = ann.time + delin[lead][0]
                qoff = ann.time + delin[lead][-1]
                qrs[lead] = SIG(sig=rec.signal[sidx][qon:qoff + 1])
            qrsdur[ann] = max(len(s.sig) for s in qrs.values())
            clustered = False
            for cluster in clusters:
                if _same_cluster(cluster[0], qrs):
                    cluster[1].add(ann)
                    clustered = True
                    break
            if not clustered:
                clusters.append((qrs, set([ann]), qrsdur[ann]))
    if not clusters:
        return False
    #We take as normal beats the cluster with highest number of annotations.
    nclust = max(clusters, key=lambda cl: len(cl[1]))
    beats = [
        ann for ann in anns
        if MIT.is_qrs_annotation(ann) and lth <= ann.time <= uth
    ]
    if len(beats) < 5:
        return False
    for i in range(len(beats) - 4):
        tach = ms2bpm(sp2ms(beats[i + 4].time - beats[i].time) / 4.0) >= 100
        bset = set(beats[i:i + 5])
        ventr = (np.min([qrsdur[b] for b in bset]) > ms2sp(110)
                 or any([bset.issubset(cl[1]) for cl in clusters]))
        if (tach and ventr and all([b not in nclust[1] for b in bset])):
            return True
    return False
def eval_tach(annotations, _):
    """Evaluates the tachycardia presence"""
    lth, uth = ms2sp((4 * 60 + 30) * 1000), ms2sp(5 * 60 * 1000)
    beats = np.array([
        b.time for b in annotations
        if MIT.is_qrs_annotation(b) and lth <= b.time <= uth
    ])
    for i in range(len(beats) - 16):
        if ms2bpm(sp2ms(beats[i + 16] - beats[i]) / 16.0) > 120:
            return True
    return False
def eval_brad(annotations, _):
    """Evaluates the bradycardia presence"""
    lth, uth = ms2sp((4 * 60 + 45) * 1000), ms2sp(5 * 60 * 1000)
    beats = np.array([b.time for b in annotations if MIT.is_qrs_annotation(b)])
    variability = np.std(np.diff(beats))
    #The default threshold is 40 bpm, but if the rhythm shows high variability,
    #we relax such threshold to 45 bpm.
    thres = 45 if variability > ms2sp(200) else 40
    lidx = bisect.bisect_left(beats, lth)
    uidx = bisect.bisect_right(beats, uth)
    for i in range(lidx, uidx - 4):
        bpm = int(ms2bpm(sp2ms(beats[i + 4] - beats[i]) / 4.0))
        if bpm <= thres:
            return True
    return False
Exemple #6
0
                     metavar='ann',
                     required=True,
                     help=('Annotations resulting from the abductive '
                           'interpretation of the ECG signal'))
 parser.add_argument('-c',
                     metavar='cluster',
                     required=True,
                     help=('Extension of the file containing the clustering'
                           ' information.'))
 parser.add_argument('-o',
                     metavar='oann',
                     default='cls',
                     help=('Save annotations with classified QRS complexes'
                           ' as annotator oann (default: cls)'))
 args = parser.parse_args()
 rec = MIT.load_MIT_record(args.r)
 set_sampling_freq(rec.frequency)
 print('Classifying record {0}'.format(args.r))
 #Reconstruction of the abductive interpretation
 annots = MIT.read_annotations('{0}.{1}'.format(args.r, args.a))
 interp = interp2annots.ann2interp(rec, annots)
 #Cluster information
 clusters = load_clustering(args.r, args.c, interp.observations)
 #QRS feature extraction
 features = get_features(interp)
 #Cluster feature extraction
 for c in clusters:
     clusters[c] = Cluster(clusters[c],
                           get_cluster_features(clusters[c], features))
 #Key function to compare clusters: First, we check clusters with more than
 #30 beats; then, the clusters with more REGULAR or AFIB beats, and finally
Exemple #7
0
ANN = '.iqrs'

RHNAMES = {
    b'(N': 'Normal rhythm',
    b'(SVTA': 'Tachycardia',
    b'(SBR': 'Bradycardia',
    b'(AFIB': 'Atrial Fibrillation',
    b'(T': 'Trigeminy',
    b'(B': 'Bigeminy',
    b'(VFL': 'Ventricular Flutter',
    b'P': 'Absence of rhythm'
}

for rec in RECORDS:
    rhctr = Counter()
    anns = MIT.read_annotations(PATH + rec + ANN)
    rhythms = (a for a in anns if a.code in (ECGCodes.RHYTHM, ECGCodes.VFON))
    try:
        start = next(rhythms)
    except StopIteration:
        continue
    print('Interpretation results for record {0}:'.format(rec))
    print('Rhythm analysis:')
    nect = len([a for a in anns if a.aux == b'(EXT'])
    nbk = len([a for a in anns if a.aux == b'(BK'])
    ncpt = len([a for a in anns if a.aux == b'(CPT'])
    while True:
        end = next(rhythms, anns[-1])
        if start.aux in RHNAMES:
            rhctr[start.aux] += end.time - start.time
        elif start.code == ECGCodes.VFON:
Exemple #8
0
      '2015_BMI/validation/training_dataset/')
#DB = '/home/tomas/Escritorio/afdb/'
#DB = '/tmp/mit/'
ANN = '.iqrs'
#ANN = '.ibatr'
OANN = '.rhy'

RECORDS = [l.strip() for l in open(DB + 'RECORDS')]

for rec in RECORDS:
    if os.path.isfile(DB+rec+OANN):
        print('Annotator "{0}" already exists. Skipping record {1}'.format(
                                                                   OANN, rec))
        continue
    print('Converting record {0}'.format(rec))
    anns = MIT.read_annotations(DB+rec+ANN)
    record = MIT.load_MIT_record(DB+rec)
    assert record.frequency == SAMPLING_FREQ
    record.leads = [VALID_LEAD_NAMES[l] for l in record.leads]
    interp = i2a.ann2interp(record, anns)
    afibs = list(interp.get_observations(o.Atrial_Fibrillation))
    i = 0
    while i < len(afibs):
        print('{0}/{1}'.format(i,len(afibs)))
        afib = afibs[i]
        beats = list(interp.get_observations(o.QRS, filt=lambda q, af=afib:
                                  af.earlystart <= q.time.start <= af.lateend))
        rpks = np.array([qrs.time.start for qrs in beats])
        #We obtain the shape representing the AFIB morphology as the qrs
        #matching the shape with more other qrss within the rhythm.
        ctr = collections.Counter()
Exemple #9
0
def ann2interp(record, anns, fmt=False):
    """
    Returns an interpretation containing the observations represented in a list
    of annotations associated to a loaded MIT record. Note that only the
    *observations* field is properly set. The optional parameter *fmt* allows
    to specify if the specific Construe format for annotation files can be
    assumed. This parameter is also inferred from the first annotation in the
    list.
    """
    fmt = (fmt or len(anns) > 0 and anns[0].code is C.NOTE
                                                 and anns[0].aux == FMT_STRING)
    interp = Interpretation()
    observations = []
    RH_VALS = set(C.RHYTHM_AUX.values())
    for i in range(len(anns)):
        ann = anns[i]
        if ann.code in (C.PWAVE, C.TWAVE):
            obs = o.PWave() if ann.code == C.PWAVE else o.TWave()
            if fmt:
                beg = next(a for a in reversed(anns[:i]) if a.time < ann.time
                           and a.code == C.WFON and a.subtype == ann.code).time
                end = next(a for a in anns[i:] if a.time > ann.time
                          and a.code == C.WFOFF and a.subtype == ann.code).time
            else:
                beg = next(a for a in reversed(anns[:i]) if a.time < ann.time
                                                     and a.code == C.WFON).time
                end = next(a for a in anns[i:] if a.time > ann.time
                                                    and a.code == C.WFOFF).time
            obs.start.set(beg, beg)
            obs.end.set(end, end)
            if fmt:
                amp = json.loads(ann.aux)
                for l in amp.keys():
                    if l not in record.leads:
                        compatible = next((l2 for l2 in VALID_LEAD_NAMES
                                          if VALID_LEAD_NAMES[l2] == l), None)
                        if compatible is None:
                            raise ValueError('Unrecognized lead {0}'.format(l))
                        obs.amplitude[compatible] = amp.pop(l)
            else:
                leads = (record.leads if ann.code is C.TWAVE
                         else set(K.PWAVE_LEADS) & set(record.leads))
                leads = record.leads
                for lead in leads:
                    sidx = record.leads.index(lead)
                    s = record.signal[sidx][beg:end+1]
                    mx, mn = np.amax(s), np.amin(s)
                    pol = (1.0 if max(mx-s[0], mx-s[-1]) >= -min(mn-s[0],mn-s[1])
                           else -1.0)
                    obs.amplitude[lead] = pol * np.ptp(s)
            observations.append(obs)
        elif MIT.is_qrs_annotation(ann):
            obs = o.QRS()
            obs.time.set(ann.time, ann.time)
            obs.tag = ann.code
            delin = json.loads(ann.aux)
            #QRS start and end is first tried to set according to delineation
            #info. If not present, it is done according to delineation
            #annotations.
            if delin:
                for l in delin.keys():
                    if l not in record.leads:
                        compatible = next((l2 for l2 in VALID_LEAD_NAMES
                                          if VALID_LEAD_NAMES[l2] == l), None)
                        if compatible is None:
                            raise ValueError('Unrecognized lead {0}'.format(l))
                        delin[compatible] = delin.pop(l)
                beg = ann.time + min(d[0] for d in delin.values())
                end = ann.time + max(d[-1] for d in delin.values())
            else:
                def extra_cond(a):
                    return a.subtype == C.SYSTOLE if fmt else True
                beg = next(a for a in reversed(anns[:i]) if a.code==C.WFON
                           and extra_cond(a)).time
                end = next(a for a in anns[i:] if a.code == C.WFOFF
                           and extra_cond(a)).time
            #Endpoints set
            obs.start.set(beg, beg)
            obs.end.set(end, end)
            for lead in delin:
                assert len(delin[lead]) % 3 == 0, 'Unrecognized delineation'
                sidx = record.leads.index(lead)
                beg = ann.time + delin[lead][0]
                end = ann.time + delin[lead][-1]
                obs.shape[lead] = o.QRSShape()
                sig = record.signal[sidx][beg:end+1]
                obs.shape[lead].sig = sig-sig[0]
                obs.shape[lead].amplitude = np.ptp(sig)
                obs.shape[lead].energy = np.sum(np.diff(sig)**2)
                obs.shape[lead].maxslope = np.max(np.abs(np.diff(sig)))
                waves = []
                for i in range(0, len(delin[lead]), 3):
                    wav = Wave()
                    wav.pts = tuple(delin[lead][i:i+3])
                    wav.move(-delin[lead][0])
                    if wav.r >= len(sig):
                        warnings.warn('Found delineation information after '
                         'the end of the signal in annotation {0}'.format(ann))
                        break
                    wav.amp = (np.sign(sig[wav.m]-sig[wav.l]) *
                               np.ptp(sig[wav.l:wav.r+1]))
                    wav.e = np.sum(np.diff(sig[wav.l:wav.r+1])**2)
                    wav.move(delin[lead][0])
                    wav.move(ann.time-obs.earlystart)
                    waves.append(wav)
                if not waves:
                    obs.shape.pop(lead)
                else:
                    obs.shape[lead].waves = tuple(waves)
                    obs.shape[lead].tag = _tag_qrs(waves)
            observations.append(obs)
        elif ann.code is C.RHYTHM and ann.aux in RH_VALS:
            rhclazz = next(rh for rh in C.RHYTHM_AUX
                           if C.RHYTHM_AUX[rh] == ann.aux)
            obs = rhclazz()
            obs.start.set(ann.time, ann.time)
            end = next((a.time for a in anns[i+1:] if a.code is C.RHYTHM),
                       anns[-1].time)
            obs.end.set(end, end)
            observations.append(obs)
        elif ann.code is C.ARFCT:
            obs = o.RDeflection()
            obs.time.set(ann.time, ann.time)
            observations.append(obs)
    interp.observations = sortedcontainers.SortedList(observations)
    return interp
Exemple #10
0
"""

import construe.utils.MIT as MIT
import construe.utils.MIT.ECGCodes as ECGCodes
from construe.utils.units_helper import samples2msec as sp2ms
import numpy as np
import matplotlib.pyplot as plt

PATH = '/home/tomas/Dropbox/Investigacion/tese/estadias/2015_BMI/data/'
RECORDS = [l.strip() for l in open(PATH + 'RECORDS')]
ANN = '.iqrs'

plt.ioff()
for rec in RECORDS:
    try:
        annots = MIT.read_annotations(PATH + rec + ANN)
    except IOError:
        print('No results found for record ' + rec)
        continue
    rpeaks = sp2ms(
        np.array([a.time for a in annots if MIT.is_qrs_annotation(a)]))
    if len(rpeaks) < 2:
        print('No hearbeats found for record ' + rec)
        continue
    pwaves = [a for a in annots if a.code == ECGCodes.PWAVE]
    #Plot creation
    fig, host = plt.subplots()
    par1 = host.twinx()
    rrstd = []
    pwf = []
    #We create one point by minute.
Exemple #11
0
if __name__ == "__main__":
    #Config variables
    PATH = '/tmp/mit/'
    REF = '.atr'
    TEST = '.cls'
    MWIN = ms2sp(150.0)
    #Records to be interpreted can be selected from command line
    SLC_STR = '0:48' if len(sys.argv) < 2 else sys.argv[1]
    #We get a slice from the input string
    SLC = slice(*[{True: lambda n: None, False: int}[x == ''](x)
                             for x in (SLC_STR.split(':') + ['', '', ''])[:3]])
    CMATS = {}
    for REC in [l.strip() for l in open(PATH + 'RECORDS')][SLC]:
        print('Record {}'.format(REC))
        tp = fn = fp = 0
        ref = [a for a in MIT.read_annotations(PATH + REC + REF)
                                                   if MIT.is_qrs_annotation(a)]
        for a in ref:
            a.code = C.aami_to_mit(C.mit_to_aami(a.code))
        test = [a for a in MIT.read_annotations(PATH + REC + TEST)
                                                   if MIT.is_qrs_annotation(a)]
        for a in test:
            a.code = C.aami_to_mit(C.mit_to_aami(a.code))
        tags = sorted(set(a.code for a in test).union(a.code for a in ref))
        #The -1 tag is used for false positives and false negatives.
        tags.insert(0, -1)
        cmat = np.zeros((len(tags), len(tags)))
        i = j = 0
        while i < len(ref) and j < len(test):
            if abs(ref[i].time - test[j].time) <= MWIN:
                #True positive, introduced in the corresponding matrix cell
    tree = ET.parse(DB_DIR + devid + '_episodes.xml')
    ep_seq = tree.find('ns:return/mg_di:diResponse/mg_di:additionalInfo', NS)
    for episode in ep_seq.findall('ns2:observationResult', NS):
        rhythm = Rhythm()
        rhythm.code = RHTAG
        tp = episode.find('ns2:observationEventTime', NS)
        start = dateutil.parser.parse(tp.attrib['low'])
        end = dateutil.parser.parse(tp.attrib['high'])
        #FIXME we need to ignore timezone for the moment
        rhythm.start = start.replace(tzinfo=None)
        rhythm.end = end.replace(tzinfo=None)
        mbg.add(rhythm)

    abd = sortedcontainers.SortedList()
    for f in glob.glob(DB_DIR + devid + '*' + ANN):
        reftime = MIT.get_datetime(f[:-len(ANN)])
        annots = MIT.read_annotations(f)
        if not annots:
            continue
        sig_episodes.add(
            Iv(reftime,
               reftime + dt.timedelta(milliseconds=s2m(annots[-1].time))))
        for r in (a for a in annots
                  if a.code == ECGCodes.RHYTHM and a.aux == RHTAG):
            rhythm = Rhythm()
            rhythm.code = r.aux
            rhythm.start = reftime + dt.timedelta(milliseconds=s2m(r.time))
            end = next((a.time for a in annots
                        if a.time > r.time and a.code == ECGCodes.RHYTHM),
                       annots[-1].time)
            rhythm.end = reftime + dt.timedelta(milliseconds=s2m(end))
Exemple #13
0
        return outstr


if __name__ == "__main__":
    DB = '/tmp/mit/'
    RECORDS = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 111, 112, 113,
               114, 115, 116, 117, 118, 119, 121, 122, 123, 124, 200, 201, 202,
               203, 205, 207, 208, 209, 210, 212, 213, 214, 215, 217, 219, 220,
               221, 222, 223, 228, 230, 231, 232, 233, 234]
    REF_ANN = '.atr'
    TEST_ANN = '.rhy'
    epicmpres = {tag : np.zeros(8) for tag in EXCLUSION_TAGS}
    results = {}
    for rec in RECORDS:
        results[rec] = defaultdict(int)
        ref = MIT.read_annotations(DB + str(rec) + REF_ANN)
        test = MIT.read_annotations(DB + str(rec) + TEST_ANN)
        reclen = ref[-1].time
        #Rhythm loading in form of intervals
        ref_rhythms = []
        test_rhythms = []
        for alst in (ref, test):
            rlst = ref_rhythms if alst is ref else test_rhythms
            for ann in (a for a in alst if a.code is ECGCodes.RHYTHM):
                if rlst:
                    rlst[-1].end = ann.time
                newrhythm = Rhythm()
                newrhythm.code = ann.aux.strip('\x00')
                newrhythm.start = ann.time
                rlst.append(newrhythm)
            rlst[-1].end = alst[-1].time