Skip to content

Commit 0a2e0b8

Browse files
committed
implement return resolution choosing option, and save memory in intermediate functions
1 parent 63d0421 commit 0a2e0b8

File tree

4 files changed

+136
-68
lines changed

4 files changed

+136
-68
lines changed

wfdb/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
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 signaltypes
34
from .readwrite.annotations import Annotation, rdann, wrann, showanncodes
45
from .readwrite.downloads import getdblist
56
from .plot.plots import plotrec, plotann

wfdb/readwrite/_signals.py

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import math
44
from . import downloads
5+
import pdb
56

67
# All defined WFDB dat formats
78
datformats = ["80","212","16","24","32"]
@@ -286,7 +287,7 @@ def adc(self, expanded=False):
286287
return d_signals
287288

288289

289-
def dac(self, expanded=False):
290+
def dac(self, expanded=False, returnres=64):
290291
"""
291292
Returns the digital to analogue conversion for a Record object's signal stored
292293
in d_signals if expanded is False, or e_d_signals if expanded is True.
@@ -295,17 +296,27 @@ def dac(self, expanded=False):
295296
# The digital nan values for each channel
296297
dnans = digi_nan(self.fmt)
297298

299+
# Get the appropriate float dtype
300+
301+
if returnres == 64:
302+
floatdtype = 'float64'
303+
elif returnres == 32:
304+
floatdtype = 'float32'
305+
else:
306+
floatdtype = 'float16'
307+
298308
if expanded:
299309
p_signal = []
300310
for ch in range(0, self.nsig):
301311
# nan locations for the channel
302312
chnanlocs = self.e_d_signals[ch] == dnans[ch]
303-
p_signal.append((self.e_d_signals[ch] - self.baseline[ch])/float(self.adcgain[ch]))
313+
p_signal.append((self.e_d_signals[ch] - np.array(self.baseline[ch], dtype=self.e_d_signals[ch].dtype))/
314+
np.array(self.adcgain[ch], dtype=floatdtype).astype(floatdtype, copy=False))
304315
p_signal[ch][chnanlocs] = np.nan
305316
else:
306317
# nan locations
307318
nanlocs = self.d_signals == dnans
308-
p_signal = (self.d_signals - self.baseline)/[float(g) for g in self.adcgain]
319+
p_signal = (self.d_signals - np.array(self.baseline, dtype=self.d_signals.dtype)) / np.array(self.adcgain, dtype=floatdtype).astype(floatdtype, copy=False)
309320
p_signal[nanlocs] = np.nan
310321

311322
return p_signal
@@ -456,6 +467,7 @@ def smoothframes(self, sigtype='physical'):
456467

457468
return signal
458469

470+
459471
#------------------- Reading Signals -------------------#
460472

461473
def rdsegment(filename, dirname, pbdir, nsig, fmt, siglen, byteoffset,
@@ -523,9 +535,10 @@ def rdsegment(filename, dirname, pbdir, nsig, fmt, siglen, byteoffset,
523535
# Signals with multiple samples/frame are smoothed, or all signals have 1 sample/frame.
524536
# Return uniform numpy array
525537
if smoothframes or sum(sampsperframe)==nsig:
526-
527-
# Allocate signal array
528-
signals = np.zeros([sampto-sampfrom, len(channels)], dtype = 'int64')
538+
# Figure out the largest required dtype for the segment to minimize memory usage
539+
maxdtype = npdtype(wfdbfmtres(fmt, maxres=True), discrete=True)
540+
# Allocate signal array. Minimize dtype
541+
signals = np.zeros([sampto-sampfrom, len(channels)], dtype = maxdtype)
529542

530543
# Read each wanted dat file and store signals
531544
for fn in w_filename:
@@ -603,10 +616,10 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
603616
extrabytenum = totalprocessbytes - totalreadbytes
604617

605618
sigbytes = np.concatenate((getdatbytes(filename, dirname, pbdir, fmt, startbyte, nreadsamples),
606-
np.zeros(extrabytenum, dtype = 'uint8')))
619+
np.zeros(extrabytenum, dtype = np.dtype(dataloadtypes[fmt]))))
607620
else:
608621
sigbytes = np.concatenate((getdatbytes(filename, dirname, pbdir, fmt, startbyte, nreadsamples),
609-
np.zeros(extraflatsamples, dtype='int64')))
622+
np.zeros(extraflatsamples, dtype = np.dtype(dataloadtypes[fmt]))))
610623
else:
611624
sigbytes = getdatbytes(filename, dirname, pbdir, fmt, startbyte, nreadsamples)
612625

@@ -620,9 +633,12 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
620633
sigbytes = sigbytes[blockfloorsamples:]
621634
# Adjust for byte offset formats
622635
elif fmt == '80':
623-
sigbytes = sigbytes - 128
636+
sigbytes = (sigbytes.astype('int16') - 128).astype('int8')
624637
elif fmt == '160':
625-
sigbytes = sigbytes - 32768
638+
sigbytes = (sigbytes.astype('int32') - 32768).astype('int16')
639+
640+
# At this point, dtype of sigbytes is the minimum integer format required for storing
641+
# final samples.
626642

627643
# No extra samples/frame. Obtain original uniform numpy array
628644
if tsampsperframe==nsig:
@@ -634,26 +650,26 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
634650
# Extra frames present to be smoothed. Obtain averaged uniform numpy array
635651
elif smoothframes:
636652

637-
# Allocate memory for smoothed signal
638-
sig = np.zeros((int(len(sigbytes)/tsampsperframe) , nsig), dtype='int64')
653+
# Allocate memory for smoothed signal.
654+
sig = np.zeros((int(len(sigbytes)/tsampsperframe) , nsig), dtype=sigbytes.dtype)
639655

640656
# Transfer and average samples
641657
for ch in range(nsig):
642658
if sampsperframe[ch] == 1:
643659
sig[:, ch] = sigbytes[sum(([0] + sampsperframe)[:ch + 1])::tsampsperframe]
644660
else:
645-
for frame in range(sampsperframe[ch]):
646-
sig[:, ch] += sigbytes[sum(([0] + sampsperframe)[:ch + 1]) + frame::tsampsperframe]
647-
# Have to change the dtype for averaging frames
648-
sig = (sig.astype('float64') / sampsperframe)
661+
if ch == 0:
662+
startind = 0
663+
else:
664+
startind = np.sum(sampsperframe[:ch])
665+
sig[:,ch] = [np.average(sigbytes[ind:ind+sampsperframe[ch]]) for ind in range(startind,len(sigbytes),tsampsperframe)]
649666
# Skew the signal
650667
sig = skewsig(sig, skew, nsig, readlen, fmt, nanreplace)
651668

652669
# Extra frames present without wanting smoothing. Return all expanded samples.
653670
else:
654671
# List of 1d numpy arrays
655672
sig=[]
656-
657673
# Transfer over samples
658674
for ch in range(nsig):
659675
# Indices of the flat signal that belong to the channel
@@ -807,8 +823,7 @@ def getdatbytes(filename, dirname, pbdir, fmt, startbyte, nsamp):
807823
fp.seek(startbyte)
808824

809825
# Read file using corresponding dtype
810-
# Cast to int64 for further processing
811-
sigbytes = np.fromfile(fp, dtype=np.dtype(dataloadtypes[fmt]), count=elementcount).astype('int')
826+
sigbytes = np.fromfile(fp, dtype=np.dtype(dataloadtypes[fmt]), count=elementcount)
812827

813828
fp.close()
814829

@@ -817,6 +832,8 @@ def getdatbytes(filename, dirname, pbdir, fmt, startbyte, nsamp):
817832
else:
818833
sigbytes = downloads.streamdat(filename, pbdir, fmt, bytecount, startbyte, dataloadtypes)
819834

835+
#pdb.set_trace()
836+
820837
return sigbytes
821838

822839

@@ -833,7 +850,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
833850
else:
834851
addedsamps = 0
835852

836-
sig = np.zeros(nsamp, dtype='int64')
853+
sigbytes = sigbytes.astype('int16')
854+
sig = np.zeros(nsamp, dtype='int16')
837855

838856
# One sample pair is stored in one byte triplet.
839857

@@ -859,8 +877,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
859877
else:
860878
addedsamps = 0
861879

862-
# 1d array of actual samples. Fill the individual triplets.
863-
sig = np.zeros(nsamp, dtype='int64')
880+
sigbytes = sigbytes.astype('int16')
881+
sig = np.zeros(nsamp, dtype='int16')
864882

865883
# One sample triplet is stored in one byte quartet
866884
# First sample is 7 msb of first byte and 3 lsb of second byte.
@@ -887,8 +905,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
887905
else:
888906
addedsamps = 0
889907

890-
# 1d array of actual samples. Fill the individual triplets.
891-
sig = np.zeros(nsamp, dtype='int64')
908+
sigbytes = sigbytes.astype('int16')
909+
sig = np.zeros(nsamp, dtype='int16')
892910

893911
# One sample triplet is stored in one byte quartet
894912
# First sample is first byte and 2 lsb of second byte.
@@ -1069,12 +1087,12 @@ def estres(signals):
10691087

10701088

10711089
# Return the most suitable wfdb format(s) to use given signal resolutions.
1072-
# If singlefmt == 1, the format for the maximum resolution will be returned.
1073-
def wfdbfmt(res, singlefmt = 1):
1090+
# If singlefmt is True, the format for the maximum resolution will be returned.
1091+
def wfdbfmt(res, singlefmt = True):
10741092

10751093
if type(res) == list:
10761094
# Return a single format
1077-
if singlefmt == 1:
1095+
if singlefmt is True:
10781096
res = [max(res)]*len(res)
10791097

10801098
fmts = []
@@ -1094,14 +1112,14 @@ def wfdbfmt(res, singlefmt = 1):
10941112
return '32'
10951113

10961114
# Return the resolution of the WFDB format(s).
1097-
def wfdbfmtres(fmt):
1115+
def wfdbfmtres(fmt, maxres=False):
10981116

10991117
if type(fmt)==list:
1100-
res = []
1101-
for f in fmt:
1102-
res.append(wfdbfmtres(f))
1118+
res = [wfdbfmtres(f) for f in fmt]
1119+
if maxres is True:
1120+
res = np.max(res)
11031121
return res
1104-
1122+
11051123
if fmt in ['8', '80']:
11061124
return 8
11071125
elif fmt in ['310', '311']:
@@ -1117,6 +1135,22 @@ def wfdbfmtres(fmt):
11171135
else:
11181136
raise ValueError('Invalid WFDB format.')
11191137

1138+
# Given the resolution of a signal, return the minimum
1139+
# dtype to store it
1140+
def npdtype(res, discrete):
1141+
1142+
if not hasattr(res, '__index__') or res>64:
1143+
raise TypeError('res must be integer based and <=64')
1144+
1145+
for npres in [8, 16, 32, 64]:
1146+
if res<=npres:
1147+
break
1148+
1149+
if discrete is True:
1150+
return 'int'+str(npres)
1151+
else:
1152+
return 'float'+str(npres)
1153+
11201154
# Write a dat file.
11211155
# All bytes are written one at a time
11221156
# to avoid endianness issues.

wfdb/readwrite/downloads.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def streamdat(filename, pbdir, fmt, bytecount, startbyte, datatypes):
5858
sigbytes = r.content
5959

6060
# Convert to numpy array
61-
# Cast to int64 for further processing
62-
sigbytes = np.fromstring(sigbytes, dtype = np.dtype(datatypes[fmt])).astype('int')
61+
sigbytes = np.fromstring(sigbytes, dtype = np.dtype(datatypes[fmt]))
6362

6463
return sigbytes
6564

0 commit comments

Comments
 (0)