Skip to content

Commit b026b30

Browse files
authored
Merge pull request MIT-LCP#288 from MIT-LCP/wfdbtime
Adds wfdbtime function
2 parents fafa01a + 608c2a3 commit b026b30

File tree

3 files changed

+190
-3
lines changed

3 files changed

+190
-3
lines changed

wfdb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from wfdb.io.record import (Record, MultiRecord, rdheader, rdrecord, rdsamp,
22
wrsamp, dl_database, edf2mit, mit2edf, wav2mit, mit2wav,
3-
wfdb2mat, csv2mit, sampfreq, signame, wfdbdesc)
3+
wfdb2mat, csv2mit, sampfreq, signame, wfdbdesc, wfdbtime)
44
from wfdb.io.annotation import (Annotation, rdann, wrann, show_ann_labels,
55
show_ann_classes, ann2rr, rr2ann)
66
from wfdb.io.download import get_dbs, get_record_list, dl_files, set_db_index_url

wfdb/io/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from wfdb.io.record import (Record, MultiRecord, rdheader, rdrecord, rdsamp, wrsamp,
22
dl_database, edf2mit, mit2edf, wav2mit, mit2wav, wfdb2mat,
3-
csv2mit, sampfreq, signame, wfdbdesc, SIGNAL_CLASSES)
3+
csv2mit, sampfreq, signame, wfdbdesc, wfdbtime, SIGNAL_CLASSES)
44
from wfdb.io._signal import est_res, wr_dat_file
55
from wfdb.io.annotation import (Annotation, rdann, wrann, show_ann_labels,
66
show_ann_classes, ann2rr, rr2ann)

wfdb/io/record.py

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3871,7 +3871,7 @@ def wfdbdesc(record_name, pn_dir=None):
38713871
if (pn_dir is not None) and ('.' not in pn_dir):
38723872
dir_list = pn_dir.split('/')
38733873
pn_dir = posixpath.join(dir_list[0], get_version(dir_list[0]),
3874-
*dir_list[1:])
3874+
*dir_list[1:])
38753875

38763876
record = rdheader(record_name, pn_dir=pn_dir)
38773877
if type(record) is MultiRecord:
@@ -3922,6 +3922,193 @@ def wfdbdesc(record_name, pn_dir=None):
39223922
print(f' Checksum: {record.checksum[i]}')
39233923

39243924

3925+
def wfdbtime(record_name, input_times, pn_dir=None):
3926+
"""
3927+
Use the specified record as a reference for determining the length of a
3928+
sample interval and the absolute time represented by sample number 0. This
3929+
program accepts one or more time arguments (in WFDB standard time format)
3930+
and produces one line on the standard output for each such argument. In
3931+
each output line, the corresponding time is written as a sample number (in
3932+
the form sNNN), as an elapsed time interval in hours, minutes, and seconds
3933+
from the beginning of the record (in the form hh:mm:ss.sss), and as an
3934+
absolute time and date (in the form [hh:mm:ss.sss DD/MM/YYYY]). If the
3935+
base time for the record is undefined, the absolute time cannot be
3936+
calculated, and in this case the elapsed time appears twice instead.
3937+
3938+
Parameters
3939+
----------
3940+
record_name : str
3941+
The name of the WFDB record to be read, without any file
3942+
extensions. If the argument contains any path delimiter
3943+
characters, the argument will be interpreted as PATH/BASE_RECORD.
3944+
Both relative and absolute paths are accepted. If the `pn_dir`
3945+
parameter is set, this parameter should contain just the base
3946+
record name, and the files fill be searched for remotely.
3947+
Otherwise, the data files will be searched for in the local path.
3948+
input_times : str, list
3949+
The desired times (or samples) to be converted and displayed for the
3950+
chosen record. This can either be a single string, or a list of
3951+
strings depending on how many results the user would like. The string
3952+
must either be an integer (in which case '1' will be interpreted as 1
3953+
second), a datetime format (hh:mm:ss.sss DD/MM/YYYY), or prefixed with
3954+
the letter 's' if the time at a selected sample is desired (i.e. 's10'
3955+
for sample time). For the datetime format, 0's do not have be filled
3956+
for all values (i.e. '5::' will be interpreted as '05:00:00.000',
3957+
':5:' will be '00:05:00.000', etc.). The string 'e' can be used to
3958+
represent the end of the record.
3959+
pn_dir : str, optional
3960+
Option used to stream data from Physionet. The Physionet
3961+
database directory from which to find the required record files.
3962+
eg. For record '100' in 'http://physionet.org/content/mitdb'
3963+
pn_dir='mitdb'.
3964+
3965+
Returns
3966+
-------
3967+
N/A
3968+
3969+
Examples
3970+
--------
3971+
* Note, the use of a single string instead of a list.
3972+
>>> wfdb.wfdbtime('sample-data/100', 's250')
3973+
s250 00:00:00.694 00:00:00.694
3974+
3975+
* Note, the elapsed time and date fields are the same since no start time
3976+
or date was provided.
3977+
>>> wfdb.wfdbtime('sample-data/100', ['s10',':5:','2'])
3978+
s10 00:00:00.028 00:00:00.028
3979+
s108000 00:05:00.000 00:05:00.000
3980+
s720 00:00:02.000 00:00:02.000
3981+
3982+
* Note, '01/01/0001' represents a start time was provided but not a date.
3983+
>>> wfdb.wfdbtime('sample-data/3000003_0003', ['s1','0:0:05','e'])
3984+
s1 00:00:00.008 [19:46:25.765000 01/01/0001]
3985+
s625 00:00:05.000 [19:46:30.757000 01/01/0001]
3986+
s1028 00:00:08.224 [19:46:33.981000 01/01/0001]
3987+
3988+
* Note, the final argument results in the same date field as the argument
3989+
but a different value for the elapsed time.
3990+
>>> wfdb.wfdbtime('sample-data/3000003_0003', ['s1','::5','e','19:46:34.981 01/01/0001'])
3991+
s1 00:00:00.008 [19:46:25.765000 01/01/0001]
3992+
s625 00:00:05.000 [19:46:30.757000 01/01/0001]
3993+
s1028 00:00:08.224 [19:46:33.981000 01/01/0001]
3994+
s1153 00:00:09.224 [19:46:34.981000 01/01/0001]
3995+
3996+
"""
3997+
if (pn_dir is not None) and ('.' not in pn_dir):
3998+
dir_list = pn_dir.split('/')
3999+
pn_dir = posixpath.join(dir_list[0], get_version(dir_list[0]),
4000+
*dir_list[1:])
4001+
4002+
record = rdheader(record_name, pn_dir=pn_dir)
4003+
try:
4004+
start_time = datetime.datetime.combine(record.base_date, record.base_time)
4005+
except TypeError:
4006+
try:
4007+
start_time = record.base_time
4008+
except AttributeError:
4009+
start_time = None
4010+
4011+
if type(input_times) is str:
4012+
input_times = [input_times]
4013+
4014+
for times in input_times:
4015+
if times == 'e':
4016+
sample_num = record.sig_len
4017+
sample_time = float(sample_num/record.fs)
4018+
seconds = float(sample_time)%60
4019+
minutes = int(float(sample_time)//60)
4020+
hours = int(float(sample_time)//60//60)
4021+
else:
4022+
if times.startswith('s'):
4023+
sample_num = int(times[1:])
4024+
new_times = float(sample_num/record.fs)
4025+
times_split = [f'{new_times}']
4026+
elif '/' in times:
4027+
out_date = datetime.datetime.strptime(times, '%H:%M:%S.%f %d/%m/%Y')
4028+
try:
4029+
start_time = datetime.datetime.combine(datetime.date.min, start_time)
4030+
except TypeError:
4031+
start_time = start_time
4032+
try:
4033+
elapsed_time = out_date - start_time
4034+
except TypeError:
4035+
raise Exception('No start date or time provided in the record.')
4036+
total_seconds = elapsed_time.total_seconds()
4037+
sample_num = 's' + str(int(elapsed_time.total_seconds() * record.fs))
4038+
hours = int(total_seconds//60//60)
4039+
minutes = int(total_seconds//60)
4040+
out_time = f'{hours:02}:{minutes:02}:{total_seconds:06.3f}'
4041+
out_date = f'[{out_date.strftime("%H:%M:%S.%f %d/%m/%Y")}]'
4042+
print(f'{sample_num:>12}{out_time:>24}{out_date:>32}')
4043+
break
4044+
else:
4045+
new_times = times
4046+
times_split = [t if t != '' else '0' for t in times.split(':')]
4047+
if len(times_split) == 1:
4048+
seconds = float(new_times)%60
4049+
minutes = int(float(new_times)//60)
4050+
hours = int(float(new_times)//60//60)
4051+
elif len(times_split) == 2:
4052+
seconds = float(times_split[1])
4053+
minutes = int(times_split[0])
4054+
hours = 0
4055+
elif len(times_split) == 3:
4056+
seconds = float(times_split[2])
4057+
minutes = int(times_split[1])
4058+
hours = int(times_split[0])
4059+
if seconds >= 60:
4060+
raise Exception('Seconds not in correct format')
4061+
if minutes >= 60:
4062+
raise Exception('Minutes not in correct format')
4063+
out_time = f'{hours:02}:{minutes:02}:{seconds:06.3f}'
4064+
out_date = _get_date_from_time(start_time, hours, minutes, seconds,
4065+
out_time)
4066+
if not times.startswith('s'):
4067+
sample_num = int(sum(x*60**i for i,x in enumerate([seconds,minutes,hours])) * record.fs)
4068+
sample_num = 's' + str(sample_num)
4069+
print(f'{sample_num:>12}{out_time:>24}{out_date:>32}')
4070+
4071+
4072+
def _get_date_from_time(start_time, hours, minutes, seconds, out_time):
4073+
"""
4074+
Convert a starting time to a date using the elapsed time.
4075+
4076+
Parameters
4077+
----------
4078+
start_time : datetime object
4079+
The time the record start if available.
4080+
hours : int
4081+
The number of hours elapsed.
4082+
minutes : int
4083+
The number of minutes elapsed.
4084+
seconds : int
4085+
The number of seconds elapsed.
4086+
out_time : str
4087+
The string formatted time elapsed for a desired sample, if available.
4088+
4089+
Returns
4090+
-------
4091+
out_date : datetime object
4092+
The time the record ends after the caculcated elapsed time.
4093+
4094+
"""
4095+
if start_time:
4096+
try:
4097+
start_time = datetime.datetime.combine(datetime.date.min, start_time) - \
4098+
datetime.datetime.min
4099+
except TypeError:
4100+
start_time = start_time - datetime.datetime.min
4101+
microseconds = int(1000000*(seconds%1))
4102+
elapsed_time = datetime.timedelta(hours=hours, minutes=minutes,
4103+
seconds=int(seconds),
4104+
microseconds=microseconds)
4105+
out_date = start_time + elapsed_time
4106+
out_date = f'[{(datetime.datetime.min+out_date).strftime("%H:%M:%S.%f %d/%m/%Y")}]'
4107+
else:
4108+
out_date = out_time
4109+
return out_date
4110+
4111+
39254112
def _get_wanted_channels(wanted_sig_names, record_sig_names, pad=False):
39264113
"""
39274114
Given some wanted signal names, and the signal names contained in a

0 commit comments

Comments
 (0)