Skip to content

Commit 9f6aab9

Browse files
committed
made plot_wfdb automatically remove empty channels when there are annotations with non-consecutive channels
1 parent e26ebd0 commit 9f6aab9

File tree

4 files changed

+77
-53
lines changed

4 files changed

+77
-53
lines changed

demo.ipynb

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@
1414
"metadata": {},
1515
"source": [
1616
"## Documentation Site\n",
17-
"\n"
17+
"\n",
18+
"http://wfdb.readthedocs.io/"
1819
]
1920
},
2021
{
2122
"cell_type": "code",
2223
"execution_count": null,
23-
"metadata": {
24-
"collapsed": true
25-
},
24+
"metadata": {},
2625
"outputs": [],
2726
"source": [
2827
"from IPython.display import display\n",
@@ -221,9 +220,7 @@
221220
{
222221
"cell_type": "code",
223222
"execution_count": null,
224-
"metadata": {
225-
"collapsed": true
226-
},
223+
"metadata": {},
227224
"outputs": [],
228225
"source": [
229226
"# Demo 10 - Read a WFDB record's digital samples and create a copy via the wrsamp() instance method \n",
@@ -247,9 +244,7 @@
247244
{
248245
"cell_type": "code",
249246
"execution_count": null,
250-
"metadata": {
251-
"collapsed": true
252-
},
247+
"metadata": {},
253248
"outputs": [],
254249
"source": [
255250
"# Demo 11 - Write a WFDB record without using a Record object via the gateway wrsamp function.\n",
@@ -272,9 +267,7 @@
272267
{
273268
"cell_type": "code",
274269
"execution_count": null,
275-
"metadata": {
276-
"collapsed": true
277-
},
270+
"metadata": {},
278271
"outputs": [],
279272
"source": [
280273
"# Demo 12 - Write a WFDB record with multiple samples/frame in a channel\n",
@@ -297,9 +290,7 @@
297290
{
298291
"cell_type": "code",
299292
"execution_count": null,
300-
"metadata": {
301-
"collapsed": true
302-
},
293+
"metadata": {},
303294
"outputs": [],
304295
"source": [
305296
"# Demo 13 - Read a WFDB annotation file and create a copy via the wrann() instance method\n",
@@ -322,9 +313,7 @@
322313
{
323314
"cell_type": "code",
324315
"execution_count": null,
325-
"metadata": {
326-
"collapsed": true
327-
},
316+
"metadata": {},
328317
"outputs": [],
329318
"source": [
330319
"# Demo 14 - Write a WFDB annotation file without using an Annotator\n",
@@ -439,9 +428,7 @@
439428
{
440429
"cell_type": "code",
441430
"execution_count": null,
442-
"metadata": {
443-
"collapsed": true
444-
},
431+
"metadata": {},
445432
"outputs": [],
446433
"source": [
447434
"import wfdb\n",
@@ -541,9 +528,7 @@
541528
{
542529
"cell_type": "code",
543530
"execution_count": null,
544-
"metadata": {
545-
"collapsed": true
546-
},
531+
"metadata": {},
547532
"outputs": [],
548533
"source": []
549534
}

docs/wfdb.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Downloading
3232
-----------
3333

3434
.. automodule:: wfdb
35-
:members: get_dbs, get_record_list, dl_database
35+
:members: get_dbs, get_record_list, dl_database, dl_files
3636

3737

3838
Plotting

wfdb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
wrsamp, dl_database)
33
from .io.annotation import (Annotation, rdann, wrann, show_ann_labels,
44
show_ann_classes)
5-
from .io.download import get_dbs, get_record_list
5+
from .io.download import get_dbs, get_record_list, dl_files
66
from .plot.plot import plot_items, plot_wfdb, plot_all_records
77

88
from .version import __version__

wfdb/plot/plot.py

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ def plot_items(signal=None, ann_samp=None, ann_sym=None, fs=None,
6060
title : str, optional
6161
The title of the graph.
6262
sig_style : list, optional
63-
A list of strings, specifying the style of the matplotlib plot for each
64-
signal channel. If the list has a length of 1, the style will be used
65-
for all channels.
63+
A list of strings, specifying the style of the matplotlib plot
64+
for each signal channel. The list length should match the number
65+
of signal channels. If the list has a length of 1, the style
66+
will be used for all channels.
6667
ann_style : list, optional
6768
A list of strings, specifying the style of the matplotlib plot for each
6869
annotation channel. If the list has a length of 1, the style will be
@@ -350,7 +351,6 @@ def plot_wfdb(record=None, annotation=None, plot_sym=False,
350351
- the sampling frequency, from the `fs` attribute if present, and if fs
351352
was not already extracted from the `record` argument.
352353
353-
354354
Parameters
355355
----------
356356
record : wfdb Record, optional
@@ -370,9 +370,10 @@ def plot_wfdb(record=None, annotation=None, plot_sym=False,
370370
of signal channels. If the list has a length of 1, the style
371371
will be used for all channels.
372372
ann_style : list, optional
373-
A list of strings, specifying the style of the matplotlib plot for each
374-
annotation channel. If the list has a length of 1, the style will be
375-
used for all channels.
373+
A list of strings, specifying the style of the matplotlib plot
374+
for each annotation channel. The list length should match the
375+
number of annotation channels. If the list has a length of 1,
376+
the style will be used for all channels.
376377
ecg_grids : list, optional
377378
A list of integers specifying channels in which to plot ecg grids. May
378379
also be set to 'all' for all channels. Major grids at 0.5mV, and minor
@@ -400,14 +401,14 @@ def plot_wfdb(record=None, annotation=None, plot_sym=False,
400401
figsize=(10,4), ecg_grids='all')
401402
402403
"""
403-
(signal, ann_samp, ann_sym, fs, sig_name,
404-
sig_units, record_name) = get_wfdb_plot_items(record=record,
405-
annotation=annotation,
406-
plot_sym=plot_sym)
404+
(signal, ann_samp, ann_sym, fs,
405+
ylabel, record_name) = get_wfdb_plot_items(record=record,
406+
annotation=annotation,
407+
plot_sym=plot_sym)
407408

408409
return plot_items(signal=signal, ann_samp=ann_samp, ann_sym=ann_sym, fs=fs,
409-
time_units=time_units, sig_name=sig_name,
410-
sig_units=sig_units, title=(title or record_name),
410+
time_units=time_units, ylabel=ylabel,
411+
title=(title or record_name),
411412
sig_style=sig_style,
412413
ann_style=ann_style, ecg_grids=ecg_grids,
413414
figsize=figsize, return_fig=return_fig)
@@ -430,31 +431,27 @@ def get_wfdb_plot_items(record, annotation, plot_sym):
430431
sig_name = record.sig_name
431432
sig_units = record.units
432433
record_name = 'Record: %s' % record.record_name
434+
ylabel = ['/'.join(pair) for pair in zip(sig_name, sig_units)]
433435
else:
434-
signal = fs = sig_name = sig_units = record_name = None
436+
signal = fs = ylabel = record_name = None
435437

436438
# Get annotation attributes
437439
if annotation:
438-
# Note: There may be instances in which the annotation `chan`
439-
# attribute has non-overlapping channels with the signal.
440-
# In this case, omit empty middle channels.
441-
442440
# Get channels
443-
all_chans = set(annotation.chan)
444-
445-
n_chans = max(all_chans) + 1
441+
ann_chans = set(annotation.chan)
442+
n_ann_chans = max(ann_chans) + 1
446443

447444
# Indices for each channel
448-
chan_inds = n_chans * [np.empty(0, dtype='int')]
445+
chan_inds = n_ann_chans * [np.empty(0, dtype='int')]
449446

450-
for chan in all_chans:
447+
for chan in ann_chans:
451448
chan_inds[chan] = np.where(annotation.chan == chan)[0]
452449

453450
ann_samp = [annotation.sample[ci] for ci in chan_inds]
454451

455452
if plot_sym:
456-
ann_sym = n_chans * [None]
457-
for ch in all_chans:
453+
ann_sym = n_ann_chans * [None]
454+
for ch in ann_chans:
458455
ann_sym[ch] = [annotation.symbol[ci] for ci in chan_inds[ch]]
459456
else:
460457
ann_sym = None
@@ -468,7 +465,49 @@ def get_wfdb_plot_items(record, annotation, plot_sym):
468465
ann_samp = None
469466
ann_sym = None
470467

471-
return signal, ann_samp, ann_sym, fs, sig_name, sig_units, record_name
468+
# Cleaning: remove empty channels and set labels and styles.
469+
470+
# Wrangle together the signal and annotation channels if necessary
471+
if record and annotation:
472+
# There may be instances in which the annotation `chan`
473+
# attribute has non-overlapping channels with the signal.
474+
# In this case, omit empty middle channels. This function should
475+
# already process labels and arrangements before passing into
476+
# `plot_items`
477+
sig_chans = set(range(signal.shape[1]))
478+
all_chans = sorted(sig_chans.union(ann_chans))
479+
480+
# Need to update ylabels and annotation values
481+
if sig_chans != all_chans:
482+
compact_ann_samp = []
483+
if plot_sym:
484+
compact_ann_sym = []
485+
else:
486+
compact_ann_sym = None
487+
ylabel = []
488+
for ch in all_chans: # ie. 0, 1, 9
489+
if ch in ann_chans:
490+
compact_ann_samp.append(ann_samp[ch])
491+
if plot_sym:
492+
compact_ann_sym.append(ann_sym[ch])
493+
if ch in sig_chans:
494+
ylabel.append(''.join([sig_name[ch], sig_units[ch]]))
495+
else:
496+
ylabel.append('ch_%d/NU' % ch)
497+
ann_samp = compact_ann_samp
498+
ann_sym = compact_ann_sym
499+
# Signals encompass annotations
500+
else:
501+
ylabel = ['/'.join(pair) for pair in zip(sig_name, sig_units)]
502+
503+
# Remove any empty middle channels from annotations
504+
elif annotation:
505+
ann_samp = [a for a in ann_samp if a.size]
506+
if ann_sym is not None:
507+
ann_sym = [a for a in ann_sym if a]
508+
ylabel = ['ch_%d/NU' % ch for ch in ann_chans]
509+
510+
return signal, ann_samp, ann_sym, fs, ylabel, record_name
472511

473512

474513
def plot_all_records(directory=''):

0 commit comments

Comments
 (0)