Skip to content

Commit 545d313

Browse files
committed
Produces WAV file from WFDB format
Introduces the conversion of WFDB files to WAV format. All data was written to the WAV file using struct.pack() and numpy.tofile() so no outside packages were introduced. This implementation conserves the -h option which can now be used with help(wfdb.mit2wav). Further, the -o option, which is used to specify their desired WAV file name, is also conserved in the form of the parameter output_filename. Finally, the -n option is conserved which is used to specify whether or not to write an accompanying header file. This is now in the parameter write_header.
1 parent 0faf29f commit 545d313

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

wfdb/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from wfdb.io.record import (Record, MultiRecord, rdheader, rdrecord, rdsamp,
2-
wrsamp, dl_database, edf2mit, mit2edf, wav2mit, wfdb2mat, sampfreq, signame)
2+
wrsamp, dl_database, edf2mit, mit2edf, wav2mit, mit2wav,
3+
wfdb2mat, sampfreq, signame)
34
from wfdb.io.annotation import (Annotation, rdann, wrann, show_ann_labels,
45
show_ann_classes, ann2rr)
56
from wfdb.io.download import get_dbs, get_record_list, dl_files, set_db_index_url

wfdb/io/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from wfdb.io.record import (Record, MultiRecord, rdheader, rdrecord, rdsamp, wrsamp,
2-
dl_database, edf2mit, mit2edf, wav2mit, wfdb2mat, sampfreq, signame, SIGNAL_CLASSES)
2+
dl_database, edf2mit, mit2edf, wav2mit, mit2wav, wfdb2mat,
3+
sampfreq, signame, SIGNAL_CLASSES)
34
from wfdb.io._signal import est_res, wr_dat_file
45
from wfdb.io.annotation import (Annotation, rdann, wrann, show_ann_labels,
56
show_ann_classes, ann2rr)

wfdb/io/record.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,160 @@ def mit2edf(record_name, pn_dir=None, sampfrom=0, sampto=None, channels=None,
18911891
print('WARNING: output contains an invalid character, {}, at byte {}'.format(val, i))
18921892

18931893

1894+
def mit2wav(record_name, pn_dir=None, sampfrom=0, sampto=None, channels=None,
1895+
output_filename='', write_header=False):
1896+
"""
1897+
This program converts a WFDB record into .wav format (format 16, multiplexed
1898+
signals, with embedded header information). Use 'wav2mit' to perform the
1899+
reverse conversion.
1900+
1901+
Parameters
1902+
----------
1903+
record_name : str
1904+
The name of the input WFDB record to be read. Can also work with both
1905+
EDF and WAV files.
1906+
pn_dir : str, optional
1907+
Option used to stream data from Physionet. The Physionet
1908+
database directory from which to find the required record files.
1909+
eg. For record '100' in 'http://physionet.org/content/mitdb'
1910+
pn_dir='mitdb'.
1911+
sampfrom : int, optional
1912+
The starting sample number to read for all channels.
1913+
sampto : int, 'end', optional
1914+
The sample number at which to stop reading for all channels.
1915+
Reads the entire duration by default.
1916+
channels : list, optional
1917+
List of integer indices specifying the channels to be read.
1918+
Reads all channels by default.
1919+
output_filename : str, optional
1920+
The desired name of the output file. If this value set to the
1921+
default value of '', then the output filename will be 'REC.wav'.
1922+
write_header : bool, optional
1923+
Whether to write (True) or not to write (False) a header file to
1924+
accompany the generated WAV file. The default value is 'False'.
1925+
1926+
Returns
1927+
-------
1928+
N/A
1929+
1930+
Notes
1931+
-----
1932+
Files that can be processed successfully using `wav2mit` always have exactly
1933+
three chunks (a header chunk, a format chunk, and a data chunk). In .wav
1934+
files, binary data are always written in little-endian format (least
1935+
significant byte first). The format of `wav2mit`'s input files is as follows:
1936+
1937+
[Header chunk]
1938+
Bytes 0 - 3: "RIFF" [4 ASCII characters]
1939+
Bytes 4 - 7: L-8 (number of bytes to follow in the file, excluding bytes 0-7)
1940+
Bytes 8 - 11: "WAVE" [4 ASCII characters]
1941+
1942+
[Format chunk]
1943+
Bytes 12 - 15: "fmt " [4 ASCII characters, note trailing space]
1944+
Bytes 16 - 19: 16 (format chunk length in bytes, excluding bytes 12-19)
1945+
Bytes 20 - 35: format specification, consisting of:
1946+
Bytes 20 - 21: 1 (format tag, indicating no compression is used)
1947+
Bytes 22 - 23: number of signals (1 - 65535)
1948+
Bytes 24 - 27: sampling frequency in Hz (per signal)
1949+
Note that the sampling frequency in a .wav file must be an
1950+
integer multiple of 1 Hz, a restriction that is not imposed
1951+
by MIT (WFDB) format.
1952+
Bytes 28 - 31: bytes per second (sampling frequency * frame size in bytes)
1953+
Bytes 32 - 33: frame size in bytes
1954+
Bytes 34 - 35: bits per sample (ADC resolution in bits)
1955+
Note that the actual ADC resolution (e.g., 12) is written in
1956+
this field, although each output sample is right-padded to fill
1957+
a full (16-bit) word. (.wav format allows for 8, 16, 24, and
1958+
32 bits per sample)
1959+
1960+
[Data chunk]
1961+
Bytes 36 - 39: "data" [4 ASCII characters]
1962+
Bytes 40 - 43: L-44 (number of bytes to follow in the data chunk)
1963+
Bytes 44 - L-1: sample data, consisting of:
1964+
Bytes 44 - 45: sample 0, channel 0
1965+
Bytes 46 - 47: sample 0, channel 1
1966+
... etc. (same order as in a multiplexed WFDB signal file)
1967+
1968+
Examples
1969+
--------
1970+
>>> wfdb.mit2wav('100', pn_dir='pwave')
1971+
1972+
The output file name is '100.wav'
1973+
1974+
"""
1975+
record = rdrecord(record_name, pn_dir=pn_dir, sampfrom=sampfrom,
1976+
sampto=sampto, smooth_frames=False)
1977+
record_name_out = record_name.split(os.sep)[-1].replace('-','_')
1978+
1979+
# Get information needed for the header and format chunks
1980+
num_samps = record.sig_len
1981+
samps_per_second = record.fs
1982+
frame_length = record.n_sig * 2
1983+
chunk_bytes = num_samps * frame_length
1984+
file_bytes = chunk_bytes + 36
1985+
bits_per_sample = max(record.adc_res)
1986+
offset = record.adc_zero
1987+
shift = [(16 - v) for v in record.adc_res]
1988+
1989+
# Start writing the file
1990+
if output_filename != '':
1991+
if not output_filename.endswith('.wav'):
1992+
raise Exception("Name of output file must end in '.wav'")
1993+
else:
1994+
output_filename = record_name_out + '.wav'
1995+
1996+
with open(output_filename, 'wb') as f:
1997+
# Write the WAV file identifier
1998+
f.write(struct.pack('>4s', b'RIFF'))
1999+
# Write the number of bytes to follow in the file
2000+
# (num_samps*frame_length) sample bytes, and 36 more bytes of miscellaneous embedded header
2001+
f.write(struct.pack('<I', file_bytes))
2002+
# Descriptor for the format of the file
2003+
f.write(struct.pack('>8s', b'WAVEfmt '))
2004+
# Number of bytes to follow in the format chunk
2005+
f.write(struct.pack('<I', 16))
2006+
# The format tag
2007+
f.write(struct.pack('<H', 1))
2008+
# The number of signals
2009+
f.write(struct.pack('<H', record.n_sig))
2010+
# The samples per second
2011+
f.write(struct.pack('<I', samps_per_second))
2012+
# The number of bytes per second
2013+
f.write(struct.pack('<I', samps_per_second * frame_length))
2014+
# The length of each frame
2015+
f.write(struct.pack('<H', frame_length))
2016+
# The number of bits per samples
2017+
f.write(struct.pack('<H', bits_per_sample))
2018+
# The descriptor to indicate that the data information is next
2019+
f.write(struct.pack('>4s', b'data'))
2020+
# The number of bytes in the signal data chunk
2021+
f.write(struct.pack('<I', chunk_bytes))
2022+
# Write the signal data... the closest I can get to the original implementation
2023+
# Mismatched elements: 723881 / 15400000 (4.7%)
2024+
# Max absolute difference: 2
2025+
# Max relative difference: 0.00444444
2026+
# x: array([ -322, 3852, -9246, ..., 0, 0, 0], dtype=int16)
2027+
# y: array([ -322, 3852, -9246, ..., 0, 0, 0], dtype=int16)
2028+
sig_data = np.left_shift(np.subtract(record.adc(), offset), shift).reshape((1, -1)).astype(np.int16)
2029+
sig_data.tofile(f)
2030+
2031+
# If asked to write the accompanying header file
2032+
if write_header:
2033+
record.adc_zero = record.n_sig * [0]
2034+
record.adc_res = record.n_sig * [16]
2035+
record.adc_gain = [(r * (1 << shift[i])) for i,r in enumerate(record.adc_gain)]
2036+
record.baseline = [(b - offset[i]) for i,b in enumerate(record.baseline)]
2037+
record.baseline = [(b * (1 << shift[i])) for i,b in enumerate(record.baseline)]
2038+
record.file_name = record.n_sig * [record_name_out + '.wav']
2039+
record.block_size = record.n_sig * [0]
2040+
record.fmt = record.n_sig * ['16']
2041+
record.samps_per_fram = record.n_sig * [1]
2042+
record.init_value = sig_data[0][:record.n_sig].tolist()
2043+
record.byte_offset = record.n_sig * [44]
2044+
# Write the header file
2045+
record.wrheader()
2046+
2047+
18942048
def wav2mit(record_name, pn_dir=None, delete_file=True, record_only=False):
18952049
"""
18962050
Convert .wav (format 16, multiplexed signals, with embedded header

0 commit comments

Comments
 (0)