Repository: mne-tools/mne-python

Issue:
Small error in interpolation tutorial
### Proposed documentation enhancement

https://mne.tools/dev/auto_tutorials/preprocessing/15_handling_bad_channels.html#interpolation-in-mne-python

Says:

> Note that we used the exclude=[] trick in the call to [pick_types()](https://mne.tools/dev/generated/mne.io.Raw.html#mne.io.Raw.pick_types) to make sure the bad channels were not automatically dropped from the selection.

But `pick_types()` isn't used anymore (we switched to `inst.pick()`)

Discussion:
welcome[bot]: Hello! 👋 Thanks for opening your first pull request here! ❤️ We will try to get back to you soon. 🚴

wmvanvliet: thanks for taking a stab at this @btkcodedev!
hoechenberger: Thanks for your contribution! I think the beginning of the sentence must also be adjusted, since we don't pass an "exclude" parameter anywhere as far as I can see
hoechenberger: Thanks, but I don't think the sentence makes too much sense the way it's phrased now. I think it would be better to just write that "pick()" retains the bad channels. It's just how the methods works, not the justification why we used it. 
mscheltienne: @btkcodedev Could you review the sentence modification I proposed and add a changelog entry?
For the chanbgelog entry, you can refer to [this section](https://mne.tools/dev/development/contributing.html#describe-your-changes-in-the-changelog) of the contributing guide. You need to add yourself to [the author list ](https://github.com/mne-tools/mne-python/blob/main/doc/changes/names.inc)(alphabetical order) and to add a file in [this folder](https://github.com/mne-tools/mne-python/tree/main/doc/changes/devel), named `12326.other.rst`, describe the change in the file and add yourself as author, e.g.

> Short description of the changes, by :newcontrib:`Firstname Lastname`.


btkcodedev: @mscheltienne Changes done :)
hoechenberger: @wmvanvliet Could you please review again? Currently your change request (which AFAICT has been addressed) is blocking the merge. Thanks!

Patch:
diff --git a/doc/changes/devel/12326.other.rst b/doc/changes/devel/12326.other.rst
new file mode 100644
index 000000000..b8f2966bb
--- /dev/null
+++ b/doc/changes/devel/12326.other.rst
@@ -0,0 +1,1 @@
+Updated the text in the preprocessing tutorial to use :class:`mne.io.Raw.pick()` instead of the legacy :class:`mne.io.Raw.pick_types()`, by :newcontrib:`btkcodedev`.
diff --git a/doc/changes/devel/12383.newfeature.rst b/doc/changes/devel/12383.newfeature.rst
new file mode 100644
index 000000000..f896572eb
--- /dev/null
+++ b/doc/changes/devel/12383.newfeature.rst
@@ -0,0 +1,1 @@
+Add ability to detect minima peaks found in :class:`mne.Evoked` if data is all positive and maxima if data is all negative.
\ No newline at end of file
diff --git a/doc/changes/names.inc b/doc/changes/names.inc
index 811029dda..0389f75e8 100644
--- a/doc/changes/names.inc
+++ b/doc/changes/names.inc
@@ -68,6 +68,8 @@
 
 .. _Bruno Nicenboim: https://bnicenboim.github.io
 
+.. _btkcodedev: https://github.com/btkcodedev
+
 .. _buildqa: https://github.com/buildqa
 
 .. _Carlos de la Torre-Ortiz: https://ctorre.me
diff --git a/mne/evoked.py b/mne/evoked.py
index c988368c3..1f694f7c1 100644
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -914,6 +914,8 @@ class Evoked(
         time_as_index=False,
         merge_grads=False,
         return_amplitude=False,
+        *,
+        strict=True,
     ):
         """Get location and latency of peak amplitude.
 
@@ -941,6 +943,12 @@ class Evoked(
             If True, return also the amplitude at the maximum response.
 
             .. versionadded:: 0.16
+        strict : bool
+            If True, raise an error if values are all positive when detecting
+            a minimum (mode='neg'), or all negative when detecting a maximum
+            (mode='pos'). Defaults to True.
+
+            .. versionadded:: 1.7
 
         Returns
         -------
@@ -1032,7 +1040,14 @@ class Evoked(
             data, _ = _merge_ch_data(data, ch_type, [])
             ch_names = [ch_name[:-1] + "X" for ch_name in ch_names[::2]]
 
-        ch_idx, time_idx, max_amp = _get_peak(data, self.times, tmin, tmax, mode)
+        ch_idx, time_idx, max_amp = _get_peak(
+            data,
+            self.times,
+            tmin,
+            tmax,
+            mode,
+            strict=strict,
+        )
 
         out = (ch_names[ch_idx], time_idx if time_as_index else self.times[time_idx])
 
@@ -1949,7 +1964,7 @@ def _write_evokeds(fname, evoked, check=True, *, on_mismatch="raise", overwrite=
         end_block(fid, FIFF.FIFFB_MEAS)
 
 
-def _get_peak(data, times, tmin=None, tmax=None, mode="abs"):
+def _get_peak(data, times, tmin=None, tmax=None, mode="abs", *, strict=True):
     """Get feature-index and time of maximum signal from 2D array.
 
     Note. This is a 'getter', not a 'finder'. For non-evoked type
@@ -1970,6 +1985,10 @@ def _get_peak(data, times, tmin=None, tmax=None, mode="abs"):
         values will be considered. If 'neg' only negative values will
         be considered. If 'abs' absolute values will be considered.
         Defaults to 'abs'.
+    strict : bool
+        If True, raise an error if values are all positive when detecting
+        a minimum (mode='neg'), or all negative when detecting a maximum
+        (mode='pos'). Defaults to True.
 
     Returns
     -------
@@ -2008,12 +2027,12 @@ def _get_peak(data, times, tmin=None, tmax=None, mode="abs"):
 
     maxfun = np.argmax
     if mode == "pos":
-        if not np.any(data[~mask] > 0):
+        if strict and not np.any(data[~mask] > 0):
             raise ValueError(
                 "No positive values encountered. Cannot " "operate in pos mode."
             )
     elif mode == "neg":
-        if not np.any(data[~mask] < 0):
+        if strict and not np.any(data[~mask] < 0):
             raise ValueError(
                 "No negative values encountered. Cannot " "operate in neg mode."
             )
diff --git a/tutorials/preprocessing/15_handling_bad_channels.py b/tutorials/preprocessing/15_handling_bad_channels.py
index daac97976..06e9ffd6e 100644
--- a/tutorials/preprocessing/15_handling_bad_channels.py
+++ b/tutorials/preprocessing/15_handling_bad_channels.py
@@ -238,8 +238,9 @@ for title, data in zip(["orig.", "interp."], [eeg_data, eeg_data_interp]):
     fig.suptitle(title, size="xx-large", weight="bold")
 
 # %%
-# Note that we used the ``exclude=[]`` trick in the call to
-# :meth:`~mne.io.Raw.pick_types` to make sure the bad channels were not
+# Note that the method :meth:`~mne.io.Raw.pick()` default
+# arguments includes ``exclude=()`` which ensures that bad
+# channels are not
 # automatically dropped from the selection. Here is the corresponding example
 # with the interpolated gradiometer channel; since there are more channels
 # we'll use a more transparent gray color this time:


Test patch:
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
index 2c5f06460..31110596b 100644
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -589,6 +589,24 @@ def test_get_peak():
     with pytest.raises(ValueError, match="No positive values"):
         evoked_all_neg.get_peak(mode="pos")
 
+    # Test finding minimum and maximum values
+    evoked_all_neg_outlier = evoked_all_neg.copy()
+    evoked_all_pos_outlier = evoked_all_pos.copy()
+
+    # Add an outlier to the data
+    evoked_all_neg_outlier.data[0, 15] = -1e-20
+    evoked_all_pos_outlier.data[0, 15] = 1e-20
+
+    ch_name, time_idx, max_amp = evoked_all_neg_outlier.get_peak(
+        mode="pos", return_amplitude=True, strict=False
+    )
+    assert max_amp == -1e-20
+
+    ch_name, time_idx, min_amp = evoked_all_pos_outlier.get_peak(
+        mode="neg", return_amplitude=True, strict=False
+    )
+    assert min_amp == 1e-20
+
     # Test interaction between `mode` and `tmin` / `tmax`
     # For the test, create an Evoked where half of the values are negative
     # and the rest is positive