Skip to content

Commit

Permalink
[MRG] open ic properties on topo click in plot_components + changes t…
Browse files Browse the repository at this point in the history
…o the wording in ica tutorial (clarifications, typos) (#3496)

* open ic properties on click

* FIX make flake happy, update docstrings

* TEST add test for properties on click in plot_ica_components

* rename data -> inst, ic_data -> data

* FIX leftover from variable renaming - overindent

* FIX typo

* DOC: mention the interactive plot_components in the tutorial

* DOC: forgot to update whats_new

* DOC: fixes and clarifications to the ica tutorial

* FIX: typos I added to the tutorial

* DOC: last fixes to the tutorial

* FIX: doc links, numbered list display

* DOC/FIX: fix the order of docstring args in corrmap

* FIX: adress comments

* FIX: clickable ic's when picks is None
  • Loading branch information
mmagnuski authored and agramfort committed Aug 15, 2016
1 parent 0a37cb0 commit 112ffac
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 47 deletions.
1 change: 1 addition & 0 deletions doc/python_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ Functions:
maxwell_filter
read_ica
run_ica
corrmap

EEG referencing:

Expand Down
3 changes: 3 additions & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Changelog

- Adding an EEG reference channel using :func:`mne.io.add_reference_channels` will now use its digitized location from the FIFF file, if present, by `Chris Bailey`_

- Added interactivity to :func:`mne.preprocessing.ICA.plot_components` - passing an instance of :class:`io.Raw` or :class:`Epochs` in ``inst`` argument allows to open component properties by clicking on component topomaps, by `Mikołaj Magnuski`_


BUG
~~~

Expand Down
2 changes: 1 addition & 1 deletion mne/preprocessing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .eog import find_eog_events, create_eog_epochs
from .ecg import find_ecg_events, create_ecg_epochs
from .ica import (ICA, ica_find_eog_events, ica_find_ecg_events,
get_score_funcs, read_ica, run_ica)
get_score_funcs, read_ica, run_ica, corrmap)
from .bads import find_outliers
from .stim import fix_stim_artifact
from .maxwell import maxwell_filter
Expand Down
31 changes: 16 additions & 15 deletions mne/preprocessing/ica.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,14 +1342,15 @@ def copy(self):
def plot_components(self, picks=None, ch_type=None, res=64, layout=None,
vmin=None, vmax=None, cmap='RdBu_r', sensors=True,
colorbar=False, title=None, show=True, outlines='head',
contours=6, image_interp='bilinear', head_pos=None):
contours=6, image_interp='bilinear', head_pos=None,
inst=None):
return plot_ica_components(self, picks=picks, ch_type=ch_type,
res=res, layout=layout, vmin=vmin,
vmax=vmax, cmap=cmap, sensors=sensors,
colorbar=colorbar, title=title, show=show,
outlines=outlines, contours=contours,
image_interp=image_interp,
head_pos=head_pos)
head_pos=head_pos, inst=inst)

@copy_function_doc_to_method_doc(plot_ica_properties)
def plot_properties(self, inst, picks=None, axes=None, dB=True,
Expand Down Expand Up @@ -2308,17 +2309,8 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
to True.
show : bool
Show figures if True.
layout : None | Layout | list of Layout
Layout instance specifying sensor positions (does not need to be
specified for Neuromag data). Or a list of Layout if projections
are from different sensor types.
cmap : None | matplotlib colormap
Colormap for the plot. If ``None``, defaults to 'Reds_r' for norm data,
otherwise to 'RdBu_r'.
sensors : bool | str
Add markers for sensor locations to the plot. Accepts matplotlib plot
format string (e.g., 'r+' for red plusses). If True, a circle will be
used (via .add_artist). Defaults to True.
verbose : bool, str, int, or None
If not None, override default verbose level (see mne.verbose).
outlines : 'head' | dict | None
The outlines to be drawn. If 'head', a head scheme will be drawn. If
dict, each key refers to a tuple of x and y positions. The values in
Expand All @@ -2328,10 +2320,19 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
outline. Moreover, a matplotlib patch object can be passed for
advanced masking options, either directly or as a function that returns
patches (required for multi-axis plots).
layout : None | Layout | list of Layout
Layout instance specifying sensor positions (does not need to be
specified for Neuromag data). Or a list of Layout if projections
are from different sensor types.
sensors : bool | str
Add markers for sensor locations to the plot. Accepts matplotlib plot
format string (e.g., 'r+' for red plusses). If True, a circle will be
used (via .add_artist). Defaults to True.
contours : int | False | None
The number of contour lines to draw. If 0, no contours will be drawn.
verbose : bool, str, int, or None
If not None, override default verbose level (see mne.verbose).
cmap : None | matplotlib colormap
Colormap for the plot. If ``None``, defaults to 'Reds_r' for norm data,
otherwise to 'RdBu_r'.
Returns
-------
Expand Down
23 changes: 23 additions & 0 deletions mne/viz/tests/test_ica.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import warnings

from numpy.testing import assert_raises, assert_equal
from nose.tools import assert_true

from mne import io, read_events, Epochs, read_cov
from mne import pick_types
Expand Down Expand Up @@ -56,6 +57,7 @@ def test_plot_ica_components():
"""Test plotting of ICA solutions
"""
import matplotlib.pyplot as plt

raw = _get_raw()
ica = ICA(noise_cov=read_cov(cov_fname), n_components=2,
max_pca_components=3, n_pca_components=3)
Expand All @@ -67,6 +69,27 @@ def test_plot_ica_components():
for components in [0, [0], [0, 1], [0, 1] * 2, None]:
ica.plot_components(components, image_interp='bilinear', res=16,
colorbar=True)

# test interactive mode (passing 'inst' arg)
plt.close('all')
ica.plot_components([0, 1], image_interp='bilinear', res=16, inst=raw)

fig = plt.gcf()
ax = [a for a in fig.get_children() if isinstance(a, plt.Axes)]
lbl = ax[1].get_label()
_fake_click(fig, ax[1], (0., 0.), xform='data')

c_fig = plt.gcf()
ax = [a for a in c_fig.get_children() if isinstance(a, plt.Axes)]
labels = [a.get_label() for a in ax]

for l in ['topomap', 'image', 'erp', 'spectrum', 'variance']:
assert_true(l in labels)

topomap_ax = ax[labels.index('topomap')]
title = topomap_ax.get_title()
assert_true(lbl == title)

ica.info = None
assert_raises(ValueError, ica.plot_components, 1)
assert_raises(RuntimeError, ica.plot_components, 1, ch_type='mag')
Expand Down
23 changes: 21 additions & 2 deletions mne/viz/topomap.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,8 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
layout=None, vmin=None, vmax=None, cmap='RdBu_r',
sensors=True, colorbar=False, title=None,
show=True, outlines='head', contours=6,
image_interp='bilinear', head_pos=None):
image_interp='bilinear', head_pos=None,
inst=None):
"""Project unmixing matrix on interpolated sensor topogrpahy.
Parameters
Expand Down Expand Up @@ -846,12 +847,19 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
the head circle. If dict, can have entries 'center' (tuple) and
'scale' (tuple) for what the center and scale of the head should be
relative to the electrode locations.
inst : Raw | Epochs | None
To be able to see component properties after clicking on component
topomap you need to pass relevant data - instances of Raw or Epochs
(for example the data that ICA was trained on). This takes effect
only when running matplotlib in interactive mode.
Returns
-------
fig : instance of matplotlib.pyplot.Figure or list
The figure object(s).
"""
from ..io import _BaseRaw
from ..epochs import _BaseEpochs
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid import make_axes_locatable
from ..channels import _get_ch_type
Expand All @@ -869,7 +877,8 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
colorbar=colorbar, title=title,
show=show, outlines=outlines,
contours=contours,
image_interp=image_interp)
image_interp=image_interp,
head_pos=head_pos, inst=inst)
figs.append(fig)
return figs
elif np.isscalar(picks):
Expand Down Expand Up @@ -919,6 +928,7 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
res=res, axes=ax, cmap=cmap[0], outlines=outlines,
image_mask=image_mask, contours=contours,
image_interp=image_interp, show=False)[0]
im.axes.set_label('IC #%03d' % ii)
if colorbar:
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
Expand All @@ -932,6 +942,15 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64,
tight_layout(fig=fig)
fig.subplots_adjust(top=0.95)
fig.canvas.draw()
if isinstance(inst, (_BaseRaw, _BaseEpochs)):
def onclick(event, ica=ica, inst=inst):
# check which component to plot
label = event.inaxes.get_label()
if 'IC #' in label:
ic = int(label[4:])
ica.plot_properties(inst, picks=ic, show=True)
fig.canvas.mpl_connect('button_press_event', onclick)

plt_show(show)
return fig

Expand Down
77 changes: 48 additions & 29 deletions tutorials/plot_artifacts_correction_ica.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
# Component properties
# --------------------
#
# Let's take a closer at properties of first three independent components
# Let's take a closer look at properties of first three independent components.

# first, component 0:
ica.plot_properties(raw, picks=0)
Expand All @@ -90,6 +90,15 @@
# we can also take a look at multiple different components at once:
ica.plot_properties(raw, picks=[1, 2], psd_args={'fmax': 35.})

###############################################################################
# Instead of opening individual figures with component properties, we can
# also pass an instance of Raw or Epochs in ``inst`` arument to
# ``ica.plot_components``. This would allow us to open component properties
# interactively by clicking on individual component topomaps. In the notebook
# this woks only when running matplotlib in interactive mode (``%matplotlib``).

# uncomment the code below to test the inteactive mode of plot_components:
# ica.plot_components(picks=range(10), inst=raw)

###############################################################################
# Advanced artifact detection
Expand Down Expand Up @@ -131,8 +140,8 @@
# by artifact detection functions. You can also manually edit them to annotate
# components.
#
# Now let's see how we would modify our signals if we would remove this
# component from the data
# Now let's see how we would modify our signals if we removed this component
# from the data
ica.plot_overlay(eog_average, exclude=eog_inds, show=False)
# red -> before, black -> after. Yes! We remove quite a lot!

Expand All @@ -159,12 +168,15 @@
# What if we don't have an EOG channel?
# -------------------------------------
#
# 1) make a bipolar reference from frontal EEG sensors and use as virtual EOG
# channel. This can be tricky though as you can only hope that the frontal
# EEG channels only reflect EOG and not brain dynamics in the prefrontal
# cortex.
# 2) Go for a semi-automated approach, using template matching.
# In MNE-Python option 2 is easily achievable and it might be better,
# We could either:
#
# 1. make a bipolar reference from frontal EEG sensors and use as virtual EOG
# channel. This can be tricky though as you can only hope that the frontal
# EEG channels only reflect EOG and not brain dynamics in the prefrontal
# cortex.
# 2. go for a semi-automated approach, using template matching.
#
# In MNE-Python option 2 is easily achievable and it might give better results,
# so let's have a look at it.

from mne.preprocessing.ica import corrmap # noqa
Expand All @@ -173,13 +185,19 @@
# The idea behind corrmap is that artefact patterns are similar across subjects
# and can thus be identified by correlating the different patterns resulting
# from each solution with a template. The procedure is therefore
# semi-automatic. Corrmap hence takes at least a list of ICA solutions and a
# template, that can be an index or an array. As we don't have different
# subjects or runs available today, here we will fit ICA models to different
# parts of the recording and then use as a user-defined template the ICA
# that we just fitted for detecting corresponding components in the three "new"
# ICAs. The following block of code addresses this point and should not be
# copied, ok?
# semi-automatic. :func:`mne.preprocessing.corrmap` hence takes a list of
# ICA solutions and a template, that can be an index or an array.
#
# As we don't have different subjects or runs available today, here we will
# simulate ICA solutions from different subjects by fitting ICA models to
# different parts of the same recording. Then we will use one of the components
# from our original ICA as a template in order to detect sufficiently similar
# components in the simulated ICAs.
#
# The following block of code simulates having ICA solutions from different
# runs/subjects so it should not be used in real analysis - use independent
# data sets instead.

# We'll start by simulating a group of subjects or runs from a subject
start, stop = [0, len(raw.times) - 1]
intervals = np.linspace(start, stop, 4, dtype=int)
Expand All @@ -194,32 +212,33 @@
icas_from_other_data.append(this_ica)

###############################################################################
# Do not copy this at home! You start by reading in a collections of ICA
# solutions, something like
# Remember, don't do this at home! Start by reading in a collection of ICA
# solutions instead. Something like:
#
# ``icas = [mne.preprocessing.read_ica(fname) for fname in ica_fnames]``
print(icas_from_other_data)

###############################################################################
# use our previous ICA as reference.
# We use our original ICA as reference.
reference_ica = ica

###############################################################################
# Investigate our reference ICA, here we use the previous fit from above.
# Investigate our reference ICA:
reference_ica.plot_components()

###############################################################################
# Which one is the bad EOG component?
# Here we rely on our previous detection algorithm. You will need to decide
# yourself in that situation where no other detection is available.
# Here we rely on our previous detection algorithm. You would need to decide
# yourself if no automatic detection was available.
reference_ica.plot_sources(eog_average, exclude=eog_inds)

###############################################################################
# Indeed it looks like an EOG, also in the average time course.
#
# We construct a list so that our reference run is the first element. Then we
# can detect similar components from the other runs using corrmap. So
# our template shall be a tuple like (reference_run_index, component_index):
# We construct a list where our reference run is the first element. Then we
# can detect similar components from the other runs using
# :func:`mne.preprocessing.corrmap`. So our template must be a tuple like
# (reference_run_index, component_index):
icas = [reference_ica] + icas_from_other_data
template = (0, eog_inds[0])

Expand All @@ -229,10 +248,10 @@
show=True, threshold=.8, ch_type='mag')

###############################################################################
# Nice, we have found similar ICs from the other runs!
# Nice, we have found similar ICs from the other (simulated) runs!
# This is even nicer if we have 20 or 100 ICA solutions in a list.
#
# You can also use SSP for correcting for artifacts. It is a bit simpler,
# faster but is less precise than ICA. And it requires that you
# know the event timing of your artifact.
# You can also use SSP to correct for artifacts. It is a bit simpler and
# faster but also less precise than ICA and requires that you know the event
# timing of your artifact.
# See :ref:`tut_artifacts_correct_ssp`.

0 comments on commit 112ffac

Please sign in to comment.