88from wfdb .io .annotation import Annotation
99
1010
11+ def _get_sampling_freq (sampling_freq , n_sig , frame_freq ):
12+ """
13+ Convert application-specified sampling frequency to a list.
14+
15+ Parameters
16+ ----------
17+ sampling_freq : number or sequence or None
18+ The sampling frequency or frequencies of the signals. If this is a
19+ list, its length must equal `n_sig`. If unset, defaults to
20+ `frame_freq`.
21+ n_sig : int
22+ Number of channels.
23+ frame_freq : number or None
24+ Default sampling frequency (record frame frequency).
25+
26+ Returns
27+ -------
28+ sampling_freq : list
29+ The sampling frequency for each channel (a list of length `n_sig`.)
30+
31+ """
32+ if sampling_freq is None :
33+ return [frame_freq ] * n_sig
34+ elif hasattr (sampling_freq , '__len__' ):
35+ if len (sampling_freq ) != n_sig :
36+ raise ValueError ('length mismatch: n_sig = {}, '
37+ 'len(sampling_freq) = {}' .format (
38+ n_sig , len (sampling_freq )))
39+ return list (sampling_freq )
40+ else :
41+ return [sampling_freq ] * n_sig
42+
43+
44+ def _get_ann_freq (ann_freq , n_annot , frame_freq ):
45+ """
46+ Convert application-specified annotation frequency to a list.
47+
48+ Parameters
49+ ----------
50+ ann_freq : number or sequence or None
51+ The sampling frequency or frequencies of the annotations. If this
52+ is a list, its length must equal `n_annot`. If unset, defaults to
53+ `frame_freq`.
54+ n_annot : int
55+ Number of channels.
56+ frame_freq : number or None
57+ Default sampling frequency (record frame frequency).
58+
59+ Returns
60+ -------
61+ ann_freq : list
62+ The sampling frequency for each channel (a list of length `n_annot`).
63+
64+ """
65+ if ann_freq is None :
66+ return [frame_freq ] * n_annot
67+ elif hasattr (ann_freq , '__len__' ):
68+ if len (ann_freq ) != n_annot :
69+ raise ValueError ('length mismatch: n_annot = {}, '
70+ 'len(ann_freq) = {}' .format (
71+ n_annot , len (ann_freq )))
72+ return list (ann_freq )
73+ else :
74+ return [ann_freq ] * n_annot
75+
76+
1177def plot_items (signal = None , ann_samp = None , ann_sym = None , fs = None ,
1278 time_units = 'samples' , sig_name = None , sig_units = None ,
1379 xlabel = None , ylabel = None , title = None , sig_style = ['' ],
1480 ann_style = ['r*' ], ecg_grids = [], figsize = None ,
1581 sharex = False , sharey = False , return_fig = False ,
16- return_fig_axes = False ):
82+ return_fig_axes = False , sampling_freq = None ,
83+ ann_freq = None ):
1784 """
1885 Subplot individual channels of signals and/or annotations.
1986
@@ -87,6 +154,14 @@ def plot_items(signal=None, ann_samp=None, ann_sym=None, fs=None,
87154 'figsize' argument passed into matplotlib.pyplot's `figure` function.
88155 return_fig : bool, optional
89156 Whether the figure is to be returned as an output argument.
157+ sampling_freq : number or sequence, optional
158+ The sampling frequency or frequencies of the signals. If this is a
159+ list, it must have the same length as the number of channels. If
160+ unspecified, defaults to `fs`.
161+ ann_freq : number or sequence, optional
162+ The sampling frequency or frequencies of the annotations. If this
163+ is a list, it must have the same length as `ann_samp`. If
164+ unspecified, defaults to `fs`.
90165
91166 Returns
92167 -------
@@ -113,18 +188,25 @@ def plot_items(signal=None, ann_samp=None, ann_sym=None, fs=None,
113188 # Figure out number of subplots required
114189 sig_len , n_sig , n_annot , n_subplots = get_plot_dims (signal , ann_samp )
115190
191+ # Convert sampling_freq and ann_freq to lists if needed
192+ sampling_freq = _get_sampling_freq (sampling_freq , n_sig , fs )
193+ ann_freq = _get_ann_freq (ann_freq , n_annot , fs )
194+
116195 # Create figure
117196 fig , axes = create_figure (n_subplots , sharex , sharey , figsize )
118197
119198 if signal is not None :
120- plot_signal (signal , sig_len , n_sig , fs , time_units , sig_style , axes )
199+ plot_signal (signal , sig_len , n_sig , fs , time_units , sig_style , axes ,
200+ sampling_freq = sampling_freq )
121201
122202 if ann_samp is not None :
123203 plot_annotation (ann_samp , n_annot , ann_sym , signal , n_sig , fs ,
124- time_units , ann_style , axes )
204+ time_units , ann_style , axes ,
205+ sampling_freq = sampling_freq , ann_freq = ann_freq )
125206
126207 if ecg_grids :
127- plot_ecg_grids (ecg_grids , fs , sig_units , time_units , axes )
208+ plot_ecg_grids (ecg_grids , fs , sig_units , time_units , axes ,
209+ sampling_freq = sampling_freq )
128210
129211 # Add title and axis labels.
130212 # First, make sure that xlabel and ylabel inputs are valid
@@ -238,7 +320,8 @@ def create_figure(n_subplots, sharex, sharey, figsize):
238320 return fig , axes
239321
240322
241- def plot_signal (signal , sig_len , n_sig , fs , time_units , sig_style , axes ):
323+ def plot_signal (signal , sig_len , n_sig , fs , time_units , sig_style , axes ,
324+ sampling_freq = None ):
242325 """
243326 Plot signal channels.
244327
@@ -262,22 +345,39 @@ def plot_signal(signal, sig_len, n_sig, fs, time_units, sig_style, axes):
262345 will be used for all channels.
263346 axes : list
264347 The information needed for each subplot.
348+ sampling_freq : number or sequence, optional
349+ The sampling frequency or frequencies of the signals. If this is a
350+ list, it must have the same length as the number of channels. If
351+ unspecified, defaults to `fs`.
265352
266353 Returns
267354 -------
268355 N/A
269356
270357 """
358+ if n_sig == 0 :
359+ return
360+
271361 # Extend signal style if necessary
272362 if len (sig_style ) == 1 :
273363 sig_style = n_sig * sig_style
274364
365+ # Convert sampling_freq to a list if needed
366+ sampling_freq = _get_sampling_freq (sampling_freq , n_sig , fs )
367+
368+ if any (f != sampling_freq [0 ] for f in sampling_freq ):
369+ raise NotImplementedError (
370+ 'multiple sampling frequencies are not supported' )
371+
275372 # Figure out time indices
276373 if time_units == 'samples' :
277374 t = np .linspace (0 , sig_len - 1 , sig_len )
278375 else :
279- downsample_factor = {'seconds' :fs , 'minutes' :fs * 60 ,
280- 'hours' :fs * 3600 }
376+ downsample_factor = {
377+ 'seconds' : sampling_freq [0 ],
378+ 'minutes' : sampling_freq [0 ] * 60 ,
379+ 'hours' : sampling_freq [0 ] * 3600
380+ }
281381 t = np .linspace (0 , sig_len - 1 , sig_len ) / downsample_factor [time_units ]
282382
283383 # Plot the signals
@@ -289,7 +389,7 @@ def plot_signal(signal, sig_len, n_sig, fs, time_units, sig_style, axes):
289389
290390
291391def plot_annotation (ann_samp , n_annot , ann_sym , signal , n_sig , fs , time_units ,
292- ann_style , axes ):
392+ ann_style , axes , sampling_freq = None , ann_freq = None ):
293393 """
294394 Plot annotations, possibly overlaid on signals.
295395 ann_samp, n_annot, ann_sym, signal, n_sig, fs, time_units, ann_style, axes
@@ -320,6 +420,14 @@ def plot_annotation(ann_samp, n_annot, ann_sym, signal, n_sig, fs, time_units,
320420 will be used for all channels.
321421 axes : list
322422 The information needed for each subplot.
423+ sampling_freq : number or sequence, optional
424+ The sampling frequency or frequencies of the signals. If this is a
425+ list, it must have the same length as the number of channels. If
426+ unspecified, defaults to `fs`.
427+ ann_freq : number or sequence, optional
428+ The sampling frequency or frequencies of the annotations. If this
429+ is a list, it must have the same length as `ann_samp`. If
430+ unspecified, defaults to `fs`.
323431
324432 Returns
325433 -------
@@ -330,25 +438,45 @@ def plot_annotation(ann_samp, n_annot, ann_sym, signal, n_sig, fs, time_units,
330438 if len (ann_style ) == 1 :
331439 ann_style = n_annot * ann_style
332440
333- # Figure out downsample factor for time indices
334- if time_units == 'samples' :
335- downsample_factor = 1
336- else :
337- downsample_factor = {'seconds' :float (fs ), 'minutes' :float (fs )* 60 ,
338- 'hours' :float (fs )* 3600 }[time_units ]
441+ # Convert sampling_freq and ann_freq to lists if needed
442+ sampling_freq = _get_sampling_freq (sampling_freq , n_sig , fs )
443+ ann_freq = _get_ann_freq (ann_freq , n_annot , fs )
339444
340445 # Plot the annotations
341446 for ch in range (n_annot ):
447+ afreq = ann_freq [ch ]
448+ if ch < n_sig :
449+ sfreq = sampling_freq [ch ]
450+ else :
451+ sfreq = afreq
452+
453+ # Figure out downsample factor for time indices
454+ if time_units == 'samples' :
455+ if afreq is None and sfreq is None :
456+ downsample_factor = 1
457+ else :
458+ downsample_factor = afreq / sfreq
459+ else :
460+ downsample_factor = {
461+ 'seconds' : float (afreq ),
462+ 'minutes' : float (afreq ) * 60 ,
463+ 'hours' : float (afreq ) * 3600
464+ }[time_units ]
465+
342466 if ann_samp [ch ] is not None and len (ann_samp [ch ]):
343467 # Figure out the y values to plot on a channel basis
344468
345469 # 1 dimensional signals
346470 try :
347471 if n_sig > ch :
472+ if sfreq == afreq :
473+ index = ann_samp [ch ]
474+ else :
475+ index = (sfreq / afreq * ann_samp [ch ]).astype ('int' )
348476 if signal .ndim == 1 :
349- y = signal [ann_samp [ ch ] ]
477+ y = signal [index ]
350478 else :
351- y = signal [ann_samp [ ch ] , ch ]
479+ y = signal [index , ch ]
352480 else :
353481 y = np .zeros (len (ann_samp [ch ]))
354482 except IndexError :
@@ -364,7 +492,7 @@ def plot_annotation(ann_samp, n_annot, ann_sym, signal, n_sig, fs, time_units,
364492 y [i ]))
365493
366494
367- def plot_ecg_grids (ecg_grids , fs , units , time_units , axes ):
495+ def plot_ecg_grids (ecg_grids , fs , units , time_units , axes , sampling_freq = None ):
368496 """
369497 Add ECG grids to the axes.
370498
@@ -381,6 +509,10 @@ def plot_ecg_grids(ecg_grids, fs, units, time_units, axes):
381509 and 'hours'.
382510 axes : list
383511 The information needed for each subplot.
512+ sampling_freq : number or sequence, optional
513+ The sampling frequency or frequencies of the signals. If this is a
514+ list, it must have the same length as the number of channels. If
515+ unspecified, defaults to `fs`.
384516
385517 Returns
386518 -------
@@ -390,15 +522,18 @@ def plot_ecg_grids(ecg_grids, fs, units, time_units, axes):
390522 if ecg_grids == 'all' :
391523 ecg_grids = range (0 , len (axes ))
392524
525+ # Convert sampling_freq to a list if needed
526+ sampling_freq = _get_sampling_freq (sampling_freq , len (axes ), fs )
527+
393528 for ch in ecg_grids :
394529 # Get the initial plot limits
395530 auto_xlims = axes [ch ].get_xlim ()
396531 auto_ylims = axes [ch ].get_ylim ()
397532
398533 (major_ticks_x , minor_ticks_x , major_ticks_y ,
399534 minor_ticks_y ) = calc_ecg_grids (auto_ylims [0 ], auto_ylims [1 ],
400- units [ch ], fs , auto_xlims [ 1 ],
401- time_units )
535+ units [ch ], sampling_freq [ ch ],
536+ auto_xlims [ 1 ], time_units )
402537
403538 min_x , max_x = np .min (minor_ticks_x ), np .max (minor_ticks_x )
404539 min_y , max_y = np .min (minor_ticks_y ), np .max (minor_ticks_y )
@@ -439,7 +574,7 @@ def calc_ecg_grids(minsig, maxsig, sig_units, fs, maxt, time_units):
439574 sig_units : list
440575 The units used for plotting each signal.
441576 fs : float
442- The sampling frequency of the record .
577+ The sampling frequency of the signal .
443578 maxt : float
444579 The max time of the signal.
445580 time_units : str
0 commit comments