diff --git a/doc/changes/v1.6.rst b/doc/changes/v1.6.rst index edb719f5043..ca782cfc247 100644 --- a/doc/changes/v1.6.rst +++ b/doc/changes/v1.6.rst @@ -4,6 +4,7 @@ Version 1.6.1 (unreleased) -------------------------- - Fix bug with type hints in :func:`mne.io.read_raw_neuralynx` (:gh:`12236` by `Richard Höchenberger`_) +- ``defusedxml`` is now an optional (rather than required) dependency and needed when reading EGI-MFF data, NEDF data, and BrainVision montages (:gh:`12264` by `Eric Larson`_) .. _changes_1_6_0: diff --git a/mne/channels/_dig_montage_utils.py b/mne/channels/_dig_montage_utils.py index 4d2e9e6af3f..0f34af975d2 100644 --- a/mne/channels/_dig_montage_utils.py +++ b/mne/channels/_dig_montage_utils.py @@ -13,9 +13,8 @@ # Copyright the MNE-Python contributors. import numpy as np -from defusedxml import ElementTree -from ..utils import Bunch, _check_fname, warn +from ..utils import Bunch, _check_fname, _soft_import, warn def _read_dig_montage_egi( @@ -28,8 +27,8 @@ def _read_dig_montage_egi( "hsp, hpi, elp, point_names, fif must all be " "None if egi is not None" ) _check_fname(fname, overwrite="read", must_exist=True) - - root = ElementTree.parse(fname).getroot() + defusedxml = _soft_import("defusedxml", "reading EGI montages") + root = defusedxml.ElementTree.parse(fname).getroot() ns = root.tag[root.tag.index("{") : root.tag.index("}") + 1] sensors = root.find("%ssensorLayout/%ssensors" % (ns, ns)) fids = dict() @@ -76,8 +75,8 @@ def _read_dig_montage_egi( def _parse_brainvision_dig_montage(fname, scale): FID_NAME_MAP = {"Nasion": "nasion", "RPA": "rpa", "LPA": "lpa"} - - root = ElementTree.parse(fname).getroot() + defusedxml = _soft_import("defusedxml", "reading BrainVision montages") + root = defusedxml.ElementTree.parse(fname).getroot() sensors = root.find("CapTrakElectrodeList") fids, dig_ch_pos = dict(), dict() diff --git a/mne/channels/_standard_montage_utils.py b/mne/channels/_standard_montage_utils.py index 43c8fa6aecd..7b70c57881b 100644 --- a/mne/channels/_standard_montage_utils.py +++ b/mne/channels/_standard_montage_utils.py @@ -9,11 +9,10 @@ from functools import partial import numpy as np -from defusedxml import ElementTree from .._freesurfer import get_mni_fiducials from ..transforms import _sph_to_cart -from ..utils import _pl, warn +from ..utils import _pl, _soft_import, warn from . import __file__ as _CHANNELS_INIT_FILE from .montage import make_dig_montage @@ -344,7 +343,8 @@ def _read_brainvision(fname, head_size): # standard electrode positions: X-axis from T7 to T8, Y-axis from Oz to # Fpz, Z-axis orthogonal from XY-plane through Cz, fit to a sphere if # idealized (when radius=1), specified in millimeters - root = ElementTree.parse(fname).getroot() + defusedxml = _soft_import("defusedxml", "reading BrainVision montages") + root = defusedxml.ElementTree.parse(fname).getroot() ch_names = [s.text for s in root.findall("./Electrode/Name")] theta = [float(s.text) for s in root.findall("./Electrode/Theta")] pol = np.deg2rad(np.array(theta)) diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index f4da1e6932e..364fde291d8 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -513,6 +513,8 @@ def test_documented(): ) def test_montage_readers(reader, file_content, expected_dig, ext, warning, tmp_path): """Test that we have an equivalent of read_montage for all file formats.""" + if file_content.startswith(" \x00""" +pytest.importorskip("defusedxml") + @pytest.mark.parametrize("nacc", (0, 3)) def test_nedf_header_parser(nacc): diff --git a/pyproject.toml b/pyproject.toml index 0da13070027..4bdffb6dc2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,6 @@ dependencies = [ "jinja2", "importlib_resources>=5.10.2; python_version<'3.9'", "lazy_loader>=0.3", - "defusedxml", ] [project.optional-dependencies] @@ -60,7 +59,8 @@ hdf5 = ["h5io", "pymatreader"] full = [ "mne[hdf5]", "qtpy", - "PyQt6", + "PyQt6!=6.6.1", + "PyQt6-Qt6!=6.6.1", "pyobjc-framework-Cocoa>=5.2.0; platform_system=='Darwin'", "sip", "scikit-learn", @@ -95,6 +95,12 @@ full = [ "darkdetect", "qdarkstyle", "threadpoolctl", + # duplicated in test_extra: + "eeglabio", + "EDFlib-Python", + "pybv", + "snirf", + "defusedxml", ] # Dependencies for running the test infrastructure