1
1
import numpy as np
2
- import os
2
+ from . import headers
3
3
4
4
# Class for WFDB annotations
5
5
class Annotation ():
@@ -23,11 +23,54 @@ def wrann(self):
23
23
# Write the header file using the specified fields
24
24
self .wrannfile ()
25
25
26
+ # Check all fields of the annotation object
26
27
def checkfields (self ):
27
28
28
- # Check field types
29
- if type (self .annsamp ):
29
+ # Mandatory write fields
30
+ for field in ['annsamp' , 'anntype' ]:
31
+ if getattr (self , field ) is None :
32
+ print ('The ' , field , ' field is mandatory for writing annotation files' )
33
+ sys .exit ()
34
+
35
+ # Check all set fields
36
+ for field in annfields :
37
+ if field is not None :
38
+ # Check the type of the field's elements
39
+ self .checkfield (field )
40
+
41
+ # Check a particular annotation field
42
+ def checkfield (self , field ):
43
+
44
+ if field == 'fs' :
45
+ # Check the field type
46
+ if type (self .fs ) not in annfieldtypes ['fs' ]:
47
+ print ('The fs field must be one of the following types: ' , annfieldtypes ['fs' ])
48
+ sys .exit ()
49
+ # Particular condition
50
+ if self .fs <= 0 :
51
+ sys .exit ('The fs field must be a non-negative number' )
52
+
53
+ else :
54
+ # Ensure the field is an array
30
55
56
+ # Check the field type of the list elements
57
+
58
+
59
+
60
+
61
+ # Ensure all set annotation fields have the same length
62
+ def checkfieldcohesion (self ):
63
+
64
+ # Number of annotation samples
65
+ nannots = len (getattr (self , annsamp ))
66
+
67
+ for field in annfields [1 :- 1 ]:
68
+ if getarr (self , field ) is not None :
69
+ if len (getattr (self , field )) != nannots :
70
+
71
+
72
+
73
+
31
74
32
75
def wrannfile (self ):
33
76
print ('on it' )
@@ -44,32 +87,31 @@ def wrannfile(self):
44
87
def rdann (recordname , annot , sampfrom = 0 , sampto = None , anndisp = 1 ):
45
88
""" Read a WFDB annotation file recordname.annot and return the fields as lists or arrays
46
89
47
- Usage: annsamp, anntype, num, subtype, chan, aux, annfs = rdann(recordname, annot,
48
- sampfrom=0, sampto=[],
49
- anndisp=1)
90
+ Usage: annotation = rdann(recordname, annot, sampfrom=0, sampto=[], anndisp=1)
50
91
51
92
Input arguments:
52
93
- recordname (required): The record name of the WFDB annotation file. ie. for
53
94
file '100.atr', recordname='100'
54
95
- annot (required): The annotator extension of the annotation file. ie. for
55
96
file '100.atr', annot='atr'
56
97
- sampfrom (default=0): The minimum sample number for annotations to be returned.
57
- - sampto (default=the final annotation sample ): The maximum sample number for
98
+ - sampto (default=None ): The maximum sample number for
58
99
annotations to be returned.
59
100
- anndisp (default = 1): The annotation display flag that controls the data type
60
101
of the 'anntype' output parameter. 'anntype' will either be an integer key(0),
61
102
a shorthand display symbol(1), or a longer annotation code(2).
62
103
63
- Output arguments:
64
- - annsamp: The annotation location in samples relative to the beginning of the record.
65
- - anntype: The annotation type according the the standard WFDB keys.
66
- - subtype: The marked class/category of the annotation.
67
- - chan: The signal channel associated with the annotations.
68
- - num: The marked annotation number. This is not equal to the index of the current annotation.
69
- - aux: The auxiliary information string for the annotation.
70
- - annfs: The sampling frequency written in the beginning of the annotation file if present.
71
-
72
- *NOTE: Every annotation contains the 'annsamp' and 'anntype' field. All
104
+ Output argument:
105
+ - annotation: The annotation object with the following fields:
106
+ - annsamp: The annotation location in samples relative to the beginning of the record.
107
+ - anntype: The annotation type according the the standard WFDB keys.
108
+ - subtype: The marked class/category of the annotation.
109
+ - chan: The signal channel associated with the annotations.
110
+ - num: The labelled annotation number.
111
+ - aux: The auxiliary information string for the annotation.
112
+ - fs: The sampling frequency written into the annotation file if present.
113
+
114
+ *NOTE: Every annotation sample contains the 'annsamp' and 'anntype' fields. All
73
115
other fields default to 0 or empty if not present.
74
116
"""
75
117
@@ -78,8 +120,6 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
78
120
if sampfrom < 0 :
79
121
raise ValueError ("sampfrom must be a non-negative integer" )
80
122
81
- dirname , baserecordname = os .path .split (recordname )
82
-
83
123
# Read the file in byte pairs
84
124
filebytes = loadbytepairs (recordname , annot )
85
125
@@ -94,22 +134,22 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
94
134
ai = 0 # Annotation index, the number of annotations processed.
95
135
96
136
# Check the beginning of the file for a potential fs field
97
- annfs , bpi = get_fs (filebytes , bpi )
137
+ fs , bpi = get_fs (filebytes , bpi )
98
138
99
139
# Process annotations. Iterate across byte pairs.
100
140
# Sequence for one ann is:
101
141
# SKIP pair (if any)
102
142
# samp + anntype pair
103
143
# other pairs (if any)
104
144
# The last byte pair is 0 indicating eof.
105
- while (bpi < annotlength - 1 ) and ts < sampto :
145
+ while (bpi < annotlength - 1 ):
106
146
107
147
# The first byte pair will either store the actual samples + anntype,
108
148
# or 0 + SKIP.
109
149
AT = filebytes [bpi , 1 ] >> 2 # anntype
110
150
111
151
# flags that specify whether to copy the previous channel/num value for
112
- # the current annotation.
152
+ # the current annotation. Set default.
113
153
cpychan , cpynum = 1 , 1
114
154
ts , annsamp , anntype , bpi = copy_prev (AT ,ts ,filebytes ,bpi ,annsamp ,anntype ,ai )
115
155
@@ -133,6 +173,9 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
133
173
# Finished processing current annotation. Move onto next.
134
174
ai = ai + 1
135
175
176
+ if sampto and sampto < ts :
177
+ break ;
178
+
136
179
# Snip the unallocated end of the arrays
137
180
annsamp ,anntype ,num ,subtype ,chan ,aux = snip_arrays (annsamp ,anntype ,num ,subtype ,chan ,aux ,ai )
138
181
@@ -145,7 +188,7 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
145
188
146
189
# Store fields in an Annotation object
147
190
annotation = Annotation (annsamp = annsamp , anntype = anntype , subtype = subtype ,
148
- chan = chan , num = num , aux = aux , fs = annfs )
191
+ chan = chan , num = num , aux = aux , fs = fs )
149
192
150
193
return annotation
151
194
@@ -168,7 +211,7 @@ def init_arrays(annotlength):
168
211
# Check the beginning of the annotation file for an fs
169
212
def get_fs (filebytes ):
170
213
171
- annfs = None # fs potentially stored in the file
214
+ fs = None # fs potentially stored in the file
172
215
bpi = 0 # Byte pair index for searching the annotation file
173
216
174
217
if filebytes .size > 24 :
@@ -182,11 +225,11 @@ def get_fs(filebytes):
182
225
# the file.
183
226
auxlen = testbytes [2 ]
184
227
testbytes = filebytes [:(12 + int (np .ceil (auxlen / 2. ))), :].flatten ()
185
- annfs = int ("" .join ([chr (char )
228
+ fs = int ("" .join ([chr (char )
186
229
for char in testbytes [24 :auxlen + 4 ]]))
187
230
# byte pair index to start reading actual annotations.
188
231
bpi = 0.5 * (auxlen + 12 + (auxlen & 1 ))
189
- return (annfs , bpi )
232
+ return (fs , bpi )
190
233
191
234
def copy_prev (AT ,ts ,filebytes ,bpi ,annsamp ,anntype ,ai ):
192
235
if AT == 59 : # Skip.
@@ -235,8 +278,19 @@ def proc_extra_fields(AT,subtype,ai,filebytes,bpi,num,chan,cpychan,cpynum,aux):
235
278
bpi = bpi + 1 + int (np .ceil (auxlen / 2. ))
236
279
return subtype ,bpi ,num ,chan ,cpychan ,cpynum ,aux
237
280
281
+ # Remove unallocated part of array
282
+ def snip_arrays (annsamp ,anntype ,num ,subtype ,chan ,aux ,ai ):
283
+ annsamp = annsamp [0 :ai ].astype (int )
284
+ anntype = anntype [0 :ai ].astype (int )
285
+ num = num [0 :ai ].astype (int )
286
+ subtype = subtype [0 :ai ].astype (int )
287
+ chan = chan [0 :ai ].astype (int )
288
+ aux = aux [0 :ai ]
289
+ return annsamp ,anntype ,num ,subtype ,chan ,aux
290
+
291
+ # Keep only the specified annotations
238
292
def apply_annotation_range (annsamp ,sampfrom ,sampto ,anntype ,num ,subtype ,chan ,aux ):
239
- # Keep the annotations in the specified range
293
+
240
294
returnempty = 0
241
295
242
296
afterfrom = np .where (annsamp >= sampfrom )[0 ]
@@ -247,6 +301,7 @@ def apply_annotation_range(annsamp,sampfrom,sampto,anntype,num,subtype,chan,aux)
247
301
248
302
if not sampto :
249
303
sampto = annsamp [- 1 ]
304
+
250
305
beforeto = np .where (annsamp <= sampto )[0 ]
251
306
252
307
if len (beforeto ) > 0 :
@@ -279,14 +334,6 @@ def format_anntype(anndisp,anntype):
279
334
return anntype
280
335
281
336
282
- def snip_arrays (annsamp ,anntype ,num ,subtype ,chan ,aux ,ai ):
283
- annsamp = annsamp [0 :ai ].astype (int )
284
- anntype = anntype [0 :ai ].astype (int )
285
- num = num [0 :ai ].astype (int )
286
- subtype = subtype [0 :ai ].astype (int )
287
- chan = chan [0 :ai ].astype (int )
288
- aux = aux [0 :ai ]
289
- return annsamp ,anntype ,num ,subtype ,chan ,aux
290
337
291
338
292
339
## ------------- /Reading Annotations ------------- ##
@@ -396,3 +443,9 @@ def snip_arrays(annsamp,anntype,num,subtype,chan,aux,ai):
396
443
41 : 'RONT' # R-on-T premature ventricular contraction */
397
444
}
398
445
446
+ annfields = ['annsamp' , 'anntype' , 'num' , 'subtype' , 'chan' , 'aux' , 'fs' ]
447
+
448
+ annfieldtypes = {'annsamp' : _headers .inttypes , 'anntypes' : [str ],
449
+ 'num' :_headers .inttypes , 'subtype' : _headers .inttypes ,
450
+ 'chan' = _headers .inttypes , 'aux' : [str ],
451
+ 'fs' : _headers .floattypes }
0 commit comments