Skip to content

Commit 1dd4b5c

Browse files
authored
Merge pull request #246 from MIT-LCP/mit2wav
Produces WAV file from WFDB format
2 parents e77cd64 + 545d313 commit 1dd4b5c

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)