Skip to content

Commit 08d594a

Browse files
committed
remove annch argument from plotrec. Change the input annotation argument
1 parent ea5812f commit 08d594a

File tree

3 files changed

+92
-47
lines changed

3 files changed

+92
-47
lines changed

README.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ Plotting Data
323323

324324
::
325325

326-
plotrec(record=None, title = None, annotation = None, annch = [0], timeunits='samples',
326+
plotrec(record=None, title = None, annotation = None, timeunits='samples',
327327
sigstyle='', figsize=None, returnfig = False, ecggrids=[]):
328328

329329
Example Usage:
@@ -340,8 +340,7 @@ Input Arguments:
340340

341341
- ``record`` (required): A wfdb Record object. The p_signals attribute will be plotted.
342342
- ``title`` (default=None): A string containing the title of the graph.
343-
- ``annotation`` (default=None): An Annotation object. The annsamp attribute locations will be overlaid on the signal.
344-
- ``annch`` (default=[0]): A list of channels on which to plot the annotation samples.
343+
- ``annotation`` (default=None): A list of Annotation objects or numpy arrays. The locations of the Annotation objects' 'annsamp' attribute, or the locations of the numpy arrays' values, will be overlaid on the signals. The list index of the annotation item corresponds to the signal channel that each annotation set will be plotted on. For channels without annotations to plot, put None in the list. This argument may also be just an Annotation object or numpy array, which will be plotted over channel 0.
345344
- ``timeunits`` (default='samples'): String specifying the x axis unit. Allowed options are: 'samples', 'seconds', 'minutes', and 'hours'.
346345
- ``sigstyle`` (default=''): String, or list of strings, specifying the styling of the matplotlib plot for the signals. If 'sigstyle' is a string, each channel will have the same style. If it is a list, each channel's style will correspond to the list element. ie. sigtype=['r','b','k'].
347346
- ``figsize`` (default=None): Tuple pair specifying the width, and height of the figure. Same as the 'figsize' argument passed into matplotlib.pyplot's figure() function.

wfdb/plot/plots.py

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@
77

88
# Plot a WFDB Record's signals
99
# Optionally, overlay annotation locations
10-
def plotrec(record=None, title = None, annotation = None, annch = [0], timeunits='samples', sigstyle='', figsize=None, returnfig = False, ecggrids=[]):
10+
def plotrec(record=None, title = None, annotation = None, timeunits='samples', sigstyle='', figsize=None, returnfig = False, ecggrids=[]):
1111
""" Subplot and label each channel of a WFDB Record.
1212
Optionally, subplot annotation locations over selected channels.
1313
1414
Usage:
15-
plotrec(record=None, title = None, annotation = None, annch = [0], timeunits='samples', sigstyle='',
15+
plotrec(record=None, title = None, annotation = None, timeunits='samples', sigstyle='',
1616
figsize=None, returnfig = False, ecggrids=[])
1717
1818
Input arguments:
1919
- record (required): A wfdb Record object. The p_signals attribute will be plotted.
2020
- title (default=None): A string containing the title of the graph.
21-
- annotation (default=None): An Annotation object. The annsamp attribute locations will be overlaid on the signal.
22-
- annch (default=[0]): A list of channels on which to plot the annotation samples.
21+
- annotation (default=None): A list of Annotation objects or numpy arrays. The locations of the Annotation
22+
objects' 'annsamp' attribute, or the locations of the numpy arrays' values, will be overlaid on the signals.
23+
The list index of the annotation item corresponds to the signal channel that each annotation set will be
24+
plotted on. For channels without annotations to plot, put None in the list. This argument may also be just
25+
an Annotation object or numpy array, which will be plotted over channel 0.
2326
- timeunits (default='samples'): String specifying the x axis unit.
2427
Allowed options are: 'samples', 'seconds', 'minutes', and 'hours'.
2528
- sigstyle (default=''): String, or list of strings, specifying the styling of the matplotlib plot for the signals.
@@ -46,8 +49,8 @@ def plotrec(record=None, title = None, annotation = None, annch = [0], timeunits
4649

4750
# Check the validity of items used to make the plot
4851
# Return the x axis time values to plot for the record (and annotation if any)
49-
t, tann = checkplotitems(record, title, annotation, annch, timeunits, sigstyle)
50-
52+
t, tann, annplot = checkplotitems(record, title, annotation, timeunits, sigstyle)
53+
5154
siglen, nsig = record.p_signals.shape
5255

5356
# Expand list styles
@@ -73,8 +76,8 @@ def plotrec(record=None, title = None, annotation = None, annch = [0], timeunits
7376
plt.title(title)
7477

7578
# Plot annotation if specified
76-
if annotation is not None and ch in annch:
77-
ax.plot(tann, record.p_signals[annotation.annsamp, ch], 'r+')
79+
if annplot[ch] is not None:
80+
ax.plot(tann[ch], record.p_signals[annplot[ch], ch], 'r+')
7881

7982
# Axis Labels
8083
if timeunits == 'samples':
@@ -168,7 +171,7 @@ def calc_ecg_grids(minsig, maxsig, units, fs, maxt, timeunits):
168171

169172
# Check the validity of items used to make the plot
170173
# Return the x axis time values to plot for the record (and annotation if any)
171-
def checkplotitems(record, title, annotation, annch, timeunits, sigstyle):
174+
def checkplotitems(record, title, annotation, timeunits, sigstyle):
172175

173176
# signals
174177
if type(record) != records.Record:
@@ -229,26 +232,54 @@ def checkplotitems(record, title, annotation, annch, timeunits, sigstyle):
229232

230233
# Annotations if any
231234
if annotation is not None:
232-
if type(annotation) != annotations.Annotation:
233-
raise TypeError("The 'annotation' argument must be a valid wfdb.Annotation object")
234-
if type(annch)!= list:
235-
raise TypeError("The 'annch' argument must be a list of integers")
236-
if min(annch)<0 or max(annch)>nsig:
237-
raise ValueError("The elements of 'annch' must be between 0 and the number of record channels")
238-
239-
# The annotation locations to plot
240-
if timeunits == 'samples':
241-
tann = annotation.annsamp
242-
elif timeunits == 'seconds':
243-
tann = annotation.annsamp/record.fs
244-
elif timeunits == 'minutes':
245-
tann = annotation.annsamp/record.fs/60
235+
236+
# The output list of numpy arrays (or Nones) to plot
237+
annplot = [None]*record.nsig
238+
239+
# Move single channel annotations to channel 0
240+
if type(annotation) == annotations.Annotation:
241+
annplot[0] = annotation.annsamp
242+
elif type(annotation) == np.ndarray:
243+
annplot[0] = annotation
244+
# Ready list.
245+
elif type(annotation) == list:
246+
if len(annotation) > record.nsig:
247+
raise ValueError("The number of annotation series to plot cannot be more than the number of channels")
248+
if len(annotation) < record.nsig:
249+
annotation = annotation+[None]*(record.nsig-len(annotation))
250+
# Check elements. Copy over to new list.
251+
for ch in range(record.nsig):
252+
if type(annotation[ch]) == annotations.Annotation:
253+
annplot[ch] = annotation[ch].annsamp
254+
elif type(annotation[ch]) == np.ndarray:
255+
annplot[ch] = annotation[ch]
256+
elif annotation[ch] is None:
257+
pass
258+
else:
259+
raise TypeError("The 'annotation' argument must be a wfdb.Annotation object, a numpy array, None, or a list of these data types")
246260
else:
247-
tann = annotation.annsamp/record.fs/3600
261+
raise TypeError("The 'annotation' argument must be a wfdb.Annotation object, a numpy array, None, or a list of these data types")
262+
263+
# The annotation locations to plot
264+
tann = [None]*record.nsig
265+
266+
for ch in range(record.nsig):
267+
if annplot[ch] is None:
268+
continue
269+
if timeunits == 'samples':
270+
tann[ch] = annplot[ch]
271+
elif timeunits == 'seconds':
272+
tann[ch] = annplot[ch]/record.fs
273+
elif timeunits == 'minutes':
274+
tann[ch] = annplot[ch]/record.fs/60
275+
else:
276+
tann[ch] = annplot[ch]/record.fs/3600
248277
else:
249278
tann = None
279+
annplot = [None]*record.nsig
250280

251-
return (t, tann)
281+
# tann is the sample values to plot for each annotation series
282+
return (t, tann, annplot)
252283

253284

254285

wfdb/readwrite/annotations.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,35 @@ def checkfield(self, field):
164164
else:
165165
fielditem = getattr(self, field)
166166

167+
# annsamp must be a numpy array, not a list.
168+
if field == 'annsamp':
169+
if type(fielditem) != np.ndarray:
170+
raise TypeError('The '+field+' field must be a numpy array')
167171
# Ensure the field item is a list or array.
168-
if type(fielditem) not in [list, np.ndarray]:
169-
raise TypeError('The '+field+' field must be a list or numpy array')
170-
171-
# Check the data types of the elements
172-
# annsamp and anntype may NOT have nones. Others may.
173-
if field in ['annsamp','anntype']:
174-
for item in fielditem:
175-
if type(item) not in annfieldtypes[field]:
176-
print("All elements of the '"+field+"' field must be one of the following types:", annfieldtypes[field])
177-
print("All elements must be present")
178-
raise Exception()
179172
else:
180-
for item in fielditem:
181-
if item is not None and type(item) not in annfieldtypes[field]:
182-
print("All elements of the '", field, "' field must be one of the following types:")
183-
print(annfieldtypes[field])
184-
print("Elements may also be set to 'None'")
185-
raise Exception()
173+
if type(fielditem) not in [list, np.ndarray]:
174+
raise TypeError('The '+field+' field must be a list or numpy array')
175+
176+
# Check the data types of the elements.
177+
# If the field is a numpy array, just check dtype. If list, check individual elements.
178+
# annsamp and anntype may NOT have nones. Others may.
179+
if type(fielditem) == np.ndarray:
180+
if fielditem.dtype not in intdtypes:
181+
raise TypeError('The '+field+' field must have one of the following dtypes:', intdtypes)
182+
else:
183+
if field =='anntype':
184+
for item in fielditem:
185+
if type(item) not in annfieldtypes[field]:
186+
print("All elements of the '"+field+"' field must be one of the following types:", annfieldtypes[field])
187+
print("All elements must be present")
188+
raise Exception()
189+
else:
190+
for item in fielditem:
191+
if item is not None and type(item) not in annfieldtypes[field]:
192+
print("All elements of the '", field, "' field must be one of the following types:")
193+
print(annfieldtypes[field])
194+
print("Elements may also be set to 'None'")
195+
raise Exception()
186196

187197
# Field specific checks
188198
# The C WFDB library stores num/sub/chan as chars.
@@ -657,6 +667,9 @@ def rdann(recordname, annotator, sampfrom=0, sampto=None, shiftsamps=False, pbdi
657667
annsamp,anntype,num,subtype,chan,aux = apply_annotation_range(annsamp,
658668
sampfrom,sampto,anntype,num,subtype,chan,aux)
659669

670+
# Convert annsamp to numpy array
671+
annsamp = np.array(annsamp, dtype='int64')
672+
660673
# Set the annotation type to the annotation codes
661674
anntype = [allannsyms[code] for code in anntype]
662675

@@ -672,7 +685,6 @@ def rdann(recordname, annotator, sampfrom=0, sampto=None, shiftsamps=False, pbdi
672685

673686
# Return annotation samples relative to starting signal index
674687
if shiftsamps and annsamp!=[] and sampfrom:
675-
annsamp = np.array(annsamp, dtype='int64')
676688
annsamp = annsamp - sampfrom
677689

678690
# Store fields in an Annotation object
@@ -1106,4 +1118,7 @@ def proc_special_types(annsamp,anntype,num,subtype,chan,aux):
11061118
annfieldtypes = {'recordname': [str], 'annotator': [str], 'annsamp': _headers.inttypes,
11071119
'anntype': [str], 'num':_headers.inttypes, 'subtype': _headers.inttypes,
11081120
'chan': _headers.inttypes, 'aux': [str], 'fs': _headers.floattypes,
1109-
'custom_anntypes': [dict]}
1121+
'custom_anntypes': [dict]}
1122+
1123+
# Acceptable numpy integer dtypes
1124+
intdtypes = ['int64', 'uint64', 'int32', 'uint32','int16','uint16']

0 commit comments

Comments
 (0)