Skip to content

Commit 58174f8

Browse files
committed
fix fs writing in wrann. Also round fs to integer in read/write records and annotations if close enough
1 parent 4062725 commit 58174f8

File tree

4 files changed

+62
-23
lines changed

4 files changed

+62
-23
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# Versions should comply with PEP440. For a discussion on single-sourcing
2121
# the version across setup.py and the project code, see
2222
# https://packaging.python.org/en/latest/single_source_version.html
23-
version='1.1.1',
23+
version='1.1.2',
2424

2525
description='The WFDB Python Toolbox',
2626
long_description=long_description,

tests/test_annotations.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_1(self):
5757
pbannotation.aux[0] = '(N'
5858

5959
# Test file writing
60-
annotation.wrann()
60+
annotation.wrann(writefs=True)
6161
annotationwrite = wfdb.rdann('100', 'atr')
6262

6363
assert (comp == [True] * 6)
@@ -107,7 +107,7 @@ def test_2(self):
107107
pbannotation = wfdb.rdann('12726', 'anI', pbdir = 'prcp')
108108

109109
# Test file writing
110-
annotation.wrann()
110+
annotation.wrann(writefs=True)
111111
annotationwrite = wfdb.rdann('12726', 'anI')
112112

113113
assert (comp == [True] * 6)
@@ -158,7 +158,7 @@ def test_3(self):
158158
pbannotation = wfdb.rdann('1003', 'atr', pbdir = 'challenge/2014/set-p2')
159159

160160
# Test file writing
161-
annotation.wrann()
161+
annotation.wrann(writefs=True)
162162
annotationwrite = wfdb.rdann('1003', 'atr')
163163

164164
assert (comp == [True] * 6)

wfdb/readwrite/_headers.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,12 @@ def wrheaderfile(self, recwritefields, sigwritefields):
227227
for field in recfieldspecs:
228228
# If the field is being used, add it with its delimiter
229229
if field in recwritefields:
230-
recordline = recordline + recfieldspecs[field].delimiter + str(getattr(self, field))
230+
stringfield = str(getattr(self, field))
231+
# If fs is float, check whether it as an integer
232+
if field == 'fs' and type(self.fs) == float:
233+
if round(self.fs, 8) == float(int(self.fs)):
234+
stringfield = str(int(self.fs))
235+
recordline = recordline + recfieldspecs[field].delimiter + stringfield
231236
headerlines.append(recordline)
232237

233238
# Create signal specification lines (if any) one channel at a time
@@ -390,13 +395,13 @@ def getheaderlines(recordname, pbdir):
390395
if pbdir is None:
391396
with open(recordname + ".hea", 'r') as fp:
392397
# Record line followed by signal/segment lines if any
393-
headerlines = []
398+
headerlines = []
394399
# Comment lines
395-
commentlines = []
400+
commentlines = []
396401
for line in fp:
397402
line = line.strip()
398403
# Comment line
399-
if line.startswith('#'):
404+
if line.startswith('#'):
400405
commentlines.append(line)
401406
# Non-empty non-comment line = header line.
402407
elif line:
@@ -434,8 +439,12 @@ def read_rec_line(recline):
434439
else:
435440
if recfieldspecs[field].allowedtypes is inttypes:
436441
d_rec[field] = int(d_rec[field])
437-
elif recfieldspecs[field].allowedtypes is floattypes:
438-
d_rec[field] = float(d_rec[field])
442+
# fs may be read as float or int
443+
elif field == 'fs':
444+
fs = float(d_rec['fs'])
445+
if round(fs, 8) == float(int(fs)):
446+
fs = int(fs)
447+
d_rec['fs'] = fs
439448

440449
return d_rec
441450

wfdb/readwrite/annotations.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import re
44
import os
55
import copy
6+
from . import records
67
from . import _headers
78
from . import downloads
89

@@ -79,11 +80,16 @@ def __eq__(self, other):
7980
return True
8081

8182
# Write an annotation file
82-
def wrann(self):
83+
def wrann(self, writefs=False):
8384
"""
8485
Instance method to write a WFDB annotation file from an Annotation object.
86+
87+
def wrann(self, writefs=False)
88+
89+
Input Parameters:
90+
- writefs (default=False): Flag specifying whether to write the fs
91+
attribute to the file.
8592
86-
Example usage:
8793
"""
8894
# Check the validity of individual fields used to write the annotation file
8995
self.checkfields()
@@ -92,7 +98,7 @@ def wrann(self):
9298
self.checkfieldcohesion()
9399

94100
# Write the header file using the specified fields
95-
self.wrannfile()
101+
self.wrannfile(writefs)
96102

97103
# Check the mandatory and set fields of the annotation object
98104
# Return indices of anntype field which are not encoded, and thus need
@@ -222,10 +228,10 @@ def checkfieldcohesion(self):
222228
raise ValueError("All written annotation fields: ['annsamp', 'anntype', 'num', 'subtype', 'chan', 'aux'] must have the same length")
223229

224230
# Write an annotation file
225-
def wrannfile(self):
231+
def wrannfile(self, writefs):
226232

227-
# Calculate the fs bytes to write if present
228-
if self.fs is not None:
233+
# Calculate the fs bytes to write if present and desired to write
234+
if self.fs is not None and writefs:
229235
fsbytes = fs2bytes(self.fs)
230236
else:
231237
fsbytes = []
@@ -359,19 +365,31 @@ def type2aux(self):
359365

360366
# Calculate the bytes written to the annotation file for the fs field
361367
def fs2bytes(fs):
362-
databytes = [0,88,23, 252,35,35,32,116,105,109,101,32,114,101,115,111,108,117,116,105,111,110,58,32]
368+
369+
# Initial indicators of encoding fs
370+
databytes = [0,88, None, 252,35,35,32,116,105,109,101,32,114,101,115,111,108,117,116,105,111,110,58,32]
371+
372+
# Be aware of potential float and int
373+
374+
# Check if fs is close enough to int
375+
if type(fs) == float:
376+
if round(fs,8) == float(int(fs)):
377+
fs = int(fs)
363378

364379
fschars = str(fs)
365380
ndigits = len(fschars)
366381

367-
for i in range(0, ndigits):
382+
for i in range(ndigits):
368383
databytes.append(ord(fschars[i]))
369384

385+
# Fill in the aux length
386+
databytes[2] = ndigits + 20
387+
370388
# odd number of digits
371389
if ndigits % 2:
372390
databytes.append(0)
373391

374-
# Add the extra -1 0 filler
392+
# Add the extra -1 0 notqrs filler
375393
databytes = databytes+[0, 236, 255, 255, 255, 255, 1, 0]
376394

377395
return np.array(databytes).astype('u1')
@@ -553,7 +571,7 @@ def wrann(recordname, annotator, annsamp, anntype, subtype = None, chan = None,
553571
# Create Annotation object
554572
annotation = Annotation(recordname, annotator, annsamp, anntype, num, subtype, chan, aux, fs)
555573
# Perform field checks and write the annotation file
556-
annotation.wrann()
574+
annotation.wrann(writefs = True)
557575

558576
# Display the annotation symbols and the codes they represent
559577
def showanncodes():
@@ -619,8 +637,6 @@ def rdann(recordname, annotator, sampfrom=0, sampto=None, pbdir=None):
619637
# bpi = byte pair index. The index at which to continue processing the bytes
620638
fs, bpi = get_fs(filebytes)
621639

622-
623-
624640
# Get the main annotation fields from the annotation bytes
625641
annsamp,anntype,num,subtype,chan,aux,ai = proc_ann_bytes(annsamp,anntype,num,
626642
subtype,chan,aux,
@@ -641,6 +657,17 @@ def rdann(recordname, annotator, sampfrom=0, sampto=None, pbdir=None):
641657
# Set the annotation type to the annotation codes
642658
anntype = [allannsyms[code] for code in anntype]
643659

660+
661+
# If the fs field was not present in the file, try to read a wfdb header
662+
# the annotation is associated with to get the fs
663+
if fs is None:
664+
# Use try except to not make rdann dependent on the validity of the header
665+
try:
666+
rec = records.rdheader(recordname, pbdir)
667+
fs = rec.fs
668+
except:
669+
pass
670+
644671
# Store fields in an Annotation object
645672
annotation = Annotation(os.path.split(recordname)[1], annotator, annsamp, anntype,
646673
subtype, chan, num, aux, fs,custom_anntypes)
@@ -689,7 +716,10 @@ def get_fs(filebytes):
689716
# the file.
690717
auxlen = testbytes[2]
691718
testbytes = filebytes[:(12 + int(np.ceil(auxlen / 2.))), :].flatten()
692-
fs = int("".join([chr(char) for char in testbytes[24:auxlen + 4]]))
719+
fs = float("".join([chr(char) for char in testbytes[24:auxlen + 4]]))
720+
# fs may be int
721+
if round(fs, 8) == float(int(fs)):
722+
fs = int(fs)
693723
# byte pair index to start reading actual annotations.
694724
bpi = int(0.5 * (auxlen + 12 + (auxlen & 1)))
695725
return (fs, bpi)

0 commit comments

Comments
 (0)