diff --git a/.github/workflows/credit.yml b/.github/workflows/credit.yml
index 5e538f6a974..9e6aa00b663 100644
--- a/.github/workflows/credit.yml
+++ b/.github/workflows/credit.yml
@@ -40,6 +40,6 @@ jobs:
git checkout -b credit
git commit -am "MAINT: Update code credit"
git push origin credit
- PR_NUM=$(gh pr create --base main --head credit --title "MAINT: Update code credit" --body "Created by credit [GitHub action](https://github.com/mne-tools/mne-python/actions/runs/${{ github.run_id }})." --label "no-changelog-entry-needed")
+ PR_NUM=$(gh pr create --base main --head credit --title "MAINT: Update code credit" --body "Created by credit [GitHub action](https://github.com/mne-tools/mne-python/actions/runs/${{ github.run_id }}).
*Adjustments may need to be made to `doc/changes/credit_tools.py` or `.mailmap` etc. to get CircleCI to pass.*" --label "no-changelog-entry-needed")
echo "Opened https://github.com/mne-tools/mne-python/pull/${PR_NUM}" >> $GITHUB_STEP_SUMMARY
if: steps.status.outputs.dirty == 'true'
diff --git a/.github/workflows/spec_zero.yml b/.github/workflows/spec_zero.yml
index a8eeb7b9c56..3f3698190fc 100644
--- a/.github/workflows/spec_zero.yml
+++ b/.github/workflows/spec_zero.yml
@@ -57,6 +57,6 @@ jobs:
git checkout -b spec_zero
git commit -am "MAINT: Update dependency specifiers"
git push origin spec_zero
- PR_NUM=$(gh pr create --base main --head spec_zero --title "MAINT: Update dependency specifiers" --body "Created by spec_zero [GitHub action](https://github.com/mne-tools/mne-python/actions/runs/${{ github.run_id }})." --label "no-changelog-entry-needed")
+ PR_NUM=$(gh pr create --base main --head spec_zero --title "MAINT: Update dependency specifiers" --body "Created by spec_zero [GitHub action](https://github.com/mne-tools/mne-python/actions/runs/${{ github.run_id }}).
*Adjustments may need to be made to shims in mne/fixes.py in this or another PR. `git grep TODO VERSION` is a good starting point for finding potential updates.*" --label "no-changelog-entry-needed")
echo "Opened https://github.com/mne-tools/mne-python/pull/${PR_NUM}" >> $GITHUB_STEP_SUMMARY
if: steps.status.outputs.dirty == 'true'
diff --git a/doc/changes/dev/13448.newfeature.rst b/doc/changes/dev/13448.newfeature.rst
new file mode 100644
index 00000000000..d329015520f
--- /dev/null
+++ b/doc/changes/dev/13448.newfeature.rst
@@ -0,0 +1 @@
+Add support for Nihon Kohden EEG-1200A V01.00, by `Eric Larson`_.
\ No newline at end of file
diff --git a/doc/changes/dev/13458.apichange.rst b/doc/changes/dev/13458.apichange.rst
new file mode 100644
index 00000000000..f2b9ec5a007
--- /dev/null
+++ b/doc/changes/dev/13458.apichange.rst
@@ -0,0 +1 @@
+Add ``encoding`` parameter to :func:`mne.io.read_raw_nihon` for better handling of annotation decoding, by `Tom Ma`_.
diff --git a/environment.yml b/environment.yml
index 8e7e083563b..e7620d41fa4 100644
--- a/environment.yml
+++ b/environment.yml
@@ -45,11 +45,11 @@ dependencies:
- PySide6 !=6.9.1
- python-neo
- python-picard
- - pyvista >=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3,!=0.38.4,!=0.38.5,!=0.38.6,!=0.42.0
- - pyvistaqt >=0.4
+ - pyvista >=0.42.1
+ - pyvistaqt >=0.11
- qdarkstyle !=3.2.2
- qtpy
- - scikit-learn >=1.3.0
+ - scikit-learn >=1.3
- scipy >=1.11
- sip
- snirf
diff --git a/mne/io/artemis123/tests/test_artemis123.py b/mne/io/artemis123/tests/test_artemis123.py
index cf905f82530..b39f35febb9 100644
--- a/mne/io/artemis123/tests/test_artemis123.py
+++ b/mne/io/artemis123/tests/test_artemis123.py
@@ -41,7 +41,6 @@ def _assert_trans(actual, desired, dist_tol=0.017, angle_tol=5.0):
assert angle <= angle_tol, f"{angle:0.3f} > {angle_tol:0.3f}° rotation"
-@pytest.mark.timeout(60) # ~25 s on Travis Linux OpenBLAS
@testing.requires_testing_data
def test_artemis_reader():
"""Test reading raw Artemis123 files."""
@@ -49,6 +48,7 @@ def test_artemis_reader():
read_raw_artemis123,
input_fname=short_hpi_1kz_fname,
pos_fname=dig_fname,
+ add_head_trans=False,
verbose="error",
)
diff --git a/mne/io/eeglab/tests/test_eeglab.py b/mne/io/eeglab/tests/test_eeglab.py
index 49d87f822cb..2b05d8da14d 100644
--- a/mne/io/eeglab/tests/test_eeglab.py
+++ b/mne/io/eeglab/tests/test_eeglab.py
@@ -9,6 +9,7 @@
import numpy as np
import pytest
+from flaky import flaky
from numpy.testing import (
assert_allclose,
assert_array_almost_equal,
@@ -23,7 +24,6 @@
from mne.channels import read_custom_montage
from mne.datasets import testing
from mne.io import read_raw_eeglab
-from mne.io.eeglab import _eeglab as eeglab_mod
from mne.io.eeglab._eeglab import _readmat
from mne.io.eeglab.eeglab import _dol_to_lod, _get_montage_information
from mne.io.tests.test_raw import _test_raw_reader
@@ -769,25 +769,20 @@ def test_eeglab_drop_nan_annotations(tmp_path):
raw = read_raw_eeglab(file_path, preload=True)
+@flaky
@testing.requires_testing_data
@pytest.mark.timeout(10)
-def test_io_set_preload_false_is_faster(monkeypatch):
+@pytest.mark.slowtest # has the advantage of not running on macOS where it errs a lot
+def test_io_set_preload_false_is_faster():
"""Using preload=False should skip the expensive data read branch."""
- real_loadmat = eeglab_mod.loadmat
- call_counts = {"n": 0}
-
- def counting_loadmat(*args, **kwargs):
- call_counts["n"] += 1
- return real_loadmat(*args, **kwargs)
-
- monkeypatch.setattr(eeglab_mod, "loadmat", counting_loadmat)
+ # warm start
+ read_raw_eeglab(raw_fname_mat, preload=False)
durations = {}
- with _record_warnings():
- for preload in (False, True):
- start = time.perf_counter()
- _ = read_raw_eeglab(raw_fname_mat, preload=preload)
- durations[preload] = time.perf_counter() - start
+ for preload in (True, False):
+ start = time.perf_counter()
+ _ = read_raw_eeglab(raw_fname_mat, preload=preload)
+ durations[preload] = time.perf_counter() - start
# preload=True should not be faster than preload=False (timings may vary
# across systems, so avoid strict thresholds)
diff --git a/mne/io/nihon/nihon.py b/mne/io/nihon/nihon.py
index c53c9e8d5c2..91db6a083d2 100644
--- a/mne/io/nihon/nihon.py
+++ b/mne/io/nihon/nihon.py
@@ -23,7 +23,9 @@ def _ensure_path(fname):
@fill_doc
-def read_raw_nihon(fname, preload=False, verbose=None) -> "RawNihon":
+def read_raw_nihon(
+ fname, preload=False, *, encoding="utf-8", verbose=None
+) -> "RawNihon":
"""Reader for an Nihon Kohden EEG file.
Parameters
@@ -32,6 +34,9 @@ def read_raw_nihon(fname, preload=False, verbose=None) -> "RawNihon":
Path to the Nihon Kohden data file (``.EEG``).
preload : bool
If True, all data are loaded at initialization.
+ %(encoding_nihon)s
+
+ .. versionadded:: 1.11
%(verbose)s
Returns
@@ -44,7 +49,7 @@ def read_raw_nihon(fname, preload=False, verbose=None) -> "RawNihon":
--------
mne.io.Raw : Documentation of attributes and methods of RawNihon.
"""
- return RawNihon(fname, preload, verbose)
+ return RawNihon(fname, preload, encoding=encoding, verbose=verbose)
_valid_headers = [
@@ -57,7 +62,7 @@ def read_raw_nihon(fname, preload=False, verbose=None) -> "RawNihon":
"EEG-2100 V02.00",
"DAE-2100D V01.30",
"DAE-2100D V02.00",
- # 'EEG-1200A V01.00', # Not working for the moment.
+ "EEG-1200A V01.00",
]
@@ -66,8 +71,10 @@ def _read_nihon_metadata(fname):
fname = _ensure_path(fname)
pnt_fname = fname.with_suffix(".PNT")
if not pnt_fname.exists():
- warn("No PNT file exists. Metadata will be blank")
- return metadata
+ pnt_fname = fname.with_suffix(".pnt")
+ if not pnt_fname.exists():
+ warn("No PNT file exists. Metadata will be blank")
+ return metadata
logger.info("Found PNT file, reading metadata.")
with open(pnt_fname) as fid:
version = np.fromfile(fid, "|S16", 1).astype("U16")[0]
@@ -315,7 +322,7 @@ def _parse_sub_event_log(sub_event_log):
return t_sub_desc, t_sub_onset
-def _read_nihon_annotations(fname):
+def _read_nihon_annotations(fname, encoding="utf-8"):
fname = _ensure_path(fname)
log_fname = fname.with_suffix(".LOG")
if not log_fname.exists():
@@ -346,15 +353,10 @@ def _read_nihon_annotations(fname):
t_onset += t_sub_onset
t_desc = t_desc.rstrip(b"\x00")
- for enc in _encodings:
- try:
- t_desc = t_desc.decode(enc)
- except UnicodeDecodeError:
- pass
- else:
- break
- else:
- warn(f"Could not decode log as one of {_encodings}")
+ try:
+ t_desc = t_desc.decode(encoding)
+ except UnicodeDecodeError:
+ warn(f"Could not decode log as {encoding}")
continue
all_onsets.append(t_onset)
@@ -414,6 +416,9 @@ class RawNihon(BaseRaw):
Path to the Nihon Kohden data ``.eeg`` file.
preload : bool
If True, all data are loaded at initialization.
+ %(encoding_nihon)s
+
+ .. versionadded:: 1.11
%(verbose)s
See Also
@@ -422,7 +427,7 @@ class RawNihon(BaseRaw):
"""
@verbose
- def __init__(self, fname, preload=False, verbose=None):
+ def __init__(self, fname, preload=False, *, encoding="utf-8", verbose=None):
fname = _check_fname(fname, "read", True, "fname")
data_name = fname.name
logger.info(f"Loading {data_name}")
@@ -468,7 +473,7 @@ def __init__(self, fname, preload=False, verbose=None):
)
# Get annotations from LOG file
- annots = _read_nihon_annotations(fname)
+ annots = _read_nihon_annotations(fname, encoding)
# Annotate acquisition skips
controlblock = header["controlblocks"][0]
diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py
index 6adb4e361e1..72c8a68ae4c 100644
--- a/mne/time_frequency/tests/test_tfr.py
+++ b/mne/time_frequency/tests/test_tfr.py
@@ -614,6 +614,7 @@ def test_tfr_decim_and_shift_time(epochs, method, freqs, decim):
assert_array_equal(freqs, tfr.freqs)
+@pytest.mark.slowtest
@pytest.mark.parametrize("inst", ("raw_tfr", "epochs_tfr", "average_tfr"))
def test_tfr_io(inst, average_tfr, request, tmp_path):
"""Test TFR I/O."""
diff --git a/mne/utils/docs.py b/mne/utils/docs.py
index de47ba6e192..337d50b462a 100644
--- a/mne/utils/docs.py
+++ b/mne/utils/docs.py
@@ -1285,6 +1285,11 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75):
encoding according to the EDF+ standard).
"""
+docdict["encoding_nihon"] = """
+encoding : str
+ Text encoding of Nihon Kohden annotations. See :ref:`standard-encodings`.
+"""
+
docdict["encoding_nirx"] = """
encoding : str
Text encoding of the NIRX header file. See :ref:`standard-encodings`.
diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py
index 66d05df5b73..500319467fd 100644
--- a/mne/viz/backends/_pyvista.py
+++ b/mne/viz/backends/_pyvista.py
@@ -212,7 +212,8 @@ def __init__(
):
from .._3d import _get_3d_option
- _require_version("pyvista", "use 3D rendering", "0.32")
+ # TODO VERSION change whenever PyVista min gets updated:
+ _require_version("pyvista", "use 3D rendering", "0.42")
multi_samples = _get_3d_option("multi_samples")
# multi_samples > 1 is broken on macOS + Intel Iris + volume rendering
if platform.system() == "Darwin":
diff --git a/pyproject.toml b/pyproject.toml
index 43c36d66eec..3455d707657 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,11 +23,11 @@ dependencies = [
"decorator",
"jinja2",
"lazy_loader >= 0.3",
- "matplotlib >= 3.8", # released: 2023/09/15
- "numpy >= 1.26,<3", # released: 2023/09/16
+ "matplotlib >= 3.8", # released 2023-09-15, will become 3.9 on 2026-05-15
+ "numpy >= 1.26, < 3", # released 2023-09-16, will become 2.0 on 2026-06-16
"packaging",
"pooch >= 1.5",
- "scipy >= 1.11", # released: 2023/06/25
+ "scipy >= 1.11", # released 2023-06-28, will become 1.12 on 2026-01-19
"tqdm",
]
description = "MNE-Python project for MEG and EEG data analysis."
@@ -78,7 +78,7 @@ doc = [
"sphinx-gallery >= 0.16",
"sphinx_copybutton",
"sphinxcontrib-bibtex >= 2.5",
- "sphinxcontrib-towncrier >=0.5.0a0",
+ "sphinxcontrib-towncrier >= 0.5.0a0",
"sphinxcontrib-youtube",
]
full = ["mne[full-no-qt]", "PyQt6 != 6.6.0", "PyQt6-Qt6 != 6.6.0, != 6.7.0"]
@@ -112,17 +112,17 @@ full-no-qt = [
"nilearn",
"numba",
"openmeeg >= 2.5.7",
- "pandas >= 2.1", # released: 2023/08/30
+ "pandas >= 2.1", # released 2023-08-30, will become 2.2 on 2026-01-19
"pillow", # for `Brain.save_image` and `mne.Report`
"pyarrow", # only needed to avoid a deprecation warning in pandas
"pybv",
"pyobjc-framework-Cocoa >= 5.2.0; platform_system == 'Darwin'",
"python-picard",
- "pyvista >= 0.32, != 0.35.2, != 0.38.0, != 0.38.1, != 0.38.2, != 0.38.3, != 0.38.4, != 0.38.5, != 0.38.6, != 0.42.0",
- "pyvistaqt >= 0.4",
+ "pyvista >= 0.42.1", # released 2023-09-06, will become 0.43 on 2025-12-06
+ "pyvistaqt >= 0.11", # released 2023-06-30, no newer version available
"qdarkstyle != 3.2.2",
"qtpy",
- "scikit-learn >=1.3.0", # released: 2023/06/30
+ "scikit-learn >= 1.3", # released 2023-06-30, will become 1.4 on 2026-01-17
"sip",
"snirf",
"statsmodels",
@@ -152,7 +152,7 @@ test = [
"pytest-timeout",
"ruff",
"toml-sort",
- "tomli; python_version<'3.11'",
+ "tomli; python_version < '3.11'",
"twine",
"vulture",
"wheel",