Skip to content

Commit 089eafd

Browse files
committed
Adds optional EDF header reader
1 parent a8cc498 commit 089eafd

File tree

1 file changed

+189
-1
lines changed

1 file changed

+189
-1
lines changed

wfdb/io/record.py

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1349,7 +1349,8 @@ def check_np_array(item, field_name, ndim, parent_class, channel_num=None):
13491349
raise TypeError(error_msg)
13501350

13511351

1352-
def edf2mit(record_name, pn_dir=None, delete_file=True, record_only=False):
1352+
def edf2mit(record_name, pn_dir=None, delete_file=True, record_only=False,
1353+
header_only=False, verbose=False):
13531354
"""
13541355
Convert EDF formatted files to MIT format.
13551356
@@ -1384,6 +1385,13 @@ def edf2mit(record_name, pn_dir=None, delete_file=True, record_only=False):
13841385
record_only : bool, optional
13851386
Whether to only return the record information (True) or not (False).
13861387
If false, this function will generate both a .dat and .hea file.
1388+
header_only : bool, optional
1389+
Whether to only return the header information (True) or not (False).
1390+
If true, this function will only return `['fs', 'sig_len', 'n_sig',
1391+
'base_date', 'base_time', 'units', 'sig_name', 'comments']`.
1392+
verbose : bool, optional
1393+
Whether to print all the information read about the file (True) or
1394+
not (False).
13871395
13881396
Returns
13891397
-------
@@ -1412,6 +1420,186 @@ def edf2mit(record_name, pn_dir=None, delete_file=True, record_only=False):
14121420
r = requests.get(file_url, allow_redirects=False)
14131421
open(record_name, 'wb').write(r.content)
14141422

1423+
# Temporary to return only the EDF header.. will later replace the
1424+
# current MNE package approach
1425+
if header_only:
1426+
# Open the desired file
1427+
edf_file = open(record_name, mode='rb')
1428+
1429+
# Remove the file if the `delete_file` flag is set
1430+
if pn_dir is not None and delete_file:
1431+
os.remove(record_name)
1432+
1433+
# Version of this data format (8 bytes)
1434+
version = struct.unpack('<8s', edf_file.read(8))[0].decode()
1435+
1436+
# Check to see that the input is an EDF file. (This check will detect
1437+
# most but not all other types of files.)
1438+
if version != '0 ':
1439+
raise Exception('Input does not appear to be EDF -- no conversion attempted')
1440+
else:
1441+
if verbose:
1442+
print('EDF version number: {}'.format(version.strip()))
1443+
1444+
# Local patient identification (80 bytes)
1445+
patient_id = struct.unpack('<80s', edf_file.read(80))[0].decode()
1446+
if verbose:
1447+
print('Patient ID: {}'.format(patient_id))
1448+
1449+
# Local recording identification (80 bytes)
1450+
# Bob Kemp recommends using this field to encode the start date
1451+
# including an abbreviated month name in English and a full (4-digit)
1452+
# year, as is done here if this information is available in the input
1453+
# record. EDF+ requires this.
1454+
record_id = struct.unpack('<80s', edf_file.read(80))[0].decode()
1455+
if verbose:
1456+
print('Recording ID: {}'.format(record_id))
1457+
1458+
# Start date of recording (dd.mm.yy) (8 bytes)
1459+
start_date = struct.unpack('<8s', edf_file.read(8))[0].decode()
1460+
if verbose:
1461+
print('Recording Date: {}'.format(start_date))
1462+
start_day, start_month, start_year = [int(i) for i in start_date.split('.')]
1463+
# This should work for a while
1464+
if start_year < 1970:
1465+
start_year += 1900
1466+
if start_year < 1970:
1467+
start_year += 100
1468+
1469+
# Start time of recording (hh.mm.ss) (8 bytes)
1470+
start_time = struct.unpack('<8s', edf_file.read(8))[0].decode()
1471+
if verbose:
1472+
print('Recording Time: {}'.format(start_time))
1473+
start_hour, start_minute, start_second = [int(i) for i in start_time.split('.')]
1474+
1475+
# Number of bytes in header (8 bytes)
1476+
header_bytes = int(struct.unpack('<8s', edf_file.read(8))[0].decode())
1477+
if verbose:
1478+
print('Number of bytes in header record: {}'.format(header_bytes))
1479+
1480+
# Reserved (44 bytes)
1481+
reserved_notes = struct.unpack('<44s', edf_file.read(44))[0].decode().strip()
1482+
if reserved_notes != '':
1483+
if verbose:
1484+
print('Free Space: {}'.format(reserved_notes))
1485+
1486+
# Number of blocks (-1 if unknown) (8 bytes)
1487+
num_blocks = int(struct.unpack('<8s', edf_file.read(8))[0].decode())
1488+
if verbose:
1489+
print('Number of data records: {}'.format(num_blocks))
1490+
1491+
# Duration of a block, in seconds (8 bytes)
1492+
block_duration = float(struct.unpack('<8s', edf_file.read(8))[0].decode())
1493+
if verbose:
1494+
print('Duration of each data record in seconds: {}'.format(block_duration))
1495+
if block_duration <= 0.0:
1496+
block_duration = 1.0
1497+
1498+
# Number of signals (4 bytes)
1499+
n_sig = int(struct.unpack('<4s', edf_file.read(4))[0].decode())
1500+
if verbose:
1501+
print('Number of signals: {}'.format(n_sig))
1502+
if n_sig < 1:
1503+
raise Exception('Done: not any signals left to read')
1504+
1505+
# Label (e.g., EEG FpzCz or Body temp) (16 bytes each)
1506+
sig_labels = []
1507+
for _ in range(n_sig):
1508+
sig_labels.append(struct.unpack('<16s', edf_file.read(16))[0].decode().strip())
1509+
if verbose:
1510+
print('Signal Labels: {}'.format(sig_labels))
1511+
1512+
# Transducer type (e.g., AgAgCl electrode) (80 bytes each)
1513+
transducer_types = []
1514+
for _ in range(n_sig):
1515+
transducer_types.append(struct.unpack('<80s', edf_file.read(80))[0].decode().strip())
1516+
if verbose:
1517+
print('Transducer Types: {}'.format(transducer_types))
1518+
1519+
# Physical dimension (e.g., uV or degreeC) (8 bytes each)
1520+
physical_dims = []
1521+
for _ in range(n_sig):
1522+
physical_dims.append(struct.unpack('<8s', edf_file.read(8))[0].decode().strip())
1523+
if verbose:
1524+
print('Physical Dimensions: {}'.format(physical_dims))
1525+
1526+
# Physical minimum (e.g., -500 or 34) (8 bytes each)
1527+
physical_min = np.array([])
1528+
for _ in range(n_sig):
1529+
physical_min = np.append(physical_min, float(struct.unpack('<8s', edf_file.read(8))[0].decode()))
1530+
if verbose:
1531+
print('Physical Minimums: {}'.format(physical_min))
1532+
1533+
# Physical maximum (e.g., 500 or 40) (8 bytes each)
1534+
physical_max = np.array([])
1535+
for _ in range(n_sig):
1536+
physical_max = np.append(physical_max, float(struct.unpack('<8s', edf_file.read(8))[0].decode()))
1537+
if verbose:
1538+
print('Physical Maximums: {}'.format(physical_max))
1539+
1540+
# Digital minimum (e.g., -2048) (8 bytes each)
1541+
digital_min = np.array([])
1542+
for _ in range(n_sig):
1543+
digital_min = np.append(digital_min, float(struct.unpack('<8s', edf_file.read(8))[0].decode()))
1544+
if verbose:
1545+
print('Digital Minimums: {}'.format(digital_min))
1546+
1547+
# Digital maximum (e.g., 2047) (8 bytes each)
1548+
digital_max = np.array([])
1549+
for _ in range(n_sig):
1550+
digital_max = np.append(digital_max, float(struct.unpack('<8s', edf_file.read(8))[0].decode()))
1551+
if verbose:
1552+
print('Digital Maximums: {}'.format(digital_max))
1553+
1554+
# Prefiltering (e.g., HP:0.1Hz LP:75Hz) (80 bytes each)
1555+
prefilter_info = []
1556+
for _ in range(n_sig):
1557+
prefilter_info.append(struct.unpack('<80s', edf_file.read(80))[0].decode().strip())
1558+
if verbose:
1559+
print('Prefiltering Information: {}'.format(prefilter_info))
1560+
1561+
# Number of samples per block (8 bytes each)
1562+
samps_per_block = []
1563+
for _ in range(n_sig):
1564+
samps_per_block.append(int(struct.unpack('<8s', edf_file.read(8))[0].decode()))
1565+
if verbose:
1566+
print('Number of Samples per Record: {}'.format(samps_per_block))
1567+
1568+
# The last 32*nsig bytes in the header are unused
1569+
for _ in range(n_sig):
1570+
struct.unpack('<32s', edf_file.read(32))[0].decode()
1571+
1572+
# Pre-process the acquired data before creating the record
1573+
sample_rate = [int(i/block_duration) for i in samps_per_block]
1574+
fs = functools.reduce(math.gcd, sample_rate)
1575+
sig_len = int(num_blocks * block_duration * fs)
1576+
base_time = datetime.time(start_hour, start_minute, start_second)
1577+
base_date = datetime.date(start_year, start_month, start_day)
1578+
comments = []
1579+
1580+
units = n_sig * ['']
1581+
for i,f in enumerate(physical_dims):
1582+
if f == 'n/a':
1583+
label = sig_labels[i].lower().split()[0]
1584+
if label in list(SIG_UNITS.keys()):
1585+
units[i] = SIG_UNITS[label]
1586+
else:
1587+
units[i] = 'n/a'
1588+
else:
1589+
f = f.replace('µ','u') # Maybe more weird symbols to check for?
1590+
units[i] = f
1591+
1592+
return {
1593+
'fs': fs,
1594+
'sig_len': sig_len,
1595+
'n_sig': n_sig,
1596+
'base_date': base_date,
1597+
'base_time': base_time,
1598+
'units': physical_dims,
1599+
'sig_name': sig_labels,
1600+
'comments': comments
1601+
}
1602+
14151603
edf_data = mne.io.read_raw_edf(record_name, preload=True)
14161604

14171605
if pn_dir is not None and delete_file:

0 commit comments

Comments
 (0)