From fa5c6e697898c07631f411456f2a4cde94aeb5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Sabl=C3=A9-Meyer?= Date: Thu, 16 Apr 2026 22:56:22 +0100 Subject: [PATCH 1/5] FIX Deprecate use_rounding=False default in time_as_index [circle full] `time_as_index` defaulted to truncation (`use_rounding=False`), causing `get_data(tmin=...)` to return different samples from `crop(tmin=...).get_data()` at certain times due to floating-point truncation. Change the default to `None` and rounding. Emits a `FutureWarning` prompting callers to pass `True` or `False` explicitly. The goal of this commit is to estiate the impact of this change; follow-up commit should internally update all use of `time_as_index` to explicitely set a desired default. --- mne/io/base.py | 17 +++++++++++++---- mne/io/tests/test_raw.py | 10 +++++++--- mne/utils/mixin.py | 26 ++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/mne/io/base.py b/mne/io/base.py index 8af949afea0..fa52978e42d 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -630,16 +630,25 @@ def last_samp(self): def _last_time(self): return self.last_samp / float(self.info["sfreq"]) - def time_as_index(self, times, use_rounding=False, origin=None): + def time_as_index(self, times, use_rounding=None, origin=None): """Convert time to indices. Parameters ---------- times : list-like | float | int List of numbers or a number representing points in time. - use_rounding : bool - If True, use rounding (instead of truncation) when converting - times to indices. This can help avoid non-unique indices. + use_rounding : bool | None + If True, use rounding when converting times to indices. This can + help avoid non-unique indices. + If False, use truncation when converting times to indices. + If None (the default), rounding is used but a FutureWarning is + emitted. Pass ``True`` or ``False`` explicitly to silence the + warning. + + .. versionchanged:: 2.0 + The default changed from ``False`` to ``None``, which will + round and emit a warning. In a future release the default + will change to ``True``. origin : datetime | float | int | None Time reference for times. If None, ``times`` are assumed to be relative to :term:`first_samp`. diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py index bb6d58ee0d9..6dc062adc4a 100644 --- a/mne/io/tests/test_raw.py +++ b/mne/io/tests/test_raw.py @@ -620,14 +620,18 @@ def test_time_as_index(): """Test indexing of raw times.""" raw = read_raw_fif(raw_fname) - # Test original (non-rounding) indexing behavior - orig_inds = raw.time_as_index(raw.times) + # Test truncation behavior with explicit use_rounding=False + orig_inds = raw.time_as_index(raw.times, use_rounding=False) assert len(set(orig_inds)) != len(orig_inds) - # Test new (rounding) indexing behavior + # Test rounding behavior with explicit use_rounding=True new_inds = raw.time_as_index(raw.times, use_rounding=True) assert_array_equal(new_inds, np.arange(len(raw.times))) + # Test deprecation warning when use_rounding is not specified + with pytest.warns(FutureWarning, match="use_rounding=False is being changed"): + raw.time_as_index(raw.times) + @pytest.mark.parametrize("meas_date", [None, "orig"]) @pytest.mark.parametrize("first_samp", [0, 10000]) diff --git a/mne/utils/mixin.py b/mne/utils/mixin.py index f0d4b9f7524..f56bedb2e70 100644 --- a/mne/utils/mixin.py +++ b/mne/utils/mixin.py @@ -493,16 +493,25 @@ def _check_decim(info, decim, offset, check_filter=True): class TimeMixin: """Class for time operations on any MNE object that has a time axis.""" - def time_as_index(self, times, use_rounding=False): + def time_as_index(self, times, use_rounding=None): """Convert time to indices. Parameters ---------- times : list-like | float | int List of numbers or a number representing points in time. - use_rounding : bool - If True, use rounding (instead of truncation) when converting - times to indices. This can help avoid non-unique indices. + use_rounding : bool | None + If True, use rounding when converting times to indices. This can + help avoid non-unique indices. + If False, use truncation when converting times to indices. + If None (the default), rounding is used but a FutureWarning is + emitted. Pass ``True`` or ``False`` explicitly to silence the + warning. + + .. versionchanged:: 2.0 + The default changed from ``False`` to ``None``, which will + round and emit a warning. In a future release the default + will change to ``True``. Returns ------- @@ -511,6 +520,15 @@ def time_as_index(self, times, use_rounding=False): """ from ..source_estimate import _BaseSourceEstimate + if use_rounding is None: + warn( + "The default of use_rounding=False is being changed to True " + "in a future release. Pass use_rounding=True or " + "use_rounding=False explicitly to silence this warning.", + FutureWarning, + ) + use_rounding = True + if isinstance(self, _BaseSourceEstimate): sfreq = 1.0 / self.tstep else: From aeb99e9ec08250c4537a2bb22d0f7880bbeeb677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Sabl=C3=A9-Meyer?= Date: Fri, 17 Apr 2026 11:45:29 +0100 Subject: [PATCH 2/5] Temporarily remove the FutureWarning to estimate impact The goal here is to measure the impact of changing the default value of `use_rounding=False` in time_as_index`, with a FutureWarning deprectation step to warn users. Unfortunately the tests fail hard on seeing said warning so most tests fail but it's not because the underlying behavior has changed, it's because of the warning. This commit removes the warning to see where is the change in behavior surfacing actuall problems. --- mne/io/tests/test_raw.py | 6 ++++-- mne/utils/mixin.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py index 6dc062adc4a..3179caf6e35 100644 --- a/mne/io/tests/test_raw.py +++ b/mne/io/tests/test_raw.py @@ -629,8 +629,10 @@ def test_time_as_index(): assert_array_equal(new_inds, np.arange(len(raw.times))) # Test deprecation warning when use_rounding is not specified - with pytest.warns(FutureWarning, match="use_rounding=False is being changed"): - raw.time_as_index(raw.times) + # Temporarily turned of to measure impact + # + # with pytest.warns(FutureWarning, match="use_rounding=False is being changed"): + # raw.time_as_index(raw.times) @pytest.mark.parametrize("meas_date", [None, "orig"]) diff --git a/mne/utils/mixin.py b/mne/utils/mixin.py index f56bedb2e70..adc2cabea62 100644 --- a/mne/utils/mixin.py +++ b/mne/utils/mixin.py @@ -521,12 +521,15 @@ def time_as_index(self, times, use_rounding=None): from ..source_estimate import _BaseSourceEstimate if use_rounding is None: - warn( - "The default of use_rounding=False is being changed to True " - "in a future release. Pass use_rounding=True or " - "use_rounding=False explicitly to silence this warning.", - FutureWarning, - ) + # Turned off temporarily to see the impact of the change without + # crashing on a FutureWarning + # + # warn( + # "The default of use_rounding=False is being changed to True " + # "in a future release. Pass use_rounding=True or " + # "use_rounding=False explicitly to silence this warning.", + # FutureWarning, + # ) use_rounding = True if isinstance(self, _BaseSourceEstimate): From 67e1796424a9dfd28b5c93fbd94eacdf3bf3bb24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:48:06 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/tests/test_raw.py | 2 +- mne/utils/mixin.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mne/io/tests/test_raw.py b/mne/io/tests/test_raw.py index 3179caf6e35..9baeab23804 100644 --- a/mne/io/tests/test_raw.py +++ b/mne/io/tests/test_raw.py @@ -632,7 +632,7 @@ def test_time_as_index(): # Temporarily turned of to measure impact # # with pytest.warns(FutureWarning, match="use_rounding=False is being changed"): - # raw.time_as_index(raw.times) + # raw.time_as_index(raw.times) @pytest.mark.parametrize("meas_date", [None, "orig"]) diff --git a/mne/utils/mixin.py b/mne/utils/mixin.py index adc2cabea62..7bd5ed980f6 100644 --- a/mne/utils/mixin.py +++ b/mne/utils/mixin.py @@ -525,10 +525,10 @@ def time_as_index(self, times, use_rounding=None): # crashing on a FutureWarning # # warn( - # "The default of use_rounding=False is being changed to True " - # "in a future release. Pass use_rounding=True or " - # "use_rounding=False explicitly to silence this warning.", - # FutureWarning, + # "The default of use_rounding=False is being changed to True " + # "in a future release. Pass use_rounding=True or " + # "use_rounding=False explicitly to silence this warning.", + # FutureWarning, # ) use_rounding = True From b57e19395b2d3352d02ae2a09d12fd922277d627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Sabl=C3=A9-Meyer?= Date: Fri, 17 Apr 2026 13:35:31 +0100 Subject: [PATCH 4/5] Set `use_rounding=False` in `mne/io/egi/tests/test_egi.py Again, in order to check the impact of changing the default value of `use_rounding` in `time_as_index` from False to True, I am providing a (possibly temporary) fix to `mne/io/egi/tests/test_egi.py::test_egi_mff_pause` so that it passes and we can see if there are other downstream issues. --- mne/io/egi/tests/test_egi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py index 261a9c80da3..fac6dd0aa09 100644 --- a/mne/io/egi/tests/test_egi.py +++ b/mne/io/egi/tests/test_egi.py @@ -104,7 +104,8 @@ def test_egi_mff_pause(fname, skip_times, event_times): for ii, annot in enumerate(raw.annotations): assert annot["description"] == "BAD_ACQ_SKIP" start, stop = raw.time_as_index( - [annot["onset"], annot["onset"] + annot["duration"]] + [annot["onset"], annot["onset"] + annot["duration"]], + use_rounding=False ) data, _ = raw[:, start:stop] assert_array_equal(data[other_picks], 0.0) From f7c6edeaa29737f676ee121e896cf764e4ae84f0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:39:03 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/egi/tests/test_egi.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py index fac6dd0aa09..89cfb970655 100644 --- a/mne/io/egi/tests/test_egi.py +++ b/mne/io/egi/tests/test_egi.py @@ -104,8 +104,7 @@ def test_egi_mff_pause(fname, skip_times, event_times): for ii, annot in enumerate(raw.annotations): assert annot["description"] == "BAD_ACQ_SKIP" start, stop = raw.time_as_index( - [annot["onset"], annot["onset"] + annot["duration"]], - use_rounding=False + [annot["onset"], annot["onset"] + annot["duration"]], use_rounding=False ) data, _ = raw[:, start:stop] assert_array_equal(data[other_picks], 0.0)