Skip to content

Commit c2772e6

Browse files
committed
refactoring annotation code
1 parent de307fb commit c2772e6

File tree

1 file changed

+122
-90
lines changed

1 file changed

+122
-90
lines changed

wfdb/io/annotation.py

Lines changed: 122 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ class Annotation(object):
1515
"""
1616
The class representing WFDB annotations.
1717
18-
Annotation objects can be created using the initializer, or by reading a
19-
WFDB annotation file with `rdann`.
18+
Annotation objects can be created using the initializer, or by
19+
reading a WFDB annotation file with `rdann`.
2020
2121
The attributes of the Annotation object give information about the
2222
annotation as specified by:
2323
https://www.physionet.org/physiotools/wag/annot-5.htm
2424
25-
Call `show_ann_labels()` to see the list of standard annotation codes. Any
26-
text used to label annotations that are not one of these codes should go in
27-
the 'aux_note' field rather than the 'sym' field.
25+
Call `show_ann_labels()` to see the list of standard annotation
26+
codes. Any text used to label annotations that are not one of these
27+
codes should go in the 'aux_note' field rather than the 'sym' field.
2828
2929
Examples
3030
--------
@@ -33,11 +33,15 @@ class Annotation(object):
3333
aux_note=[None, None, 'Serious Vfib'])
3434
3535
"""
36+
# The data fields for each individual annotation
37+
DATA_FIELDS = ['sample', 'symbol', 'subtype', 'chan', 'num',
38+
'aux_note', 'label_store', 'description']
39+
3640

3741
def __init__(self, record_name, extension, sample, symbol=None,
38-
subtype=None, chan=None, num=None, aux_note=None, fs=None,
39-
label_store=None, description=None, custom_labels=None,
40-
contained_labels=None):
42+
subtype=None, chan=None, num=None, aux_note=None,
43+
fs=None, label_store=None, description=None,
44+
custom_labels=None):
4145
"""
4246
Parameters
4347
----------
@@ -47,19 +51,19 @@ def __init__(self, record_name, extension, sample, symbol=None,
4751
extension : str
4852
The file extension of the file the annotation is stored in.
4953
sample : numpy array
50-
A numpy array containing the annotation locations in samples relative to
51-
the beginning of the record.
52-
symbol : list, or numpy array, optional
53-
The symbols used to display the annotation labels. List or numpy array.
54-
If this field is present, `label_store` must not be present.
54+
A numpy array containing the annotation locations in samples
55+
relative to the beginning of the record.
56+
symbol : numpy array, optional
57+
The symbols used to display the annotation labels.
5558
subtype : numpy array, optional
56-
A numpy array containing the marked class/category of each annotation.
57-
chan : numpy array, optional
58-
A numpy array containing the signal channel associated with each
59+
A numpy array containing the marked class/category of each
5960
annotation.
61+
chan : numpy array, optional
62+
A numpy array containing the signal channel associated with
63+
each annotation.
6064
num : numpy array, optional
61-
A numpy array containing the labelled annotation number for each
62-
annotation.
65+
A numpy array containing the labelled annotation number for
66+
each annotation.
6367
aux_note : list, optional
6468
A list containing the auxiliary information string (or None for
6569
annotations without notes) for each annotation.
@@ -74,9 +78,6 @@ def __init__(self, record_name, extension, sample, symbol=None,
7478
the relationship between the three label fields. The data type is a
7579
pandas DataFrame with three columns:
7680
['label_store', 'symbol', 'description']
77-
contained_labels : pandas dataframe, optional
78-
The unique labels contained in this annotation. Same structure as
79-
`custom_labels`.
8081
8182
"""
8283
self.record_name = record_name
@@ -97,9 +98,6 @@ def __init__(self, record_name, extension, sample, symbol=None,
9798
self.custom_labels = custom_labels
9899
self.contained_labels = contained_labels
99100

100-
self.ann_len = len(self.sample)
101-
102-
#__label_map__: (storevalue, symbol, description) hidden attribute
103101

104102
# Equal comparison operator for objects of this type
105103
def __eq__(self, other):
@@ -150,8 +148,6 @@ def apply_range(self, sampfrom=0, sampto=None):
150148

151149
self.aux_note = [self.aux_note[i] for i in kept_inds]
152150

153-
self.ann_len = len(self.sample)
154-
155151
def wrann(self, write_fs=False, write_dir=''):
156152
"""
157153
Write a WFDB annotation file from this object.
@@ -203,17 +199,16 @@ def get_label_fields(self):
203199

204200
return present_label_fields
205201

206-
# Check the set fields of the annotation object
202+
207203
def check_fields(self):
208-
# Check all set fields
204+
"""
205+
Check the set fields of the annotation object
206+
"""
209207
for field in ALLOWED_TYPES:
210-
if getattr(self, field) is not None:
211-
# Check the type of the field's elements
208+
if hasattr(self, field):
212209
self.check_field(field)
213210
return
214211

215-
216-
217212
def check_field(self, field):
218213
"""
219214
Check a particular annotation field
@@ -648,9 +643,11 @@ def calc_fs_bytes(self):
648643

649644
return np.array(data_bytes).astype('u1')
650645

651-
# Calculate the bytes written to the annotation file for the
652-
# custom_labels field
653646
def calc_cl_bytes(self):
647+
"""
648+
Calculate the bytes written to the annotation file for the
649+
custom_labels field
650+
"""
654651

655652
if self.custom_labels is None:
656653
return []
@@ -675,7 +672,7 @@ def calc_cl_bytes(self):
675672
# custombytes = [customcode2bytes(triplet) for triplet in writecontent]
676673
# custombytes = [item for sublist in custombytes for item in sublist]
677674

678-
return np.array(headbytes+custom_bytes+tailbytes).astype('u1')
675+
return np.array(headbytes + custom_bytes + tailbytes).astype('u1')
679676

680677
def calc_core_bytes(self):
681678
"""
@@ -687,13 +684,13 @@ def calc_core_bytes(self):
687684
else:
688685
sampdiff = np.concatenate(([self.sample[0]], np.diff(self.sample)))
689686

690-
# Create a copy of the annotation object with a
691-
# compact version of fields to write
687+
# Create a copy of the annotation object with a compact version
688+
# of fields to write
692689
compact_annotation = copy.deepcopy(self)
693-
compact_annotation.compact_fields()
694-
690+
compact_annotation._compact_fields()
695691

696-
# The optional fields to be written. Write if they are not None or all empty
692+
# The optional fields to be written. Write if they are not None
693+
# or all empty
697694
extra_write_fields = []
698695

699696
for field in ['num', 'subtype', 'chan', 'aux_note']:
@@ -721,7 +718,7 @@ def calc_core_bytes(self):
721718

722719
# Compact all of the object's fields so that the output
723720
# writing annotation file writes as few bytes as possible
724-
def compact_fields(self):
721+
def _compact_fields(self):
725722

726723
# Number of annotations
727724
nannots = len(self.sample)
@@ -785,7 +782,7 @@ def sym_to_aux(self):
785782
def get_contained_labels(self, inplace=True):
786783
"""
787784
Get the set of unique labels contained in this annotation.
788-
Returns a pandas dataframe or sets the __contained__ labels
785+
Returns a pandas dataframe or sets the contained_labels
789786
attribute of the object.
790787
791788
@@ -876,15 +873,18 @@ def set_label_elements(self, wanted_label_elements):
876873
unwanted_label_elements = list(set(ANN_LABEL_FIELDS)
877874
- set(wanted_label_elements))
878875

879-
self.rm_attributes(unwanted_label_elements)
876+
self._rm_attributes(unwanted_label_elements)
880877

881878
return
882879

883-
def rm_attributes(self, attributes):
880+
def _rm_attributes(self, attributes):
881+
"""
882+
Remove the specified attributes from the object.
883+
"""
884884
if isinstance(attributes, str):
885885
attributes = [attributes]
886886
for a in attributes:
887-
setattr(self, a, None)
887+
delattr(self, a)
888888
return
889889

890890
def convert_label_attribute(self, source_field, target_field,
@@ -1187,7 +1187,20 @@ def wrann(record_name, extension, sample, symbol=None, subtype=None, chan=None,
11871187

11881188
def show_ann_labels():
11891189
"""
1190-
Display the standard wfdb annotation label mapping.
1190+
Display the standard wfdb annotation label mapping table.
1191+
1192+
When writing WFDB annotation files, please adhere to these standards
1193+
for all annotation definitions present in this table, and define the
1194+
`custom_labels` field for items that are not present.
1195+
1196+
Columns
1197+
-------
1198+
label_store :
1199+
The integer values used to store the labels in the file.
1200+
symbol :
1201+
The symbol used to display each label.
1202+
description :
1203+
The full description of what each label means.
11911204
11921205
Examples
11931206
--------
@@ -1199,7 +1212,20 @@ def show_ann_labels():
11991212

12001213
def show_ann_classes():
12011214
"""
1202-
Display the standard wfdb annotation file extensions.
1215+
Display the standard WFDB annotation file extensions and their
1216+
meanings.
1217+
1218+
When writing WFDB annotation files, please adhere to these
1219+
standards.
1220+
1221+
Columns
1222+
-------
1223+
extension :
1224+
The file extension.
1225+
description :
1226+
The description of the annotation content.
1227+
human_reviewed :
1228+
Whether the annotations were reviewed by humans.
12031229
12041230
Examples
12051231
--------
@@ -1208,9 +1234,10 @@ def show_ann_classes():
12081234
"""
12091235
print(ANN_EXTENSIONS)
12101236

1237+
['sample', 'symbol', 'subtype', 'chan', 'num', 'aux_note', 'label_store', 'description']
12111238

12121239
def rdann(record_name, extension, sampfrom=0, sampto=None, shift_samps=False,
1213-
pb_dir=None, return_label_elements=['symbol'],
1240+
pb_dir=None, data_fields=Annotation.DATA_FIELDS,
12141241
summarize_labels=False, return_df=False):
12151242
"""
12161243
Read a WFDB annotation file record_name.extension and return an
@@ -1676,7 +1703,7 @@ def rm_last(*args):
16761703
ANN_LABEL_FIELDS = ('label_store', 'symbol', 'description')
16771704

16781705

1679-
# Standard annotation file extensions
1706+
# Standard WFDB annotation file extensions
16801707
ANN_EXTENSIONS = pd.DataFrame(data=[
16811708
('atr', 'Reference ECG annotations', True),
16821709

@@ -1702,45 +1729,50 @@ def rm_last(*args):
17021729

17031730
# The standard library annotation label map
17041731
ANN_LABELS = pd.DataFrame(data=[
1705-
(0, ' ', 'NOTANN', 'Not an actual annotation'),
1706-
(1, 'N', 'NORMAL', 'Normal beat'),
1707-
(2, 'L', 'LBBB', 'Left bundle branch block beat'),
1708-
(3, 'R', 'RBBB', 'Right bundle branch block beat'),
1709-
(4, 'a', 'ABERR', 'Aberrated atrial premature beat'),
1710-
(5, 'V', 'PVC', 'Premature ventricular contraction'),
1711-
(6, 'F', 'FUSION', 'Fusion of ventricular and normal beat'),
1712-
(7, 'J', 'NPC', 'Nodal (junctional) premature beat'),
1713-
(8, 'A', 'APC', 'Atrial premature contraction'),
1714-
(9, 'S', 'SVPB', 'Premature or ectopic supraventricular beat'),
1715-
(10, 'E', 'VESC', 'Ventricular escape beat'),
1716-
(11, 'j', 'NESC', 'Nodal (junctional) escape beat'),
1717-
(12, '/', 'PACE', 'Paced beat'),
1718-
(13, 'Q', 'UNKNOWN', 'Unclassifiable beat'),
1719-
(14, '~', 'NOISE', 'Signal quality change'),
1720-
(16, '|', 'ARFCT', 'Isolated QRS-like artifact'),
1721-
(18, 's', 'STCH', 'ST change'),
1722-
(19, 'T', 'TCH', 'T-wave change'),
1723-
(20, '*', 'SYSTOLE', 'Systole'),
1724-
(21, 'D', 'DIASTOLE', 'Diastole'),
1725-
(22, '"', 'NOTE', 'Comment annotation'),
1726-
(23, '=', 'MEASURE', 'Measurement annotation'),
1727-
(24, 'p', 'PWAVE', 'P-wave peak'),
1728-
(25, 'B', 'BBB', 'Left or right bundle branch block'),
1729-
(26, '^', 'PACESP', 'Non-conducted pacer spike'),
1730-
(27, 't', 'TWAVE', 'T-wave peak'),
1731-
(28, '+', 'RHYTHM', 'Rhythm change'),
1732-
(29, 'u', 'UWAVE', 'U-wave peak'),
1733-
(30, '?', 'LEARN', 'Learning'),
1734-
(31, '!', 'FLWAV', 'Ventricular flutter wave'),
1735-
(32, '[', 'VFON', 'Start of ventricular flutter/fibrillation'),
1736-
(33, ']', 'VFOFF', 'End of ventricular flutter/fibrillation'),
1737-
(34, 'e', 'AESC', 'Atrial escape beat'),
1738-
(35, 'n', 'SVESC', 'Supraventricular escape beat'),
1739-
(36, '@', 'LINK', 'Link to external data (aux_note contains URL)'),
1740-
(37, 'x', 'NAPC', 'Non-conducted P-wave (blocked APB)'),
1741-
(38, 'f', 'PFUS', 'Fusion of paced and normal beat'),
1742-
(39, '(', 'WFON', 'Waveform onset'),
1743-
(40, ')', 'WFOFF', 'Waveform end'),
1744-
(41, 'r', 'RONT', 'R-on-T premature ventricular contraction'),
1745-
], columns=['label_store', 'symbol', 'short_description', 'description']
1732+
# 0 is used in the file as an indicator flag.
1733+
# (0, ' ', 'Not an actual annotation'),
1734+
(1, 'N', 'Normal beat'),
1735+
(2, 'L', 'Left bundle branch block beat'),
1736+
(3, 'R', 'Right bundle branch block beat'),
1737+
(4, 'a', 'Aberrated atrial premature beat'),
1738+
(5, 'V', 'Premature ventricular contraction'),
1739+
(6, 'F', 'Fusion of ventricular and normal beat'),
1740+
(7, 'J', 'Nodal (junctional) premature beat'),
1741+
(8, 'A', 'Atrial premature contraction'),
1742+
(9, 'S', 'Premature or ectopic supraventricular beat'),
1743+
(10, 'E', 'Ventricular escape beat'),
1744+
(11, 'j', 'Nodal (junctional) escape beat'),
1745+
(12, '/', 'Paced beat'),
1746+
(13, 'Q', 'Unclassifiable beat'),
1747+
(14, '~', 'Signal quality change'),
1748+
(16, '|', 'Isolated QRS-like artifact'),
1749+
(18, 's', 'ST change'),
1750+
(19, 'T', 'T-wave change'),
1751+
(20, '*', 'Systole'),
1752+
(21, 'D', 'Diastole'),
1753+
(22, '"', 'Comment annotation'),
1754+
(23, '=', 'Measurement annotation'),
1755+
(24, 'p', 'P-wave peak'),
1756+
(25, 'B', 'Left or right bundle branch block'),
1757+
(26, '^', 'Non-conducted pacer spike'),
1758+
(27, 't', 'T-wave peak'),
1759+
(28, '+', 'Rhythm change'),
1760+
(29, 'u', 'U-wave peak'),
1761+
(30, '?', 'Learning'),
1762+
(31, '!', 'Ventricular flutter wave'),
1763+
(32, '[', 'Start of ventricular flutter/fibrillation'),
1764+
(33, ']', 'End of ventricular flutter/fibrillation'),
1765+
(34, 'e', 'Atrial escape beat'),
1766+
(35, 'n', 'Supraventricular escape beat'),
1767+
(36, '@', 'Link to external data (aux_note contains URL)'),
1768+
(37, 'x', 'Non-conducted P-wave (blocked APB)'),
1769+
(38, 'f', 'Fusion of paced and normal beat'),
1770+
(39, '(', 'Waveform onset'),
1771+
(40, ')', 'Waveform end'),
1772+
(41, 'r', 'R-on-T premature ventricular contraction'),
1773+
], columns=['label_store', 'symbol', 'description']
17461774
)
1775+
1776+
# The allowed integer range for the label_store value in wfdb
1777+
# annotations. 0 is used to
1778+
LABEL_RANGE = (1, 49)

0 commit comments

Comments
 (0)