Repository: mne-tools/mne-python

Issue:
ENH: add option to average time bins when plotting evoked topomaps
@agramfort I think this would be chouette. 



Discussion:

Resolving patch:
diff --git a/examples/plot_evoked_topomap.py b/examples/plot_evoked_topomap.py
--- a/examples/plot_evoked_topomap.py
+++ b/examples/plot_evoked_topomap.py
@@ -33,10 +33,13 @@
 # plot magnetometer data as topomaps
 evoked.plot_topomap(times, ch_type='mag')
 
+# compute a 50 ms bin to stabilize topographies
+evoked.plot_topomap(times, ch_type='mag', average=0.05)
+
 # plot gradiometer data (plots the RMS for each pair of gradiometers)
 evoked.plot_topomap(times, ch_type='grad')
 
-# plot magnetometer data as topomap at 1 time point : 100ms
+# plot magnetometer data as topomap at 1 time point : 100 ms
 # and add channel labels and title
 evoked.plot_topomap(0.1, ch_type='mag', show_names=True, colorbar=False,
                     size=6, res=128, title='Auditory response')
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -268,7 +268,6 @@ def __init__(self, fname, condition=None, baseline=None, proj=True,
         # Run baseline correction
         self.data = rescale(self.data, times, baseline, 'mean', copy=False)
 
-
     def save(self, fname):
         """Save dataset to file.
 
@@ -433,7 +432,7 @@ def plot_topomap(self, times=None, ch_type='mag', layout=None, vmin=None,
                      format="%3.1f", time_format='%01d ms', proj=False,
                      show=True, show_names=False, title=None, mask=None,
                      mask_params=None, outlines='head', contours=6,
-                     image_interp='bilinear'):
+                     image_interp='bilinear', average=None):
         """Plot topographic maps of specific time points
 
         Parameters
@@ -515,7 +514,11 @@ def plot_topomap(self, times=None, ch_type='mag', layout=None, vmin=None,
         image_interp : str
             The image interpolation to be used. All matplotlib options are
             accepted.
-
+        average : float | None
+            The time window around a given time to be used for averaging
+            (seconds). For example, 0.01 would translate into window that
+            starts 5 ms before and ends 5 ms after a given time point.
+            Defaults to None, which means no averaging.
         """
         return plot_evoked_topomap(self, times=times, ch_type=ch_type,
                                    layout=layout, vmin=vmin,
@@ -528,7 +531,8 @@ def plot_topomap(self, times=None, ch_type='mag', layout=None, vmin=None,
                                    title=title, mask=mask,
                                    mask_params=mask_params,
                                    outlines=outlines, contours=contours,
-                                   image_interp=image_interp)
+                                   image_interp=image_interp,
+                                   average=average)
 
     def plot_field(self, surf_maps, time=None, time_label='t = %0.0f ms',
                    n_jobs=1):
diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py
--- a/mne/viz/topomap.py
+++ b/mne/viz/topomap.py
@@ -66,8 +66,8 @@ def _prepare_topo_plot(obj, ch_type, layout):
 
     ch_names = [info['ch_names'][k] for k in picks]
     if merge_grads:
-        # change names so that vectorview combined grads appear as MEG014x instead
-        # of MEG0142 or MEG0143 which are the 2 planar grads.
+        # change names so that vectorview combined grads appear as MEG014x
+        # instead of MEG0142 or MEG0143 which are the 2 planar grads.
         ch_names = [ch_names[k][:-1] + 'x' for k in range(0, len(ch_names), 2)]
 
     return picks, pos, merge_grads, ch_names
@@ -237,8 +237,8 @@ def _check_outlines(pos, outlines, head_scale=0.85):
         # Define the outline of the head, ears and nose
         if outlines is not None:
             outlines = dict(head=(head_x, head_y), nose=(nose_x, nose_y),
-                            ear_left=(ear_x,  ear_y),
-                            ear_right=(-ear_x,  ear_y))
+                            ear_left=(ear_x, ear_y),
+                            ear_right=(-ear_x, ear_y))
         else:
             outlines = dict()
 
@@ -825,7 +825,7 @@ def plot_evoked_topomap(evoked, times=None, ch_type='mag', layout=None,
                         time_format='%01d ms', proj=False, show=True,
                         show_names=False, title=None, mask=None,
                         mask_params=None, outlines='head', contours=6,
-                        image_interp='bilinear'):
+                        image_interp='bilinear', average=None):
     """Plot topographic maps of specific time points of evoked data
 
     Parameters
@@ -908,6 +908,11 @@ def plot_evoked_topomap(evoked, times=None, ch_type='mag', layout=None,
     image_interp : str
         The image interpolation to be used. All matplotlib options are
         accepted.
+    average : float | None
+        The time window around a given time to be used for averaging (seconds).
+        For example, 0.01 would translate into window that starts 5 ms before
+        and ends 5 ms after a given time point. Defaults to None, which means
+        no averaging.
     """
     import matplotlib.pyplot as plt
 
@@ -959,8 +964,26 @@ def plot_evoked_topomap(evoked, times=None, ch_type='mag', layout=None,
         data = evoked.copy().apply_proj().data
     else:
         data = evoked.data
+    if average is None:
+        data = data[np.ix_(picks, time_idx)]
+    elif isinstance(average, float):
+        if not average > 0:
+            raise ValueError('The average parameter must be positive. You '
+                             'passed a negative value')
+        data_ = np.zeros((len(picks), len(time_idx)))
+        ave_time = float(average) / 2.
+        iter_times = evoked.times[time_idx]
+        for ii, (idx, tmin_, tmax_) in enumerate(zip(time_idx,
+                                                     iter_times - ave_time,
+                                                     iter_times + ave_time)):
+            my_range = (tmin_ < evoked.times) & (evoked.times < tmax_)
+            data_[:, ii] = data[picks][:, my_range].mean(-1)
+        data = data_
+    else:
+        raise ValueError('The average parameter must be None or a float.'
+                         'Check your input.')
 
-    data = data[np.ix_(picks, time_idx)] * scale
+    data *= scale
     if merge_grads:
         from ..layouts.layout import _merge_grad_data
         data = _merge_grad_data(data)


Test patch:
diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py
--- a/mne/viz/tests/test_topomap.py
+++ b/mne/viz/tests/test_topomap.py
@@ -68,6 +68,10 @@ def test_plot_topomap():
         evoked.plot_topomap(times, ch_type='planar2', res=res)
         evoked.plot_topomap(times, ch_type='grad', mask=mask, res=res,
                             show_names=True, mask_params={'marker': 'x'})
+        assert_raises(ValueError, evoked.plot_topomap, times, ch_type='eeg',
+                      res=res, average=-1000)
+        assert_raises(ValueError, evoked.plot_topomap, times, ch_type='eeg',
+                      res=res, average='hahahahah')
 
         p = evoked.plot_topomap(times, ch_type='grad', res=res,
                                 show_names=lambda x: x.replace('MEG', ''),
@@ -83,7 +87,7 @@ def get_texts(p):
             return [x.get_text() for x in p.get_children() if
                     isinstance(x, matplotlib.text.Text)]
 
-        p = evoked.plot_topomap(times, ch_type='eeg', res=res)
+        p = evoked.plot_topomap(times, ch_type='eeg', res=res, average=0.01)
         assert_equal(len(get_texts(p)), 0)
         p = evoked.plot_topomap(times, ch_type='eeg', title='Custom', res=res)
         texts = get_texts(p)