diff --git a/.circleci/config.yml b/.circleci/config.yml index a8943cc0094..6f0f8cc6957 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,7 +116,7 @@ jobs: - uv-cache-0 - restore_cache: keys: - - user-install-bin-cache-310 + - user-install-bin-cache-311 # Hack in uninstalls of libraries as necessary if pip doesn't do the right thing in upgrading for us... - run: @@ -129,9 +129,9 @@ jobs: paths: - ~/.cache/uv - save_cache: - key: user-install-bin-cache-310 + key: user-install-bin-cache-311 paths: - - ~/.local/lib/python3.10/site-packages + - ~/.local/lib/python3.11/site-packages - ~/.local/bin - run: diff --git a/.github/workflows/scheduled_updates.yml b/.github/workflows/scheduled_updates.yml index 71f2e5f4040..11407858aa4 100644 --- a/.github/workflows/scheduled_updates.yml +++ b/.github/workflows/scheduled_updates.yml @@ -73,7 +73,7 @@ jobs: # uv pip compile requires setting the python version explicitly in the command :( run: | uv pip compile pyproject.toml \ - --python "3.10" --python-platform "x86_64-unknown-linux-gnu" \ + --python "3.11" --python-platform "x86_64-unknown-linux-gnu" \ --group test --group lockfile_extras \ --resolution lowest-direct \ --format pylock.toml --output-file tools/pylock.ci-old.toml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 561c89598e9..8b0763afd7f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,7 +66,7 @@ jobs: python: '3.12' kind: minimal - os: ubuntu-22.04 - python: '3.10' + python: '3.11' kind: old - os: ubuntu-latest python: '3.14t' # free-threaded diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 696687b4ff7..85aa852d912 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,6 @@ repos: - pymef # for mne/io/mef - quantities # for mne/io/neuralynx - scipy - - typing_extensions # TODO VERSION remove once we require Python 3.11 args: [--no-python-downloads, --no-project] # Codespell diff --git a/doc/changes/dev/14140.dependency.rst b/doc/changes/dev/14140.dependency.rst new file mode 100644 index 00000000000..2bdabad0dc5 --- /dev/null +++ b/doc/changes/dev/14140.dependency.rst @@ -0,0 +1 @@ +Updated minimum Python version to 3.11, by `Thomas Binns`_. \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py index 6cec4f97214..875b88c7935 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -13,7 +13,8 @@ import os import subprocess import sys -from datetime import datetime, timezone +import tomllib +from datetime import UTC, datetime from importlib.metadata import metadata from pathlib import Path @@ -58,7 +59,7 @@ # -- Project information ----------------------------------------------------- project = "MNE" -td = datetime.now(tz=timezone.utc) +td = datetime.now(tz=UTC) # We need to triage which date type we use so that incremental builds work # (Sphinx looks at variable changes and rewrites all files if some change) @@ -443,17 +444,11 @@ "pooch.HTTPDownloader", } numpydoc_validate = True -try: - import tomllib - # TODO VERSION: Can be removed once Python 3.11 is required -except Exception: - pass -else: - pyproject_path = Path(__file__).parent.parent / "pyproject.toml" - pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) - pyproject_nv = pyproject["tool"]["numpydoc_validation"] - numpydoc_validation_checks = set(pyproject_nv["checks"]) - numpydoc_validation_exclude = set(pyproject_nv["exclude"]) +pyproject_path = Path(__file__).parent.parent / "pyproject.toml" +pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) +pyproject_nv = pyproject["tool"]["numpydoc_validation"] +numpydoc_validation_checks = set(pyproject_nv["checks"]) +numpydoc_validation_exclude = set(pyproject_nv["exclude"]) # -- Sphinx-gallery configuration -------------------------------------------- diff --git a/environment.yml b/environment.yml index 4bcba1108cc..491b7aa3c52 100644 --- a/environment.yml +++ b/environment.yml @@ -3,7 +3,7 @@ name: mne channels: - conda-forge dependencies: - - python >=3.10 + - python >=3.11 - antio >=0.5.0 - conda - curryreader >=0.1.2 diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index bf0a8882759..176091c9a0b 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -2010,7 +2010,7 @@ def _check_consistency(self, prepend_error=""): if ( not isinstance(self["meas_date"], datetime.datetime) or self["meas_date"].tzinfo is None - or self["meas_date"].tzinfo is not datetime.timezone.utc + or self["meas_date"].tzinfo is not datetime.UTC ): raise RuntimeError( f'{prepend_error}info["meas_date"] must be a datetime object in UTC' @@ -3777,9 +3777,7 @@ def anonymize_info(info, daysback=None, keep_his=False, verbose=None): for field in keep_fields: _check_option("keep_his", field, valid_fields) - default_anon_dos = datetime.datetime( - 2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + default_anon_dos = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) default_str = "mne_anonymize" default_subject_id = 0 default_sex = 0 diff --git a/mne/_fiff/tests/test_meas_info.py b/mne/_fiff/tests/test_meas_info.py index 8446c232acc..108060e2e2f 100644 --- a/mne/_fiff/tests/test_meas_info.py +++ b/mne/_fiff/tests/test_meas_info.py @@ -5,7 +5,7 @@ import json import pickle import string -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from pathlib import Path import numpy as np @@ -350,7 +350,7 @@ def test_read_write_info(tmp_path): # Check that having a very old date in fine until you try to save it to fif with info._unlock(check_after=True): - info["meas_date"] = datetime(1800, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + info["meas_date"] = datetime(1800, 1, 1, 0, 0, 0, tzinfo=UTC) fname = tmp_path / "test.fif" with pytest.raises(RuntimeError, match="must be between "): write_info(fname, info, overwrite=True) @@ -406,7 +406,7 @@ def test_info_serialization_special_types(): info = create_info(ch_names=["EEG1"], sfreq=1000.0, ch_types="eeg") # Test meas_date (datetime) - meas_date = datetime(2023, 11, 13, 10, 30, 0, tzinfo=timezone.utc) + meas_date = datetime(2023, 11, 13, 10, 30, 0, tzinfo=UTC) with info._unlock(): info["meas_date"] = meas_date @@ -726,7 +726,7 @@ def _test_anonymize_info(base_info, tmp_path): assert isinstance(base_info, Info) base_info = base_info.copy() - default_anon_dos = datetime(2000, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + default_anon_dos = datetime(2000, 1, 1, 0, 0, 0, tzinfo=UTC) default_str = "mne_anonymize" default_subject_id = 0 default_desc = "Anonymized using a time shift" + " to preserve age at acquisition" @@ -739,7 +739,7 @@ def _test_anonymize_info(base_info, tmp_path): # Fake some additional data _complete_info(base_info) - meas_date = datetime(2010, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date = datetime(2010, 1, 1, 0, 0, 0, tzinfo=UTC) with base_info._unlock(): base_info["meas_date"] = meas_date base_info["subject_info"].update( @@ -940,7 +940,7 @@ def test_meas_date_convert(stamp, dt): meas_datetime = _stamp_to_dt(stamp) stamp2 = _dt_to_stamp(meas_datetime) assert stamp == stamp2 - assert meas_datetime == datetime(*dt, tzinfo=timezone.utc) + assert meas_datetime == datetime(*dt, tzinfo=UTC) # smoke test for info __repr__ info = create_info(1, 1000.0, "eeg") with info._unlock(): @@ -988,7 +988,7 @@ def _complete_info(info): info["helium_info"] = dict( he_level_raw=np.float32(12.34), helium_level=np.float32(45.67), - meas_date=datetime(2024, 11, 14, 14, 8, 2, tzinfo=timezone.utc), + meas_date=datetime(2024, 11, 14, 14, 8, 2, tzinfo=UTC), orig_file_guid="e", ) info["experimenter"] = "f" diff --git a/mne/annotations.py b/mne/annotations.py index 3d124be6a01..ab2dd9beb6c 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -8,7 +8,7 @@ from collections import Counter, OrderedDict, UserDict, UserList from collections.abc import Iterable from copy import deepcopy -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from itertools import takewhile from textwrap import shorten @@ -425,7 +425,7 @@ def __init__( try: # only warn if `orig_time` is not the default '1970-01-01 00:00:00' if _handle_meas_date(0) == datetime.strptime( orig_time, "%Y-%m-%d %H:%M:%S" - ).replace(tzinfo=timezone.utc): + ).replace(tzinfo=UTC): pass except ValueError: # error if incorrect datetime format AND not the default warn( @@ -1772,7 +1772,7 @@ def _handle_meas_date(meas_date): except ValueError: meas_date = None else: - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) elif isinstance(meas_date, tuple): # old way meas_date = _stamp_to_dt(meas_date) diff --git a/mne/export/_egimff.py b/mne/export/_egimff.py index 185afb5f558..fd9491e259b 100644 --- a/mne/export/_egimff.py +++ b/mne/export/_egimff.py @@ -71,7 +71,7 @@ def export_evokeds_mff(fname, evoked, history=None, *, overwrite=False, verbose= if op.exists(fname): os.remove(fname) if op.isfile(fname) else shutil.rmtree(fname) writer = mffpy.Writer(fname) - current_time = datetime.datetime.now(datetime.timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) writer.addxml("fileInfo", recordTime=current_time) try: device = info["device_info"]["type"] diff --git a/mne/export/tests/test_export.py b/mne/export/tests/test_export.py index b86d6aa8530..4423aa3b0f6 100644 --- a/mne/export/tests/test_export.py +++ b/mne/export/tests/test_export.py @@ -5,7 +5,7 @@ # Copyright the MNE-Python contributors. from contextlib import nullcontext -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime from pathlib import Path import numpy as np @@ -52,7 +52,7 @@ ["meas_date", "orig_time", "ext"], [ [None, None, ".vhdr"], - [datetime(2022, 12, 3, 19, 1, 10, 720100, tzinfo=timezone.utc), None, ".eeg"], + [datetime(2022, 12, 3, 19, 1, 10, 720100, tzinfo=UTC), None, ".eeg"], ], ) def test_export_raw_pybv(tmp_path, meas_date, orig_time, ext): @@ -198,7 +198,7 @@ def _create_raw_for_edf_tests(stim_channel_index=None): def test_double_export_edf(tmp_path): """Test exporting an EDF file multiple times.""" raw = _create_raw_for_edf_tests(stim_channel_index=2) - raw.info.set_meas_date(datetime(2023, 9, 4, 14, 53, 9, tzinfo=timezone.utc)) + raw.info.set_meas_date(datetime(2023, 9, 4, 14, 53, 9, tzinfo=UTC)) raw.set_annotations(Annotations(onset=[1], duration=[0], description=["test"])) # include subject info and measurement date @@ -379,7 +379,7 @@ def test_rawarray_edf(tmp_path): hour=time_now.hour, minute=time_now.minute, second=time_now.second, - tzinfo=timezone.utc, + tzinfo=UTC, ) raw.set_meas_date(meas_date) temp_fname = tmp_path / "test.edf" @@ -427,7 +427,7 @@ def test_channel_label_too_long_for_edf_raises_error(tmp_path): def test_measurement_date_outside_range_valid_for_edf(tmp_path): """Test trying to save an EDF with a measurement date before 1985-01-01.""" raw = _create_raw_for_edf_tests() - raw.set_meas_date(datetime(year=1984, month=1, day=1, tzinfo=timezone.utc)) + raw.set_meas_date(datetime(year=1984, month=1, day=1, tzinfo=UTC)) with pytest.raises(ValueError, match="EDF only allows dates from 1985 to 2084"): raw.export(tmp_path / "test.edf", overwrite=True) diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py index 95714717975..9c9d978a861 100644 --- a/mne/io/brainvision/brainvision.py +++ b/mne/io/brainvision/brainvision.py @@ -8,7 +8,7 @@ import os import os.path as op import re -from datetime import datetime, timezone +from datetime import UTC, datetime from io import StringIO from pathlib import Path from typing import Literal @@ -517,7 +517,7 @@ def _str_to_meas_date(date_str): else: raise - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) return meas_date diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py index 6af6891aa86..3a500dc428c 100644 --- a/mne/io/brainvision/tests/test_brainvision.py +++ b/mne/io/brainvision/tests/test_brainvision.py @@ -1048,7 +1048,7 @@ def test_event_id_stability_when_save_and_fif_reload(tmp_path): def test_parse_impedance(): """Test case for parsing the impedances from header.""" expected_imp_meas_time = datetime.datetime( - 2013, 11, 13, 16, 12, 27, tzinfo=datetime.timezone.utc + 2013, 11, 13, 16, 12, 27, tzinfo=datetime.UTC ) expected_imp_unit = "kOhm" expected_electrodes = [ diff --git a/mne/io/ctf/tests/test_ctf.py b/mne/io/ctf/tests/test_ctf.py index 64f9bec1825..3cdccec03f0 100644 --- a/mne/io/ctf/tests/test_ctf.py +++ b/mne/io/ctf/tests/test_ctf.py @@ -5,7 +5,7 @@ import copy import os import shutil -from datetime import datetime, timezone +from datetime import UTC, datetime from os import path as op import numpy as np @@ -740,4 +740,4 @@ def _convert_time_bad(date_str, time_str): raw = read_raw_ctf(ctf_dir / ctf_fname_continuous, verbose=True) log = log.getvalue() assert "No date or time found" in log - assert raw.info["meas_date"] == datetime.fromtimestamp(0, tz=timezone.utc) + assert raw.info["meas_date"] == datetime.fromtimestamp(0, tz=UTC) diff --git a/mne/io/curry/curry.py b/mne/io/curry/curry.py index 066851119f2..50b87381f8e 100644 --- a/mne/io/curry/curry.py +++ b/mne/io/curry/curry.py @@ -4,7 +4,7 @@ # Copyright the MNE-Python contributors. import re -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import numpy as np @@ -161,7 +161,7 @@ def _get_curry_meas_info(fname): try: year, month, day, hour, minute, second, millisec = meas_date meas_date = datetime( - year, month, day, hour, minute, second, millisec * 1000, timezone.utc + year, month, day, hour, minute, second, millisec * 1000, UTC ) except Exception: meas_date = None diff --git a/mne/io/curry/tests/test_curry.py b/mne/io/curry/tests/test_curry.py index 6ef4b19d46a..ae9907152fb 100644 --- a/mne/io/curry/tests/test_curry.py +++ b/mne/io/curry/tests/test_curry.py @@ -3,7 +3,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from shutil import copyfile @@ -632,7 +632,7 @@ def test_read_files_missing_channel(fname, expected_channel_list): [ pytest.param( Ref_chan_omitted_file, - datetime(2018, 11, 21, 12, 53, 48, 525000, tzinfo=timezone.utc), + datetime(2018, 11, 21, 12, 53, 48, 525000, tzinfo=UTC), id="valid start date", ), pytest.param(curry7_rfDC_file, None, id="start date year is 0"), diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 89c044e3e21..e818dea51f0 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -6,7 +6,7 @@ import os import re -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from enum import Enum from pathlib import Path from typing import Any, Literal @@ -1144,7 +1144,7 @@ def _read_edf_header( except Exception: hour, minute, second = 0, 0, 0 meas_date = meas_date.replace( - hour=hour, minute=minute, second=second, tzinfo=timezone.utc + hour=hour, minute=minute, second=second, tzinfo=UTC ) else: fid.read(8) # skip the file's measurement time @@ -1394,7 +1394,7 @@ def _read_gdf_header(fname, exclude, include=None): int(tm[10:12]), int(tm[12:14]), int(tm[14:16]) * pow(10, 4), - tzinfo=timezone.utc, + tzinfo=UTC, ) except Exception: pass @@ -1565,7 +1565,7 @@ def _read_gdf_header(fname, exclude, include=None): meas_date = read_from_file_or_buffer(fid, UINT64, 1)[0] if meas_date != 0: - meas_date = datetime(1, 1, 1, tzinfo=timezone.utc) + timedelta( + meas_date = datetime(1, 1, 1, tzinfo=UTC) + timedelta( meas_date * pow(2, -32) - 367 ) else: @@ -1573,14 +1573,14 @@ def _read_gdf_header(fname, exclude, include=None): birthday = read_from_file_or_buffer(fid, UINT64, 1).tolist()[0] if birthday == 0: - birthday = datetime(1, 1, 1, tzinfo=timezone.utc) + birthday = datetime(1, 1, 1, tzinfo=UTC) else: - birthday = datetime(1, 1, 1, tzinfo=timezone.utc) + timedelta( + birthday = datetime(1, 1, 1, tzinfo=UTC) + timedelta( birthday * pow(2, -32) - 367 ) patient["birthday"] = birthday - if patient["birthday"] != datetime(1, 1, 1, 0, 0, tzinfo=timezone.utc): - today = datetime.now(tz=timezone.utc) + if patient["birthday"] != datetime(1, 1, 1, 0, 0, tzinfo=UTC): + today = datetime.now(tz=UTC) patient["age"] = today.year - patient["birthday"].year # fudge the day by -1 if today happens to be a leap day day = 28 if today.month == 2 and today.day == 29 else today.day diff --git a/mne/io/edf/tests/test_gdf.py b/mne/io/edf/tests/test_gdf.py index 4a62163b89d..095232f7793 100644 --- a/mne/io/edf/tests/test_gdf.py +++ b/mne/io/edf/tests/test_gdf.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. import shutil -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from io import BytesIO import numpy as np @@ -85,8 +85,8 @@ def test_gdf2_birthday(tmp_path): new_fname = tmp_path / "temp.gdf" shutil.copyfile(gdf2_path.with_name(gdf2_path.name + ".gdf"), new_fname) # go back 44.5 years so the subject should show up as 44 - offset_edf = datetime.now(tz=timezone.utc) - datetime( # to their ref - 1, 1, 1, tzinfo=timezone.utc + offset_edf = datetime.now(tz=UTC) - datetime( # to their ref + 1, 1, 1, tzinfo=UTC ) offset_44_yr = offset_edf - timedelta(days=int(365 * 44.5)) # 44.5 yr ago offset_44_yr_days = offset_44_yr.total_seconds() / (24 * 60 * 60) # days @@ -101,7 +101,7 @@ def test_gdf2_birthday(tmp_path): raw = read_raw_gdf(new_fname, eog=None, misc=None, preload=True) assert "subject_info" not in raw._raw_extras[0] assert raw.info["subject_info"] is not None - birthdate = datetime(1, 1, 1, tzinfo=timezone.utc) + offset_44_yr + birthdate = datetime(1, 1, 1, tzinfo=UTC) + offset_44_yr assert raw.info["subject_info"]["birthday"] == date( birthdate.year, birthdate.month, diff --git a/mne/io/egi/egimff.py b/mne/io/egi/egimff.py index 5d0b904805b..8dfee16292e 100644 --- a/mne/io/egi/egimff.py +++ b/mne/io/egi/egimff.py @@ -458,7 +458,7 @@ def __init__( egi_info["new_trigger"] = None assert egi_events.shape[1] == egi_info["last_samps"][-1] - meas_dt_utc = egi_info["meas_dt_local"].astimezone(datetime.timezone.utc) + meas_dt_utc = egi_info["meas_dt_local"].astimezone(datetime.UTC) info = _empty_info(egi_info["sfreq"]) info["meas_date"] = _ensure_meas_date_none_or_dt(meas_dt_utc) info["utc_offset"] = egi_info["utc_offset"] diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py index 91d2bd363aa..10a7435666e 100644 --- a/mne/io/egi/tests/test_egi.py +++ b/mne/io/egi/tests/test_egi.py @@ -5,7 +5,7 @@ import os from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import numpy as np @@ -547,7 +547,7 @@ def test_meas_date(fname, timestamp, utc_offset): """Test meas date conversion.""" raw = read_raw_egi(fname, verbose="warning") dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f%z") - measdate = dt.astimezone(timezone.utc) + measdate = dt.astimezone(UTC) hour_local = int(dt.strftime("%H")) hour_utc = int(raw.info["meas_date"].strftime("%H")) local_utc_diff = hour_local - hour_utc diff --git a/mne/io/hitachi/hitachi.py b/mne/io/hitachi/hitachi.py index fcc4c0df717..cfb32a08c4c 100644 --- a/mne/io/hitachi/hitachi.py +++ b/mne/io/hitachi/hitachi.py @@ -306,7 +306,7 @@ def _get_hitachi_info(fname, S_offset, D_offset, ignore_names): ) if meas_date is None: meas_date = dt.datetime(2000, 1, 1, 0, 0, 0) - meas_date = meas_date.replace(tzinfo=dt.timezone.utc) + meas_date = meas_date.replace(tzinfo=dt.UTC) if subject_info: info_extra["subject_info"] = subject_info diff --git a/mne/io/hitachi/tests/test_hitachi.py b/mne/io/hitachi/tests/test_hitachi.py index 9ec1d43545c..5784c4ceeb5 100644 --- a/mne/io/hitachi/tests/test_hitachi.py +++ b/mne/io/hitachi/tests/test_hitachi.py @@ -220,7 +220,7 @@ def test_hitachi_basic( assert raw.info["lowpass"] == lowpass assert raw.info["subject_info"]["sex"] == sex assert np.isfinite(raw.get_data()).all() - assert raw.info["meas_date"] == dt.datetime(*date, tzinfo=dt.timezone.utc) + assert raw.info["meas_date"] == dt.datetime(*date, tzinfo=dt.UTC) # bad distances (zero) distances = source_detector_distances(raw.info) want = [np.nan] * (n_ch - 4) diff --git a/mne/io/mef/mef.py b/mne/io/mef/mef.py index 46ab9fbc6c7..c4a3787c63d 100644 --- a/mne/io/mef/mef.py +++ b/mne/io/mef/mef.py @@ -132,7 +132,7 @@ def __init__(self, fname, password="", *, preload=False, verbose=None): # Indexing the start time meas_date = ( ( - dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc) + dt.datetime(1970, 1, 1, tzinfo=dt.UTC) + dt.timedelta(microseconds=int(start_uutc)) ) if start_uutc diff --git a/mne/io/nedf/nedf.py b/mne/io/nedf/nedf.py index 2d3404ecb75..63242a51bee 100644 --- a/mne/io/nedf/nedf.py +++ b/mne/io/nedf/nedf.py @@ -5,7 +5,7 @@ """Import NeuroElectrics DataFormat (NEDF) files.""" from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -117,7 +117,7 @@ def _parse_nedf_header(header): dt.append(("data", np.dtype(datadt), (5,))) date = headerxml.findtext("StepDetails/StartDate_firstEEGTimestamp", 0) - info["meas_date"] = datetime.fromtimestamp(int(date) / 1000, timezone.utc) + info["meas_date"] = datetime.fromtimestamp(int(date) / 1000, UTC) n_samples = int(_getsubnodetext(eegset, "NumberOfRecordsOfEEG")) n_full, n_last = divmod(n_samples, 5) diff --git a/mne/io/neuralynx/neuralynx.py b/mne/io/neuralynx/neuralynx.py index b084eecfa5c..29e23026e66 100644 --- a/mne/io/neuralynx/neuralynx.py +++ b/mne/io/neuralynx/neuralynx.py @@ -197,7 +197,7 @@ def __init__( ] with info._unlock(): - info["meas_date"] = meas_dates[0].astimezone(datetime.timezone.utc) + info["meas_date"] = meas_dates[0].astimezone(datetime.UTC) info["highpass"] = np.max(highpass_freqs) info["lowpass"] = np.min(lowpass_freqs) diff --git a/mne/io/neuralynx/tests/test_neuralynx.py b/mne/io/neuralynx/tests/test_neuralynx.py index 18578ef4ab7..64f940f1312 100644 --- a/mne/io/neuralynx/tests/test_neuralynx.py +++ b/mne/io/neuralynx/tests/test_neuralynx.py @@ -4,7 +4,7 @@ import os from ast import literal_eval -from datetime import datetime, timezone +from datetime import UTC, datetime import numpy as np import pytest @@ -139,7 +139,7 @@ def test_neuralynx(): assert raw.info["lowpass"] == expected_lp_freq, "lowpass freq not set correctly" assert raw.info["sfreq"] == expected_sfreq, "sampling freq not set correctly" - meas_date_utc = expected_meas_date.astimezone(timezone.utc) + meas_date_utc = expected_meas_date.astimezone(UTC) assert raw.info["meas_date"] == meas_date_utc, "meas_date not set correctly" # test that channel selection worked diff --git a/mne/io/nihon/nihon.py b/mne/io/nihon/nihon.py index c1a98b3f313..61dabd4a03f 100644 --- a/mne/io/nihon/nihon.py +++ b/mne/io/nihon/nihon.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. from collections import OrderedDict -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -91,7 +91,7 @@ def _read_nihon_metadata(fname): fid.seek(0x40) meas_str = np.fromfile(fid, "|S14", 1).astype("U14")[0] meas_date = datetime.strptime(meas_str, "%Y%m%d%H%M%S") - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) metadata["meas_date"] = meas_date return metadata diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index 15448ee7d46..11512b416a7 100644 --- a/mne/io/nirx/nirx.py +++ b/mne/io/nirx/nirx.py @@ -272,7 +272,7 @@ def __init__(self, fname, saturated, *, preload=False, encoding=None, verbose=No except ValueError: pass else: - meas_date = meas_date.replace(tzinfo=dt.timezone.utc) + meas_date = meas_date.replace(tzinfo=dt.UTC) do_break = True logger.debug(f"Measurement date language {loc} detected: {dt_code}") break @@ -287,7 +287,7 @@ def __init__(self, fname, saturated, *, preload=False, encoding=None, verbose=No "The date is being set to January 1st, 2000, " f"instead of {repr(datetime_str)}." ) - meas_date = dt.datetime(2000, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc) + meas_date = dt.datetime(2000, 1, 1, 0, 0, 0, tzinfo=dt.UTC) # Extract frequencies of light used by machine if is_aurora: diff --git a/mne/io/nirx/tests/test_nirx.py b/mne/io/nirx/tests/test_nirx.py index 3ba2a925139..56354e9e5a6 100644 --- a/mne/io/nirx/tests/test_nirx.py +++ b/mne/io/nirx/tests/test_nirx.py @@ -279,7 +279,7 @@ def test_nirx_15_2_short(): assert raw._data.shape == (26, 145) assert raw.info["sfreq"] == 12.5 assert raw.info["meas_date"] == dt.datetime( - 2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.timezone.utc + 2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.UTC ) # Test channel naming @@ -486,7 +486,7 @@ def test_locale_encoding(tmp_path): for line in hdr: fid.write(line) raw = read_raw_nirx(fname, verbose="debug") - want_dt = dt.datetime(2020, 1, 24, 10, 57, 41, 454000, tzinfo=dt.timezone.utc) + want_dt = dt.datetime(2020, 1, 24, 10, 57, 41, 454000, tzinfo=dt.UTC) assert raw.info["meas_date"] == want_dt @@ -499,7 +499,7 @@ def test_nirx_15_2(): assert raw._data.shape == (64, 67) assert raw.info["sfreq"] == 3.90625 assert raw.info["meas_date"] == dt.datetime( - 2019, 10, 2, 9, 8, 47, 511000, tzinfo=dt.timezone.utc + 2019, 10, 2, 9, 8, 47, 511000, tzinfo=dt.UTC ) # Test channel naming @@ -596,7 +596,7 @@ def test_nirx_15_0(): assert raw._data.shape == (20, 92) assert raw.info["sfreq"] == 6.25 assert raw.info["meas_date"] == dt.datetime( - 2019, 10, 27, 13, 53, 34, 209000, tzinfo=dt.timezone.utc + 2019, 10, 27, 13, 53, 34, 209000, tzinfo=dt.UTC ) # Test channel naming diff --git a/mne/io/nsx/nsx.py b/mne/io/nsx/nsx.py index dc7b19f31da..f582fa46135 100644 --- a/mne/io/nsx/nsx.py +++ b/mne/io/nsx/nsx.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. import os -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any, Literal @@ -291,7 +291,7 @@ def _read_header(fname): "millisecond", ) ], - tzinfo=timezone.utc, + tzinfo=UTC, ) basic_header["meas_date"] = time_origin return basic_header diff --git a/mne/io/persyst/persyst.py b/mne/io/persyst/persyst.py index c33e70799b8..2c6bfb7f0bb 100644 --- a/mne/io/persyst/persyst.py +++ b/mne/io/persyst/persyst.py @@ -5,7 +5,7 @@ import os import os.path as op from collections import OrderedDict -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -183,7 +183,7 @@ def __init__(self, fname, preload=False, verbose=None): hour=testtime.hour, minute=testtime.minute, second=testtime.second, - tzinfo=timezone.utc, + tzinfo=UTC, ) # Create mne structure diff --git a/mne/io/snirf/_snirf.py b/mne/io/snirf/_snirf.py index be89f90c16a..7f0acf71443 100644 --- a/mne/io/snirf/_snirf.py +++ b/mne/io/snirf/_snirf.py @@ -593,7 +593,7 @@ def natural_keys(text): meas_date = datetime.datetime(2000, 1, 1, 0, 0, 0) else: meas_date = datetime.datetime(2000, 1, 1, 0, 0, 0) - meas_date = meas_date.replace(tzinfo=datetime.timezone.utc) + meas_date = meas_date.replace(tzinfo=datetime.UTC) with info._unlock(): info["meas_date"] = meas_date diff --git a/mne/preprocessing/tests/test_annotate_amplitude.py b/mne/preprocessing/tests/test_annotate_amplitude.py index 7f2699f6906..98850313d26 100644 --- a/mne/preprocessing/tests/test_annotate_amplitude.py +++ b/mne/preprocessing/tests/test_annotate_amplitude.py @@ -16,7 +16,7 @@ from mne.io import RawArray, read_raw_fif from mne.preprocessing import annotate_amplitude -date = datetime.datetime(2021, 12, 10, 7, 52, 24, 405305, tzinfo=datetime.timezone.utc) +date = datetime.datetime(2021, 12, 10, 7, 52, 24, 405305, tzinfo=datetime.UTC) data_path = Path(testing.data_path(download=False)) skip_fname = data_path / "misc" / "intervalrecording_raw.fif" diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 029d1a4b564..a727cbe55ea 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -4,7 +4,7 @@ import sys from collections import OrderedDict -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from itertools import repeat from pathlib import Path @@ -97,7 +97,7 @@ def test_basics(): assert annot.orig_time is None else: assert isinstance(annot.orig_time, datetime) - assert annot.orig_time.tzinfo is timezone.utc + assert annot.orig_time.tzinfo is UTC pytest.raises(ValueError, Annotations, onset, duration, description[:9]) pytest.raises(ValueError, Annotations, [onset, 1], duration, description) @@ -993,7 +993,7 @@ def _assert_annotations_equal(a, b, tol=0, comp_extras_as_str=False): assert exa == exb, f"extras[{i}][{col}]" -_ORIG_TIME = datetime.fromtimestamp(1038942071.7201, timezone.utc) +_ORIG_TIME = datetime.fromtimestamp(1038942071.7201, UTC) @pytest.fixture(scope="function", params=("ch_names", "fmt", "with_extras")) @@ -1250,7 +1250,7 @@ def test_handle_meas_date(meas_date, out): """Test meas date formats.""" if out is not None: assert out >= 0 # otherwise it'll break on Windows - out = datetime.fromtimestamp(out, timezone.utc) + out = datetime.fromtimestamp(out, UTC) assert _handle_meas_date(meas_date) == out @@ -1268,7 +1268,7 @@ def test_read_annotation_txt_header(tmp_path): with open(fname, "w") as f: f.write(content) orig_time, _, n_rows_header = _read_annotations_txt_parse_header(fname) - want = datetime.fromtimestamp(1038942071.7201, timezone.utc) + want = datetime.fromtimestamp(1038942071.7201, UTC) assert orig_time == want assert n_rows_header == 5 @@ -1847,9 +1847,9 @@ def test_annot_concat_crop(meas_date, first_samp_1, first_samp_2, setting): meas_date_1 = meas_date_2 = None assert meas_date in (None, "first", "second", "both") if meas_date in ("first", "both"): - meas_date_1 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date_1 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=UTC) if meas_date in ("second", "both"): - meas_date_2 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date_2 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=UTC) del meas_date def _create_raw(eeg, sfreq, onset, description, meas_date, first_samp, setting): diff --git a/mne/tests/test_docstring_parameters.py b/mne/tests/test_docstring_parameters.py index 4602112af6a..e6f36d7ec32 100644 --- a/mne/tests/test_docstring_parameters.py +++ b/mne/tests/test_docstring_parameters.py @@ -6,6 +6,7 @@ import importlib import inspect import re +import tomllib import types import typing from pathlib import Path @@ -55,11 +56,6 @@ pyproject_path = Path(__file__).parents[2] / "pyproject.toml" if not pyproject_path.is_file(): pytest.skip(f"pyproject.toml not found: {pyproject_path}", allow_module_level=True) -try: - import tomllib -except ModuleNotFoundError: - # TODO VERSION: Remove this when Python 3.11+ is required - pytest.skip("tomllib not available", allow_module_level=True) pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) numpydoc_checks = pyproject["tool"]["numpydoc_validation"]["checks"] diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index 4b5c14d5985..02e256313eb 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -668,7 +668,7 @@ def test_tfr_io(inst, average_tfr, request, tmp_path): minute=0, second=sec_microsec_tuple[0], microsecond=sec_microsec_tuple[1], - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) assert tfr_loaded.info["meas_date"] == want with tfr.info._unlock(): diff --git a/mne/utils/_typing.py b/mne/utils/_typing.py index 5a047e6070c..a00d68b8380 100644 --- a/mne/utils/_typing.py +++ b/mne/utils/_typing.py @@ -4,14 +4,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import sys -from typing import IO - -if sys.version_info >= (3, 11): - from typing import Self -else: - # TODO VERSION: Remove this when Python 3.11+ is required (use typing.Self) - from typing_extensions import Self +from typing import IO, Self # A Matplotlib color: a named/hex string, or an RGB(A) tuple of floats. This is # the runtime meaning of the ``color`` numpydoc pseudo-type. diff --git a/mne/utils/numerics.py b/mne/utils/numerics.py index c637bf42b20..d6b148dff8e 100644 --- a/mne/utils/numerics.py +++ b/mne/utils/numerics.py @@ -11,7 +11,7 @@ import shutil import sys from contextlib import contextmanager -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from io import BytesIO, StringIO from math import ceil, sqrt from pathlib import Path @@ -981,7 +981,7 @@ def _julian_to_date(jd): # https://aa.usno.navy.mil/data/docs/JulianDate.php # Thursday, A.D. 1970 Jan 1 12:00:00.0 2440588.000000 jd_t0 = 2440588 - datetime_t0 = datetime(1970, 1, 1, 12, 0, 0, 0, tzinfo=timezone.utc) + datetime_t0 = datetime(1970, 1, 1, 12, 0, 0, 0, tzinfo=UTC) dt = timedelta(days=(jd - jd_t0)) return (datetime_t0 + dt).date() @@ -1013,11 +1013,7 @@ def _date_to_julian(jd_date): def _check_dt(dt): - if ( - not isinstance(dt, datetime) - or dt.tzinfo is None - or dt.tzinfo is not timezone.utc - ): + if not isinstance(dt, datetime) or dt.tzinfo is None or dt.tzinfo is not UTC: raise ValueError(f"Date must be datetime object in UTC: {repr(dt)}") @@ -1033,7 +1029,7 @@ def _stamp_to_dt(utc_stamp): stamp = [int(s) for s in utc_stamp] if len(stamp) == 1: # In case there is no microseconds information stamp.append(0) - return datetime.fromtimestamp(0, tz=timezone.utc) + timedelta( + return datetime.fromtimestamp(0, tz=UTC) + timedelta( seconds=stamp[0], microseconds=stamp[1] ) diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index 6fd4369f15a..90f8275c765 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -9,6 +9,7 @@ import random import re import time +import tomllib from functools import partial from pathlib import Path from urllib.error import URLError @@ -147,7 +148,6 @@ def test_sys_info_windowing_system(monkeypatch): def test_sys_info_complete(): """Test that sys_info is sufficiently complete.""" - tomllib = pytest.importorskip("tomllib") # TODO VERSION remove on Python 3.11+ pyproject = Path(__file__).parents[3] / "pyproject.toml" if not pyproject.is_file(): pytest.skip("Does not appear to be a dev installation") diff --git a/pyproject.toml b/pyproject.toml index 56402be5563..fb5d896ea4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,6 @@ dependencies = [ "pooch >= 1.5", "scipy >= 1.14", # released 2024-06-24, will become 1.15 on 2027-01-03 "tqdm >= 4.66", - "typing-extensions >= 4.15; python_version < '3.11'", # for typing.Self TODO VERSION ] description = "MNE-Python project for MEG and EEG data analysis." dynamic = ["version"] @@ -134,7 +133,7 @@ license-files = ["LICENSE.txt"] maintainers = [{email = "dan@mccloy.info", name = "Dan McCloy"}] name = "mne" readme = {content-type = "text/x-rst", file = "README.rst"} -requires-python = ">= 3.10" +requires-python = ">= 3.11" # ↑↑↑↑↑↑↑↑↑↑↑ when this changes, bump the `--python-version` in the `uv pip compile ...` # command in `.github/workflows/scheduled_updates.yml` (astral-sh/uv/#16333), and also # the `python` version for the `kind: old` matrix entry in `.github/workflows/tests.yml` @@ -469,7 +468,7 @@ name = "Other changes" showcontent = true [tool.ty.environment] -python-version = "3.10" # minimum supported, keep in sync with requires-python +python-version = "3.11" # minimum supported, keep in sync with requires-python # Files that are strictly type-checked with no rule overrides. A file is added # here only once it passes ty cleanly; grow this list as more of the codebase is diff --git a/tools/check_pyproject_helpers.py b/tools/check_pyproject_helpers.py index 19277410cfb..1de1b9320a5 100644 --- a/tools/check_pyproject_helpers.py +++ b/tools/check_pyproject_helpers.py @@ -21,7 +21,7 @@ def get_deps_to_check(): + pyproject_data["project"]["dependencies"] + pyproject_data["dependency-groups"]["lockfile_extras"] ) - n_want_deps = 13 # update when we add more core deps or auto-bumped pins! + n_want_deps = 12 # update when we add more core deps or auto-bumped pins! assert len(check_deps) == n_want_deps, ( f"Number of dependencies being checked ({len(check_deps)=}) is not as " f"expected {n_want_deps=}" diff --git a/tools/dev/check_steering_committee.py b/tools/dev/check_steering_committee.py index dd62b8e5130..1de8f5e1962 100755 --- a/tools/dev/check_steering_committee.py +++ b/tools/dev/check_steering_committee.py @@ -13,7 +13,7 @@ import os import pprint from collections import Counter -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from github import Auth, Github from github.Commit import Commit @@ -30,9 +30,7 @@ team_names = [team.name for team in teams] team = teams[team_names.index("MNE-Python Maintainers")] members = list(team.get_members()) -when = (datetime.now().astimezone(timezone.utc) - timedelta(days=daysback)).replace( - tzinfo=None -) +when = (datetime.now().astimezone(UTC) - timedelta(days=daysback)).replace(tzinfo=None) events = { user.login: Counter(Total=0, Commit=0, IssueComment=0, PullRequestComment=0) for user in members diff --git a/tools/dev/update_credit_json.py b/tools/dev/update_credit_json.py index 001d6606152..f1457cb4205 100644 --- a/tools/dev/update_credit_json.py +++ b/tools/dev/update_credit_json.py @@ -36,7 +36,7 @@ # every PR merged within the lookback window is seen regardless of how old or # low-numbered it is (the monthly action runs far more often than this window). # For a full-history rebuild, remove the cutoff `break` below. -cutoff = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=90) +cutoff = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=90) pulls_iter = repo.get_pulls(state="closed", sort="updated", direction="desc") iter_ = tqdm(pulls_iter, unit="pr", desc="Traversing") n_added = 0 diff --git a/tools/generate_codemeta.py b/tools/generate_codemeta.py index 5606707ffee..88383437ef5 100644 --- a/tools/generate_codemeta.py +++ b/tools/generate_codemeta.py @@ -3,12 +3,11 @@ # Copyright the MNE-Python contributors. import subprocess +import tomllib from argparse import ArgumentParser from datetime import date from pathlib import Path -import tomllib - parser = ArgumentParser(description="Generate codemeta.json and CITATION.cff") parser.add_argument("release_version", type=str) release_version = parser.parse_args().release_version diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index 510a50c9e66..4ee5591e323 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -13,7 +13,7 @@ case "$MNE_CI_KIND" in minimal | old) ;; # unique code paths, worth the slower C tracer *) case "$PYTHON_VERSION" in - 3.10 | 3.11 | 3.12 | 3.13) COV_ARGS="" ;; + 3.11 | 3.12 | 3.13) COV_ARGS="" ;; esac ;; esac diff --git a/tools/github_actions_verify_python.sh b/tools/github_actions_verify_python.sh index 9e5cd938782..4fae44a2ca5 100755 --- a/tools/github_actions_verify_python.sh +++ b/tools/github_actions_verify_python.sh @@ -10,7 +10,7 @@ else fi WANT_PYTHON_VERSION=$(echo "$WANT_PYTHON_VERSION" | sed 's/t$//g') if [[ -z "$WANT_PYTHON_VERSION" ]]; then - echo "✕ ERROR: Missing required argument: want Python version (e.g., 3.10)" + echo "✕ ERROR: Missing required argument: want Python version (e.g., 3.11)" exit 1 fi diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index 9c858ca70ad..aa1aee87792 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -5,9 +5,8 @@ # Copyright the MNE-Python contributors. import re -from pathlib import Path - import tomllib +from pathlib import Path repo_root = Path(__file__).resolve().parents[2] with open(repo_root / "pyproject.toml", "rb") as fid: @@ -29,9 +28,6 @@ "pymef", # not on conda-forge } deps -= pip_deps -deps -= { - "typing-extensions >= 4.15; python_version < '3.11'" -} # for typing.Self TODO VERSION def remove_spaces(version_spec): diff --git a/tools/pylock.ci-old.toml b/tools/pylock.ci-old.toml index 73834aefa1a..65091197adb 100644 --- a/tools/pylock.ci-old.toml +++ b/tools/pylock.ci-old.toml @@ -1,8 +1,8 @@ # This file was autogenerated by uv via the following command: -# uv pip compile pyproject.toml --python 3.10 --python-platform x86_64-unknown-linux-gnu --group test --group lockfile_extras --resolution lowest-direct --format pylock.toml --output-file tools/pylock.ci-old.toml +# uv pip compile pyproject.toml --python 3.11 --python-platform x86_64-unknown-linux-gnu --group test --group lockfile_extras --resolution lowest-direct --format pylock.toml --output-file tools/pylock.ci-old.toml lock-version = "1.0" created-by = "uv" -requires-python = ">=3.10" +requires-python = ">=3.11" [[packages]] name = "alabaster" @@ -39,7 +39,7 @@ name = "charset-normalizer" version = "3.4.4" sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", upload-time = 2025-10-14T04:42:32Z, size = 129418, hashes = { sha256 = "94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a" } } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:40:19Z, size = 153609, hashes = { sha256 = "9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313" } }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:40:40Z, size = 151550, hashes = { sha256 = "840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381" } }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", upload-time = 2025-10-14T04:42:31Z, size = 53402, hashes = { sha256 = "7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f" } }, ] @@ -52,14 +52,14 @@ sdist = { url = "https://files.pythonhosted.org/packages/bd/64/d16fc0b110dbe3567 name = "contourpy" version = "1.3.2" sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", upload-time = 2025-04-15T17:47:53Z, size = 13466130, hashes = { sha256 = "b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-04-15T17:35:11Z, size = 325027, hashes = { sha256 = "ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-04-15T17:36:18Z, size = 326246, hashes = { sha256 = "3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7" } }] [[packages]] name = "coverage" version = "7.13.3" sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", upload-time = 2026-02-03T14:02:30Z, size = 826832, hashes = { sha256 = "f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac" } } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2026-02-03T13:59:44Z, size = 248228, hashes = { sha256 = "fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0" } }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2026-02-03T14:00:05Z, size = 252111, hashes = { sha256 = "0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c" } }, { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", upload-time = 2026-02-03T14:02:27Z, size = 211237, hashes = { sha256 = "90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910" } }, ] @@ -81,13 +81,6 @@ version = "0.19" sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/330ea8d383eb2ce973df34d1239b3b21e91cd8c865d21ff82902d952f91f/docutils-0.19.tar.gz", upload-time = 2022-07-05T20:17:31Z, size = 2056383, hashes = { sha256 = "33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6" } } wheels = [{ url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", upload-time = 2022-07-05T20:17:26Z, size = 570472, hashes = { sha256 = "5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" } }] -[[packages]] -name = "exceptiongroup" -version = "1.3.1" -marker = "python_full_version < '3.11'" -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", upload-time = 2025-11-21T23:01:54Z, size = 30371, hashes = { sha256 = "8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", upload-time = 2025-11-21T23:01:53Z, size = 16740, hashes = { sha256 = "a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598" } }] - [[packages]] name = "execnet" version = "2.1.2" @@ -105,7 +98,7 @@ name = "fonttools" version = "4.61.1" sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", upload-time = 2025-12-12T17:31:24Z, size = 3565756, hashes = { sha256 = "6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69" } } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-12-12T17:29:34Z, size = 4859076, hashes = { sha256 = "a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6" } }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-12-12T17:29:53Z, size = 5036039, hashes = { sha256 = "75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9" } }, { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", upload-time = 2025-12-12T17:31:21Z, size = 1148996, hashes = { sha256 = "17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371" } }, ] @@ -155,7 +148,7 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e27 name = "kiwisolver" version = "1.4.9" sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", upload-time = 2025-08-10T21:27:49Z, size = 97564, hashes = { sha256 = "c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", upload-time = 2025-08-10T21:25:39Z, size = 1628458, hashes = { sha256 = "b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-08-10T21:25:56Z, size = 1435596, hashes = { sha256 = "dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61" } }] [[packages]] name = "lazy-loader" @@ -167,13 +160,13 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/a1/c3/65b3814e155836a name = "markupsafe" version = "3.0.3" sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", upload-time = 2025-09-27T18:37:40Z, size = 80313, hashes = { sha256 = "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-27T18:36:08Z, size = 20681, hashes = { sha256 = "f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-27T18:36:22Z, size = 22940, hashes = { sha256 = "0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf" } }] [[packages]] name = "matplotlib" version = "3.9.0" sdist = { url = "https://files.pythonhosted.org/packages/c5/a4/a7236bf8b0137deff48737c6ccf2154ef4486e57c6a5b7c309bf515992bd/matplotlib-3.9.0.tar.gz", upload-time = 2024-05-15T23:38:15Z, size = 36069890, hashes = { sha256 = "e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/a7/68/16e7b9154fae61fb29f0f3450b39b855b89e6d2c598d67302e70f96883af/matplotlib-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-05-15T23:37:12Z, size = 8296303, hashes = { sha256 = "eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/41/f1/115e7c79b4506b4f0533acba742babd9718ff92eeca6d4205843173b6173/matplotlib-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-05-15T23:37:27Z, size = 8307002, hashes = { sha256 = "76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb" } }] [[packages]] name = "matplotlib-inline" @@ -185,7 +178,7 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a name = "numpy" version = "2.0.0" sdist = { url = "https://files.pythonhosted.org/packages/05/35/fb1ada118002df3fe91b5c3b28bc0d90f879b881a5d8f68b1f9b79c44bfe/numpy-2.0.0.tar.gz", upload-time = 2024-06-16T13:24:44Z, size = 18326228, hashes = { sha256 = "cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/d6/a8/6a2419c40c7b6f7cb4ef52c532c88e55490c4fa92885964757d507adddce/numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-06-16T13:08:22Z, size = 19282097, hashes = { sha256 = "6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/d1/27/2a7bd6855dc717aeec5f553073a3c426b9c816126555f8e616392eab856b/numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-06-16T13:11:54Z, size = 19292279, hashes = { sha256 = "a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581" } }] [[packages]] name = "numpydoc" @@ -203,7 +196,7 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/d8/5b/3098db49a61ccc8 name = "pandas" version = "2.2.2" sdist = { url = "https://files.pythonhosted.org/packages/88/d9/ecf715f34c73ccb1d8ceb82fc01cd1028a65a5f6dbc57bfa6ea155119058/pandas-2.2.2.tar.gz", upload-time = 2024-04-10T19:45:48Z, size = 4398391, hashes = { sha256 = "9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/89/1b/12521efcbc6058e2673583bb096c2b5046a9df39bd73eca392c1efed24e5/pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-04-10T19:44:19Z, size = 13032214, hashes = { sha256 = "8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/fc/a5/4d82be566f069d7a9a702dcdf6f9106df0e0b042e738043c0cc7ddd7e3f6/pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-04-10T19:44:46Z, size = 13031332, hashes = { sha256 = "6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee" } }] [[packages]] name = "parso" @@ -223,8 +216,8 @@ name = "pillow" version = "10.2.0" sdist = { url = "https://files.pythonhosted.org/packages/f8/3e/32cbd0129a28686621434cbf17bb64bf1458bfb838f1f668262fefce145c/pillow-10.2.0.tar.gz", upload-time = 2024-01-02T09:16:59Z, size = 46212712, hashes = { sha256 = "e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e" } } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/0b/54df8b49ac8b85ed5aae68b2d8573ed1fb73d0a18a0830a988d0b3431080/pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-01-02T09:15:08Z, size = 4405870, hashes = { sha256 = "da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4" } }, - { url = "https://files.pythonhosted.org/packages/cb/c3/98faa3e92cf866b9446c4842f1fe847e672b2f54e000cb984157b8095797/pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", upload-time = 2024-01-02T09:15:10Z, size = 4487681, hashes = { sha256 = "322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2" } }, + { url = "https://files.pythonhosted.org/packages/a5/23/3c59ba2bb48f2ab2f11c3597f50458f63ed46dcc4cedd3308f6e4ec7271f/pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-01-02T09:15:27Z, size = 4414949, hashes = { sha256 = "11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e" } }, + { url = "https://files.pythonhosted.org/packages/66/9c/2e1877630eb298bbfd23f90deeec0a3f682a4163d5ca9f178937de57346c/pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", upload-time = 2024-01-02T09:15:30Z, size = 4494803, hashes = { sha256 = "a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311" } }, ] [[packages]] @@ -345,13 +338,13 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/ac/22/0fc6119373ee933 name = "scikit-learn" version = "1.5.0" sdist = { url = "https://files.pythonhosted.org/packages/bf/8a/06e499bca463905000f50e461c9445e949aafdd33ea3b62024aa2238b83d/scikit_learn-1.5.0.tar.gz", upload-time = 2024-05-21T16:34:07Z, size = 7820839, hashes = { sha256 = "789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/1e/7d/1a2ea8eb5b4df373c30c7418cf26305a4a05e2a0e56c80a8043b791595f3/scikit_learn-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-05-21T16:33:13Z, size = 13343697, hashes = { sha256 = "2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/46/c0/63d3a8da39a2ee051df229111aa93f6dca2b56f8080abd34993938166455/scikit_learn-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-05-21T16:33:29Z, size = 13328661, hashes = { sha256 = "118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415" } }] [[packages]] name = "scipy" version = "1.14.0" sdist = { url = "https://files.pythonhosted.org/packages/4e/e5/0230da034a2e1b1feb32621d7cd57c59484091d6dccc9e6b855b0d309fc9/scipy-1.14.0.tar.gz", upload-time = 2024-06-24T20:35:18Z, size = 58618870, hashes = { sha256 = "b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/e2/20/15c8fe0dfebb6facd81b3d08bf45dfa080e305deb17172b0a40eba59e927/scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-06-24T20:32:25Z, size = 41135959, hashes = { sha256 = "42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0" } }] +wheels = [{ url = "https://files.pythonhosted.org/packages/89/bb/80c9c98d887c855710fd31fc5ae5574133e98203b3475b07579251803662/scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-06-24T20:33:26Z, size = 41132874, hashes = { sha256 = "9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74" } }] [[packages]] name = "six" @@ -447,16 +440,9 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836 [[packages]] name = "twine" version = "1.0.1" -sdist = { url = "https://files.pythonhosted.org/packages/33/16/b777faf3d2cc0284e96ff021962eb7cb241194d380952b56508a59870b51/twine-1.0.1.tar.gz", upload-time = 2013-09-26T12:49:18Z, size = 15775, hashes = { sha256 = "e60919dd698d09cc0877d02f8f24677ed7de7bd1728a73b9d7f29ffc5f6c1fa8" } } +sdist = { url = "https://files.pythonhosted.org/packages/a4/08/dcc58e09165733f78c34c8328a305d4d018802ebbe8cfd77704974c8e18c/twine-1.0.1.tar.bz2", upload-time = 2013-09-26T12:49:15Z, size = 15047, hashes = { sha256 = "6912cf86354f7a992385786b4d7595935d67b4104ac9c9147a82bfc4d9c4d251" } } wheels = [{ url = "https://files.pythonhosted.org/packages/da/07/baf5870c94b669c86ac37cf52e60aa878cd45b29fd31692463f6b1df7162/twine-1.0.1-py2.py3-none-any.whl", upload-time = 2013-09-26T12:48:13Z, size = 12015, hashes = { sha256 = "54f16ad27606c23c921646afa75e8d5c533dddd3bd680aee372a3fd3a48416b1" } }] -[[packages]] -name = "typing-extensions" -version = "4.15.0" -marker = "python_full_version < '3.11'" -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", upload-time = 2025-08-25T13:49:26Z, size = 109391, hashes = { sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } } -wheels = [{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", upload-time = 2025-08-25T13:49:24Z, size = 44614, hashes = { sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" } }] - [[packages]] name = "tzdata" version = "2025.3" diff --git a/tutorials/preprocessing/58_fmri_gradient_artifact.py b/tutorials/preprocessing/58_fmri_gradient_artifact.py index 68553d43edc..6448377bfc3 100644 --- a/tutorials/preprocessing/58_fmri_gradient_artifact.py +++ b/tutorials/preprocessing/58_fmri_gradient_artifact.py @@ -67,7 +67,7 @@ n_samples = n_trs * tr_samples ch_names = ["Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8", "C3", "C4", "P3", "P4", "O1"] n_channels = len(ch_names) -start_time = datetime.datetime(2026, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc) +start_time = datetime.datetime(2026, 1, 1, 12, 0, 0, tzinfo=datetime.UTC) rng = np.random.default_rng(0)