From 03d78f43bf46e98cc4e2905c6e98e6190cf01924 Mon Sep 17 00:00:00 2001 From: Jacob Woessner Date: Wed, 24 Jan 2024 12:43:52 -0600 Subject: [PATCH] minimum/maximum value of the all-positive/negative data (#12383) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Larson --- doc/changes/devel/12383.newfeature.rst | 1 + mne/evoked.py | 27 ++++++++++++++++++++++---- mne/tests/test_evoked.py | 18 +++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 doc/changes/devel/12383.newfeature.rst diff --git a/doc/changes/devel/12383.newfeature.rst b/doc/changes/devel/12383.newfeature.rst new file mode 100644 index 00000000000..f896572eb93 --- /dev/null +++ b/doc/changes/devel/12383.newfeature.rst @@ -0,0 +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/mne/evoked.py b/mne/evoked.py index c988368c314..1f694f7c11b 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -914,6 +914,8 @@ def get_peak( time_as_index=False, merge_grads=False, return_amplitude=False, + *, + strict=True, ): """Get location and latency of peak amplitude. @@ -941,6 +943,12 @@ def get_peak( 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 @@ def get_peak( 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/mne/tests/test_evoked.py b/mne/tests/test_evoked.py index 2c5f064606d..31110596be6 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