From fbb8cc5b0082ed53a03b9ad6a1e25bfed67d1705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 11:46:22 +0200 Subject: [PATCH 1/8] Add type overloads to Epochs.average() --- mne/epochs.py | 56 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index 55b256fffd2..f60e560ec30 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -10,6 +10,8 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +from __future__ import annotations + import json import operator import os.path as op @@ -18,8 +20,10 @@ from functools import partial from inspect import getfullargspec from pathlib import Path +from typing import Callable, Literal, overload import numpy as np +from numpy.typing import NDArray from scipy.interpolate import interp1d from ._fiff.constants import FIFF @@ -1070,21 +1074,49 @@ def subtract_evoked(self, evoked=None): return self + @overload + @fill_doc + def average( + self, + picks=None, + method: Literal["mean", "median"] + | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + by_event_type: Literal[False] = False, + ) -> EvokedArray: ... + + @overload + @fill_doc + def average( + self, + picks=None, + method: Literal["mean", "median"] + | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + by_event_type: Literal[True] = True, + ) -> list[EvokedArray]: ... + @fill_doc - def average(self, picks=None, method="mean", by_event_type=False): + def average( + self, + picks=None, + method: Literal["mean", "median"] + | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + by_event_type: bool = False, + ) -> EvokedArray | list[EvokedArray]: """Compute an average over epochs. Parameters ---------- %(picks_all_data)s - method : str | callable - How to combine the data. If "mean"/"median", the mean/median - are returned. - Otherwise, must be a callable which, when passed an array of shape - (n_epochs, n_channels, n_time) returns an array of shape - (n_channels, n_time). - Note that due to file type limitations, the kind for all - these will be "average". + method : "mean" | "median" | callable + How to average the data across epochs, time-point by time-point. + Pass ``"mean"`` for the arithmeic mean, or ``"median"`` for the median. + + .. note:: In typical ERP and ERF analyses, `"mean"`` (the default) should + be used. + + Can also be a function accepting an array of shape + ``(n_epochs, n_channels, n_time)`` and returning an array of shape + ``(n_channels, n_time)`` (i.e., collapsing the epochs dimensioon). %(by_event_type)s Returns @@ -1097,7 +1129,7 @@ def average(self, picks=None, method="mean", by_event_type=False): they correspond to different conditions. To average by condition, do ``epochs[condition].average()`` for each condition separately. - When picks is None and epochs contain only ICA channels, no channels + When ``picks`` is ``None`` and epochs contain only ICA channels, no channels are selected, resulting in an error. This is because ICA channels are not considered data channels (they are of misc type) and only data channels are selected when picks is None. @@ -1110,6 +1142,10 @@ def average(self, picks=None, method="mean", by_event_type=False): >>> epochs.average(method=trim) # doctest:+SKIP This would compute the trimmed mean. + + The "kind" for all these operations, including custom functions, will be + labeled as "average" when writing the data to disk, due to limitations of the + FIFF file format. """ self._handle_empty("raise", "average") if by_event_type: From e5ded4c571449f50e2a05726b9ab0557d291a694 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 09:47:27 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/epochs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index f60e560ec30..10f37659c19 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1080,7 +1080,7 @@ def average( self, picks=None, method: Literal["mean", "median"] - | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + | Callable[[NDArray[np.float64]], NDArray[np.float64]] = "mean", by_event_type: Literal[False] = False, ) -> EvokedArray: ... @@ -1090,7 +1090,7 @@ def average( self, picks=None, method: Literal["mean", "median"] - | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + | Callable[[NDArray[np.float64]], NDArray[np.float64]] = "mean", by_event_type: Literal[True] = True, ) -> list[EvokedArray]: ... @@ -1099,7 +1099,7 @@ def average( self, picks=None, method: Literal["mean", "median"] - | Callable[[NDArray[np.float_]], NDArray[np.float_]] = "mean", + | Callable[[NDArray[np.float64]], NDArray[np.float64]] = "mean", by_event_type: bool = False, ) -> EvokedArray | list[EvokedArray]: """Compute an average over epochs. @@ -4235,7 +4235,7 @@ def _read_one_epoch_file(f, tree, preload): @verbose -def read_epochs(fname, proj=True, preload=True, verbose=None) -> "EpochsFIF": +def read_epochs(fname, proj=True, preload=True, verbose=None) -> EpochsFIF: """Read epochs from a fif file. Parameters From 9bedf0f8dcf6c5fc33d7554b1e619d1a29c2af72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 11:50:00 +0200 Subject: [PATCH 3/8] Remove fill_doc decorators --- mne/epochs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index f60e560ec30..b0e174f856d 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1075,7 +1075,6 @@ def subtract_evoked(self, evoked=None): return self @overload - @fill_doc def average( self, picks=None, @@ -1085,7 +1084,6 @@ def average( ) -> EvokedArray: ... @overload - @fill_doc def average( self, picks=None, From 8df00fa186a76ece3b5c65df037fca3115c8f453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 11:52:10 +0200 Subject: [PATCH 4/8] Update mne/epochs.py --- mne/epochs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/epochs.py b/mne/epochs.py index a109db06cd4..a1d66aa65ed 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1109,7 +1109,7 @@ def average( How to average the data across epochs, time-point by time-point. Pass ``"mean"`` for the arithmeic mean, or ``"median"`` for the median. - .. note:: In typical ERP and ERF analyses, `"mean"`` (the default) should + .. note:: In typical ERP and ERF analyses, ``"mean"`` (the default) should be used. Can also be a function accepting an array of shape From e7465998ecba4afbd17bd7aa29770b24396d9e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 13:06:22 +0200 Subject: [PATCH 5/8] Fix docstring --- mne/epochs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index a1d66aa65ed..d2bfe6ecad9 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1109,8 +1109,8 @@ def average( How to average the data across epochs, time-point by time-point. Pass ``"mean"`` for the arithmeic mean, or ``"median"`` for the median. - .. note:: In typical ERP and ERF analyses, ``"mean"`` (the default) should - be used. + .. note:: In typical ERP and ERF analyses, ``"mean"`` (the default) should + be used. Can also be a function accepting an array of shape ``(n_epochs, n_channels, n_time)`` and returning an array of shape From 33581a4d09ed313c638a37c074641e5844375131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 13:12:25 +0200 Subject: [PATCH 6/8] Add changelog entry --- doc/changes/devel/newfeature.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/changes/devel/newfeature.rst diff --git a/doc/changes/devel/newfeature.rst b/doc/changes/devel/newfeature.rst new file mode 100644 index 00000000000..e3027de2ba7 --- /dev/null +++ b/doc/changes/devel/newfeature.rst @@ -0,0 +1,4 @@ +When creating :class:`~mne.Evoked` by averaging :class:`mne.Epochs` via the :meth:`~mne.Epochs.average` +method, static analysis tools like Pylance will now correctly whether a list of :class:`~mne.EvokedArray` +or a single :class:`~mne.EvokedArray` is returned that a `pathlib.Path`, enabling better editor support like +automated code completions on the returned object, by `Richard Höchenberger`_. \ No newline at end of file From 1a76f24ff18f4b693f9dc4e54e9dc7c52013e730 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:12:58 +0000 Subject: [PATCH 7/8] [autofix.ci] apply automated fixes --- doc/changes/devel/{newfeature.rst => 12769.newfeature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changes/devel/{newfeature.rst => 12769.newfeature.rst} (100%) diff --git a/doc/changes/devel/newfeature.rst b/doc/changes/devel/12769.newfeature.rst similarity index 100% rename from doc/changes/devel/newfeature.rst rename to doc/changes/devel/12769.newfeature.rst From 502d0390897960d83d3f986522213836ec8d194f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20H=C3=B6chenberger?= Date: Fri, 2 Aug 2024 13:13:27 +0200 Subject: [PATCH 8/8] Fix typo --- doc/changes/devel/newfeature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/devel/newfeature.rst b/doc/changes/devel/newfeature.rst index e3027de2ba7..9ebdc0a73c8 100644 --- a/doc/changes/devel/newfeature.rst +++ b/doc/changes/devel/newfeature.rst @@ -1,4 +1,4 @@ When creating :class:`~mne.Evoked` by averaging :class:`mne.Epochs` via the :meth:`~mne.Epochs.average` -method, static analysis tools like Pylance will now correctly whether a list of :class:`~mne.EvokedArray` +method, static analysis tools like Pylance will now correctly infer whether a list of :class:`~mne.EvokedArray` or a single :class:`~mne.EvokedArray` is returned that a `pathlib.Path`, enabling better editor support like automated code completions on the returned object, by `Richard Höchenberger`_. \ No newline at end of file