Skip to content

Commit ac78f0c

Browse files
committed
update annotation and signal names and labels
1 parent ca8a9ba commit ac78f0c

File tree

3 files changed

+125
-157
lines changed

3 files changed

+125
-157
lines changed

wfdb/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .readwrite.records import Record, MultiRecord, rdheader, rdsamp, srdsamp, wrsamp, dldatabase, dldatabasefiles
22
from .readwrite._signals import estres, wrdatfile
3-
from .readwrite._headers import sigclasses
4-
from .readwrite.annotations import Annotation, rdann, wrann, showanncodes, annclasses
3+
from .readwrite._headers import sig_classes
4+
from .readwrite.annotations import Annotation, rdann, wrann, show_ann_labels, ann_classes, ann_labels, ann_label_table
55
from .readwrite.downloads import getdblist
66
from .plot.plots import plotrec, plotann
77
from . import processing

wfdb/readwrite/_headers.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,16 @@ def __init__(self, allowedtypes, delimiter, dependency, write_req, read_def, wri
633633

634634
# ---------- For storing WFDB Signal definitions ---------- #
635635

636+
637+
# Unit scales used for default display scales.
638+
unitscale = {
639+
'Voltage': ['pV', 'nV', 'uV', 'mV', 'V', 'kV'],
640+
'Temperature': ['C'],
641+
'Pressure': ['mmHg'],
642+
}
643+
644+
645+
636646
# Signal class with all its parameters
637647
class SignalClass(object):
638648
def __init__(self, abbreviation, description, signalnames):
@@ -644,15 +654,8 @@ def __init__(self, abbreviation, description, signalnames):
644654
def __str__(self):
645655
return self.abbreviation
646656

647-
# Unit scales used for default display scales.
648-
unitscale = {
649-
'Voltage': ['pV', 'nV', 'uV', 'mV', 'V', 'kV'],
650-
'Temperature': ['C'],
651-
'Pressure': ['mmHg'],
652-
}
653-
654657
# All signal types. Make sure signal names are in lower case.
655-
signalclasses = [
658+
sig_classes = [
656659
SignalClass('BP', 'Blood Pressure', ['bp','abp','pap','cvp',]),
657660
SignalClass('CO2', 'Carbon Dioxide', ['co2']),
658661
SignalClass('CO', 'Carbon Monoxide', ['co']),

wfdb/readwrite/annotations.py

Lines changed: 112 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Annotation(object):
3232
def __init__(self, recordname, annotator, annsamp, anntype, subtype = None,
3333
chan = None, num = None, aux = None, fs = None, custom_anntypes = None)
3434
35-
Call 'showanncodes()' to see the list of standard annotation codes. Any text used to label
35+
Call 'show_ann_labels()' to see the list of standard annotation codes. Any text used to label
3636
annotations that are not one of these codes should go in the 'aux' field rather than the
3737
'anntype' field.
3838
@@ -202,9 +202,9 @@ def checkfield(self, field):
202202
raise ValueError('WFDB annotation files cannot store sample differences greater than 2**31')
203203
elif field == 'anntype':
204204
# Ensure all fields lie in standard WFDB annotation codes or custom codes
205-
if set(self.anntype) - set(annsyms.values()).union() != set():
205+
if set(self.anntype) - set(ann_label_table['Symbol'].values).union() != set():
206206
print("The 'anntype' field contains items not encoded in the WFDB library, or in this object's custom defined anntypes.")
207-
print('To see the valid annotation codes call: showanncodes()')
207+
print('To see the valid annotation codes call: show_ann_labels()')
208208
print('To transfer non-encoded anntype items into the aux field call: self.type2aux()')
209209
print("To define custom codes, set the custom_anntypes field as a dictionary with format: {custom anntype character:description}")
210210
raise Exception()
@@ -346,7 +346,7 @@ def type2aux(self):
346346
if type(at) != str:
347347
raise TypeError('anntype elements must all be strings')
348348

349-
external_anntypes = set(self.anntype) - set(annsyms.values())
349+
external_anntypes = set(self.anntype) - set(ann_label_table['Symbol'].values)
350350

351351
# Nothing to do
352352
if external_anntypes == set():
@@ -412,7 +412,7 @@ def ca2bytes(custom_anntypes):
412412
105,116,105,111,110,115,0,0,236,255,255,255,255,1,0]
413413

414414
# Annotation codes range from 0-49.
415-
freenumbers = list(set(range(50)) - set(annsyms.keys()))
415+
freenumbers = list(set(range(50)) - set(ann_label_table['Store-Value'].values))
416416

417417
if len(custom_anntypes) > len(freenumbers):
418418
raise Exception('There can only be a maximum of '+len(freenumbers)+' custom annotation codes.')
@@ -494,9 +494,9 @@ def field2bytes(field, value):
494494

495495
# annsamp and anntype bytes come together
496496
if field == 'samptype':
497-
498497
# Numerical value encoding annotation symbol
499-
typecode = revannsyms[value[1]]
498+
typecode = ann_label_table.loc[ann_label_table['Symbol']==value[1], 'Store-Value'].values[0]
499+
500500
# sample difference
501501
sd = value[0]
502502

@@ -579,16 +579,23 @@ def wrann(recordname, annotator, annsamp, anntype, subtype = None, chan = None,
579579
# Perform field checks and write the annotation file
580580
annotation.wrann(writefs = True)
581581

582-
# Display the annotation symbols and the codes they represent
583-
def showanncodes():
582+
583+
def show_ann_labels():
584584
"""
585-
Display the annotation symbols and the codes they represent according to the
586-
standard WFDB library 10.5.24
585+
Display the standard wfdb annotation label mapping
587586
588587
Usage:
589-
showanncodes()
588+
show_ann_labels()
590589
"""
591-
print(symcodes)
590+
print(ann_label_table)
591+
592+
593+
def show_ann_classes():
594+
"""
595+
Display the standard wfdb annotation classes
596+
"""
597+
598+
pass
592599

593600
## ------------- Reading Annotations ------------- ##
594601

@@ -873,7 +880,6 @@ def carry_fields(ai, cpychan, cpynum, chan, num):
873880
return chan, num
874881

875882

876-
877883
# Remove unallocated part of array
878884
def snip_arrays(annsamp,anntype,num,subtype,chan,aux,ai):
879885
annsamp = annsamp[0:ai].astype(int)
@@ -943,7 +949,7 @@ def proc_special_types(annsamp,anntype,num,subtype,chan,aux):
943949
# Custom annotation types
944950
custom_anntypes = {}
945951
# The annotation symbol dictionary to modify and use
946-
allannsyms = annsyms.copy()
952+
allannsyms = dict(zip(ann_label_table['Store-Value'].values, ann_label_table['Symbol'].values))
947953

948954
if special_inds != []:
949955
# The annotation indices to be removed
@@ -988,125 +994,7 @@ def proc_special_types(annsamp,anntype,num,subtype,chan,aux):
988994
## ------------- /Reading Annotations ------------- ##
989995

990996

991-
# Annotation mnemonic symbols for the 'anntype' field as specified in annot.c
992-
# from wfdb software library 10.5.24. At this point, several values are blank.
993-
# Commented out values are present in original file but have no meaning.
994-
annsyms = {
995-
0: ' ', # not-QRS (not a getann/putann codedict) */
996-
1: 'N', # normal beat */
997-
2: 'L', # left bundle branch block beat */
998-
3: 'R', # right bundle branch block beat */
999-
4: 'a', # aberrated atrial premature beat */
1000-
5: 'V', # premature ventricular contraction */
1001-
6: 'F', # fusion of ventricular and normal beat */
1002-
7: 'J', # nodal (junctional) premature beat */
1003-
8: 'A', # atrial premature contraction */
1004-
9: 'S', # premature or ectopic supraventricular beat */
1005-
10: 'E', # ventricular escape beat */
1006-
11: 'j', # nodal (junctional) escape beat */
1007-
12: '/', # paced beat */
1008-
13: 'Q', # unclassifiable beat */
1009-
14: '~', # signal quality change */
1010-
# 15: '[15]',
1011-
16: '|', # isolated QRS-like artifact */
1012-
# 17: '[17]',
1013-
18: 's', # ST change */
1014-
19: 'T', # T-wave change */
1015-
20: '*', # systole */
1016-
21: 'D', # diastole */
1017-
22: '"', # comment annotation */
1018-
23: '=', # measurement annotation */
1019-
24: 'p', # P-wave peak */
1020-
25: 'B', # left or right bundle branch block */
1021-
26: '^', # non-conducted pacer spike */
1022-
27: 't', # T-wave peak */
1023-
28: '+', # rhythm change */
1024-
29: 'u', # U-wave peak */
1025-
30: '?', # learning */
1026-
31: '!', # ventricular flutter wave */
1027-
32: '[', # start of ventricular flutter/fibrillation */
1028-
33: ']', # end of ventricular flutter/fibrillation */
1029-
34: 'e', # atrial escape beat */
1030-
35: 'n', # supraventricular escape beat */
1031-
36: '@', # link to external data (aux contains URL) */
1032-
37: 'x', # non-conducted P-wave (blocked APB) */
1033-
38: 'f', # fusion of paced and normal beat */
1034-
39: '(', # waveform onset */
1035-
40: ')', # waveform end */
1036-
41: 'r', # R-on-T premature ventricular contraction */
1037-
# 42: '[42]',
1038-
# 43: '[43]',
1039-
# 44: '[44]',
1040-
# 45: '[45]',
1041-
# 46: '[46]',
1042-
# 47: '[47]',
1043-
# 48: '[48]',
1044-
# 49: '[49]',
1045-
}
1046-
# Reverse ann symbols for mapping symbols back to numbers
1047-
revannsyms = {v: k for k, v in annsyms.items()}
1048-
1049-
# Annotation codes for 'anntype' field as specified in ecgcodes.h from
1050-
# wfdb software library 10.5.24. Commented out values are present in
1051-
# original file but have no meaning.
1052-
anncodes = {
1053-
0: 'NOTQRS', # not-QRS (not a getann/putann codedict) */
1054-
1: 'NORMAL', # normal beat */
1055-
2: 'LBBB', # left bundle branch block beat */
1056-
3: 'RBBB', # right bundle branch block beat */
1057-
4: 'ABERR', # aberrated atrial premature beat */
1058-
5: 'PVC', # premature ventricular contraction */
1059-
6: 'FUSION', # fusion of ventricular and normal beat */
1060-
7: 'NPC', # nodal (junctional) premature beat */
1061-
8: 'APC', # atrial premature contraction */
1062-
9: 'SVPB', # premature or ectopic supraventricular beat */
1063-
10: 'VESC', # ventricular escape beat */
1064-
11: 'NESC', # nodal (junctional) escape beat */
1065-
12: 'PACE', # paced beat */
1066-
13: 'UNKNOWN', # unclassifiable beat */
1067-
14: 'NOISE', # signal quality change */
1068-
# 15: '',
1069-
16: 'ARFCT', # isolated QRS-like artifact */
1070-
# 17: '',
1071-
18: 'STCH', # ST change */
1072-
19: 'TCH', # T-wave change */
1073-
20: 'SYSTOLE', # systole */
1074-
21: 'DIASTOLE', # diastole */
1075-
22: 'NOTE', # comment annotation */
1076-
23: 'MEASURE', # measurement annotation */
1077-
24: 'PWAVE', # P-wave peak */
1078-
25: 'BBB', # left or right bundle branch block */
1079-
26: 'PACESP', # non-conducted pacer spike */
1080-
27: 'TWAVE', # T-wave peak */
1081-
28: 'RHYTHM', # rhythm change */
1082-
29: 'UWAVE', # U-wave peak */
1083-
30: 'LEARN', # learning */
1084-
31: 'FLWAV', # ventricular flutter wave */
1085-
32: 'VFON', # start of ventricular flutter/fibrillation */
1086-
33: 'VFOFF', # end of ventricular flutter/fibrillation */
1087-
34: 'AESC', # atrial escape beat */
1088-
35: 'SVESC', # supraventricular escape beat */
1089-
36: 'LINK', # link to external data (aux contains URL) */
1090-
37: 'NAPC', # non-conducted P-wave (blocked APB) */
1091-
38: 'PFUS', # fusion of paced and normal beat */
1092-
39: 'WFON', # waveform onset */
1093-
40: 'WFOFF', # waveform end */
1094-
41: 'RONT', # R-on-T premature ventricular contraction */
1095-
# 42: '',
1096-
# 43: '',
1097-
# 44: '',
1098-
# 45: '',
1099-
# 46: '',
1100-
# 47: '',
1101-
# 48: '',
1102-
# 49: ''
1103-
}
1104-
1105-
1106-
# Mapping annotation symbols to the annotation codes
1107-
# For printing/user guidance
1108-
symcodes = pd.DataFrame({'Ann Symbol': list(annsyms.values()), 'Ann Code Meaning': list(anncodes.values())})
1109-
symcodes = symcodes.set_index('Ann Symbol', list(annsyms.values()))
997+
1110998

1111999
# All annotation fields. Note: custom_anntypes placed first to check field before anntype
11121000
annfields = ['recordname', 'annotator', 'custom_anntypes', 'annsamp', 'anntype', 'num', 'subtype', 'chan', 'aux', 'fs']
@@ -1121,30 +1009,107 @@ def proc_special_types(annsamp,anntype,num,subtype,chan,aux):
11211009

11221010

11231011

1012+
11241013
# Classes = extensions
11251014
class AnnotationClass(object):
1126-
def __init__(self, extension, description, isreference):
1015+
def __init__(self, extension, description, human_reviewed):
1016+
11271017
self.extension = extension
11281018
self.description = description
1129-
self.isreference = isreference
1019+
self.human_reviewed = human_reviewed
1020+
11301021

1131-
annclasses = [
1022+
ann_classes = [
11321023
AnnotationClass('atr', 'Reference ECG annotations', True),
1133-
AnnotationClass('apn', 'Reference apnea annotations', True),
1134-
AnnotationClass('alarm', 'Machine alarm annotations', False),
1024+
1025+
AnnotationClass('blh', 'Human reviewed beat labels', True),
1026+
AnnotationClass('blm', 'Machine beat labels', False),
1027+
1028+
AnnotationClass('alh', 'Human reviewed alarms', True),
1029+
AnnotationClass('alm', 'Machine alarms', False),
1030+
1031+
AnnotationClass('qrsc', 'Human reviewed qrs detections', True),
1032+
AnnotationClass('qrs', 'Machine QRS detections', False),
1033+
1034+
AnnotationClass('bph', 'Human reviewed BP beat detections', True),
1035+
AnnotationClass('bpm', 'Machine BP beat detections', False),
1036+
1037+
#AnnotationClass('alh', 'Human reviewed BP alarms', True),
1038+
#AnnotationClass('alm', 'Machine BP alarms', False),
1039+
# separate ecg and other signal category alarms?
1040+
# Can we use signum to determine the channel it was triggered off?
1041+
1042+
#ppg alarms?
1043+
#eeg alarms
11351044
]
11361045

11371046
# Individual annotation labels
11381047
class AnnotationLabel(object):
1139-
def __init__(self, storevalue, symbol, description):
1140-
self.storevalue = storevalue
1048+
def __init__(self, store_value, symbol, short_description, description):
1049+
self.store_value = store_value
11411050
self.symbol = symbol
1051+
self.short_description = short_description
11421052
self.description = description
11431053

1144-
annlabels = [
1145-
AnnotationLabel(0, ' ', 'Not an actual annotation'),
1146-
AnnotationLabel(1, 'N', 'Normal beat'),
1147-
AnnotationLabel(2, 'L', 'Left bundle branch block beat'),
1148-
AnnotationLabel(3, 'R', 'Right bundle branch block beat'),
1149-
AnnotationLabel(4, 'a', 'Aberrated atrial premature beat'),
1054+
def __str__(self):
1055+
return str(self.store_value)+', '+str(self.symbol)+', '+str(self.short_description)+', '+str(self.description)
1056+
1057+
ann_labels = [
1058+
AnnotationLabel(0, " ", 'NOTQRS', 'Not an actual annotation'),
1059+
AnnotationLabel(1, "N", 'NORMAL', 'Normal beat'),
1060+
AnnotationLabel(2, "L", 'LBBB', 'Left bundle branch block beat'),
1061+
AnnotationLabel(3, "R", 'RBBB', 'Right bundle branch block beat'),
1062+
AnnotationLabel(4, "a", 'ABERR', 'Aberrated atrial premature beat'),
1063+
AnnotationLabel(5, "V", 'PVC', 'Premature ventricular contraction'),
1064+
AnnotationLabel(6, "F", 'FUSION', 'Fusion of ventricular and normal beat'),
1065+
AnnotationLabel(7, "J", 'NPC', 'Nodal (junctional) premature beat'),
1066+
AnnotationLabel(8, "A", 'APC', 'Atrial premature contraction'),
1067+
AnnotationLabel(9, "S", 'SVPB', 'Premature or ectopic supraventricular beat'),
1068+
AnnotationLabel(10, "E", 'VESC', 'Ventricular escape beat'),
1069+
AnnotationLabel(11, "j", 'NESC', 'Nodal (junctional) escape beat'),
1070+
AnnotationLabel(12, "/", 'PACE', 'Paced beat'),
1071+
AnnotationLabel(13, "Q", 'UNKNOWN', 'Unclassifiable beat'),
1072+
AnnotationLabel(14, "~", 'NOISE', 'Signal quality change'),
1073+
# AnnotationLabel(15, None, None, None),
1074+
AnnotationLabel(16, "|", 'ARFCT', 'Isolated QRS-like artifact'),
1075+
# AnnotationLabel(17, None, None, None),
1076+
AnnotationLabel(18, "s", 'STCH', 'ST change'),
1077+
AnnotationLabel(19, "T", 'TCH', 'T-wave change'),
1078+
AnnotationLabel(20, "*", 'SYSTOLE', 'Systole'),
1079+
AnnotationLabel(21, "D", 'DIASTOLE', 'Diastole'),
1080+
AnnotationLabel(22, '"', 'NOTE', 'Comment annotation'),
1081+
AnnotationLabel(23, "=", 'MEASURE', 'Measurement annotation'),
1082+
AnnotationLabel(24, "p", 'PWAVE', 'P-wave peak'),
1083+
AnnotationLabel(25, "B", 'BBB', 'Left or right bundle branch block'),
1084+
AnnotationLabel(26, "^", 'PACESP', 'Non-conducted pacer spike'),
1085+
AnnotationLabel(27, "t", 'TWAVE', 'T-wave peak'),
1086+
AnnotationLabel(28, "+", 'RHYTHM', 'Rhythm change'),
1087+
AnnotationLabel(29, "u", 'UWAVE', 'U-wave peak'),
1088+
AnnotationLabel(30, "?", 'LEARN', 'Learning'),
1089+
AnnotationLabel(31, "!", 'FLWAV', 'Ventricular flutter wave'),
1090+
AnnotationLabel(32, "[", 'VFON', 'Start of ventricular flutter/fibrillation'),
1091+
AnnotationLabel(33, "]", 'VFOFF', 'End of ventricular flutter/fibrillation'),
1092+
AnnotationLabel(34, "e", 'AESC', 'Atrial escape beat'),
1093+
AnnotationLabel(35, "n", 'SVESC', 'Supraventricular escape beat'),
1094+
AnnotationLabel(36, "@", 'LINK', 'Link to external data (aux contains URL)'),
1095+
AnnotationLabel(37, "x", 'NAPC', 'Non-conducted P-wave (blocked APB)'),
1096+
AnnotationLabel(38, "f", 'PFUS', 'Fusion of paced and normal beat'),
1097+
AnnotationLabel(39, "(", 'WFON', 'Waveform onset'),
1098+
AnnotationLabel(40, ")", 'WFOFF', 'Waveform end'),
1099+
AnnotationLabel(41, "r", 'RONT', 'R-on-T premature ventricular contraction'),
1100+
# AnnotationLabel(42, None, None, None),
1101+
# AnnotationLabel(43, None, None, None),
1102+
# AnnotationLabel(44, None, None, None),
1103+
# AnnotationLabel(45, None, None, None),
1104+
# AnnotationLabel(46, None, None, None),
1105+
# AnnotationLabel(47, None, None, None),
1106+
# AnnotationLabel(48, None, None, None),
1107+
# AnnotationLabel(49, None, None, None),
11501108
]
1109+
1110+
1111+
ann_label_table = pd.DataFrame({'Store-Value':[al.store_value for al in ann_labels], 'Symbol':[al.symbol for al in ann_labels],
1112+
'Short-Description':[al.short_description for al in ann_labels], 'Description':[al.description for al in ann_labels]})
1113+
ann_label_table.set_index('Store-Value', list(ann_label_table['Store-Value'].values))
1114+
ann_label_table = ann_label_table[['Store-Value','Symbol','Short-Description','Description']]
1115+

0 commit comments

Comments
 (0)