2
2
import os
3
3
import math
4
4
from . import downloads
5
+ import pdb
5
6
6
7
# All defined WFDB dat formats
7
8
datformats = ["80" ,"212" ,"16" ,"24" ,"32" ]
@@ -286,7 +287,7 @@ def adc(self, expanded=False):
286
287
return d_signals
287
288
288
289
289
- def dac (self , expanded = False ):
290
+ def dac (self , expanded = False , returnres = 64 ):
290
291
"""
291
292
Returns the digital to analogue conversion for a Record object's signal stored
292
293
in d_signals if expanded is False, or e_d_signals if expanded is True.
@@ -295,17 +296,27 @@ def dac(self, expanded=False):
295
296
# The digital nan values for each channel
296
297
dnans = digi_nan (self .fmt )
297
298
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
+
298
308
if expanded :
299
309
p_signal = []
300
310
for ch in range (0 , self .nsig ):
301
311
# nan locations for the channel
302
312
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 ))
304
315
p_signal [ch ][chnanlocs ] = np .nan
305
316
else :
306
317
# nan locations
307
318
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 )
309
320
p_signal [nanlocs ] = np .nan
310
321
311
322
return p_signal
@@ -456,6 +467,7 @@ def smoothframes(self, sigtype='physical'):
456
467
457
468
return signal
458
469
470
+
459
471
#------------------- Reading Signals -------------------#
460
472
461
473
def rdsegment (filename , dirname , pbdir , nsig , fmt , siglen , byteoffset ,
@@ -523,9 +535,10 @@ def rdsegment(filename, dirname, pbdir, nsig, fmt, siglen, byteoffset,
523
535
# Signals with multiple samples/frame are smoothed, or all signals have 1 sample/frame.
524
536
# Return uniform numpy array
525
537
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 )
529
542
530
543
# Read each wanted dat file and store signals
531
544
for fn in w_filename :
@@ -603,10 +616,10 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
603
616
extrabytenum = totalprocessbytes - totalreadbytes
604
617
605
618
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 ]) )))
607
620
else :
608
621
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 ]) )))
610
623
else :
611
624
sigbytes = getdatbytes (filename , dirname , pbdir , fmt , startbyte , nreadsamples )
612
625
@@ -620,9 +633,12 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
620
633
sigbytes = sigbytes [blockfloorsamples :]
621
634
# Adjust for byte offset formats
622
635
elif fmt == '80' :
623
- sigbytes = sigbytes - 128
636
+ sigbytes = ( sigbytes . astype ( 'int16' ) - 128 ). astype ( 'int8' )
624
637
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.
626
642
627
643
# No extra samples/frame. Obtain original uniform numpy array
628
644
if tsampsperframe == nsig :
@@ -634,26 +650,26 @@ def rddat(filename, dirname, pbdir, fmt, nsig,
634
650
# Extra frames present to be smoothed. Obtain averaged uniform numpy array
635
651
elif smoothframes :
636
652
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 )
639
655
640
656
# Transfer and average samples
641
657
for ch in range (nsig ):
642
658
if sampsperframe [ch ] == 1 :
643
659
sig [:, ch ] = sigbytes [sum (([0 ] + sampsperframe )[:ch + 1 ])::tsampsperframe ]
644
660
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 )]
649
666
# Skew the signal
650
667
sig = skewsig (sig , skew , nsig , readlen , fmt , nanreplace )
651
668
652
669
# Extra frames present without wanting smoothing. Return all expanded samples.
653
670
else :
654
671
# List of 1d numpy arrays
655
672
sig = []
656
-
657
673
# Transfer over samples
658
674
for ch in range (nsig ):
659
675
# Indices of the flat signal that belong to the channel
@@ -807,8 +823,7 @@ def getdatbytes(filename, dirname, pbdir, fmt, startbyte, nsamp):
807
823
fp .seek (startbyte )
808
824
809
825
# 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 )
812
827
813
828
fp .close ()
814
829
@@ -817,6 +832,8 @@ def getdatbytes(filename, dirname, pbdir, fmt, startbyte, nsamp):
817
832
else :
818
833
sigbytes = downloads .streamdat (filename , pbdir , fmt , bytecount , startbyte , dataloadtypes )
819
834
835
+ #pdb.set_trace()
836
+
820
837
return sigbytes
821
838
822
839
@@ -833,7 +850,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
833
850
else :
834
851
addedsamps = 0
835
852
836
- sig = np .zeros (nsamp , dtype = 'int64' )
853
+ sigbytes = sigbytes .astype ('int16' )
854
+ sig = np .zeros (nsamp , dtype = 'int16' )
837
855
838
856
# One sample pair is stored in one byte triplet.
839
857
@@ -859,8 +877,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
859
877
else :
860
878
addedsamps = 0
861
879
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 ' )
864
882
865
883
# One sample triplet is stored in one byte quartet
866
884
# First sample is 7 msb of first byte and 3 lsb of second byte.
@@ -887,8 +905,8 @@ def bytes2samples(sigbytes, nsamp, fmt):
887
905
else :
888
906
addedsamps = 0
889
907
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 ' )
892
910
893
911
# One sample triplet is stored in one byte quartet
894
912
# First sample is first byte and 2 lsb of second byte.
@@ -1069,12 +1087,12 @@ def estres(signals):
1069
1087
1070
1088
1071
1089
# 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 ):
1074
1092
1075
1093
if type (res ) == list :
1076
1094
# Return a single format
1077
- if singlefmt == 1 :
1095
+ if singlefmt is True :
1078
1096
res = [max (res )]* len (res )
1079
1097
1080
1098
fmts = []
@@ -1094,14 +1112,14 @@ def wfdbfmt(res, singlefmt = 1):
1094
1112
return '32'
1095
1113
1096
1114
# Return the resolution of the WFDB format(s).
1097
- def wfdbfmtres (fmt ):
1115
+ def wfdbfmtres (fmt , maxres = False ):
1098
1116
1099
1117
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 )
1103
1121
return res
1104
-
1122
+
1105
1123
if fmt in ['8' , '80' ]:
1106
1124
return 8
1107
1125
elif fmt in ['310' , '311' ]:
@@ -1117,6 +1135,22 @@ def wfdbfmtres(fmt):
1117
1135
else :
1118
1136
raise ValueError ('Invalid WFDB format.' )
1119
1137
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
+
1120
1154
# Write a dat file.
1121
1155
# All bytes are written one at a time
1122
1156
# to avoid endianness issues.
0 commit comments