Skip to content

Commit 30e94f0

Browse files
committed
dev
1 parent 04e5621 commit 30e94f0

File tree

2 files changed

+89
-37
lines changed

2 files changed

+89
-37
lines changed

tests/test_annotations.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import numpy as np
2-
import wfdb
32
import re
4-
3+
import wfdb
54

65
class test_rdann():
76

wfdb/annotations.py

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import numpy as np
2-
import os
2+
from . import headers
33

44
# Class for WFDB annotations
55
class Annotation():
@@ -23,11 +23,54 @@ def wrann(self):
2323
# Write the header file using the specified fields
2424
self.wrannfile()
2525

26+
# Check all fields of the annotation object
2627
def checkfields(self):
2728

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
3055

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+
3174

3275
def wrannfile(self):
3376
print('on it')
@@ -44,32 +87,31 @@ def wrannfile(self):
4487
def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
4588
""" Read a WFDB annotation file recordname.annot and return the fields as lists or arrays
4689
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)
5091
5192
Input arguments:
5293
- recordname (required): The record name of the WFDB annotation file. ie. for
5394
file '100.atr', recordname='100'
5495
- annot (required): The annotator extension of the annotation file. ie. for
5596
file '100.atr', annot='atr'
5697
- 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
5899
annotations to be returned.
59100
- anndisp (default = 1): The annotation display flag that controls the data type
60101
of the 'anntype' output parameter. 'anntype' will either be an integer key(0),
61102
a shorthand display symbol(1), or a longer annotation code(2).
62103
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
73115
other fields default to 0 or empty if not present.
74116
"""
75117

@@ -78,8 +120,6 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
78120
if sampfrom < 0:
79121
raise ValueError("sampfrom must be a non-negative integer")
80122

81-
dirname, baserecordname = os.path.split(recordname)
82-
83123
# Read the file in byte pairs
84124
filebytes = loadbytepairs(recordname, annot)
85125

@@ -94,22 +134,22 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
94134
ai = 0 # Annotation index, the number of annotations processed.
95135

96136
# 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)
98138

99139
# Process annotations. Iterate across byte pairs.
100140
# Sequence for one ann is:
101141
# SKIP pair (if any)
102142
# samp + anntype pair
103143
# other pairs (if any)
104144
# The last byte pair is 0 indicating eof.
105-
while (bpi < annotlength - 1) and ts<sampto:
145+
while (bpi < annotlength - 1):
106146

107147
# The first byte pair will either store the actual samples + anntype,
108148
# or 0 + SKIP.
109149
AT = filebytes[bpi, 1] >> 2 # anntype
110150

111151
# flags that specify whether to copy the previous channel/num value for
112-
# the current annotation.
152+
# the current annotation. Set default.
113153
cpychan, cpynum = 1, 1
114154
ts, annsamp, anntype, bpi = copy_prev(AT,ts,filebytes,bpi,annsamp,anntype,ai)
115155

@@ -133,6 +173,9 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
133173
# Finished processing current annotation. Move onto next.
134174
ai = ai + 1
135175

176+
if sampto and sampto<ts:
177+
break;
178+
136179
# Snip the unallocated end of the arrays
137180
annsamp,anntype,num,subtype,chan,aux = snip_arrays(annsamp,anntype,num,subtype,chan,aux,ai)
138181

@@ -145,7 +188,7 @@ def rdann(recordname, annot, sampfrom=0, sampto=None, anndisp=1):
145188

146189
# Store fields in an Annotation object
147190
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)
149192

150193
return annotation
151194

@@ -168,7 +211,7 @@ def init_arrays(annotlength):
168211
# Check the beginning of the annotation file for an fs
169212
def get_fs(filebytes):
170213

171-
annfs = None # fs potentially stored in the file
214+
fs = None # fs potentially stored in the file
172215
bpi = 0 # Byte pair index for searching the annotation file
173216

174217
if filebytes.size > 24:
@@ -182,11 +225,11 @@ def get_fs(filebytes):
182225
# the file.
183226
auxlen = testbytes[2]
184227
testbytes = filebytes[:(12 + int(np.ceil(auxlen / 2.))), :].flatten()
185-
annfs = int("".join([chr(char)
228+
fs = int("".join([chr(char)
186229
for char in testbytes[24:auxlen + 4]]))
187230
# byte pair index to start reading actual annotations.
188231
bpi = 0.5 * (auxlen + 12 + (auxlen & 1))
189-
return (annfs, bpi)
232+
return (fs, bpi)
190233

191234
def copy_prev(AT,ts,filebytes,bpi,annsamp,anntype,ai):
192235
if AT == 59: # Skip.
@@ -235,8 +278,19 @@ def proc_extra_fields(AT,subtype,ai,filebytes,bpi,num,chan,cpychan,cpynum,aux):
235278
bpi = bpi + 1 + int(np.ceil(auxlen / 2.))
236279
return subtype,bpi,num,chan,cpychan,cpynum,aux
237280

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
238292
def apply_annotation_range(annsamp,sampfrom,sampto,anntype,num,subtype,chan,aux):
239-
# Keep the annotations in the specified range
293+
240294
returnempty = 0
241295

242296
afterfrom = np.where(annsamp >= sampfrom)[0]
@@ -247,6 +301,7 @@ def apply_annotation_range(annsamp,sampfrom,sampto,anntype,num,subtype,chan,aux)
247301

248302
if not sampto:
249303
sampto = annsamp[-1]
304+
250305
beforeto = np.where(annsamp <= sampto)[0]
251306

252307
if len(beforeto) > 0:
@@ -279,14 +334,6 @@ def format_anntype(anndisp,anntype):
279334
return anntype
280335

281336

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
290337

291338

292339
## ------------- /Reading Annotations ------------- ##
@@ -396,3 +443,9 @@ def snip_arrays(annsamp,anntype,num,subtype,chan,aux,ai):
396443
41: 'RONT' # R-on-T premature ventricular contraction */
397444
}
398445

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

Comments
 (0)