Skip to content

Commit 30c16e0

Browse files
committed
enable byte offset and skew writing
1 parent 791abc4 commit 30c16e0

File tree

4 files changed

+193
-30
lines changed

4 files changed

+193
-30
lines changed

tests/test_records.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ def test_2(self):
3737
del(record.comments[0])
3838

3939
# Test file writing
40-
#record.wrsamp()
41-
#recordwrite = wfdb.rdsamp('100')
40+
record.wrsamp()
41+
recordwrite = wfdb.rdsamp('100')
4242

4343
assert np.array_equal(sig, targetsig)
4444
assert record.__eq__(pbrecord)
45-
#assert record.__eq__(recordwrite)
45+
assert record.__eq__(recordwrite)
4646

4747
# Test 3 - Format 16/Entire signal/Digital
4848
# Target file created with: rdsamp -r sampledata/test01_00s | cut -f 2- >
@@ -55,8 +55,15 @@ def test_3(self):
5555
# Compare data streaming from physiobank
5656
pbrecord = wfdb.rdsamp('test01_00s', physical=False, pbdir = 'macecgdb')
5757

58+
# Test file writing
59+
record2 = wfdb.rdsamp('sampledata/test01_00s', physical=False)
60+
record2.signame = ['ECG1', 'ECG2', 'ECG3', 'ECG4']
61+
record2.wrsamp()
62+
recordwrite = wfdb.rdsamp('test01_00s', physical=False)
63+
5864
assert np.array_equal(sig, targetsig)
59-
assert record.__eq__(record)
65+
assert record.__eq__(pbrecord)
66+
assert record2.__eq__(recordwrite)
6067

6168
# Test 4 - Format 16 with byte offset/Selected Duration/Selected Channels/Physical
6269
# Target file created with: rdsamp -r sampledata/a103l -f 50 -t 160 -s 2 0
@@ -85,8 +92,14 @@ def test_5(self):
8592
# Compare data streaming from physiobank
8693
pbrecord = wfdb.rdsamp('a103l', pbdir = 'challenge/2015/training',
8794
sampfrom=20000, channels=[0, 1], physical=False)
95+
96+
# Test file writing
97+
record.wrsamp()
98+
recordwrite = wfdb.rdsamp('a103l')
99+
88100
assert np.array_equal(sig, targetsig)
89101
assert record.__eq__(pbrecord)
102+
assert record.__eq__(recordwrite)
90103

91104
# Test 6 - Format 80/Selected Duration/Selected Channels/Physical
92105
# Target file created with: rdsamp -r sampledata/3000003_0003 -f 1 -t 8 -s

wfdb/_signals.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@ def wrdats(self):
1414

1515
if not self.nsig:
1616
return
17-
1817
# Get all the fields used to write the header
1918
# Assuming this method was called through wrsamp,
2019
# these will have already been checked in wrheader()
2120
writefields = self.getwritefields()
2221

2322
# Check the validity of the d_signals field
2423
self.checkfield('d_signals')
25-
2624
# Check the cohesion of the d_signals field against the other fields used to write the header
2725
self.checksignalcohesion(writefields)
28-
2926
# Write each of the specified dat files
3027
self.wrdatfiles()
3128

@@ -232,15 +229,18 @@ def wrdatfiles(self):
232229
# the channels to be written to each file.
233230
filenames, datchannels = orderedsetlist(self.filename)
234231

235-
# Get the fmt corresponding to each dat file
232+
# Get the fmt and byte offset corresponding to each dat file
236233
datfmts={}
234+
datoffsets={}
237235
for fn in filenames:
238236
datfmts[fn] = self.fmt[datchannels[fn][0]]
237+
datoffsets[fn] = self.byteoffset[datchannels[fn][0]]
239238

240239
# Write the dat files
240+
# Create a copy to prevent overwrite
241+
dsig = self.d_signals.copy()
241242
for fn in filenames:
242-
wrdatfile(fn, datfmts[fn],
243-
self.d_signals[:, datchannels[fn][0]:datchannels[fn][-1]+1])
243+
wrdatfile(fn, datfmts[fn], dsig[:, datchannels[fn][0]:datchannels[fn][-1]+1], datoffsets[fn])
244244

245245

246246

@@ -383,7 +383,6 @@ def processwfdbbytes(filename, dirname, pbdir, fmt, startbyte, readlen, nsig, sa
383383
np.bitwise_and(sigbytes[1::3], 0x0f) # Even numbered samples
384384
if len(sig > 1):
385385
# Odd numbered samples
386-
print(len(sig[1::2]))
387386
sig[1::2] = sigbytes[2::3] + 256*np.bitwise_and(sigbytes[1::3] >> 4, 0x0f)
388387
if floorsamp: # Remove extra sample read
389388
sig = sig[floorsamp:]
@@ -831,17 +830,17 @@ def wfdbfmtres(fmt):
831830
sys.exit('Invalid WFDB format.')
832831

833832
# Write a dat file.
834-
def wrdatfile(filename, fmt, d_signals):
835-
f=open(filename,'wb')
836-
837-
# All bytes are written one at a time
838-
# to avoid endianness issues.
839-
833+
# All bytes are written one at a time
834+
# to avoid endianness issues.
835+
def wrdatfile(filename, fmt, d_signals, byteoffset):
836+
f=open(filename,'wb')
840837
nsig = d_signals.shape[1]
841838

842839
if fmt == '80':
843840
# convert to 8 bit offset binary form
844841
d_signals = d_signals + 128
842+
# Concatenate into 1D
843+
d_signals = d_signals.reshape(-1)
845844
# Convert to unsigned 8 bit dtype to write
846845
bwrite = d_signals.astype('uint8')
847846

@@ -897,7 +896,6 @@ def wrdatfile(filename, fmt, d_signals):
897896
bwrite = bwrite.reshape((1,-1))[0]
898897
# Convert to unsigned 8 bit dtype to write
899898
bwrite = bwrite.astype('uint8')
900-
901899
elif fmt == '24':
902900
# convert to 24 bit two's complement
903901
d_signals[d_signals<0] = d_signals[d_signals<0] + 16777216
@@ -933,12 +931,17 @@ def wrdatfile(filename, fmt, d_signals):
933931
bwrite = bwrite.astype('uint8')
934932
else:
935933
sys.exit('This library currently only supports the following formats: 80, 16, 24, 32')
934+
935+
# Byte offset in the file
936+
if byteoffset is not None and byteoffset>0:
937+
print('Writing file '+filename+' with '+str(byteoffset)+' empty leading bytes')
938+
bwrite = np.append(np.zeros(byteoffset, dtype = 'uint8'), bwrite)
939+
936940
# Write the file
937941
bwrite.tofile(f)
938942

939943
f.close()
940944

941-
942945
# Returns the unique elements in a list in the order that they appear.
943946
# Also returns the indices of the original list that correspond to each output element.
944947
def orderedsetlist(fulllist):

wfdb/records.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,15 @@ def checkfield(self, field, ch=None):
114114
f = self.skew[ch]
115115
if f < 0:
116116
sys.exit('skew values must be non-negative integers')
117-
if f > 0:
118-
sys.exit('Sorry, I have not implemented skew into wrsamp yet')
117+
# Writing a signal with skew shouldn't require anything different
118+
#if f > 0:
119+
# sys.exit('Sorry, I have not implemented skew into wrsamp yet')
119120
elif field == 'byteoffset':
120121
f = self.byteoffset[ch]
121122
if f < 0:
122123
sys.exit('byteoffset values must be non-negative integers')
123-
if f > 0:
124-
sys.exit('Sorry, I have not implemented skew into wrsamp yet')
124+
#if f > 0:
125+
# sys.exit('Sorry, I have not implemented byte offset into wrsamp yet')
125126
elif field == 'adcgain':
126127
f = self.adcgain[ch]
127128
if f <= 0:
@@ -357,7 +358,6 @@ def wrsamp(self):
357358

358359
# Perform field validity and cohesion checks, and write the header file.
359360
self.wrheader()
360-
361361
if self.nsig>0:
362362
# Perform signal validity and cohesion checks, and write the associated dat files.
363363
self.wrdats()
@@ -368,8 +368,7 @@ def arrangefields(self, channels, usersiglen):
368368
# Rearrange signal specification fields
369369
for field in _headers.sigfieldspecs:
370370
item = getattr(self, field)
371-
if item != self.nsig*[None]:
372-
setattr(self, field, [item[c] for c in channels])
371+
setattr(self, field, [item[c] for c in channels])
373372

374373
# Checksum and initvalue to be updated if present
375374
# unless the whole signal length was input
@@ -716,7 +715,6 @@ def rdsamp(recordname, sampfrom=0, sampto=None, channels = None, physical = True
716715
record.p_signals = record.dac()
717716
# Clear memory
718717
record.d_signals = None
719-
720718
# A multi segment record
721719

722720
# We can make another rdsamp function (called rdsamp_segment) to call
@@ -822,7 +820,7 @@ def rdheader(recordname, pbdir = None):
822820
# Set the comments field
823821
record.comments = []
824822
for line in commentlines:
825-
record.comments.append(line.strip('\s#'))
823+
record.comments.append(line.strip(' \t#'))
826824

827825
return record
828826

wfdbdemo.ipynb

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,161 @@
161161
"metadata": {
162162
"collapsed": false
163163
},
164-
"outputs": [],
164+
"outputs": [
165+
{
166+
"name": "stdout",
167+
"output_type": "stream",
168+
"text": [
169+
"Writing file a103l.mat with 24 empty leading bytes\n"
170+
]
171+
}
172+
],
165173
"source": [
166174
"import wfdb\n",
167175
"import numpy as np\n",
168176
"from IPython.display import display\n",
169-
"record = wfdb.rdsamp('sampledata/100', physical = False)"
177+
"record = wfdb.rdsamp('sampledata/a103l',\n",
178+
" sampfrom=20000, channels=[0, 1], physical=False)\n",
179+
"sig = record.d_signals\n",
180+
"targetsig = np.genfromtxt('tests/targetoutputdata/target5')\n",
181+
"\n",
182+
"# Compare data streaming from physiobank\n",
183+
"pbrecord = wfdb.rdsamp('a103l', pbdir = 'challenge/2015/training',\n",
184+
" sampfrom=20000, channels=[0, 1], physical=False)\n",
185+
"\n",
186+
"# Test file writing\n",
187+
"record.wrsamp()\n",
188+
"recordwrite = wfdb.rdsamp('a103l', physical=False)\n",
189+
"\n",
190+
"assert np.array_equal(sig, targetsig)\n",
191+
"assert record.__eq__(pbrecord)\n",
192+
"assert record.__eq__(recordwrite)"
193+
]
194+
},
195+
{
196+
"cell_type": "code",
197+
"execution_count": 2,
198+
"metadata": {
199+
"collapsed": false
200+
},
201+
"outputs": [
202+
{
203+
"data": {
204+
"text/plain": [
205+
"True"
206+
]
207+
},
208+
"execution_count": 2,
209+
"metadata": {},
210+
"output_type": "execute_result"
211+
}
212+
],
213+
"source": [
214+
"record.__eq__(recordwrite)"
215+
]
216+
},
217+
{
218+
"cell_type": "code",
219+
"execution_count": 4,
220+
"metadata": {
221+
"collapsed": false
222+
},
223+
"outputs": [
224+
{
225+
"data": {
226+
"text/plain": [
227+
"{'adcgain': [7247.0, 10520.0],\n",
228+
" 'adcres': [16, 16],\n",
229+
" 'adczero': [0, 0],\n",
230+
" 'basecounter': None,\n",
231+
" 'basedate': None,\n",
232+
" 'baseline': [0, 0],\n",
233+
" 'basetime': None,\n",
234+
" 'blocksize': [0, 0],\n",
235+
" 'byteoffset': [24, 24],\n",
236+
" 'checksum': [15084, 49781],\n",
237+
" 'comments': ['Asystole', 'False alarm'],\n",
238+
" 'counterfreq': None,\n",
239+
" 'd_signals': array([[-970, 8716],\n",
240+
" [-960, 8628],\n",
241+
" [-742, 8634],\n",
242+
" ..., \n",
243+
" [-296, 7883],\n",
244+
" [-342, 7976],\n",
245+
" [-339, 8011]]),\n",
246+
" 'filename': ['a103l.mat', 'a103l.mat'],\n",
247+
" 'fmt': ['16', '16'],\n",
248+
" 'fs': 250.0,\n",
249+
" 'initvalue': [-970, 8716],\n",
250+
" 'nsig': 2,\n",
251+
" 'p_signals': None,\n",
252+
" 'recordname': 'a103l',\n",
253+
" 'sampsperframe': [None, None],\n",
254+
" 'siglen': 62500,\n",
255+
" 'signame': ['II', 'V'],\n",
256+
" 'skew': [None, None],\n",
257+
" 'units': ['mV', 'mV']}"
258+
]
259+
},
260+
"execution_count": 4,
261+
"metadata": {},
262+
"output_type": "execute_result"
263+
}
264+
],
265+
"source": [
266+
"record.__dict__"
267+
]
268+
},
269+
{
270+
"cell_type": "code",
271+
"execution_count": 5,
272+
"metadata": {
273+
"collapsed": false
274+
},
275+
"outputs": [
276+
{
277+
"data": {
278+
"text/plain": [
279+
"{'adcgain': [7247.0, 10520.0],\n",
280+
" 'adcres': [16, 16],\n",
281+
" 'adczero': [0, 0],\n",
282+
" 'basecounter': None,\n",
283+
" 'basedate': None,\n",
284+
" 'baseline': [0, 0],\n",
285+
" 'basetime': None,\n",
286+
" 'blocksize': [0, 0],\n",
287+
" 'byteoffset': [24, 24],\n",
288+
" 'checksum': [15084, 49781],\n",
289+
" 'comments': ['Asystole', 'False alarm'],\n",
290+
" 'counterfreq': None,\n",
291+
" 'd_signals': None,\n",
292+
" 'filename': ['a103l.mat', 'a103l.mat'],\n",
293+
" 'fmt': ['16', '16'],\n",
294+
" 'fs': 250.0,\n",
295+
" 'initvalue': [-970, 8716],\n",
296+
" 'nsig': 2,\n",
297+
" 'p_signals': array([[-0.13384849, 0.82851711],\n",
298+
" [-0.13246861, 0.82015209],\n",
299+
" [-0.10238719, 0.82072243],\n",
300+
" ..., \n",
301+
" [-0.04084449, 0.7493346 ],\n",
302+
" [-0.04719194, 0.7581749 ],\n",
303+
" [-0.04677798, 0.7615019 ]]),\n",
304+
" 'recordname': 'a103l',\n",
305+
" 'sampsperframe': [None, None],\n",
306+
" 'siglen': 62500,\n",
307+
" 'signame': ['II', 'V'],\n",
308+
" 'skew': [None, None],\n",
309+
" 'units': ['mV', 'mV']}"
310+
]
311+
},
312+
"execution_count": 5,
313+
"metadata": {},
314+
"output_type": "execute_result"
315+
}
316+
],
317+
"source": [
318+
"recordwrite.__dict__"
170319
]
171320
},
172321
{

0 commit comments

Comments
 (0)