Repository: mne-tools/mne-python

Issue:
ENH: find peak for vector sum
Many¹ Elekta users like to analyze combined gradiometer data. `mne.viz.plot_evoked_topo` supports this via the `merge_grads` argument. Should `mne.Evoked.get_peak()` support a similar argument?


¹anecdotal evidence


Discussion:
I would say yes

I could whip up a PR.

Resolving patch:
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -14,6 +14,7 @@
 from .channels.channels import (ContainsMixin, UpdateChannelsMixin,
                                 SetChannelsMixin, InterpolationMixin,
                                 equalize_channels)
+from .channels.layout import _merge_grad_data, _pair_grad_sensors
 from .filter import resample, detrend, FilterMixin
 from .utils import (check_fname, logger, verbose, _time_mask, warn, sizeof_fmt,
                     SizeMixin, copy_function_doc_to_method_doc)
@@ -559,8 +560,8 @@ def __neg__(self):
         out.comment = '-' + (out.comment or 'unknown')
         return out
 
-    def get_peak(self, ch_type=None, tmin=None, tmax=None, mode='abs',
-                 time_as_index=False):
+    def get_peak(self, ch_type=None, tmin=None, tmax=None,
+                 mode='abs', time_as_index=False, merge_grads=False):
         """Get location and latency of peak amplitude.
 
         Parameters
@@ -582,6 +583,8 @@ def get_peak(self, ch_type=None, tmin=None, tmax=None, mode='abs',
             Defaults to 'abs'.
         time_as_index : bool
             Whether to return the time index instead of the latency in seconds.
+        merge_grads : bool
+            If True, compute peak from merged gradiometer data.
 
         Returns
         -------
@@ -611,6 +614,13 @@ def get_peak(self, ch_type=None, tmin=None, tmax=None, mode='abs',
                                'must not be `None`, pass a sensor type '
                                'value instead')
 
+        if merge_grads:
+            if ch_type != 'grad':
+                raise ValueError('Channel type must be grad for merge_grads')
+            elif mode == 'neg':
+                raise ValueError('Negative mode (mode=neg) does not make '
+                                 'sense with merge_grads=True')
+
         meg = eeg = misc = seeg = ecog = fnirs = False
         picks = None
         if ch_type in ('mag', 'grad'):
@@ -627,18 +637,25 @@ def get_peak(self, ch_type=None, tmin=None, tmax=None, mode='abs',
             fnirs = ch_type
 
         if ch_type is not None:
-            picks = pick_types(self.info, meg=meg, eeg=eeg, misc=misc,
-                               seeg=seeg, ecog=ecog, ref_meg=False,
-                               fnirs=fnirs)
-
+            if merge_grads:
+                picks = _pair_grad_sensors(self.info, topomap_coords=False)
+            else:
+                picks = pick_types(self.info, meg=meg, eeg=eeg, misc=misc,
+                                   seeg=seeg, ecog=ecog, ref_meg=False,
+                                   fnirs=fnirs)
         data = self.data
         ch_names = self.ch_names
+
         if picks is not None:
             data = data[picks]
             ch_names = [ch_names[k] for k in picks]
+
+        if merge_grads:
+            data = _merge_grad_data(data)
+            ch_names = [ch_name[:-1] + 'X' for ch_name in ch_names[::2]]
+
         ch_idx, time_idx = _get_peak(data, self.times, tmin,
                                      tmax, mode)
-
         return (ch_names[ch_idx],
                 time_idx if time_as_index else self.times[time_idx])
 


Test patch:
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -327,6 +327,10 @@ def test_get_peak():
     assert_true(time_idx < len(evoked.times))
     assert_equal(ch_name, 'MEG 1421')
 
+    assert_raises(ValueError, evoked.get_peak, ch_type='mag', merge_grads=True)
+    ch_name, time_idx = evoked.get_peak(ch_type='grad', merge_grads=True)
+    assert_equal(ch_name, 'MEG 244X')
+
     data = np.array([[0., 1.,  2.],
                      [0., -3.,  0]])