Skip to content

Commit 3572834

Browse files
committed
Adds EDF+ annotation to WFDB object/file; fixes csv2ann bug
1 parent ec6d9ba commit 3572834

File tree

1 file changed

+70
-22
lines changed

1 file changed

+70
-22
lines changed

wfdb/io/annotation.py

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,28 +2487,16 @@ def csv2ann(file_name, extension='atr', fs=None, record_only=False,
24872487
if df_CSV.shape[1] == 2:
24882488
if verbose:
24892489
print('onset,description format detected')
2490+
if not header:
2491+
df_CSV.columns = ['onset', 'description']
24902492
df_out = df_CSV
24912493
elif df_CSV.shape[1] == 3:
24922494
if verbose:
24932495
print('onset,duration,description format detected')
24942496
print('Converting durations to single time-point events')
2495-
# Create two separate dataframes for the start and end annotation
2496-
# then remove them from the original
2497-
df_start = df_CSV[df_CSV['duration'] > 0]
2498-
df_end = df_CSV[df_CSV['duration'] > 0]
2499-
df_trunc = df_CSV[df_CSV['duration'] == 0]
2500-
# Append parentheses at the start for annotation start and end for
2501-
# annotation end
2502-
df_start['description'] = '(' + df_start['description'].astype(str)
2503-
df_end['description'] = df_end['description'].astype(str) + ')'
2504-
# Add the duration time to the onset for the end annotation to convert
2505-
# to single time annotations only
2506-
df_end['onset'] = df_end['onset'] + df_end['duration']
2507-
# Concatenate all of the dataframes
2508-
df_out = pd.concat([df_trunc, df_start, df_end], ignore_index=True)
2509-
# Make sure the sorting is correct
2510-
df_out['col_index'] = df_out.index
2511-
df_out = df_out.sort_values(['onset', 'col_index'])
2497+
if not header:
2498+
df_CSV.columns = ['onset', 'duration', 'description']
2499+
df_out = _format_ann_from_df(df_CSV)
25122500
else:
25132501
raise Exception("""The number of columns in the CSV was not
25142502
recognized.""")
@@ -2656,6 +2644,7 @@ def rdedfann(record_name, pn_dir=None, delete_file=True, info_only=True,
26562644
annotation_string = annotation_string.replace(rep,' ')
26572645

26582646
# Parse the resulting annotation string
2647+
onsets = []
26592648
onset_times = []
26602649
sample_nums = []
26612650
comments = []
@@ -2675,6 +2664,7 @@ def rdedfann(record_name, pn_dir=None, delete_file=True, info_only=True,
26752664
comment = ' '.join(ann_split[2:])
26762665
if verbose:
26772666
print(f'{onset_time}\t{sample_num}\t{comment}\t\tduration: {duration}')
2667+
onsets.append(onset)
26782668
onset_times.append(onset_time)
26792669
sample_nums.append(sample_num)
26802670
comments.append(comment)
@@ -2689,12 +2679,70 @@ def rdedfann(record_name, pn_dir=None, delete_file=True, info_only=True,
26892679
'comment': comments,
26902680
'duration': durations
26912681
}
2692-
elif record_only:
2693-
# TODO: return WFDB-formatted annotation object
2694-
pass
26952682
else:
2696-
# TODO: Create the WFDB annotation file and don't return the object
2697-
pass
2683+
df_in = pd.DataFrame(data={
2684+
'onset': onsets, 'duration': durations, 'description': comments
2685+
})
2686+
df_out = _format_ann_from_df(df_in)
2687+
# Remove extension from input file name
2688+
record_name = record_name.split(os.sep)[-1].split('.')[0]
2689+
extension = 'atr'
2690+
fs = rec.fs
2691+
sample = (df_out['onset'].to_numpy()*fs).astype(np.int64)
2692+
# Assume each annotation is a comment
2693+
symbol = ['"']*len(df_out.index)
2694+
subtype = np.array([22]*len(df_out.index))
2695+
# Assume each annotation belongs with the 1st channel
2696+
chan = np.array([0]*len(df_out.index))
2697+
num = np.array([0]*len(df_out.index))
2698+
aux_note = df_out['description'].tolist()
2699+
2700+
if record_only:
2701+
return Annotation(record_name=record_name, extension=extension,
2702+
sample=sample, symbol=symbol, subtype=subtype,
2703+
chan=chan, num=num, aux_note=aux_note, fs=fs)
2704+
else:
2705+
wrann(record_name, extension, sample=sample, symbol=symbol,
2706+
subtype=subtype, chan=chan, num=num, aux_note=aux_note,
2707+
fs=fs)
2708+
2709+
2710+
def _format_ann_from_df(df_in):
2711+
"""
2712+
Parameters
2713+
----------
2714+
df_in : Pandas dataframe
2715+
Contains all the information needed to create WFDB-formatted
2716+
annotations. Of the form:
2717+
onset,duration,description
2718+
onset_1,duration_1,description_1
2719+
onset_2,duration_2,description_2
2720+
...,...,...
2721+
2722+
Returns
2723+
-------
2724+
N/A : Pandas dataframe
2725+
The WFDB-formatted input dataframe.
2726+
2727+
"""
2728+
# Create two separate dataframes for the start and end annotation
2729+
# then remove them from the original
2730+
df_start = df_in[df_in['duration'] > 0]
2731+
df_end = df_in[df_in['duration'] > 0]
2732+
df_trunc = df_in[df_in['duration'] == 0]
2733+
# Append parentheses at the start for annotation start and end for
2734+
# annotation end
2735+
df_start['description'] = '(' + df_start['description'].astype(str)
2736+
df_end['description'] = df_end['description'].astype(str) + ')'
2737+
# Add the duration time to the onset for the end annotation to convert
2738+
# to single time annotations only
2739+
df_end['onset'] = df_end['onset'] + df_end['duration']
2740+
# Concatenate all of the dataframes
2741+
df_out = pd.concat([df_trunc, df_start, df_end], ignore_index=True)
2742+
# Make sure the sorting is correct
2743+
df_out['col_index'] = df_out.index
2744+
return df_out.sort_values(['onset', 'col_index'])
2745+
26982746

26992747

27002748
## ------------- Annotation Field Specifications ------------- ##

0 commit comments

Comments
 (0)