-
Notifications
You must be signed in to change notification settings - Fork 310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plotting multi-frequency data #348
Conversation
The annotation files given here may be useful as examples and for testing. They were generated by the following commands (WFDB 10.6.2): gqrs -r 03700181 -o gqrsl (an annotation file with no explicit time resolution - meaning the resolution is assumed to be one tick per WFDB frame, i.e. 1/125 s.) gqrs -H -r 03700181 -o gqrsh (an annotation file with a time resolution of 1/500 s, which, in this case, matches the sampling interval of the signal in question.) sqrs -r 03700181 && mv 03700181.qrs 03700181.sqrs (an annotation file with a time resolution of 1/250 s, which doesn't match either the frame interval or the sampling interval.)
An annotation file (represented by a wfdb.Annotation object) may have a "sampling frequency" that differs from the sampling frequency or frequencies of the signals themselves. This will be the case, for example, for annotations generated by programs like sqrs that operate on an upsampled or downsampled copy of the input signals. In any event, when plotting annotations, we need to translate the annotation time into a sample number in order to display it in the correct location. Furthermore, in the future, we want to permit plotting multiple synchronized signals that are sampled at different frequencies. Therefore, to disambiguate between the many possible "sampling frequencies" invvolved, add parameters 'sampling_freq' and 'ann_freq'. Both parameters are optional and default to the value of 'fs'. Either may be a list (one entry per channel), allowing the API to accomodate multi-frequency data. Currently, if 'sampling_freq' is a list, all of its elements must be equal.
An annotation file (represented by a wfdb.Annotation object) may have a "sampling frequency" that differs from the sampling frequency or frequencies of the signals themselves. This will be the case, for example, for annotations generated by programs like sqrs that operate on an upsampled or downsampled copy of the input signals. To handle this case, since record.fs does not equal annotation.fs, we must explicitly specify both sampling_freq and ann_freq when calling plot_items.
There is no sig_len parameter to plot_annotation.
If we are plotting digital (d_signal) values, then the values on the Y axis are ADC units, not physical units. Don't label the axes as physical units, and don't try to calculate grid lines as if ADC units were physical units.
This function accepts either None, a one-dimensional array, a two-dimensional array, or a list (or other non-numpy sequence) of one-dimensional arrays, and converts the result to list of one-dimensional arrays (where each element represents one channel.) This will be used by various plotting functions to accept "non-smooth" signal data as the 'signal' argument while keeping backward compatibility.
When plotting signals, in addition to allowing the signal argument to be a one-dimensional or two-dimensional array, allow it to be a list of one-dimensional arrays (so that each channel can have a different length.) If signal is a 1D or 2D array, convert it to a list of arrays using _expand_channels. This allows the later logic to be simplified.
When plotting signals, in addition to allowing the signal argument to be a one-dimensional or two-dimensional array, allow it to be a list of one-dimensional arrays (so that each channel can have a different length.) This means that the array of X values (t) must be calculated separately for each channel, as both the sampling frequency and the number of samples may vary. (Here, we don't require that the lengths of the signals, in seconds, must all be the same, although they always will be when plotting a WFDB record.) The sig_len parameter makes no sense if the channels have different lengths; this parameter is now unused, and marked as deprecated.
In most cases, the same X-coordinate array will be used for more than one channel; the contents of this array only depend on ch_len and ch_freq (and time_units), so we can cache these arrays in a dictionary, saving some (likely small) amount of time and memory when plotting a huge record.
When plotting signals, in addition to allowing the signal argument to be a one-dimensional or two-dimensional array, allow it to be a list of one-dimensional arrays (so that each channel can have a different length.) Using _expand_channels here, as in plot_annotation, allows the logic to be simplified. The sig_len return value makes no sense if the channels have different lengths; this return value is now marked as deprecated, and will be set to None if the channel lengths differ.
When plotting signals, in addition to allowing the signal argument to be a one-dimensional or two-dimensional array, allow it to be a list of one-dimensional arrays (so that each channel can have a different length.)
If a record is loaded in "non-smooth" ("expanded") mode, it can contain signals of different lengths sampled at different frequencies. In such a case, we want to plot each signal at its original frequency in order to see the effect of the sampling and the temporal relationships between the signals. plot_items now allows the signal argument to be either a numpy array (as in p_signal or d_signal, loaded when 'smooth_frames=True') or a list of arrays (as in e_p_signal or e_d_signal, loaded when 'smooth_frames=False'); in the latter case, plot_wfdb has to calculate and provide the correct per-channel sampling frequencies. (The annotation file, if any, may have its own sampling frequency, which may differ from the signal sampling frequencies. The *default*, if the annotation file doesn't specify a frequency, is always the record frame frequency (fs), not the sampling frequency of any particular signal.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plot looks good. Very useful.
wfdb/plot/plot.py
Outdated
assumed to be a one channel signal. If it is 2, axes 0 and 1, must | ||
represent time and channel number respectively. | ||
signal : 1d or 2d numpy array or list, optional | ||
The uniformly sampled signal or signals to be plotted. If signal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The uniformly sampled signal or signals to be plotted. If signal | |
The signal or signals to be plotted. If signal |
wfdb/plot/plot.py
Outdated
assumed to be a one channel signal. If it is 2, axes 0 and 1, must | ||
represent time and channel number respectively. | ||
signal : 1d or 2d numpy array or list, optional | ||
The uniformly sampled signal or signals to be plotted. If signal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you added signal = _expand_channels(signal)
in plot_items
right before get_plot_dims
is (solely) called. Perhaps get_plot_dims
should only accept signal
as a list then?
wfdb/plot/plot.py
Outdated
signal : ndarray | ||
Tranformed expanded signal into uniform signal. | ||
signal : 1d or 2d numpy array or list | ||
The uniformly sampled signal or signals to be plotted. If signal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The uniformly sampled signal or signals to be plotted. If signal | |
The signal or signals to be plotted. If signal |
wfdb/plot/plot.py
Outdated
sig_len : int | ||
The signal length (per channel) of the dat file. | ||
signal : 1d or 2d numpy array or list | ||
The uniformly sampled signal or signals to be plotted. If signal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The uniformly sampled signal or signals to be plotted. If signal | |
The signal or signals to be plotted. If signal |
t /= downsample_factor[time_units] | ||
tarrays[ch_len, ch_freq] = t | ||
|
||
axes[ch].plot(t, signal[ch], sig_style[ch], zorder=3) | ||
|
||
|
||
def plot_annotation(ann_samp, n_annot, ann_sym, signal, n_sig, fs, time_units, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forget, is this an internal helper function? If so, add leading underscore.
@@ -577,8 +768,9 @@ def plot_wfdb(record=None, annotation=None, plot_sym=False, | |||
function, while allowing direct input of WFDB objects. | |||
|
|||
If the record object is input, the function will extract from it: | |||
- signal values, from the `p_signal` (priority) or `d_signal` attribute | |||
- sampling frequency, from the `fs` attribute | |||
- signal values, from the `e_p_signal`, `e_d_signal`, `p_signal`, or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a future major version, there should definitely be an enum param for this.
Yeah, I'm never quite sure what should be treated as "public" or "private". I would suggest that we plan to do a major version bump in the near future, and rename all of the internal modules/functions/methods at that time. See also issue #339. |
Agreed but I actually like the DWIM-by-default behavior in this case. I have a Record object, just show me what's there and don't bother me with the details. |
Same. The default enum value/behavior should be that. |
We want to be able to plot multi-frequency records, with each signal at its native frequency (issue #337).
To see the individual samples, try:
Compare to the smoothed version:
A more dramatic example:
contrasted with:
To see the signals synchronized with annotations stored at different frequencies, try:
(Yeah, the sampfrom/sampto thing is a bit annoying.)
Things not appearing in this pull request: I think the default should be
sharex=True
, or at least, all of the subplots should have identical x-limits by default.plot_wfdb
should probably grow asharex
parameter. However,sharex=True
is incompatible with multi-frequency andtime_units='samples'
, though I think this could possibly be worked-around using: https://matplotlib.org/stable/api/_as_gen/matplotlib.axis.Axis.set_major_formatter.html