Repository: mne-tools/mne-python

Issue:
ENH: make _get_ica_map a public method of ICA
We ever so often get people wondering how to retrieve ICA maps as numpy arrays, and I keep referring them to mne.preprocessing.ica._get_ica_map. It would probably be easier to make it a public method of ICA. Opinions?



Discussion:
+1 but maybe get it as an EvokedArray to keep channel names?

ica.get_components ?

> get it as an EvokedArray to keep channel names

For the use cases I've come across, that'd be unnecessary and most of the time users would have to do ica.get_components().data in that case. See e.g. #3460, #2404 and the mailing list one.

@mmagnuski opinion? 

Yes, in this case I also think simpler is better - the channel names are in ica object and in most cases one just needs to get n_channels by n_components arrays so exactly the way `_get_ica_map` works now.

@agramfort okay? `ica.get_components()` to simply return an array?

We can also give it a single parameter to optionally return either an Evoked object, or e.g. a pandas dataframe.

or an xarray ;)
probably would be nice to have a param to choose between getting mixing or unmixing matrix?

yes fine with array if you can make a valid EvokedArray with something like

mne.EvokedArray(ica.get_components, ica.info, tmin=0.)

to be added as a test.


Resolving patch:
diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py
--- a/mne/preprocessing/ica.py
+++ b/mne/preprocessing/ica.py
@@ -662,6 +662,18 @@ def _transform_evoked(self, evoked):
 
         return sources
 
+    def get_components(self):
+        """Get ICA topomap for components as numpy arrays.
+
+        Returns
+        -------
+        components : array (n_channels, n_components)
+            The ICA components (maps).
+        """
+        fast_dot = _get_fast_dot()
+        return fast_dot(self.mixing_matrix_[:, :self.n_components_].T,
+                        self.pca_components_[:self.n_components_]).T
+
     def get_sources(self, inst, add_channels=None, start=None, stop=None):
         """Estimate sources given the unmixing matrix.
 
@@ -2149,16 +2161,6 @@ def _band_pass_filter(ica, sources, target, l_freq, h_freq, verbose=None):
 # #############################################################################
 # CORRMAP
 
-def _get_ica_map(ica, components=None):
-    """Get ICA topomap for components."""
-    fast_dot = _get_fast_dot()
-    if components is None:
-        components = list(range(ica.n_components_))
-    maps = fast_dot(ica.mixing_matrix_[:, components].T,
-                    ica.pca_components_[:ica.n_components_])
-    return maps
-
-
 def _find_max_corrs(all_maps, target, threshold):
     """Compute correlations between template and target components."""
     all_corrs = [compute_corr(target, subj.T) for subj in all_maps]
@@ -2352,7 +2354,7 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
     if threshold == 'auto':
         threshold = np.arange(60, 95, dtype=np.float64) / 100.
 
-    all_maps = [_get_ica_map(ica) for ica in icas]
+    all_maps = [ica.get_components().T for ica in icas]
 
     # check if template is an index to one IC in one ICA object, or an array
     if len(template) == 2:
@@ -2422,7 +2424,7 @@ def corrmap(icas, template, threshold="auto", label=None, ch_type="eeg",
                 ica.labels_[label] = list(set(list(max_corr) +
                                           ica.labels_.get(label, list())))
             if plot is True:
-                allmaps.extend(_get_ica_map(ica, components=max_corr))
+                allmaps.extend(ica.get_components()[:, max_corr].T)
                 subjs.extend([ii] * len(max_corr))
                 indices.extend(max_corr)
         else:
diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py
--- a/mne/viz/topomap.py
+++ b/mne/viz/topomap.py
@@ -727,7 +727,6 @@ def _plot_ica_topomap(ica, idx=0, ch_type=None, res=64, layout=None,
     """Plot single ica map to axes."""
     import matplotlib as mpl
     from ..channels import _get_ch_type
-    from ..preprocessing.ica import _get_ica_map
 
     if ica.info is None:
         raise RuntimeError('The ICA\'s measurement info is missing. Please '
@@ -737,7 +736,7 @@ def _plot_ica_topomap(ica, idx=0, ch_type=None, res=64, layout=None,
                          'got %s instead.' % type(axes))
     ch_type = _get_ch_type(ica, ch_type)
 
-    data = _get_ica_map(ica, components=idx)
+    data = ica.get_components()[:, idx]
     data_picks, pos, merge_grads, names, _ = _prepare_topo_plot(
         ica, ch_type, layout)
     pos, outlines = _check_outlines(pos, outlines, head_pos)
diff --git a/tutorials/plot_artifacts_correction_ica.py b/tutorials/plot_artifacts_correction_ica.py
--- a/tutorials/plot_artifacts_correction_ica.py
+++ b/tutorials/plot_artifacts_correction_ica.py
@@ -236,20 +236,30 @@
 # Indeed it looks like an EOG, also in the average time course.
 #
 # 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
+# can detect similar components from the other runs (the other ICA objects)
+# 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])
 
 ###############################################################################
-# Now we can do the corrmap.
+# Now we can run the CORRMAP algorithm.
 fig_template, fig_detected = corrmap(icas, template=template, label="blinks",
                                      show=True, threshold=.8, ch_type='mag')
 
 ###############################################################################
 # 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.
+# In this way, you can detect a type of artifact semi-automatically for example
+# for all subjects in a study.
+# The detected template can also be retrieved as an array and stored; this
+# array can be used as an alternative template to
+# :func:`mne.preprocessing.corrmap`.
+eog_component = reference_ica.get_components()[:, eog_inds[0]]
+
+# If you calculate a new ICA solution, you can provide this array instead of
+# specifying the template in reference to the list of ICA objects you want
+# to run CORRMAP on. (Of course, the retrieved component map arrays can
+# also be used for other purposes than artifact correction.)
 #
 # 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


Test patch:
diff --git a/mne/preprocessing/tests/test_ica.py b/mne/preprocessing/tests/test_ica.py
--- a/mne/preprocessing/tests/test_ica.py
+++ b/mne/preprocessing/tests/test_ica.py
@@ -16,12 +16,13 @@
 from scipy import stats
 from itertools import product
 
-from mne import Epochs, read_events, pick_types, create_info, EpochsArray
+from mne import (Epochs, read_events, pick_types, create_info, EpochsArray,
+                 EvokedArray)
 from mne.cov import read_cov
 from mne.preprocessing import (ICA, ica_find_ecg_events, ica_find_eog_events,
                                read_ica, run_ica)
-from mne.preprocessing.ica import (get_score_funcs, corrmap, _get_ica_map,
-                                   _ica_explained_variance, _sort_components)
+from mne.preprocessing.ica import (get_score_funcs, corrmap, _sort_components,
+                                   _ica_explained_variance)
 from mne.io import read_raw_fif, Info, RawArray
 from mne.io.meas_info import _kind_dict
 from mne.io.pick import _DATA_CH_TYPES_SPLIT
@@ -321,10 +322,15 @@ def test_ica_additional():
     corrmap([ica, ica2], (0, 0), threshold=2, plot=False, show=False)
     assert_true(ica.labels_["blinks"] == ica2.labels_["blinks"])
     assert_true(0 in ica.labels_["blinks"])
-    template = _get_ica_map(ica)[0]
+    # test retrieval of component maps as arrays
+    components = ica.get_components()
+    template = components[:, 0]
+    EvokedArray(components, ica.info, tmin=0.).plot_topomap([0])
+
     corrmap([ica, ica3], template, threshold='auto', label='blinks', plot=True,
             ch_type="mag")
     assert_true(ica2.labels_["blinks"] == ica3.labels_["blinks"])
+
     plt.close('all')
 
     # test warnings on bad filenames