-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Allow subclasses of FigureClass to be passed to plot_raw/plot_epochs #13979
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
Open
frankier
wants to merge
1
commit into
mne-tools:main
Choose a base branch
from
frankier:allow-figureclass-subclasses
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Allow subclasses of `MNEBrowseFigure` to be passed to plot_raw/plot_epochs, as well as the corresponding `plot(...)` methods of the raw and epochs classes, by :newcontrib:`Frankie Robertson` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| """ | ||
| ============================================================== | ||
| Advanced plotting customization by subclassing MNEBrowseFigure | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ============================================================== | ||
|
|
||
| This example shows how plot_epochs(...) and plot_raw(...) can be customized by | ||
| subclassing MNEBrowseFigure and using the `figure_class` argument. | ||
| It plots one EEG trace overlaid ("onion-skinned") on top of another. | ||
|
|
||
| This example is "bad code" in a few ways: | ||
|
|
||
| * Since the interface for MNEBrowseFigure is not public, it is liable to | ||
| break between minor and even patch versions of MNE without warning | ||
| * Some functionality is reimplemented from MNEBrowseFigure in a more or | ||
| less copy-paste style | ||
| * The code is backend-specific, in particular it is limited to the | ||
| matplotlib backend, and will not work with the qt browser | ||
| * Since there is no way to pass another EEG directly to the MNEBrowseFigure, | ||
| it is passed through a global variable | ||
|
|
||
| Nevertheless, the example shows that the "escape hatch" of using a subclass is | ||
| available when other customization possibilities offered by MNE are not | ||
| sufficient. | ||
| """ | ||
|
|
||
| from mne.datasets import eegbci | ||
| from mne.io import read_raw_edf | ||
| from mne.viz import set_browser_backend | ||
| from mne.viz._mpl_figure import MNEBrowseFigure as MNEBrowseFigureOrig | ||
|
|
||
| set_browser_backend("matplotlib") | ||
|
|
||
|
|
||
| onionskin_eeg = None | ||
|
|
||
|
|
||
| def _set_onionskin_eeg(eeg): | ||
| global onionskin_eeg | ||
| onionskin_eeg = eeg | ||
|
|
||
|
|
||
| class OnionskinMNEBrowseFigure(MNEBrowseFigureOrig): | ||
| """ | ||
| Subclass of MNEBrowseFigure adding in onion-skin functionality, | ||
| i.e. plotting one EEG trace overlaid on top of another. | ||
| """ | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| import numpy as np | ||
|
|
||
| super().__init__(*args, **kwargs) | ||
| onionskin_kwargs = { | ||
| **self.mne.trace_kwargs, | ||
| } | ||
| self.mne.onionskins = self.mne.ax_main.plot( | ||
| np.full((1, self.mne.n_channels), np.nan), **onionskin_kwargs | ||
| ) | ||
|
|
||
| def _update_data(self): | ||
| import numpy as np | ||
|
|
||
| from mne.io.base import BaseRaw | ||
|
|
||
| super()._update_data() | ||
| if not onionskin_eeg: | ||
| self.mne.onionskin_data = None | ||
| return | ||
| start, stop = self._get_start_stop() | ||
| if isinstance(onionskin_eeg, BaseRaw): | ||
| if stop is None: | ||
| data = onionskin_eeg[:, start:] | ||
| else: | ||
| data = onionskin_eeg[:, start:stop] | ||
| data = data[0] | ||
| else: | ||
| ix_start = np.searchsorted( | ||
| self.mne.boundary_times, self.mne.t_start - self.mne.sampling_period | ||
| ) | ||
| ix_stop = ix_start + self.mne.n_epochs | ||
| item = slice(ix_start, ix_stop) | ||
| print(type(onionskin_eeg)) | ||
| data = np.concatenate( | ||
| onionskin_eeg.get_data(item=item, copy=False), axis=-1 | ||
| ) | ||
| data = self._process_data(data, start, stop, picks=self.mne.picks) | ||
| self.mne.onionskin_data = data | ||
|
|
||
| def _draw_traces(self): | ||
| import numpy as np | ||
| from matplotlib.colors import to_rgba_array | ||
| from matplotlib.patches import Rectangle | ||
|
|
||
| super()._draw_traces() | ||
| if self.mne.onionskin_data is None: | ||
| return | ||
| picks = self.mne.picks | ||
| offset_ixs = ( | ||
| picks | ||
| if self.mne.butterfly and self.mne.ch_selections is None | ||
| else slice(None) | ||
| ) | ||
| offsets = self.mne.trace_offsets[offset_ixs] | ||
|
|
||
| ch_colors = to_rgba_array(self.mne.ch_colors) | ||
| ch_colors[:, 3] *= 0.5 | ||
|
|
||
| decim = np.ones_like(picks) | ||
| data_picks_mask = np.isin(picks, self.mne.picks_data) | ||
| decim[data_picks_mask] = self.mne.decim | ||
| # decim can vary by channel type, so compute different `times` vectors | ||
| decim_times = { | ||
| decim_value: self.mne.times[::decim_value] + self.mne.first_time | ||
| for decim_value in set(decim) | ||
| } | ||
|
|
||
| time_range = (self.mne.times + self.mne.first_time)[[0, -1]] | ||
| ylim = self.mne.ax_main.get_ylim() | ||
| for ii, line in enumerate(self.mne.onionskins): | ||
| this_offset = offsets[ii] | ||
| this_times = decim_times[decim[ii]] | ||
| this_data = ( | ||
| this_offset - self.mne.onionskin_data[ii] * self.mne.scale_factor | ||
| ) | ||
| this_data = this_data[..., :: decim[ii]] | ||
| clip = 0.2 if self.mne.butterfly else 0.5 | ||
| bottom = max(this_offset - clip, ylim[1]) | ||
| height = min(2 * clip, ylim[0] - bottom) | ||
| rect = Rectangle( | ||
| xy=np.array([time_range[0], bottom]), | ||
| width=time_range[1] - time_range[0], | ||
| height=height, | ||
| transform=self.mne.ax_main.transData, | ||
| ) | ||
| line.set_clip_path(rect) | ||
| line.set_xdata(this_times) | ||
| line.set_ydata(this_data) | ||
| color = ch_colors[ii] | ||
| line.set_color(color) | ||
| line.set_zorder(self.mne.zorder["data"] - 1) | ||
|
|
||
|
|
||
| subjects = [1] | ||
| runs = [1, 2] | ||
| raw_fnames = eegbci.load_data(subjects, runs) | ||
| first_data = read_raw_edf(raw_fnames[0], preload=True) | ||
| second_data = read_raw_edf(raw_fnames[1], preload=True) | ||
|
|
||
| first_data.plot(title="First plot", n_channels=3) | ||
| second_data.plot(title="Second plot", n_channels=3) | ||
|
|
||
| _set_onionskin_eeg(first_data) | ||
| second_data.plot( | ||
| title="Onionskinned plot", | ||
| n_channels=3, | ||
| block=True, | ||
| figure_class=OnionskinMNEBrowseFigure, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

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.
single backtick is default linking mode (which for us is
:py:obj:I think), if not adding to public API need code mode