Repository: mne-tools/mne-python

Issue:
Amplitude of interpolated channels very small
Hello,

I encountered some weird behavior regarding the interpolation of bad channels using MNE.

In this [Dropbox folder](https://www.dropbox.com/sh/11rjc45bbg2b38m/AACUW6rtZKcJKitEMfbMIKVqa?dl=0) are two examples of data sets before interpolation and after interpolation (marked with 'bcc' in the filename). MEG channels 7, 10, 142, and 156 are dead in the original data sets. After interpolation, the channels still appear to be dead/ flat when plotting the data, however, looking at the raw data for these channels we can see that the values are now different from zero. Redoing the interpolation yields the same result for these data sets.

This is how you can reproduce my results:

    import mne
    raw_fname = '/path/to/file'
    raw = mne.io.Raw(raw_fname, preload=True)
    data = raw.get_data()

    # mark bads during plot
    raw.plot(block=True)
    raw.interpolate_bads(reset_bads=True)

    # plot result
    raw.plot(block=True)

    # compare raw data
    data_bcc = raw.get_data()
    plot data[6], data_bcc[6]

To test this further I selected three channels randomly (50, 126, 226) to be interpolated in addition to the dead channels.
For the empty room data set, channels 126 and 226 appear to be dead in the resulting plot. If I interpolate channel 50 I can clearly see variation in the plot, however, the amplitude seems to be only about half the size of the surrounding channels.
For the other data set all channels appear to be dead in the resulting plot.

If you need more information please let me know.


Discussion:
I talked to a colleague and he might have an idea what causes the problem with the interpolation. I will try to convey his thoughts.
`_interpolate_bads_meg()` calls `mapping = _map_meg_channels(info_from, info_to, mode=mode)` which implicitly uses  `_map_meg_channels(..., origin=(0., 0., 0.04))`

Changing the call to
`mapping = _map_meg_channels(info_from, info_to, mode=mode, origin=(0., 0., 0.01))`
has a fairly significant impact on the result.

The reason why this might help is because the default head-coordinate system used in Jülich is tilted by 45deg about the PA-axis.  point (0. ,0., 0.04) tends to be somewhere near the cortex.

We should probably expose this `origin` parameter to make it explicit that it matters

Resolving patch:
diff --git a/mne/channels/channels.py b/mne/channels/channels.py
--- a/mne/channels/channels.py
+++ b/mne/channels/channels.py
@@ -888,7 +888,7 @@ class InterpolationMixin(object):
 
     @verbose
     def interpolate_bads(self, reset_bads=True, mode='accurate',
-                         verbose=None):
+                         origin=(0., 0., 0.04), verbose=None):
         """Interpolate bad MEG and EEG channels.
 
         Operates in place.
@@ -901,6 +901,12 @@ def interpolate_bads(self, reset_bads=True, mode='accurate',
             Either ``'accurate'`` or ``'fast'``, determines the quality of the
             Legendre polynomial expansion used for interpolation of MEG
             channels.
+        origin : array-like, shape (3,) | str
+            Origin of the sphere in the head coordinate frame and in meters.
+            Can be ``'auto'``, which means a head-digitization-based origin
+            fit. Default is ``(0., 0., 0.04)``.
+
+            .. versionadded:: 0.17
         verbose : bool, str, int, or None
             If not None, override default verbose level (see
             :func:`mne.verbose` and :ref:`Logging documentation <tut_logging>`
@@ -924,7 +930,7 @@ def interpolate_bads(self, reset_bads=True, mode='accurate',
             return self
 
         _interpolate_bads_eeg(self)
-        _interpolate_bads_meg(self, mode=mode)
+        _interpolate_bads_meg(self, mode=mode, origin=origin)
 
         if reset_bads is True:
             self.info['bads'] = []
diff --git a/mne/channels/interpolation.py b/mne/channels/interpolation.py
--- a/mne/channels/interpolation.py
+++ b/mne/channels/interpolation.py
@@ -156,7 +156,8 @@ def _interpolate_bads_eeg(inst, verbose=None):
 
 
 @verbose
-def _interpolate_bads_meg(inst, mode='accurate', verbose=None):
+def _interpolate_bads_meg(inst, mode='accurate', origin=(0., 0., 0.04),
+                          verbose=None):
     """Interpolate bad channels from data in good channels.
 
     Parameters
@@ -167,6 +168,10 @@ def _interpolate_bads_meg(inst, mode='accurate', verbose=None):
         Either `'accurate'` or `'fast'`, determines the quality of the
         Legendre polynomial expansion used for interpolation. `'fast'` should
         be sufficient for most applications.
+    origin : array-like, shape (3,) | str
+        Origin of the sphere in the head coordinate frame and in meters.
+        Can be ``'auto'``, which means a head-digitization-based origin
+        fit. Default is ``(0., 0., 0.04)``.
     verbose : bool, str, int, or None
         If not None, override default verbose level (see :func:`mne.verbose`
         and :ref:`Logging documentation <tut_logging>` for more).
@@ -192,5 +197,5 @@ def _interpolate_bads_meg(inst, mode='accurate', verbose=None):
     inst_info['comps'] = []
     info_from = pick_info(inst_info, picks_good)
     info_to = pick_info(inst_info, picks_bad)
-    mapping = _map_meg_channels(info_from, info_to, mode=mode)
+    mapping = _map_meg_channels(info_from, info_to, mode=mode, origin=origin)
     _do_interp_dots(inst, mapping, picks_good, picks_bad)
diff --git a/mne/forward/_field_interpolation.py b/mne/forward/_field_interpolation.py
--- a/mne/forward/_field_interpolation.py
+++ b/mne/forward/_field_interpolation.py
@@ -118,6 +118,10 @@ def _map_meg_channels(info_from, info_to, mode='fast', origin=(0., 0., 0.04)):
         Either `'accurate'` or `'fast'`, determines the quality of the
         Legendre polynomial expansion used. `'fast'` should be sufficient
         for most applications.
+    origin : array-like, shape (3,) | str
+        Origin of the sphere in the head coordinate frame and in meters.
+        Can be ``'auto'``, which means a head-digitization-based origin
+        fit. Default is ``(0., 0., 0.04)``.
 
     Returns
     -------
@@ -239,9 +243,9 @@ def _make_surface_mapping(info, surf, ch_type='meg', trans=None, mode='fast',
     n_jobs : int
         Number of permutations to run in parallel (requires joblib package).
     origin : array-like, shape (3,) | str
-        Origin of internal and external multipolar moment space in head
-        coords and in meters. The default is ``'auto'``, which means
-        a head-digitization-based origin fit.
+        Origin of the sphere in the head coordinate frame and in meters.
+        The default is ``'auto'``, which means a head-digitization-based
+        origin fit.
     verbose : bool, str, int, or None
         If not None, override default verbose level (see :func:`mne.verbose`
         and :ref:`Logging documentation <tut_logging>` for more).
@@ -363,20 +367,17 @@ def make_field_map(evoked, trans='auto', subject=None, subjects_dir=None,
         Should be ``'helmet'`` or ``'head'`` to specify in which surface
         to compute the MEG field map. The default value is ``'helmet'``
     origin : array-like, shape (3,) | str
-        Origin of internal and external multipolar moment space in head
-        coords and in meters. Can be ``'auto'``, which means
-        a head-digitization-based origin fit. Default is ``(0., 0., 0.04)``.
+        Origin of the sphere in the head coordinate frame and in meters.
+        Can be ``'auto'``, which means a head-digitization-based origin
+        fit. Default is ``(0., 0., 0.04)``.
 
         .. versionadded:: 0.11
-
     n_jobs : int
         The number of jobs to run in parallel.
     verbose : bool, str, int, or None
         If not None, override default verbose level (see :func:`mne.verbose`
         and :ref:`Logging documentation <tut_logging>` for more).
 
-        .. versionadded:: 0.11
-
     Returns
     -------
     surf_maps : list


Test patch:
diff --git a/mne/channels/tests/test_interpolation.py b/mne/channels/tests/test_interpolation.py
--- a/mne/channels/tests/test_interpolation.py
+++ b/mne/channels/tests/test_interpolation.py
@@ -145,10 +145,10 @@ def test_interpolation():
     assert np.corrcoef(data1, data2)[0, 1] > thresh
     assert len(epochs_meg.info['bads']) == 0
 
-    # MEG -- evoked
+    # MEG -- evoked (plus auto origin)
     data1 = evoked.data[pick]
     evoked.info.normalize_proj()
-    data2 = evoked.interpolate_bads().data[pick]
+    data2 = evoked.interpolate_bads(origin='auto').data[pick]
     assert np.corrcoef(data1, data2)[0, 1] > thresh