diff --git a/README.rst b/README.rst index cfa7488324f..79717ee8b40 100644 --- a/README.rst +++ b/README.rst @@ -80,7 +80,6 @@ The minimum required dependencies to run MNE-Python are: - `tqdm `__ - `Jinja2 `__ - `decorator `__ -- `lazy-loader `__ ≥ 0.3 - `packaging `__ .. ↑↑↑ END CORE DEPS LIST. DO NOT EDIT! HANDLED BY PRE-COMMIT HOOK ↑↑↑ diff --git a/codemeta.json b/codemeta.json index d5ec88c23e9..5d6ff012a2a 100644 --- a/codemeta.json +++ b/codemeta.json @@ -40,7 +40,6 @@ "python>= 3.10", "decorator", "jinja2", - "lazy_loader >= 0.3", "matplotlib >= 3.6", "numpy >= 1.23,<3", "packaging", diff --git a/environment.yml b/environment.yml index 78c773e56bf..29abecd6e44 100644 --- a/environment.yml +++ b/environment.yml @@ -22,7 +22,6 @@ dependencies: - jinja2 - joblib - jupyter - - lazy_loader >=0.3 - mamba - matplotlib >=3.7 - mffpy >=0.5.7 diff --git a/mne/__init__.py b/mne/__init__.py index 6abe2bccb62..8210ed19136 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -3,6 +3,7 @@ # Copyright the MNE-Python contributors. """MNE software for MEG and EEG data analysis.""" + # PEP0440 compatible formatted version, see: # https://www.python.org/dev/peps/pep-0440/ # @@ -17,8 +18,6 @@ # X.Y # Final release # # Dev branch marker is: 'X.Y.devN' where N is an integer. -# -import lazy_loader as lazy try: from importlib.metadata import version @@ -27,10 +26,445 @@ except Exception: __version__ = "0.0.0" -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +# have to import verbose first since it's needed by many things +from .utils import ( + set_log_level, + set_log_file, + verbose, + set_config, + get_config, + get_config_path, + set_cache_dir, + set_memmap_min_size, + grand_average, + sys_info, + open_docs, + use_log_level, +) +from ._fiff.pick import ( + pick_types, + pick_channels, + pick_channels_regexp, + pick_channels_forward, + pick_types_forward, + pick_channels_cov, + pick_info, + channel_type, + channel_indices_by_type, +) +from .io.base import concatenate_raws, match_channel_orders +from ._fiff.meas_info import create_info, Info +from ._fiff.proj import Projection +from .io.kit import read_epochs_kit +from .io.eeglab import read_epochs_eeglab +from ._fiff.reference import ( + set_eeg_reference, + set_bipolar_reference, + add_reference_channels, +) +from ._fiff.what import what +from .bem import ( + make_sphere_model, + make_bem_model, + make_bem_solution, + read_bem_surfaces, + write_bem_surfaces, + write_head_bem, + read_bem_solution, + write_bem_solution, +) +from .cov import ( + read_cov, + write_cov, + Covariance, + compute_raw_covariance, + compute_covariance, + whiten_evoked, + make_ad_hoc_cov, +) +from .event import ( + read_events, + write_events, + find_events, + merge_events, + pick_events, + make_fixed_length_events, + concatenate_events, + find_stim_steps, + AcqParserFIF, + count_events, +) +from ._freesurfer import ( + head_to_mni, + head_to_mri, + read_talxfm, + get_volume_labels_from_aseg, + read_freesurfer_lut, + vertex_to_mni, + read_lta, +) +from .forward import ( + read_forward_solution, + apply_forward, + apply_forward_raw, + average_forward_solutions, + Forward, + write_forward_solution, + make_forward_solution, + convert_forward_solution, + make_field_map, + make_forward_dipole, + use_coil_def, +) +from .source_estimate import ( + read_source_estimate, + SourceEstimate, + VectorSourceEstimate, + VolSourceEstimate, + VolVectorSourceEstimate, + MixedSourceEstimate, + MixedVectorSourceEstimate, + grade_to_tris, + spatial_src_adjacency, + spatial_tris_adjacency, + spatial_dist_adjacency, + spatial_inter_hemi_adjacency, + spatio_temporal_src_adjacency, + spatio_temporal_tris_adjacency, + spatio_temporal_dist_adjacency, + extract_label_time_course, + stc_near_sensors, +) +from .surface import ( + read_surface, + write_surface, + decimate_surface, + read_tri, + get_head_surf, + get_meg_helmet_surf, + dig_mri_distances, + get_montage_volume_labels, +) +from .morph_map import read_morph_map +from .morph import ( + SourceMorph, + read_source_morph, + grade_to_vertices, + compute_source_morph, +) +from .annotations import ( + Annotations, + read_annotations, + annotations_from_events, + events_from_annotations, + count_annotations, +) +from .epochs import ( + BaseEpochs, + Epochs, + EpochsArray, + read_epochs, + concatenate_epochs, + make_fixed_length_epochs, +) +from .evoked import Evoked, EvokedArray, read_evokeds, write_evokeds, combine_evoked +from .label import ( + read_label, + label_sign_flip, + write_label, + stc_to_label, + grow_labels, + Label, + split_label, + BiHemiLabel, + read_labels_from_annot, + write_labels_to_annot, + random_parcellation, + morph_labels, + labels_to_stc, +) +from .misc import parse_config, read_reject_parameters +from .coreg import ( + create_default_subject, + scale_bem, + scale_mri, + scale_labels, + scale_source_space, +) +from .transforms import read_trans, write_trans, transform_surface_to, Transform +from .proj import ( + read_proj, + write_proj, + compute_proj_epochs, + compute_proj_evoked, + compute_proj_raw, + sensitivity_map, +) +from .dipole import read_dipole, Dipole, DipoleFixed, fit_dipole +from .channels import ( + equalize_channels, + rename_channels, + find_layout, + read_vectorview_selection, +) +from .report import Report, open_report -# initialize logging -from .utils import set_log_level, set_log_file +from .io import ( + read_epochs_fieldtrip, + read_evoked_besa, + read_evoked_fieldtrip, + read_evokeds_mff, +) +from .rank import compute_rank + +from .source_space import ( + SourceSpaces, + add_source_space_distances, + compute_distance_to_sensors, + get_decimated_surfaces, + get_volume_labels_from_src, + morph_source_spaces, + read_source_spaces, + setup_source_space, + setup_volume_source_space, + write_source_spaces, +) +from . import beamformer +from . import channels +from . import chpi +from . import commands +from . import coreg +from . import cuda +from . import datasets +from . import decoding +from . import defaults +from . import dipole +from . import epochs +from . import event +from . import forward +from . import io +from . import filter +from . import gui +from . import inverse_sparse +from . import minimum_norm +from . import preprocessing +from . import report +from . import simulation +from . import source_space +from . import stats +from . import surface +from . import time_frequency +from . import viz +from . import export + +# initialize logging set_log_level(None, False) set_log_file() + +__all__ = [ + "__version__", + "set_log_level", + "set_log_file", + "verbose", + "set_config", + "get_config", + "get_config_path", + "set_cache_dir", + "set_memmap_min_size", + "grand_average", + "sys_info", + "open_docs", + "use_log_level", + "pick_types", + "pick_channels", + "pick_channels_regexp", + "pick_channels_forward", + "pick_types_forward", + "pick_channels_cov", + "pick_info", + "channel_type", + "channel_indices_by_type", + "concatenate_raws", + "match_channel_orders", + "create_info", + "Info", + "Projection", + "read_epochs_kit", + "read_epochs_eeglab", + "set_eeg_reference", + "set_bipolar_reference", + "add_reference_channels", + "what", + "make_sphere_model", + "make_bem_model", + "make_bem_solution", + "read_bem_surfaces", + "write_bem_surfaces", + "write_head_bem", + "read_bem_solution", + "write_bem_solution", + "read_cov", + "write_cov", + "Covariance", + "compute_raw_covariance", + "compute_covariance", + "whiten_evoked", + "make_ad_hoc_cov", + "read_events", + "write_events", + "find_events", + "merge_events", + "pick_events", + "make_fixed_length_events", + "concatenate_events", + "find_stim_steps", + "AcqParserFIF", + "count_events", + "head_to_mni", + "head_to_mri", + "read_talxfm", + "get_volume_labels_from_aseg", + "read_freesurfer_lut", + "vertex_to_mni", + "read_lta", + "read_forward_solution", + "apply_forward", + "apply_forward_raw", + "average_forward_solutions", + "Forward", + "write_forward_solution", + "make_forward_solution", + "convert_forward_solution", + "make_field_map", + "make_forward_dipole", + "use_coil_def", + "read_source_estimate", + "SourceEstimate", + "VectorSourceEstimate", + "VolSourceEstimate", + "VolVectorSourceEstimate", + "MixedSourceEstimate", + "MixedVectorSourceEstimate", + "grade_to_tris", + "spatial_src_adjacency", + "spatial_tris_adjacency", + "spatial_dist_adjacency", + "spatial_inter_hemi_adjacency", + "spatio_temporal_src_adjacency", + "spatio_temporal_tris_adjacency", + "spatio_temporal_dist_adjacency", + "extract_label_time_course", + "stc_near_sensors", + "read_surface", + "write_surface", + "decimate_surface", + "read_tri", + "get_head_surf", + "get_meg_helmet_surf", + "dig_mri_distances", + "get_montage_volume_labels", + "read_morph_map", + "SourceMorph", + "read_source_morph", + "grade_to_vertices", + "compute_source_morph", + "Annotations", + "read_annotations", + "annotations_from_events", + "events_from_annotations", + "count_annotations", + "BaseEpochs", + "Epochs", + "EpochsArray", + "read_epochs", + "concatenate_epochs", + "make_fixed_length_epochs", + "Evoked", + "EvokedArray", + "read_evokeds", + "write_evokeds", + "combine_evoked", + "read_label", + "label_sign_flip", + "write_label", + "stc_to_label", + "grow_labels", + "Label", + "split_label", + "BiHemiLabel", + "read_labels_from_annot", + "write_labels_to_annot", + "random_parcellation", + "morph_labels", + "labels_to_stc", + "parse_config", + "read_reject_parameters", + "create_default_subject", + "scale_bem", + "scale_mri", + "scale_labels", + "scale_source_space", + "read_trans", + "write_trans", + "transform_surface_to", + "Transform", + "read_proj", + "write_proj", + "compute_proj_epochs", + "compute_proj_evoked", + "compute_proj_raw", + "sensitivity_map", + "read_dipole", + "Dipole", + "DipoleFixed", + "fit_dipole", + "equalize_channels", + "rename_channels", + "find_layout", + "read_vectorview_selection", + "Report", + "open_report", + "read_epochs_fieldtrip", + "read_evoked_besa", + "read_evoked_fieldtrip", + "read_evokeds_mff", + "compute_rank", + "SourceSpaces", + "add_source_space_distances", + "compute_distance_to_sensors", + "get_decimated_surfaces", + "get_volume_labels_from_src", + "morph_source_spaces", + "read_source_spaces", + "setup_source_space", + "setup_volume_source_space", + "write_source_spaces", + "beamformer", + "channels", + "chpi", + "commands", + "coreg", + "cuda", + "datasets", + "decoding", + "defaults", + "dipole", + "epochs", + "event", + "forward", + "io", + "filter", + "gui", + "inverse_sparse", + "minimum_norm", + "preprocessing", + "report", + "simulation", + "source_space", + "stats", + "surface", + "time_frequency", + "viz", + "export", +] diff --git a/mne/__init__.pyi b/mne/__init__.pyi deleted file mode 100644 index d50b5209346..00000000000 --- a/mne/__init__.pyi +++ /dev/null @@ -1,430 +0,0 @@ -__all__ = [ - "AcqParserFIF", - "Annotations", - "BaseEpochs", - "BiHemiLabel", - "Covariance", - "Dipole", - "DipoleFixed", - "Epochs", - "EpochsArray", - "Evoked", - "EvokedArray", - "Forward", - "Info", - "Label", - "MixedSourceEstimate", - "MixedVectorSourceEstimate", - "Projection", - "Report", - "SourceEstimate", - "SourceMorph", - "SourceSpaces", - "Transform", - "VectorSourceEstimate", - "VolSourceEstimate", - "VolVectorSourceEstimate", - "add_reference_channels", - "add_source_space_distances", - "annotations_from_events", - "apply_forward", - "apply_forward_raw", - "average_forward_solutions", - "beamformer", - "channel_indices_by_type", - "channel_type", - "channels", - "chpi", - "combine_evoked", - "commands", - "compute_covariance", - "compute_proj_epochs", - "compute_proj_evoked", - "compute_proj_raw", - "compute_rank", - "compute_raw_covariance", - "compute_source_morph", - "concatenate_epochs", - "concatenate_events", - "concatenate_raws", - "convert_forward_solution", - "coreg", - "count_annotations", - "count_events", - "create_default_subject", - "create_info", - "cuda", - "datasets", - "decimate_surface", - "decoding", - "defaults", - "dig_mri_distances", - "dipole", - "epochs", - "equalize_channels", - "event", - "events_from_annotations", - "export", - "extract_label_time_course", - "filter", - "find_events", - "find_layout", - "find_stim_steps", - "fit_dipole", - "forward", - "get_config", - "get_config_path", - "get_head_surf", - "get_meg_helmet_surf", - "get_montage_volume_labels", - "get_volume_labels_from_aseg", - "get_volume_labels_from_src", - "grade_to_tris", - "grade_to_vertices", - "grand_average", - "grow_labels", - "gui", - "head_to_mni", - "head_to_mri", - "inverse_sparse", - "io", - "label_sign_flip", - "labels_to_stc", - "make_ad_hoc_cov", - "make_bem_model", - "make_bem_solution", - "make_field_map", - "make_fixed_length_epochs", - "make_fixed_length_events", - "make_forward_dipole", - "make_forward_solution", - "make_sphere_model", - "match_channel_orders", - "merge_events", - "minimum_norm", - "morph_labels", - "morph_source_spaces", - "open_docs", - "open_report", - "parse_config", - "pick_channels", - "pick_channels_cov", - "pick_channels_forward", - "pick_channels_regexp", - "pick_events", - "pick_info", - "pick_types", - "pick_types_forward", - "preprocessing", - "random_parcellation", - "read_annotations", - "read_bem_solution", - "read_bem_surfaces", - "read_cov", - "read_dipole", - "read_epochs", - "read_epochs_eeglab", - "read_epochs_fieldtrip", - "read_epochs_kit", - "read_events", - "read_evoked_besa", - "read_evoked_fieldtrip", - "read_evokeds", - "read_evokeds_mff", - "read_forward_solution", - "read_freesurfer_lut", - "read_label", - "read_labels_from_annot", - "read_lta", - "read_morph_map", - "read_proj", - "read_reject_parameters", - "read_source_estimate", - "read_source_morph", - "read_source_spaces", - "read_surface", - "read_talxfm", - "read_trans", - "read_tri", - "read_vectorview_selection", - "rename_channels", - "report", - "scale_bem", - "scale_labels", - "scale_mri", - "scale_source_space", - "sensitivity_map", - "set_bipolar_reference", - "set_cache_dir", - "set_config", - "set_eeg_reference", - "set_log_file", - "set_log_level", - "set_memmap_min_size", - "setup_source_space", - "setup_volume_source_space", - "simulation", - "source_space", - "spatial_dist_adjacency", - "spatial_inter_hemi_adjacency", - "spatial_src_adjacency", - "spatial_tris_adjacency", - "spatio_temporal_dist_adjacency", - "spatio_temporal_src_adjacency", - "spatio_temporal_tris_adjacency", - "split_label", - "stats", - "stc_near_sensors", - "stc_to_label", - "surface", - "sys_info", - "time_frequency", - "transform_surface_to", - "use_coil_def", - "use_log_level", - "verbose", - "vertex_to_mni", - "viz", - "what", - "whiten_evoked", - "write_bem_solution", - "write_bem_surfaces", - "write_cov", - "write_events", - "write_evokeds", - "write_forward_solution", - "write_head_bem", - "write_label", - "write_labels_to_annot", - "write_proj", - "write_source_spaces", - "write_surface", - "write_trans", -] -from . import ( - beamformer, - channels, - chpi, - commands, - coreg, - cuda, - datasets, - decoding, - defaults, - dipole, - epochs, - event, - export, - filter, - forward, - gui, - inverse_sparse, - io, - minimum_norm, - preprocessing, - report, - simulation, - source_space, - stats, - surface, - time_frequency, - viz, -) -from ._fiff.meas_info import Info, create_info -from ._fiff.pick import ( - channel_indices_by_type, - channel_type, - pick_channels, - pick_channels_cov, - pick_channels_forward, - pick_channels_regexp, - pick_info, - pick_types, - pick_types_forward, -) -from ._fiff.proj import Projection -from ._fiff.reference import ( - add_reference_channels, - set_bipolar_reference, - set_eeg_reference, -) -from ._fiff.what import what -from ._freesurfer import ( - get_volume_labels_from_aseg, - head_to_mni, - head_to_mri, - read_freesurfer_lut, - read_lta, - read_talxfm, - vertex_to_mni, -) -from .annotations import ( - Annotations, - annotations_from_events, - count_annotations, - events_from_annotations, - read_annotations, -) -from .bem import ( - make_bem_model, - make_bem_solution, - make_sphere_model, - read_bem_solution, - read_bem_surfaces, - write_bem_solution, - write_bem_surfaces, - write_head_bem, -) -from .channels import ( - equalize_channels, - find_layout, - read_vectorview_selection, - rename_channels, -) -from .coreg import ( - create_default_subject, - scale_bem, - scale_labels, - scale_mri, - scale_source_space, -) -from .cov import ( - Covariance, - compute_covariance, - compute_raw_covariance, - make_ad_hoc_cov, - read_cov, - whiten_evoked, - write_cov, -) -from .dipole import Dipole, DipoleFixed, fit_dipole, read_dipole -from .epochs import ( - BaseEpochs, - Epochs, - EpochsArray, - concatenate_epochs, - make_fixed_length_epochs, - read_epochs, -) -from .event import ( - AcqParserFIF, - concatenate_events, - count_events, - find_events, - find_stim_steps, - make_fixed_length_events, - merge_events, - pick_events, - read_events, - write_events, -) -from .evoked import Evoked, EvokedArray, combine_evoked, read_evokeds, write_evokeds -from .forward import ( - Forward, - apply_forward, - apply_forward_raw, - average_forward_solutions, - convert_forward_solution, - make_field_map, - make_forward_dipole, - make_forward_solution, - read_forward_solution, - use_coil_def, - write_forward_solution, -) -from .io import ( - read_epochs_fieldtrip, - read_evoked_besa, - read_evoked_fieldtrip, - read_evokeds_mff, -) -from .io.base import concatenate_raws, match_channel_orders -from .io.eeglab import read_epochs_eeglab -from .io.kit import read_epochs_kit -from .label import ( - BiHemiLabel, - Label, - grow_labels, - label_sign_flip, - labels_to_stc, - morph_labels, - random_parcellation, - read_label, - read_labels_from_annot, - split_label, - stc_to_label, - write_label, - write_labels_to_annot, -) -from .misc import parse_config, read_reject_parameters -from .morph import ( - SourceMorph, - compute_source_morph, - grade_to_vertices, - read_source_morph, -) -from .morph_map import read_morph_map -from .proj import ( - compute_proj_epochs, - compute_proj_evoked, - compute_proj_raw, - read_proj, - sensitivity_map, - write_proj, -) -from .rank import compute_rank -from .report import Report, open_report -from .source_estimate import ( - MixedSourceEstimate, - MixedVectorSourceEstimate, - SourceEstimate, - VectorSourceEstimate, - VolSourceEstimate, - VolVectorSourceEstimate, - extract_label_time_course, - grade_to_tris, - read_source_estimate, - spatial_dist_adjacency, - spatial_inter_hemi_adjacency, - spatial_src_adjacency, - spatial_tris_adjacency, - spatio_temporal_dist_adjacency, - spatio_temporal_src_adjacency, - spatio_temporal_tris_adjacency, - stc_near_sensors, -) -from .source_space._source_space import ( - SourceSpaces, - add_source_space_distances, - get_volume_labels_from_src, - morph_source_spaces, - read_source_spaces, - setup_source_space, - setup_volume_source_space, - write_source_spaces, -) -from .surface import ( - decimate_surface, - dig_mri_distances, - get_head_surf, - get_meg_helmet_surf, - get_montage_volume_labels, - read_surface, - read_tri, - write_surface, -) -from .transforms import Transform, read_trans, transform_surface_to, write_trans -from .utils import ( - get_config, - get_config_path, - grand_average, - open_docs, - set_cache_dir, - set_config, - set_log_file, - set_log_level, - set_memmap_min_size, - sys_info, - use_log_level, - verbose, -) diff --git a/mne/_fiff/__init__.py b/mne/_fiff/__init__.py index 48a5871c58b..6d14d005fd4 100644 --- a/mne/_fiff/__init__.py +++ b/mne/_fiff/__init__.py @@ -5,4 +5,4 @@ # Copyright the MNE-Python contributors. # All imports should be done directly to submodules, so we don't import -# anything here or use lazy_loader. +# anything here. diff --git a/mne/_fiff/open.py b/mne/_fiff/open.py index 1d99bd8ddc2..a0716d4d673 100644 --- a/mne/_fiff/open.py +++ b/mne/_fiff/open.py @@ -7,7 +7,6 @@ from pathlib import Path import numpy as np -from scipy.sparse import issparse from ..utils import _check_fname, _file_like, _validate_type, logger, verbose, warn from .constants import FIFF @@ -295,6 +294,8 @@ def _show_tree( show_bytes=False, ): """Show FIFF tree.""" + from scipy.sparse import issparse + this_idt = indent * level next_idt = indent * (level + 1) # print block-level information diff --git a/mne/_fiff/tag.py b/mne/_fiff/tag.py index 6a4636ef264..6d43ac54c29 100644 --- a/mne/_fiff/tag.py +++ b/mne/_fiff/tag.py @@ -10,7 +10,6 @@ from typing import Any import numpy as np -from scipy.sparse import csc_array, csr_array from ..utils import _check_option, warn from ..utils.numerics import _julian_to_date @@ -146,6 +145,8 @@ def _read_tag_header(fid, pos): def _read_matrix(fid, tag, shape, rlims): """Read a matrix (dense or sparse) tag.""" + from scipy.sparse import csc_array, csr_array + # This should be easy to implement (see _frombuffer_rows) # if we need it, but for now, it's not... if shape is not None or rlims is not None: diff --git a/mne/_fiff/write.py b/mne/_fiff/write.py index 8486ca13121..5cdac668c25 100644 --- a/mne/_fiff/write.py +++ b/mne/_fiff/write.py @@ -11,7 +11,6 @@ from gzip import GzipFile import numpy as np -from scipy.sparse import csc_array, csr_array from ..utils import _check_fname, _file_like, _validate_type, logger from ..utils.numerics import _date_to_julian @@ -422,6 +421,8 @@ def write_float_sparse_rcs(fid, kind, mat): def write_float_sparse(fid, kind, mat, fmt="auto"): """Write a single-precision floating-point sparse matrix tag.""" + from scipy.sparse import csc_array, csr_array + if fmt == "auto": fmt = "csr" if isinstance(mat, csr_array) else "csc" need = csr_array if fmt == "csr" else csc_array diff --git a/mne/_ola.py b/mne/_ola.py index e43e7cd3d31..e95d8137793 100644 --- a/mne/_ola.py +++ b/mne/_ola.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.signal import get_window from .utils import _ensure_int, _validate_type, logger, verbose @@ -280,6 +279,8 @@ def __init__( name="COLA", verbose=None, ): + from scipy.signal import get_window + n_samples = _ensure_int(n_samples, "n_samples") n_overlap = _ensure_int(n_overlap, "n_overlap") n_total = _ensure_int(n_total, "n_total") diff --git a/mne/annotations.py b/mne/annotations.py index 629ee7b20cb..43d5662056f 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -13,7 +13,6 @@ from textwrap import shorten import numpy as np -from scipy.io import loadmat from ._fiff.constants import FIFF from ._fiff.open import fiff_open @@ -1299,6 +1298,7 @@ def _read_brainstorm_annotations(fname, orig_time=None): annot : instance of Annotations | None The annotations. """ + from scipy.io import loadmat def get_duration_from_times(t): return t[1] - t[0] if t.shape[0] == 2 else np.zeros(len(t[0])) diff --git a/mne/beamformer/__init__.py b/mne/beamformer/__init__.py index 16a5b363eb6..5493ced6959 100644 --- a/mne/beamformer/__init__.py +++ b/mne/beamformer/__init__.py @@ -3,6 +3,22 @@ # Copyright the MNE-Python contributors. """Beamformers for source localization.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._lcmv import ( + make_lcmv, + apply_lcmv, + apply_lcmv_epochs, + apply_lcmv_raw, + apply_lcmv_cov, +) +from ._dics import ( + make_dics, + apply_dics, + apply_dics_epochs, + apply_dics_tfr_epochs, + apply_dics_csd, +) +from ._rap_music import rap_music +from ._rap_music import trap_music +from ._compute_beamformer import Beamformer, read_beamformer +from .resolution_matrix import make_lcmv_resolution_matrix diff --git a/mne/beamformer/__init__.pyi b/mne/beamformer/__init__.pyi deleted file mode 100644 index f7d6eb9d203..00000000000 --- a/mne/beamformer/__init__.pyi +++ /dev/null @@ -1,34 +0,0 @@ -__all__ = [ - "Beamformer", - "apply_dics", - "apply_dics_csd", - "apply_dics_epochs", - "apply_dics_tfr_epochs", - "apply_lcmv", - "apply_lcmv_cov", - "apply_lcmv_epochs", - "apply_lcmv_raw", - "make_dics", - "make_lcmv", - "make_lcmv_resolution_matrix", - "rap_music", - "read_beamformer", - "trap_music", -] -from ._compute_beamformer import Beamformer, read_beamformer -from ._dics import ( - apply_dics, - apply_dics_csd, - apply_dics_epochs, - apply_dics_tfr_epochs, - make_dics, -) -from ._lcmv import ( - apply_lcmv, - apply_lcmv_cov, - apply_lcmv_epochs, - apply_lcmv_raw, - make_lcmv, -) -from ._rap_music import rap_music, trap_music -from .resolution_matrix import make_lcmv_resolution_matrix diff --git a/mne/beamformer/_rap_music.py b/mne/beamformer/_rap_music.py index b09e1d23a87..705a121f0e6 100644 --- a/mne/beamformer/_rap_music.py +++ b/mne/beamformer/_rap_music.py @@ -5,7 +5,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg from .._fiff.pick import pick_channels_forward, pick_info from ..fixes import _safe_svd @@ -48,6 +47,8 @@ def _apply_rap_music( Data explained by the dipoles using a least square fitting with the selected active dipoles and their estimated orientation. """ + from scipy import linalg + info = pick_info(info, picks) del picks # things are much simpler if we avoid surface orientation diff --git a/mne/bem.py b/mne/bem.py index 22aa02d2a0d..4ecd1b79c99 100644 --- a/mne/bem.py +++ b/mne/bem.py @@ -16,7 +16,6 @@ from pathlib import Path import numpy as np -from scipy.optimize import fmin_cobyla from ._fiff._digitization import _dig_kind_dict, _dig_kind_ints, _dig_kind_rev from ._fiff.constants import FIFF, FWD @@ -791,6 +790,8 @@ def _one_step(mu, u): def _fwd_eeg_fit_berg_scherg(m, nterms, nfit): """Fit the Berg-Scherg equivalent spherical model dipole parameters.""" + from scipy.optimize import fmin_cobyla + assert nfit >= 2 u = dict(nfit=nfit, nterms=nterms) @@ -1107,6 +1108,8 @@ def _fit_sphere_to_headshape(info, dig_kinds, verbose=None): def _fit_sphere(points, disp="auto"): """Fit a sphere to an arbitrary set of points.""" + from scipy.optimize import fmin_cobyla + if isinstance(disp, str) and disp == "auto": disp = True if logger.level <= 20 else False # initial guess for center and radius diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py index 901a237da24..df1c0ed4e5f 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -7,6 +7,90 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy +from ..defaults import HEAD_SIZE_DEFAULT +from .layout import ( + Layout, + make_eeg_layout, + make_grid_layout, + read_layout, + find_layout, + generate_2d_layout, +) +from .montage import ( + DigMontage, + get_builtin_montages, + make_dig_montage, + read_dig_dat, + read_dig_egi, + read_dig_captrak, + read_dig_fif, + read_dig_polhemus_isotrak, + read_polhemus_fastscan, + compute_dev_head_t, + make_standard_montage, + read_custom_montage, + read_dig_hpts, + read_dig_localite, + transform_to_head, + compute_native_head_t, +) +from .channels import ( + equalize_channels, + rename_channels, + fix_mag_coil_types, + read_ch_adjacency, + find_ch_adjacency, + make_1020_channel_selections, + combine_channels, + read_vectorview_selection, + unify_bad_channels, + _SELECTIONS, + _EEG_SELECTIONS, + _divide_to_regions, + get_builtin_ch_adjacencies, +) -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__all__ = [ + # Data Structures + "DigMontage", + "Layout", + "_SELECTIONS", + "_EEG_SELECTIONS", + # Factory Methods + "make_dig_montage", + "make_eeg_layout", + "make_grid_layout", + "make_standard_montage", + # Readers + "read_ch_adjacency", + "read_dig_captrak", + "read_dig_dat", + "read_dig_egi", + "read_dig_fif", + "read_dig_localite", + "read_dig_polhemus_isotrak", + "read_layout", + "read_polhemus_fastscan", + "read_custom_montage", + "read_dig_hpts", + # Helpers + "rename_channels", + "make_1020_channel_selections", + "_get_ch_type", + "equalize_channels", + "find_ch_adjacency", + "find_layout", + "fix_mag_coil_types", + "generate_2d_layout", + "get_builtin_montages", + "combine_channels", + "read_vectorview_selection", + "transform_to_head", + "unify_bad_channels", + "_divide_to_regions", + # Adjacency + "get_builtin_ch_adjacencies", + # Other + "compute_dev_head_t", + "compute_native_head_t", +] diff --git a/mne/channels/__init__.pyi b/mne/channels/__init__.pyi deleted file mode 100644 index 05f273a713d..00000000000 --- a/mne/channels/__init__.pyi +++ /dev/null @@ -1,78 +0,0 @@ -__all__ = [ - "DigMontage", - "Layout", - "_EEG_SELECTIONS", - "_SELECTIONS", - "_divide_to_regions", - "combine_channels", - "compute_dev_head_t", - "compute_native_head_t", - "equalize_channels", - "find_ch_adjacency", - "find_layout", - "fix_mag_coil_types", - "generate_2d_layout", - "get_builtin_ch_adjacencies", - "get_builtin_montages", - "make_1020_channel_selections", - "make_dig_montage", - "make_eeg_layout", - "make_grid_layout", - "make_standard_montage", - "read_ch_adjacency", - "read_custom_montage", - "read_dig_captrak", - "read_dig_dat", - "read_dig_egi", - "read_dig_fif", - "read_dig_hpts", - "read_dig_localite", - "read_dig_polhemus_isotrak", - "read_layout", - "read_polhemus_fastscan", - "read_vectorview_selection", - "rename_channels", - "transform_to_head", - "unify_bad_channels", -] -from .channels import ( - _EEG_SELECTIONS, - _SELECTIONS, - _divide_to_regions, - combine_channels, - equalize_channels, - find_ch_adjacency, - fix_mag_coil_types, - get_builtin_ch_adjacencies, - make_1020_channel_selections, - read_ch_adjacency, - read_vectorview_selection, - rename_channels, - unify_bad_channels, -) -from .layout import ( - Layout, - find_layout, - generate_2d_layout, - make_eeg_layout, - make_grid_layout, - read_layout, -) -from .montage import ( - DigMontage, - compute_dev_head_t, - compute_native_head_t, - get_builtin_montages, - make_dig_montage, - make_standard_montage, - read_custom_montage, - read_dig_captrak, - read_dig_dat, - read_dig_egi, - read_dig_fif, - read_dig_hpts, - read_dig_localite, - read_dig_polhemus_isotrak, - read_polhemus_fastscan, - transform_to_head, -) diff --git a/mne/channels/channels.py b/mne/channels/channels.py index d0e57eecb5f..972287385b2 100644 --- a/mne/channels/channels.py +++ b/mne/channels/channels.py @@ -15,10 +15,6 @@ from pathlib import Path import numpy as np -from scipy.io import loadmat -from scipy.sparse import csr_array, lil_array -from scipy.spatial import Delaunay -from scipy.stats import zscore from .._fiff.constants import FIFF from .._fiff.meas_info import ( # noqa F401 @@ -1520,6 +1516,8 @@ def read_ch_adjacency(fname, picks=None): :func:`mne.stats.combine_adjacency` to prepare a final "adjacency" to pass to the eventual function. """ + from scipy.io import loadmat + if op.isabs(fname): fname = str( _check_fname( @@ -1583,6 +1581,8 @@ def _ch_neighbor_adjacency(ch_names, neighbors): ch_adjacency : scipy.sparse.spmatrix The adjacency matrix. """ + from scipy.sparse import csr_array + if len(ch_names) != len(neighbors): raise ValueError("`ch_names` and `neighbors` must have the same length") set_neighbors = {c for d in neighbors for c in d} @@ -1739,6 +1739,9 @@ def _compute_ch_adjacency(info, ch_type): ch_names : list The list of channel names present in adjacency matrix. """ + from scipy.sparse import csr_array, lil_array + from scipy.spatial import Delaunay + from ..channels.layout import _find_topomap_coords, _pair_grad_sensors from ..source_estimate import spatial_tris_adjacency @@ -2161,6 +2164,8 @@ def combine_channels( def _divide_to_regions(info, add_stim=True): """Divide channels to regions by positions.""" + from scipy.stats import zscore + picks = _pick_data_channels(info, exclude=[]) chs_in_lobe = len(picks) // 4 pos = np.array([ch["loc"][:3] for ch in info["chs"]]) diff --git a/mne/channels/interpolation.py b/mne/channels/interpolation.py index 7d5d1a981b7..1bb804a1a53 100644 --- a/mne/channels/interpolation.py +++ b/mne/channels/interpolation.py @@ -4,9 +4,6 @@ import numpy as np from numpy.polynomial.legendre import legval -from scipy.interpolate import RectBivariateSpline -from scipy.linalg import pinv -from scipy.spatial.distance import pdist, squareform from .._fiff.meas_info import _simplify_info from .._fiff.pick import pick_channels, pick_info, pick_types @@ -85,6 +82,8 @@ def _make_interpolation_matrix(pos_from, pos_to, alpha=1e-5): Spherical splines for scalp potential and current density mapping. Electroencephalography Clinical Neurophysiology, Feb; 72(2):184-7. """ + from scipy.linalg import pinv + pos_from = pos_from.copy() pos_to = pos_to.copy() n_from = pos_from.shape[0] @@ -253,6 +252,8 @@ def _interpolate_bads_meeg( @verbose def _interpolate_bads_nirs(inst, exclude=(), verbose=None): + from scipy.spatial.distance import pdist, squareform + from mne.preprocessing.nirs import _validate_nirs_info if len(pick_types(inst.info, fnirs=True, exclude=())) == 0: @@ -291,6 +292,7 @@ def _interpolate_bads_nirs(inst, exclude=(), verbose=None): def _find_seeg_electrode_shaft(pos, tol_shaft=0.002, tol_spacing=1): + from scipy.spatial.distance import pdist, squareform # 1) find nearest neighbor to define the electrode shaft line # 2) find all contacts on the same line # 3) remove contacts with large distances @@ -361,6 +363,8 @@ def _find_seeg_electrode_shaft(pos, tol_shaft=0.002, tol_spacing=1): def _interpolate_bads_seeg( inst, exclude=None, tol_shaft=0.002, tol_spacing=1, verbose=None ): + from scipy.interpolate import RectBivariateSpline + if exclude is None: exclude = list() picks = pick_types(inst.info, meg=False, seeg=True, exclude=exclude) diff --git a/mne/channels/layout.py b/mne/channels/layout.py index 31d0650037e..9e941b50b08 100644 --- a/mne/channels/layout.py +++ b/mne/channels/layout.py @@ -9,7 +9,6 @@ from pathlib import Path import numpy as np -from scipy.spatial.distance import pdist, squareform from .._fiff.constants import FIFF from .._fiff.meas_info import Info @@ -765,6 +764,7 @@ def _box_size(points, width=None, height=None, padding=0.0): height : float Height of the box """ + from scipy.spatial.distance import pdist def xdiff(a, b): return np.abs(a[0] - b[0]) @@ -893,6 +893,8 @@ def _auto_topomap_coords(info, picks, ignore_overlap, to_sphere, sphere): locs : array, shape = (n_sensors, 2) An array of positions of the 2 dimensional map. """ + from scipy.spatial.distance import pdist, squareform + sphere = _check_sphere(sphere, info) logger.debug(f"Generating coords using: {sphere}") diff --git a/mne/chpi.py b/mne/chpi.py index f3e04cd21b3..34c543b4612 100644 --- a/mne/chpi.py +++ b/mne/chpi.py @@ -24,9 +24,6 @@ from functools import partial import numpy as np -from scipy.linalg import orth -from scipy.optimize import fmin_cobyla -from scipy.spatial.distance import cdist from ._fiff.constants import FIFF from ._fiff.meas_info import Info, _simplify_info @@ -538,6 +535,8 @@ def _magnetic_dipole_delta_multi(whitened_fwd_svd, B, B2): def _fit_magnetic_dipole(B_orig, x0, too_close, whitener, coils, guesses): """Fit a single bit of data (x0 = pos).""" + from scipy.optimize import fmin_cobyla + B = np.dot(whitener, B_orig) B2 = np.dot(B, B) objective = partial( @@ -722,6 +721,8 @@ def _reorder_inv_model(inv_model, n_freqs): def _setup_ext_proj(info, ext_order): + from scipy.linalg import orth + meg_picks = pick_types(info, meg=True, eeg=False, exclude="bads") info = pick_info(_simplify_info(info), meg_picks) # makes a copy _, _, _, _, mag_or_fine = _get_mf_picks_fix_mags( @@ -1548,6 +1549,8 @@ def filter_chpi( def _compute_good_distances(hpi_coil_dists, new_pos, dist_limit=0.005): """Compute good coils based on distances.""" + from scipy.spatial.distance import cdist + these_dists = cdist(new_pos, new_pos) these_dists = np.abs(hpi_coil_dists - these_dists) # there is probably a better algorithm for finding the bad ones... diff --git a/mne/commands/__init__.py b/mne/commands/__init__.py index 1c7134ea78b..bb2c3c0311e 100644 --- a/mne/commands/__init__.py +++ b/mne/commands/__init__.py @@ -3,6 +3,5 @@ # Copyright the MNE-Python contributors. """Command-line utilities.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from . import utils diff --git a/mne/commands/__init__.pyi b/mne/commands/__init__.pyi deleted file mode 100644 index c0caf04abbb..00000000000 --- a/mne/commands/__init__.pyi +++ /dev/null @@ -1,2 +0,0 @@ -__all__ = ["utils"] -from . import utils diff --git a/mne/coreg.py b/mne/coreg.py index c7549ee028a..9432bc1883e 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -16,8 +16,6 @@ from glob import glob, iglob import numpy as np -from scipy.optimize import leastsq -from scipy.spatial.distance import cdist from ._fiff._digitization import _get_data_as_dict_from_dig from ._fiff.constants import FIFF @@ -285,6 +283,8 @@ def _decimate_points(pts, res=10): pts : array, shape = (n_points, 3) The decimated points. """ + from scipy.spatial.distance import cdist + pts = np.asarray(pts) # find the bin edges for the voxel space @@ -472,6 +472,8 @@ def fit_matched_points( def _generic_fit(src_pts, tgt_pts, param_info, weights, x0): + from scipy.optimize import leastsq + if param_info[1]: # translate src_pts = np.hstack((src_pts, np.ones((len(src_pts), 1)))) diff --git a/mne/cov.py b/mne/cov.py index 694c836d0cd..ff8f26847f7 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -7,7 +7,6 @@ from math import log import numpy as np -from scipy.sparse import issparse from . import viz from ._fiff.constants import FIFF @@ -2343,6 +2342,8 @@ def whiten_evoked( def _read_cov(fid, node, cov_kind, limited=False, verbose=None): """Read a noise covariance matrix.""" # Find all covariance matrices + from scipy.sparse import issparse + from ._fiff.write import _safe_name_list covs = dir_tree_find(node, FIFF.FIFFB_MNE_COV) diff --git a/mne/cuda.py b/mne/cuda.py index f44dc653a1e..0f59818e24b 100644 --- a/mne/cuda.py +++ b/mne/cuda.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.fft import irfft, rfft from .utils import ( _check_option, @@ -166,6 +165,8 @@ def _setup_cuda_fft_multiply_repeated(n_jobs, h, n_fft, kind="FFT FIR filtering" ----- This function is designed to be used with fft_multiply_repeated(). """ + from scipy.fft import irfft, rfft + cuda_dict = dict(n_fft=n_fft, rfft=rfft, irfft=irfft, h_fft=rfft(h, n=n_fft)) if isinstance(n_jobs, str): _check_option("n_jobs", n_jobs, ("cuda",)) @@ -261,6 +262,8 @@ def _setup_cuda_fft_resample(n_jobs, W, new_len): ----- This function is designed to be used with fft_resample(). """ + from scipy.fft import irfft, rfft + cuda_dict = dict(use_cuda=False, rfft=rfft, irfft=irfft) rfft_len_x = len(W) // 2 + 1 # fold the window onto inself (should be symmetric) and truncate diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py index 700dab0608e..f75378c3b31 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -7,6 +7,77 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy +from . import fieldtrip_cmc +from . import brainstorm +from . import visual_92_categories +from . import kiloword +from . import eegbci +from . import hf_sef +from . import misc +from . import mtrf +from . import sample +from . import somato +from . import multimodal +from . import fnirs_motor +from . import opm +from . import spm_face +from . import testing +from . import _fake +from . import phantom_4dbti +from . import sleep_physionet +from . import limo +from . import refmeg_noise +from . import ssvep +from . import erp_core +from . import epilepsy_ecog +from . import eyelink +from . import ucl_opm_auditory -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._fetch import fetch_dataset +from .utils import ( + _download_all_example_data, + default_path, + fetch_aparc_sub_parcellation, + fetch_hcp_mmp_parcellation, + has_dataset, +) +from ._fsaverage.base import fetch_fsaverage +from ._infant.base import fetch_infant_template +from ._phantom.base import fetch_phantom + +__all__ = [ + "_download_all_example_data", + "_fake", + "brainstorm", + "default_path", + "eegbci", + "fetch_aparc_sub_parcellation", + "fetch_fsaverage", + "fetch_infant_template", + "fetch_hcp_mmp_parcellation", + "fieldtrip_cmc", + "hf_sef", + "kiloword", + "misc", + "mtrf", + "multimodal", + "opm", + "phantom_4dbti", + "sample", + "sleep_physionet", + "somato", + "spm_face", + "ssvep", + "testing", + "visual_92_categories", + "limo", + "erp_core", + "epilepsy_ecog", + "fetch_dataset", + "fetch_phantom", + "has_dataset", + "refmeg_noise", + "fnirs_motor", + "eyelink", + "ucl_opm_auditory", +] diff --git a/mne/datasets/__init__.pyi b/mne/datasets/__init__.pyi deleted file mode 100644 index 2f69a1027e5..00000000000 --- a/mne/datasets/__init__.pyi +++ /dev/null @@ -1,78 +0,0 @@ -__all__ = [ - "_download_all_example_data", - "_fake", - "brainstorm", - "eegbci", - "epilepsy_ecog", - "erp_core", - "eyelink", - "default_path", - "fetch_aparc_sub_parcellation", - "fetch_dataset", - "fetch_fsaverage", - "fetch_hcp_mmp_parcellation", - "fetch_infant_template", - "fetch_phantom", - "fieldtrip_cmc", - "fnirs_motor", - "has_dataset", - "hf_sef", - "kiloword", - "limo", - "misc", - "mtrf", - "multimodal", - "opm", - "phantom_4dbti", - "phantom_kernel", - "phantom_kit", - "refmeg_noise", - "sample", - "sleep_physionet", - "somato", - "spm_face", - "ssvep", - "testing", - "ucl_opm_auditory", - "visual_92_categories", -] -from . import ( - _fake, - brainstorm, - eegbci, - epilepsy_ecog, - erp_core, - eyelink, - fieldtrip_cmc, - fnirs_motor, - hf_sef, - kiloword, - limo, - misc, - mtrf, - multimodal, - opm, - phantom_4dbti, - phantom_kernel, - phantom_kit, - refmeg_noise, - sample, - sleep_physionet, - somato, - spm_face, - ssvep, - testing, - ucl_opm_auditory, - visual_92_categories, -) -from ._fetch import fetch_dataset -from ._fsaverage.base import fetch_fsaverage -from ._infant import fetch_infant_template -from ._phantom.base import fetch_phantom -from .utils import ( - _download_all_example_data, - default_path, - fetch_aparc_sub_parcellation, - fetch_hcp_mmp_parcellation, - has_dataset, -) diff --git a/mne/datasets/limo/limo.py b/mne/datasets/limo/limo.py index f0696a78b1e..9bf4ad9c326 100644 --- a/mne/datasets/limo/limo.py +++ b/mne/datasets/limo/limo.py @@ -7,7 +7,6 @@ from pathlib import Path import numpy as np -from scipy.io import loadmat from ..._fiff.meas_info import create_info from ...channels import make_standard_montage @@ -290,6 +289,8 @@ def load_data(subject, path=None, force_update=False, update_path=None, verbose= The epochs. """ # noqa: E501 pd = _check_pandas_installed() + from scipy.io import loadmat + # subject in question if isinstance(subject, int) and 1 <= subject <= 18: subj = f"S{subject}" diff --git a/mne/decoding/__init__.py b/mne/decoding/__init__.py index b0dc90e1048..479b4b5b36a 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -3,6 +3,26 @@ # Copyright the MNE-Python contributors. """Decoding and encoding, including machine learning and receptive fields.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +try: + import sklearn +except ImportError: + pass +else: + from .transformer import ( + PSDEstimator, + Vectorizer, + UnsupervisedSpatialFilter, + TemporalFilter, + Scaler, + FilterEstimator, + TransformerMixin, + ) + from .base import BaseEstimator, LinearModel, get_coef, cross_val_multiscore + from .csp import CSP, SPoC + from .ssd import SSD + from .ems import compute_ems, EMS + from .time_frequency import TimeFrequency + from .receptive_field import ReceptiveField + from .time_delaying_ridge import TimeDelayingRidge + from .search_light import SlidingEstimator, GeneralizingEstimator diff --git a/mne/decoding/__init__.pyi b/mne/decoding/__init__.pyi deleted file mode 100644 index 2b6c89b2140..00000000000 --- a/mne/decoding/__init__.pyi +++ /dev/null @@ -1,45 +0,0 @@ -__all__ = [ - "BaseEstimator", - "CSP", - "EMS", - "FilterEstimator", - "GeneralizingEstimator", - "LinearModel", - "PSDEstimator", - "ReceptiveField", - "SPoC", - "SSD", - "Scaler", - "SlidingEstimator", - "TemporalFilter", - "TimeDelayingRidge", - "TimeFrequency", - "TransformerMixin", - "UnsupervisedSpatialFilter", - "Vectorizer", - "compute_ems", - "cross_val_multiscore", - "get_coef", -] -from .base import ( - BaseEstimator, - LinearModel, - TransformerMixin, - cross_val_multiscore, - get_coef, -) -from .csp import CSP, SPoC -from .ems import EMS, compute_ems -from .receptive_field import ReceptiveField -from .search_light import GeneralizingEstimator, SlidingEstimator -from .ssd import SSD -from .time_delaying_ridge import TimeDelayingRidge -from .time_frequency import TimeFrequency -from .transformer import ( - FilterEstimator, - PSDEstimator, - Scaler, - TemporalFilter, - UnsupervisedSpatialFilter, - Vectorizer, -) diff --git a/mne/decoding/csp.py b/mne/decoding/csp.py index ea38fd58ca3..1e5e7ee978f 100644 --- a/mne/decoding/csp.py +++ b/mne/decoding/csp.py @@ -5,7 +5,6 @@ import copy as cp import numpy as np -from scipy.linalg import eigh from sklearn.base import BaseEstimator from sklearn.utils.validation import check_is_fitted @@ -621,6 +620,8 @@ def _epoch_cov(self, x_class, *, cov_kind, log_rank): return cov, weight def _decompose_covs(self, covs, sample_weights): + from scipy.linalg import eigh + n_classes = len(covs) n_channels = covs[0].shape[0] assert self._rank is not None # should happen in _compute_covariance_matrices @@ -878,6 +879,8 @@ def fit(self, X, y): self : instance of SPoC Returns the modified instance. """ + from scipy.linalg import eigh + X, y = self._check_data(X, y=y, fit=True, return_y=True) self._validate_params(y=y) diff --git a/mne/decoding/receptive_field.py b/mne/decoding/receptive_field.py index 99412cf56b7..c80cd643074 100644 --- a/mne/decoding/receptive_field.py +++ b/mne/decoding/receptive_field.py @@ -5,7 +5,6 @@ import numbers import numpy as np -from scipy.stats import pearsonr from sklearn.base import ( BaseEstimator, MetaEstimatorMixin, @@ -505,6 +504,8 @@ def _reshape_for_est(X_del): # Create a correlation scikit-learn-style scorer def _corr_score(y_true, y, multioutput=None): + from scipy.stats import pearsonr + assert multioutput == "raw_values" for this_y in (y_true, y): if this_y.ndim != 2: diff --git a/mne/decoding/ssd.py b/mne/decoding/ssd.py index 111ded9f274..6aaa2e3515a 100644 --- a/mne/decoding/ssd.py +++ b/mne/decoding/ssd.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.linalg import eigh from sklearn.base import BaseEstimator from sklearn.utils.validation import check_is_fitted @@ -195,6 +194,8 @@ def fit(self, X, y=None): self : instance of SSD Returns the modified instance. """ + from scipy.linalg import eigh + X = self._check_X(X, y=y, fit=True) self._validate_params(X) if isinstance(self.info, Info): @@ -371,6 +372,8 @@ def apply(self, X): def _dimensionality_reduction(cov_signal, cov_noise, info, rank): """Perform dimensionality reduction on the covariance matrices.""" + from scipy.linalg import eigh + n_channels = cov_signal.shape[0] # find ranks of covariance matrices diff --git a/mne/decoding/time_delaying_ridge.py b/mne/decoding/time_delaying_ridge.py index b08f997e4f4..afa88e2c78a 100644 --- a/mne/decoding/time_delaying_ridge.py +++ b/mne/decoding/time_delaying_ridge.py @@ -5,7 +5,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg from scipy.signal import fftconvolve from scipy.sparse.csgraph import laplacian from sklearn.base import BaseEstimator, RegressorMixin @@ -153,6 +152,8 @@ def _toeplitz_dot(a, b): def _compute_reg_neighbors(n_ch_x, n_delays, reg_type, method="direct", normed=False): """Compute regularization parameter from neighbors.""" + from scipy import linalg + known_types = ("ridge", "laplacian") if isinstance(reg_type, str): reg_type = (reg_type,) * 2 @@ -204,6 +205,8 @@ def _compute_reg_neighbors(n_ch_x, n_delays, reg_type, method="direct", normed=F def _fit_corrs(x_xt, x_y, n_ch_x, reg_type, alpha, n_ch_in): """Fit the model using correlation matrices.""" + from scipy import linalg + # do the regularized solving n_ch_out = x_y.shape[1] assert x_y.shape[0] % n_ch_x == 0 diff --git a/mne/dipole.py b/mne/dipole.py index a40e9708db2..3e2fae2f33d 100644 --- a/mne/dipole.py +++ b/mne/dipole.py @@ -10,8 +10,6 @@ from functools import partial import numpy as np -from scipy.linalg import eigh -from scipy.optimize import fmin_cobyla from ._fiff.constants import FIFF from ._fiff.pick import pick_types @@ -997,6 +995,8 @@ def _fit_dipoles( rhoend, ): """Fit a single dipole to the given whitened, projected data.""" + from scipy.optimize import fmin_cobyla + parallel, p_fun, n_jobs = parallel_func(fun, n_jobs) # parallel over time points res = parallel( @@ -1135,6 +1135,8 @@ def _fit_confidence(*, rd, Q, ori, whitener, fwd_data, sensors): # # And then the confidence interval is the diagonal of C, scaled by 1.96 # (for 95% confidence). + from scipy.linalg import eigh + direction = np.empty((3, 3)) # The coordinate system has the x axis aligned with the dipole orientation, direction[0] = ori diff --git a/mne/epochs.py b/mne/epochs.py index 96f247875d9..7710c70a441 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -14,7 +14,6 @@ from pathlib import Path import numpy as np -from scipy.interpolate import interp1d from ._fiff.constants import FIFF from ._fiff.meas_info import ( @@ -3906,6 +3905,8 @@ def _get_drop_indices(sample_nums, method, random_state): def _minimize_time_diff(t_shorter, t_longer): """Find a boolean mask to minimize timing differences.""" + from scipy.interpolate import interp1d + keep = np.ones((len(t_longer)), dtype=bool) # special case: length zero or one if len(t_shorter) < 2: # interp1d won't work diff --git a/mne/evoked.py b/mne/evoked.py index 7bd2355e4ee..01998ca84b9 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -251,7 +251,6 @@ def get_data(self, picks=None, units=None, tmin=None, tmax=None): ----- .. versionadded:: 0.24 """ - # Avoid circular import from .io.base import _get_ch_factors picks = _picks_to_idx(self.info, picks, "all", exclude=()) diff --git a/mne/export/__init__.py b/mne/export/__init__.py index 7a07df656d6..40d88ffaa67 100644 --- a/mne/export/__init__.py +++ b/mne/export/__init__.py @@ -3,6 +3,6 @@ # Copyright the MNE-Python contributors. """Functions for exporting data to non-FIF formats.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._export import export_raw, export_epochs, export_evokeds +from ._egimff import export_evokeds_mff diff --git a/mne/export/__init__.pyi b/mne/export/__init__.pyi deleted file mode 100644 index 6cd2a374ef3..00000000000 --- a/mne/export/__init__.pyi +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ["export_epochs", "export_evokeds", "export_evokeds_mff", "export_raw"] -from ._egimff import export_evokeds_mff -from ._export import export_epochs, export_evokeds, export_raw diff --git a/mne/filter.py b/mne/filter.py index acdf867c63a..07ccdbf3529 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -10,8 +10,6 @@ from math import gcd import numpy as np -from scipy import fft, signal -from scipy.stats import f as fstat from ._fiff.pick import _picks_to_idx from ._ola import _COLA @@ -387,7 +385,9 @@ def _1d_overlap_filter(x, n_h, n_edge, phase, cuda_dict, pad, n_fft): def _filter_attenuation(h, freq, gain): """Compute minimum attenuation at stop frequency.""" - _, filt_resp = signal.freqz(h.ravel(), worN=np.pi * freq) + from scipy.signal import freqz + + _, filt_resp = freqz(h.ravel(), worN=np.pi * freq) filt_resp = np.abs(filt_resp) # use amplitude response filt_resp[np.where(gain == 1)] = 0 idx = np.argmax(filt_resp) @@ -420,6 +420,8 @@ def _prep_for_filtering(x, copy, picks=None): def _firwin_design(N, freq, gain, window, sfreq): """Construct a FIR filter using firwin.""" + from scipy.signal import firwin + assert freq[0] == 0 assert len(freq) > 1 assert len(freq) == len(gain) @@ -444,7 +446,7 @@ def _firwin_design(N, freq, gain, window, sfreq): f"requires {this_N} samples" ) # Construct a lowpass - this_h = signal.firwin( + this_h = firwin( this_N, (prev_freq + this_freq) / 2.0, window=window, @@ -475,10 +477,11 @@ def _construct_fir_filter( """ assert freq[0] == 0 if fir_design == "firwin2": - fir_design = signal.firwin2 + from scipy.signal import firwin2 as fir_design else: assert fir_design == "firwin" fir_design = partial(_firwin_design, sfreq=sfreq) + # issue a warning if attenuation is less than this min_att_db = 12 if phase == "minimum-half" else 20 @@ -526,9 +529,13 @@ def _check_zero_phase_length(N, phase, gain_nyq=0): def _check_coefficients(system): """Check for filter stability.""" if isinstance(system, tuple): - z, p, k = signal.tf2zpk(*system) + from scipy.signal import tf2zpk + + z, p, k = tf2zpk(*system) else: # sos - z, p, k = signal.sos2zpk(system) + from scipy.signal import sos2zpk + + z, p, k = sos2zpk(system) if np.any(np.abs(p) > 1.0): raise RuntimeError( "Filter poles outside unit circle, filter will be " @@ -540,29 +547,25 @@ def _check_coefficients(system): def _iir_filter(x, iir_params, picks, n_jobs, copy, phase="zero"): """Call filtfilt or lfilter.""" # set up array for filtering, reshape to 2D, operate on last axis + from scipy.signal import filtfilt, lfilter, sosfilt, sosfiltfilt + x, orig_shape, picks = _prep_for_filtering(x, copy, picks) if phase in ("zero", "zero-double"): padlen = min(iir_params["padlen"], x.shape[-1] - 1) if "sos" in iir_params: - fun = partial( - signal.sosfiltfilt, sos=iir_params["sos"], padlen=padlen, axis=-1 - ) + fun = partial(sosfiltfilt, sos=iir_params["sos"], padlen=padlen, axis=-1) _check_coefficients(iir_params["sos"]) else: fun = partial( - signal.filtfilt, - b=iir_params["b"], - a=iir_params["a"], - padlen=padlen, - axis=-1, + filtfilt, b=iir_params["b"], a=iir_params["a"], padlen=padlen, axis=-1 ) _check_coefficients((iir_params["b"], iir_params["a"])) else: if "sos" in iir_params: - fun = partial(signal.sosfilt, sos=iir_params["sos"], axis=-1) + fun = partial(sosfilt, sos=iir_params["sos"], axis=-1) _check_coefficients(iir_params["sos"]) else: - fun = partial(signal.lfilter, b=iir_params["b"], a=iir_params["a"], axis=-1) + fun = partial(lfilter, b=iir_params["b"], a=iir_params["a"], axis=-1) _check_coefficients((iir_params["b"], iir_params["a"])) parallel, p_fun, n_jobs = parallel_func(fun, n_jobs) if n_jobs == 1: @@ -592,6 +595,8 @@ def estimate_ringing_samples(system, max_try=100000): n : int The approximate ringing. """ + from scipy import signal + if isinstance(system, tuple): # TF kind = "ba" b, a = system @@ -771,6 +776,8 @@ def construct_iir_filter( For more information, see the tutorials :ref:`disc-filtering` and :ref:`tut-filter-resample`. """ # noqa: E501 + from scipy.signal import freqz, iirdesign, iirfilter, sosfreqz + known_filters = ( "bessel", "butter", @@ -842,7 +849,7 @@ def construct_iir_filter( for key in ("rp", "rs"): if key in iir_params: kwargs[key] = iir_params[key] - system = signal.iirfilter(**kwargs) + system = iirfilter(**kwargs) if phase in ("zero", "zero-double"): ptype, pmul = "(effective, after forward-backward)", 2 else: @@ -857,7 +864,7 @@ def construct_iir_filter( raise ValueError( "iir_params must have at least 'gstop' and 'gpass' (or N) entries." ) - system = signal.iirdesign( + system = iirdesign( Wp, Ws, iir_params["gpass"], @@ -874,9 +881,9 @@ def construct_iir_filter( # get the gains at the cutoff frequencies if Wp is not None: if output == "sos": - cutoffs = signal.sosfreqz(system, worN=Wp * np.pi)[1] + cutoffs = sosfreqz(system, worN=Wp * np.pi)[1] else: - cutoffs = signal.freqz(system[0], system[1], worN=Wp * np.pi)[1] + cutoffs = freqz(system[0], system[1], worN=Wp * np.pi)[1] cutoffs = 20 * np.log10(np.abs(cutoffs)) # 2 * 20 here because we do forward-backward filtering if phase in ("zero", "zero-double"): @@ -1582,6 +1589,8 @@ def notch_filter( def _get_window_thresh(n_times, sfreq, mt_bandwidth, p_value): + from scipy import stats + from .time_frequency.multitaper import _compute_mt_params # figure out what tapers to use @@ -1590,7 +1599,7 @@ def _get_window_thresh(n_times, sfreq, mt_bandwidth, p_value): ) # F-stat of 1-p point - threshold = fstat.ppf(1 - p_value / n_times, 2, 2 * len(window_fun) - 2) + threshold = stats.f.ppf(1 - p_value / n_times, 2, 2 * len(window_fun) - 2) return window_fun, threshold @@ -1888,6 +1897,8 @@ def resample( def _prep_polyphase(ratio, x_len, final_len, window): + from scipy.signal import firwin + if isinstance(window, str) and window == "auto": window = ("kaiser", 5.0) # SciPy default up = final_len @@ -1901,26 +1912,31 @@ def _prep_polyphase(ratio, x_len, final_len, window): max_rate = max(up, down) f_c = 1.0 / max_rate # cutoff of FIR filter (rel. to Nyquist) half_len = 10 * max_rate # reasonable cutoff for sinc-like function - window = signal.firwin(2 * half_len + 1, f_c, window=window) + window = firwin(2 * half_len + 1, f_c, window=window) return up, down, window def _resample_polyphase(x, *, up, down, pad, window, n_jobs): + from scipy.signal import resample_poly + if pad == "auto": pad = "reflect" kwargs = dict(padtype=pad, window=window, up=up, down=down) _validate_type( n_jobs, (None, "int-like"), "n_jobs", extra="when method='polyphase'" ) - parallel, p_fun, n_jobs = parallel_func(signal.resample_poly, n_jobs) + parallel, p_fun, n_jobs = parallel_func(resample_poly, n_jobs) if n_jobs == 1: - y = signal.resample_poly(x, axis=-1, **kwargs) + y = resample_poly(x, axis=-1, **kwargs) else: y = np.array(parallel(p_fun(x_, **kwargs) for x_ in x)) return y def _resample_fft(x_flat, *, ratio, final_len, pad, window, npad, n_jobs): + from scipy import fft + from scipy.signal import get_window + x_len = x_flat.shape[-1] pad = "reflect_limited" if pad == "auto" else pad if (isinstance(window, str) and window == "auto") or window is None: @@ -1952,7 +1968,7 @@ def _resample_fft(x_flat, *, ratio, final_len, pad, window, npad, n_jobs): elif isinstance(window, np.ndarray) and window.shape == (orig_len,): W = window else: - W = fft.ifftshift(signal.get_window(window, orig_len)) + W = fft.ifftshift(get_window(window, orig_len)) W *= float(new_len) / float(orig_len) # figure out if we should use CUDA @@ -2055,6 +2071,8 @@ def detrend(x, order=1, axis=-1): >>> bool((detrend(x) - noise).max() < 0.01) True """ + from scipy.signal import detrend + if axis > len(x.shape): raise ValueError(f"x does not have {axis} axes") if order == 0: @@ -2064,7 +2082,7 @@ def detrend(x, order=1, axis=-1): else: raise ValueError("order must be 0 or 1") - y = signal.detrend(x, axis=axis, type=fit) + y = detrend(x, axis=axis, type=fit) return y @@ -2412,6 +2430,8 @@ def savgol_filter(self, h_freq, verbose=None): >>> evoked.savgol_filter(10.) # low-pass at around 10 Hz # doctest:+SKIP >>> evoked.plot() # doctest:+SKIP """ # noqa: E501 + from scipy.signal import savgol_filter + from .source_estimate import _BaseSourceEstimate _check_preload(self, "inst.savgol_filter") @@ -2426,7 +2446,7 @@ def savgol_filter(self, h_freq, verbose=None): # savitzky-golay filtering window_length = (int(np.round(s_freq / h_freq)) // 2) * 2 + 1 logger.info("Using savgol length %d", window_length) - self._data[:] = signal.savgol_filter( + self._data[:] = savgol_filter( self._data, axis=-1, polyorder=5, window_length=window_length ) return self @@ -2804,8 +2824,10 @@ def _my_hilbert(x, n_fft=None, envelope=False): out : array, shape (n_times) The hilbert transform of the signal, or the envelope. """ + from scipy.signal import hilbert + n_x = x.shape[-1] - out = signal.hilbert(x, N=n_fft, axis=-1)[..., :n_x] + out = hilbert(x, N=n_fft, axis=-1)[..., :n_x] if envelope: out = np.abs(out) return out @@ -2852,6 +2874,8 @@ def design_mne_c_filter( 4197 frequencies are directly constructed, with zeroes in the stop-band and ones in the passband, with squared cosine ramps in between. """ + from scipy.fft import irfft + n_freqs = (4096 + 2 * 2048) // 2 + 1 freq_resp = np.ones(n_freqs) l_freq = 0 if l_freq is None else float(l_freq) @@ -2891,7 +2915,7 @@ def design_mne_c_filter( freq_resp[start:stop] *= np.cos(np.pi / 4.0 * k) ** 2 freq_resp[stop:] = 0.0 # Get the time-domain version of this signal - h = fft.irfft(freq_resp, n=2 * len(freq_resp) - 1) + h = irfft(freq_resp, n=2 * len(freq_resp) - 1) h = np.roll(h, n_freqs - 1) # center the impulse like a linear-phase filt return h diff --git a/mne/fixes.py b/mne/fixes.py index 070d4125d18..b3647e19fba 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -75,7 +75,7 @@ def _median_complex(data, axis): def _safe_svd(A, **kwargs): - """Get around the SVD did not converge error of death.""" + """Workaround the SVD did not converge error of death.""" # Intel has a bug with their GESVD driver: # https://software.intel.com/en-us/forums/intel-distribution-for-python/topic/628049 # noqa: E501 # For SciPy 0.18 and up, we can work around it by using @@ -253,6 +253,7 @@ class EmpiricalCovariance(_EstimatorMixin): precision_ : 2D ndarray, shape (n_features, n_features) Estimated pseudo-inverse matrix. (stored only if store_precision is True) + """ def __init__(self, store_precision=True, assume_centered=False): @@ -289,7 +290,6 @@ def get_precision(self): ------- precision_ : array-like, The precision matrix associated to the current covariance object. - """ from scipy import linalg @@ -514,7 +514,8 @@ def _assess_dimension_(spectrum, rank, n_samples, n_features): return ll -def svd_flip(u, v, u_based_decision=True): # noqa: D103 +def svd_flip(u, v, u_based_decision=True): + """Sign correction to ensure deterministic output from SVD.""" if u_based_decision: # columns of u, rows of v max_abs_cols = np.argmax(np.abs(u), axis=0) diff --git a/mne/forward/__init__.py b/mne/forward/__init__.py index 3dcafb625de..ecba5677832 100644 --- a/mne/forward/__init__.py +++ b/mne/forward/__init__.py @@ -3,6 +3,49 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" -import lazy_loader as lazy # for testing purposes -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .forward import ( + Forward, + read_forward_solution, + write_forward_solution, + is_fixed_orient, + _read_forward_meas_info, + _select_orient_forward, + compute_orient_prior, + compute_depth_prior, + apply_forward, + apply_forward_raw, + restrict_forward_to_stc, + restrict_forward_to_label, + average_forward_solutions, + _stc_src_sel, + _fill_measurement_info, + _apply_forward, + _subject_from_forward, + convert_forward_solution, + _merge_fwds, + _do_forward_solution, +) +from ._make_forward import ( + make_forward_solution, + _prepare_for_forward, + _prep_meg_channels, + _prep_eeg_channels, + _to_forward_dict, + _create_meg_coils, + _read_coil_defs, + _transform_orig_meg_coils, + make_forward_dipole, + use_coil_def, +) +from ._compute_forward import ( + _magnetic_dipole_field_vec, + _compute_forwards, + _concatenate_coils, +) +from ._field_interpolation import ( + _make_surface_mapping, + make_field_map, + _as_meg_type_inst, + _map_meg_or_eeg_channels, +) diff --git a/mne/forward/__init__.pyi b/mne/forward/__init__.pyi deleted file mode 100644 index dcac0d30881..00000000000 --- a/mne/forward/__init__.pyi +++ /dev/null @@ -1,86 +0,0 @@ -__all__ = [ - "Forward", - "_apply_forward", - "_as_meg_type_inst", - "_compute_forwards", - "_concatenate_coils", - "_create_meg_coils", - "_do_forward_solution", - "_fill_measurement_info", - "_lead_dots", - "_magnetic_dipole_field_vec", - "_make_surface_mapping", - "_map_meg_or_eeg_channels", - "_merge_fwds", - "_prep_eeg_channels", - "_prep_meg_channels", - "_prepare_for_forward", - "_read_coil_defs", - "_read_forward_meas_info", - "_select_orient_forward", - "_stc_src_sel", - "_subject_from_forward", - "_to_forward_dict", - "_transform_orig_meg_coils", - "apply_forward", - "apply_forward_raw", - "average_forward_solutions", - "compute_depth_prior", - "compute_orient_prior", - "convert_forward_solution", - "is_fixed_orient", - "make_field_map", - "make_forward_dipole", - "make_forward_solution", - "read_forward_solution", - "restrict_forward_to_label", - "restrict_forward_to_stc", - "use_coil_def", - "write_forward_solution", -] -from . import _lead_dots -from ._compute_forward import ( - _compute_forwards, - _concatenate_coils, - _magnetic_dipole_field_vec, -) -from ._field_interpolation import ( - _as_meg_type_inst, - _make_surface_mapping, - _map_meg_or_eeg_channels, - make_field_map, -) -from ._make_forward import ( - _create_meg_coils, - _prep_eeg_channels, - _prep_meg_channels, - _prepare_for_forward, - _read_coil_defs, - _to_forward_dict, - _transform_orig_meg_coils, - make_forward_dipole, - make_forward_solution, - use_coil_def, -) -from .forward import ( - Forward, - _apply_forward, - _do_forward_solution, - _fill_measurement_info, - _merge_fwds, - _read_forward_meas_info, - _select_orient_forward, - _stc_src_sel, - _subject_from_forward, - apply_forward, - apply_forward_raw, - average_forward_solutions, - compute_depth_prior, - compute_orient_prior, - convert_forward_solution, - is_fixed_orient, - read_forward_solution, - restrict_forward_to_label, - restrict_forward_to_stc, - write_forward_solution, -) diff --git a/mne/forward/_field_interpolation.py b/mne/forward/_field_interpolation.py index e98a147b560..6335f0e7d10 100644 --- a/mne/forward/_field_interpolation.py +++ b/mne/forward/_field_interpolation.py @@ -9,7 +9,6 @@ from copy import deepcopy import numpy as np -from scipy.interpolate import interp1d from .._fiff.constants import FIFF from .._fiff.meas_info import _simplify_info @@ -34,6 +33,8 @@ def _setup_dots(mode, info, coils, ch_type): """Set up dot products.""" + from scipy.interpolate import interp1d + int_rad = 0.06 noise = make_ad_hoc_cov(info, dict(mag=20e-15, grad=5e-13, eeg=1e-6)) n_coeff, interp = (50, "nearest") if mode == "fast" else (100, "linear") diff --git a/mne/forward/forward.py b/mne/forward/forward.py index f1c2c2d11d7..33e1192d113 100644 --- a/mne/forward/forward.py +++ b/mne/forward/forward.py @@ -16,7 +16,6 @@ from time import time import numpy as np -from scipy import sparse from .._fiff.constants import FIFF from .._fiff.matrix import ( @@ -293,6 +292,8 @@ def _block_diag(A, n): bd : scipy.sparse.csc_array The block diagonal matrix """ + from scipy import sparse + if sparse.issparse(A): # then make block sparse raise NotImplementedError("sparse reversal not implemented yet") ma, na = A.shape @@ -749,6 +750,8 @@ def convert_forward_solution( fwd : Forward The modified forward solution. """ + from scipy import sparse + fwd = fwd.copy() if copy else fwd if force_fixed is True: diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index c06c8dc9939..ddc40dc4c0a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -3,6 +3,6 @@ # Copyright the MNE-Python contributors. """Convenience functions for opening GUIs.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__all__ = ["_GUIScraper", "coregistration"] +from ._gui import _GUIScraper, coregistration diff --git a/mne/gui/__init__.pyi b/mne/gui/__init__.pyi deleted file mode 100644 index 086c51a4904..00000000000 --- a/mne/gui/__init__.pyi +++ /dev/null @@ -1,2 +0,0 @@ -__all__ = ["_GUIScraper", "coregistration"] -from ._gui import _GUIScraper, coregistration diff --git a/mne/html_templates/__init__.py b/mne/html_templates/__init__.py index b8234d27037..0d4be5f51de 100644 --- a/mne/html_templates/__init__.py +++ b/mne/html_templates/__init__.py @@ -3,6 +3,5 @@ # Copyright the MNE-Python contributors. """Jinja2 HTML templates.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._templates import _get_html_template, _get_html_templates_env diff --git a/mne/html_templates/__init__.pyi b/mne/html_templates/__init__.pyi deleted file mode 100644 index 2312227d990..00000000000 --- a/mne/html_templates/__init__.pyi +++ /dev/null @@ -1,2 +0,0 @@ -__all__ = ["_get_html_template"] -from ._templates import _get_html_template diff --git a/mne/inverse_sparse/__init__.py b/mne/inverse_sparse/__init__.py index 615c3062bdb..8d45653f79d 100644 --- a/mne/inverse_sparse/__init__.py +++ b/mne/inverse_sparse/__init__.py @@ -4,6 +4,5 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy - -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .mxne_inverse import mixed_norm, tf_mixed_norm, make_stc_from_dipoles +from ._gamma_map import gamma_map diff --git a/mne/inverse_sparse/__init__.pyi b/mne/inverse_sparse/__init__.pyi deleted file mode 100644 index 557921114ca..00000000000 --- a/mne/inverse_sparse/__init__.pyi +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ["gamma_map", "make_stc_from_dipoles", "mixed_norm", "tf_mixed_norm"] -from ._gamma_map import gamma_map -from .mxne_inverse import make_stc_from_dipoles, mixed_norm, tf_mixed_norm diff --git a/mne/io/__init__.py b/mne/io/__init__.py index 40f385391cd..43d1f4b2f6d 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -4,6 +4,87 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy +import sys as _sys -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .base import BaseRaw, concatenate_raws, match_channel_orders +from ..utils import warn + +from .ant import read_raw_ant +from .array import RawArray +from .besa import read_evoked_besa +from .brainvision import read_raw_brainvision +from .bti import read_raw_bti +from .cnt import read_raw_cnt +from .ctf import read_raw_ctf +from .curry import read_raw_curry +from .edf import read_raw_edf, read_raw_bdf, read_raw_gdf +from .egi import read_raw_egi, read_evokeds_mff +from .kit import read_raw_kit, read_epochs_kit +from .fiff import read_raw_fif, Raw +from .fil import read_raw_fil +from .nedf import read_raw_nedf +from .nicolet import read_raw_nicolet +from .artemis123 import read_raw_artemis123 +from .eeglab import read_raw_eeglab, read_epochs_eeglab +from .eximia import read_raw_eximia +from .hitachi import read_raw_hitachi +from .nirx import read_raw_nirx +from .boxy import read_raw_boxy +from .snirf import read_raw_snirf +from .persyst import read_raw_persyst +from .fieldtrip import read_raw_fieldtrip, read_epochs_fieldtrip, read_evoked_fieldtrip +from .neuralynx import read_raw_neuralynx +from .nihon import read_raw_nihon +from .nsx import read_raw_nsx +from ._read_raw import read_raw +from .eyelink import read_raw_eyelink + +# Backward compat since these were in the public API before switching to _fiff +# (and _empty_info is convenient to keep here for tests and is private) +from .._fiff.meas_info import ( + read_info, + write_info, + anonymize_info, + read_fiducials, + write_fiducials, + _empty_info, + Info as _info, +) +from .._fiff.meas_info import Info +from .._fiff.open import show_fiff +from .._fiff.pick import get_channel_type_constants # moved up a level + + +def __getattr__(name): + """Try getting attribute from fiff submodule.""" + if name in ("meas_info", "proj", "reference"): + warn(f"mne.io.{name} {_dep_msg}", FutureWarning) + return importlib.import_module(f"mne.io.{name}") + elif name in ( + "set_eeg_reference", + "set_bipolar_reference", + "add_reference_channels", + ): + warn( + f"mne.io.{name} is deprecated and will be removed in 1.6, " + "use mne.{name} instead", + FutureWarning, + ) + return getattr(reference, name) + elif name == "RawFIF": + warn( + "RawFIF is deprecated and will be removed in 1.6, use Raw instead", + FutureWarning, + ) + return Raw + elif name == "Info": + warn( + "mne.io.Info is deprecated and will be removed in 1.6, " + "use mne.Info instead", + FutureWarning, + ) + return Info + try: + return globals()[name] + except KeyError: + raise AttributeError(f"module {__name__} has no attribute {name}") from None diff --git a/mne/io/__init__.pyi b/mne/io/__init__.pyi deleted file mode 100644 index a9c11415f15..00000000000 --- a/mne/io/__init__.pyi +++ /dev/null @@ -1,91 +0,0 @@ -__all__ = [ - "BaseRaw", - "Raw", - "RawArray", - "anonymize_info", - "concatenate_raws", - "constants", - "get_channel_type_constants", - "match_channel_orders", - "pick", - "read_epochs_eeglab", - "read_epochs_fieldtrip", - "read_epochs_kit", - "read_evoked_besa", - "read_evoked_fieldtrip", - "read_evokeds_mff", - "read_fiducials", - "read_info", - "read_raw", - "read_raw_ant", - "read_raw_artemis123", - "read_raw_bdf", - "read_raw_boxy", - "read_raw_brainvision", - "read_raw_bti", - "read_raw_cnt", - "read_raw_ctf", - "read_raw_curry", - "read_raw_edf", - "read_raw_eeglab", - "read_raw_egi", - "read_raw_eximia", - "read_raw_eyelink", - "read_raw_fieldtrip", - "read_raw_fif", - "read_raw_fil", - "read_raw_gdf", - "read_raw_hitachi", - "read_raw_kit", - "read_raw_nedf", - "read_raw_neuralynx", - "read_raw_nicolet", - "read_raw_nihon", - "read_raw_nirx", - "read_raw_nsx", - "read_raw_persyst", - "read_raw_snirf", - "show_fiff", - "write_fiducials", - "write_info", -] -from . import constants, pick -from ._fiff_wrap import ( - anonymize_info, - get_channel_type_constants, - read_fiducials, - read_info, - show_fiff, - write_fiducials, - write_info, -) -from ._read_raw import read_raw -from .ant import read_raw_ant -from .array import RawArray -from .artemis123 import read_raw_artemis123 -from .base import BaseRaw, concatenate_raws, match_channel_orders -from .besa import read_evoked_besa -from .boxy import read_raw_boxy -from .brainvision import read_raw_brainvision -from .bti import read_raw_bti -from .cnt import read_raw_cnt -from .ctf import read_raw_ctf -from .curry import read_raw_curry -from .edf import read_raw_bdf, read_raw_edf, read_raw_gdf -from .eeglab import read_epochs_eeglab, read_raw_eeglab -from .egi import read_evokeds_mff, read_raw_egi -from .eximia import read_raw_eximia -from .eyelink import read_raw_eyelink -from .fieldtrip import read_epochs_fieldtrip, read_evoked_fieldtrip, read_raw_fieldtrip -from .fiff import Raw, read_raw_fif -from .fil import read_raw_fil -from .hitachi import read_raw_hitachi -from .kit import read_epochs_kit, read_raw_kit -from .nedf import read_raw_nedf -from .neuralynx import read_raw_neuralynx -from .nicolet import read_raw_nicolet -from .nihon import read_raw_nihon -from .nirx import read_raw_nirx -from .nsx import read_raw_nsx -from .persyst import read_raw_persyst -from .snirf import read_raw_snirf diff --git a/mne/io/artemis123/artemis123.py b/mne/io/artemis123/artemis123.py index 349a62822b6..588890ba20c 100644 --- a/mne/io/artemis123/artemis123.py +++ b/mne/io/artemis123/artemis123.py @@ -7,7 +7,6 @@ import os.path as op import numpy as np -from scipy.spatial.distance import cdist from ..._fiff._digitization import DigPoint, _make_dig_points from ..._fiff.constants import FIFF @@ -336,6 +335,8 @@ def __init__( pos_fname=None, add_head_trans=True, ): + from scipy.spatial.distance import cdist + from ...chpi import ( _fit_coil_order_dev_head_trans, compute_chpi_amplitudes, diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 763ef4f91eb..e2d11666163 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -10,7 +10,6 @@ from pathlib import Path import numpy as np -from scipy.interpolate import interp1d from ..._fiff.constants import FIFF from ..._fiff.meas_info import _empty_info, _unique_channel_names @@ -352,6 +351,8 @@ def _read_ch(fid, subtype, samp, dtype_byte, dtype=None): def _read_segment_file(data, idx, fi, start, stop, raw_extras, filenames, cals, mult): """Read a chunk of raw data.""" + from scipy.interpolate import interp1d + n_samps = raw_extras["n_samps"] buf_len = int(raw_extras["max_samp"]) dtype = raw_extras["dtype_np"] diff --git a/mne/io/eeglab/_eeglab.py b/mne/io/eeglab/_eeglab.py index 28df469fada..9f81f55aa9c 100644 --- a/mne/io/eeglab/_eeglab.py +++ b/mne/io/eeglab/_eeglab.py @@ -4,13 +4,6 @@ import numpy as np -try: - from scipy.io.matlab import MatlabFunction, MatlabOpaque -except ImportError: # scipy < 1.8 - from scipy.io.matlab.mio5 import MatlabFunction - from scipy.io.matlab.mio5_params import MatlabOpaque -from scipy.io import loadmat - from ...utils import _import_pymatreader_funcs @@ -37,6 +30,11 @@ def _todict_from_np_struct(data): # taken from pymatreader.utils def _handle_scipy_ndarray(data): # taken from pymatreader.utils + try: + from scipy.io.matlab import MatlabFunction + except ImportError: # scipy < 1.8 + from scipy.io.matlab.mio5 import MatlabFunction + if data.dtype == np.dtype("object") and not isinstance(data, MatlabFunction): as_list = [] for element in data: @@ -54,6 +52,11 @@ def _handle_scipy_ndarray(data): # taken from pymatreader.utils def _check_for_scipy_mat_struct(data): # taken from pymatreader.utils """Convert all scipy.io.matlab.mio5_params.mat_struct elements.""" + try: + from scipy.io.matlab import MatlabOpaque + except ImportError: # scipy < 1.8 + from scipy.io.matlab.mio5_params import MatlabOpaque + if isinstance(data, dict): for key in data: data[key] = _check_for_scipy_mat_struct(data[key]) @@ -75,6 +78,8 @@ def _readmat(fname, uint16_codec=None): try: read_mat = _import_pymatreader_funcs("EEGLAB I/O") except RuntimeError: # pymatreader not installed + from scipy.io import loadmat + eeg = loadmat( fname, squeeze_me=True, mat_dtype=False, uint16_codec=uint16_codec ) diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index 5d9b79b57cc..138ffc32ba5 100644 --- a/mne/io/nirx/nirx.py +++ b/mne/io/nirx/nirx.py @@ -10,7 +10,6 @@ from configparser import ConfigParser, RawConfigParser import numpy as np -from scipy.io import loadmat from ..._fiff.constants import FIFF from ..._fiff.meas_info import _format_dig_points, create_info @@ -94,6 +93,8 @@ class RawNIRX(BaseRaw): @verbose def __init__(self, fname, saturated, *, preload=False, encoding=None, verbose=None): + from scipy.io import loadmat + logger.info(f"Loading {fname}") _validate_type(fname, "path-like", "fname") _validate_type(saturated, str, "saturated") diff --git a/mne/label.py b/mne/label.py index 02bf9dc09c0..bf2cd758aad 100644 --- a/mne/label.py +++ b/mne/label.py @@ -10,7 +10,6 @@ from colorsys import hsv_to_rgb, rgb_to_hsv import numpy as np -from scipy import linalg, sparse from .fixes import _safe_svd from .morph_map import read_morph_map @@ -915,6 +914,8 @@ def distances_to_outside( .. versionadded:: 0.24 """ + from scipy.sparse.csgraph import dijkstra + rr, tris = self._load_surface(subject, subjects_dir, surface) adjacency = mesh_dist(tris, rr) mask = np.zeros(len(rr)) @@ -922,7 +923,7 @@ def distances_to_outside( border_vert = _mesh_borders(tris, mask) # vertices on the edge outside_vert = np.setdiff1d(border_vert, self.vertices) - dist, _, outside = sparse.csgraph.dijkstra( + dist, _, outside = dijkstra( adjacency, indices=outside_vert, min_only=True, return_predecessors=True ) dist = dist[self.vertices] * 1e-3 # mm to m @@ -1352,6 +1353,8 @@ def split_label(label, parts=2, subject=None, subjects_dir=None, freesurfer=Fals projecting all label vertex coordinates onto this axis and dividing them at regular spatial intervals. """ + from scipy import linalg + label, subject, subjects_dir = _prep_label_split(label, subject, subjects_dir) # find the parts @@ -2554,6 +2557,8 @@ def _check_values_labels(values, n_labels): def _labels_to_stc_surf(labels, values, tmin, tstep, subject): + from scipy import sparse + subject = _check_labels_subject(labels, subject, "subject") _check_values_labels(values, len(labels)) vertices = dict(lh=[], rh=[]) diff --git a/mne/minimum_norm/__init__.py b/mne/minimum_norm/__init__.py index 975eb5ed3e5..e7fff3b1496 100644 --- a/mne/minimum_norm/__init__.py +++ b/mne/minimum_norm/__init__.py @@ -3,6 +3,31 @@ # Copyright the MNE-Python contributors. """Linear inverse solvers based on L2 Minimum Norm Estimates (MNE).""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .inverse import ( + InverseOperator, + read_inverse_operator, + apply_inverse, + apply_inverse_raw, + make_inverse_operator, + apply_inverse_epochs, + apply_inverse_tfr_epochs, + write_inverse_operator, + compute_rank_inverse, + prepare_inverse_operator, + estimate_snr, + apply_inverse_cov, + INVERSE_METHODS, +) +from .time_frequency import ( + source_band_induced_power, + source_induced_power, + compute_source_psd, + compute_source_psd_epochs, +) +from .resolution_matrix import ( + make_inverse_resolution_matrix, + get_point_spread, + get_cross_talk, +) +from .spatial_resolution import resolution_metrics diff --git a/mne/minimum_norm/__init__.pyi b/mne/minimum_norm/__init__.pyi deleted file mode 100644 index fb800479383..00000000000 --- a/mne/minimum_norm/__init__.pyi +++ /dev/null @@ -1,50 +0,0 @@ -__all__ = [ - "INVERSE_METHODS", - "InverseOperator", - "apply_inverse", - "apply_inverse_cov", - "apply_inverse_epochs", - "apply_inverse_raw", - "apply_inverse_tfr_epochs", - "compute_rank_inverse", - "compute_source_psd", - "compute_source_psd_epochs", - "estimate_snr", - "get_cross_talk", - "get_point_spread", - "make_inverse_operator", - "make_inverse_resolution_matrix", - "prepare_inverse_operator", - "read_inverse_operator", - "resolution_metrics", - "source_band_induced_power", - "source_induced_power", - "write_inverse_operator", -] -from .inverse import ( - INVERSE_METHODS, - InverseOperator, - apply_inverse, - apply_inverse_cov, - apply_inverse_epochs, - apply_inverse_raw, - apply_inverse_tfr_epochs, - compute_rank_inverse, - estimate_snr, - make_inverse_operator, - prepare_inverse_operator, - read_inverse_operator, - write_inverse_operator, -) -from .resolution_matrix import ( - get_cross_talk, - get_point_spread, - make_inverse_resolution_matrix, -) -from .spatial_resolution import resolution_metrics -from .time_frequency import ( - compute_source_psd, - compute_source_psd_epochs, - source_band_induced_power, - source_induced_power, -) diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index 7c789503ac1..e0e73fba886 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -6,8 +6,6 @@ from math import sqrt import numpy as np -from scipy import linalg -from scipy.stats import chi2 from .._fiff.constants import FIFF from .._fiff.matrix import ( @@ -635,6 +633,8 @@ def prepare_inverse_operator( inv : instance of InverseOperator Prepared inverse operator. """ + from scipy import linalg + if nave <= 0: raise ValueError("The number of averages should be positive") @@ -2200,6 +2200,8 @@ def estimate_snr(evoked, inv, verbose=None): .. versionadded:: 0.9.0 """ # noqa: E501 + from scipy.stats import chi2 + _check_reference(evoked, inv["info"]["ch_names"]) _check_ch_names(inv, evoked.info) inv = prepare_inverse_operator(inv, evoked.nave, 1.0 / 9.0, "MNE", copy="non-src") diff --git a/mne/morph.py b/mne/morph.py index a8278731f3c..533256fd582 100644 --- a/mne/morph.py +++ b/mne/morph.py @@ -7,7 +7,6 @@ import warnings import numpy as np -from scipy import sparse from .fixes import _eye_array, _get_img_fdata from .morph_map import read_morph_map @@ -604,6 +603,7 @@ def compute_vol_morph_mat(self, *, verbose=None): def _morph_vols(self, vols, mesg, subselect=True): from dipy.align.reslice import reslice + from scipy import sparse interp = self.src_data["interpolator"].tocsc()[ :, np.concatenate(self._vol_vertices_from) @@ -773,6 +773,7 @@ def _debug_img(data, affine, title, shape=None): # Uncomment these lines for debugging help with volume morph: # # import nibabel as nib + # from scipy import sparse # if sparse.issparse(data): # data = data.toarray() # data = np.asarray(data) @@ -1180,6 +1181,8 @@ def _compute_morph_matrix( xhemi=False, ): """Compute morph matrix.""" + from scipy import sparse + logger.info("Computing morph matrix...") subjects_dir = get_subjects_dir(subjects_dir, raise_error=True) @@ -1222,6 +1225,8 @@ def _compute_morph_matrix( def _hemi_morph(tris, vertices_to, vertices_from, smooth, maps, warn): + from scipy import sparse + _validate_type(smooth, (str, None, "int-like"), "smoothing steps") if len(vertices_from) == 0: return sparse.csr_array((len(vertices_to), 0)) @@ -1334,6 +1339,8 @@ def grade_to_vertices(subject, grade, subjects_dir=None, n_jobs=None, verbose=No # Takes ~20 ms to hash, ~100 ms to compute (5x speedup) @_custom_lru_cache(20) def _surf_nearest(vertices, adj_mat): + from scipy import sparse + # Vertices can be out of order, so sort them to start ... order = np.argsort(vertices) vertices = vertices[order] @@ -1371,6 +1378,8 @@ def _csr_row_norm(data, row_norm): @_custom_lru_cache(20) def _surf_upsampling_mat(idx_from, e, smooth): """Upsample data on a subject's surface given mesh edges.""" + from scipy import sparse + # we're in CSR format and it's to==from assert isinstance(e, sparse.csr_array) n_tot = e.shape[0] diff --git a/mne/morph_map.py b/mne/morph_map.py index d2fccd3c075..854dbe1caf1 100644 --- a/mne/morph_map.py +++ b/mne/morph_map.py @@ -8,7 +8,6 @@ import os import numpy as np -from scipy.sparse import csr_array from ._fiff.constants import FIFF from ._fiff.open import fiff_open @@ -206,6 +205,8 @@ def _make_morph_map(subject_from, subject_to, subjects_dir, xhemi): def _make_morph_map_hemi(subject_from, subject_to, subjects_dir, reg_from, reg_to): """Construct morph map for one hemisphere.""" + from scipy.sparse import csr_array + # add speedy short-circuit for self-maps if subject_from == subject_to and reg_from == reg_to: fname = subjects_dir / subject_from / "surf" / reg_from diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index dac4012b6ef..f61b7fdc895 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -4,6 +4,53 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy +from ._annotate_amplitude import annotate_amplitude +from .ssp import compute_proj_ecg, compute_proj_eog +from .eog import find_eog_events, create_eog_epochs +from .ecg import find_ecg_events, create_ecg_epochs +from .ica import ( + ICA, + ica_find_eog_events, + ica_find_ecg_events, + get_score_funcs, + read_ica, + corrmap, + read_ica_eeglab, +) +from .otp import oversampled_temporal_projection +from ._peak_finder import peak_finder +from .infomax_ import infomax +from .stim import fix_stim_artifact +from .maxwell import ( + maxwell_filter, + find_bad_channels_maxwell, + compute_maxwell_basis, + maxwell_filter_prepare_emptyroom, +) +from .realign import realign_raw +from ._csd import compute_current_source_density, compute_bridged_electrodes -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .artifact_detection import ( + annotate_movement, + compute_average_dev_head_t, + annotate_muscle_zscore, + annotate_break, +) +from ._regress import regress_artifact, EOGRegression, read_eog_regression +from ._fine_cal import ( + compute_fine_calibration, + read_fine_calibration, + write_fine_calibration, +) +from ._annotate_nan import annotate_nan +from .interpolate import equalize_bads, interpolate_bridged_electrodes + +from ._css import cortical_signal_suppression +from .hfc import compute_proj_hfc +from ._lof import find_bad_channels_lof +from ._pca_obs import apply_pca_obs + +try: + from .xdawn import Xdawn +except ImportError: + pass diff --git a/mne/preprocessing/__init__.pyi b/mne/preprocessing/__init__.pyi deleted file mode 100644 index c54685dba34..00000000000 --- a/mne/preprocessing/__init__.pyi +++ /dev/null @@ -1,93 +0,0 @@ -__all__ = [ - "EOGRegression", - "ICA", - "Xdawn", - "annotate_amplitude", - "annotate_break", - "annotate_movement", - "annotate_muscle_zscore", - "annotate_nan", - "compute_average_dev_head_t", - "compute_bridged_electrodes", - "compute_current_source_density", - "compute_fine_calibration", - "compute_maxwell_basis", - "compute_proj_ecg", - "compute_proj_eog", - "compute_proj_hfc", - "corrmap", - "cortical_signal_suppression", - "create_ecg_epochs", - "create_eog_epochs", - "equalize_bads", - "eyetracking", - "find_bad_channels_lof", - "find_bad_channels_maxwell", - "find_ecg_events", - "find_eog_events", - "fix_stim_artifact", - "get_score_funcs", - "ica_find_ecg_events", - "ica_find_eog_events", - "ieeg", - "infomax", - "interpolate_bridged_electrodes", - "maxwell_filter", - "maxwell_filter_prepare_emptyroom", - "nirs", - "oversampled_temporal_projection", - "peak_finder", - "read_eog_regression", - "read_fine_calibration", - "read_ica", - "read_ica_eeglab", - "realign_raw", - "regress_artifact", - "write_fine_calibration", - "apply_pca_obs", -] -from . import eyetracking, ieeg, nirs -from ._annotate_amplitude import annotate_amplitude -from ._annotate_nan import annotate_nan -from ._csd import compute_bridged_electrodes, compute_current_source_density -from ._css import cortical_signal_suppression -from ._fine_cal import ( - compute_fine_calibration, - read_fine_calibration, - write_fine_calibration, -) -from ._lof import find_bad_channels_lof -from ._pca_obs import apply_pca_obs -from ._peak_finder import peak_finder -from ._regress import EOGRegression, read_eog_regression, regress_artifact -from .artifact_detection import ( - annotate_break, - annotate_movement, - annotate_muscle_zscore, - compute_average_dev_head_t, -) -from .ecg import create_ecg_epochs, find_ecg_events -from .eog import create_eog_epochs, find_eog_events -from .hfc import compute_proj_hfc -from .ica import ( - ICA, - corrmap, - get_score_funcs, - ica_find_ecg_events, - ica_find_eog_events, - read_ica, - read_ica_eeglab, -) -from .infomax_ import infomax -from .interpolate import equalize_bads, interpolate_bridged_electrodes -from .maxwell import ( - compute_maxwell_basis, - find_bad_channels_maxwell, - maxwell_filter, - maxwell_filter_prepare_emptyroom, -) -from .otp import oversampled_temporal_projection -from .realign import realign_raw -from .ssp import compute_proj_ecg, compute_proj_eog -from .stim import fix_stim_artifact -from .xdawn import Xdawn diff --git a/mne/preprocessing/_csd.py b/mne/preprocessing/_csd.py index 35a32e90245..2476b350d70 100644 --- a/mne/preprocessing/_csd.py +++ b/mne/preprocessing/_csd.py @@ -11,8 +11,6 @@ # code. import numpy as np -from scipy.optimize import minimize_scalar -from scipy.stats import gaussian_kde from .._fiff.constants import FIFF from .._fiff.pick import pick_types @@ -261,6 +259,9 @@ def compute_bridged_electrodes( ---------- .. footbibliography:: """ + from scipy.optimize import minimize_scalar + from scipy.stats import gaussian_kde + _check_preload(inst, "Computing bridged electrodes") inst = inst.copy() # don't modify original picks = pick_types(inst.info, eeg=True) diff --git a/mne/preprocessing/_fine_cal.py b/mne/preprocessing/_fine_cal.py index 06041cd7f8e..941a8f11887 100644 --- a/mne/preprocessing/_fine_cal.py +++ b/mne/preprocessing/_fine_cal.py @@ -6,7 +6,6 @@ from functools import partial import numpy as np -from scipy.optimize import minimize from .._fiff.pick import pick_info, pick_types from .._fiff.tag import _coil_trans_to_loc, _loc_to_coil_trans @@ -284,6 +283,8 @@ def _vector_angle(x, y): def _adjust_mag_normals(info, data, origin, ext_order, *, angle_limit, err_limit): """Adjust coil normals using magnetometers and empty-room data.""" + from scipy.optimize import minimize + # in principle we could allow using just mag or mag+grad, but MF uses # just mag so let's follow suit mag_scale = 100.0 diff --git a/mne/preprocessing/_pca_obs.py b/mne/preprocessing/_pca_obs.py index be226a73889..d78b6a39305 100755 --- a/mne/preprocessing/_pca_obs.py +++ b/mne/preprocessing/_pca_obs.py @@ -7,8 +7,6 @@ import math import numpy as np -from scipy.interpolate import PchipInterpolator as pchip -from scipy.signal import detrend from ..io.fiff.raw import Raw from ..utils import _PCA, _validate_type, logger, verbose @@ -90,6 +88,8 @@ def _pca_obs( n_components: int, ) -> np.ndarray: """Algorithm to remove heart artefact from EEG data (array of length n_times).""" + from scipy.signal import detrend + # set to baseline data = data - np.mean(data) @@ -276,6 +276,9 @@ def _fit_ecg_template( ------- tuple[np.ndarray, int]: the fitted artifact and the next peak index """ + from scipy.interpolate import pchip + from scipy.signal import detrend + # post_idx_next_peak is passed in in PCA_OBS, used here as post_idx_previous_peak # Then next_peak is returned at the end and the process repeats # select window of template diff --git a/mne/preprocessing/artifact_detection.py b/mne/preprocessing/artifact_detection.py index 8674d6e22b3..75ee7181f83 100644 --- a/mne/preprocessing/artifact_detection.py +++ b/mne/preprocessing/artifact_detection.py @@ -4,9 +4,6 @@ import numpy as np -from scipy.ndimage import distance_transform_edt, label -from scipy.signal import find_peaks -from scipy.stats import zscore from ..annotations import ( Annotations, @@ -89,6 +86,9 @@ def annotate_muscle_zscore( ---------- .. footbibliography:: """ + from scipy.ndimage import label + from scipy.stats import zscore + raw_copy = raw.copy() if ch_type is None: @@ -406,6 +406,9 @@ def _raw_hp_weights(raw, pos): def _annotations_from_mask(times, mask, annot_name, orig_time=None): """Construct annotations from boolean mask of the data.""" + from scipy.ndimage import distance_transform_edt + from scipy.signal import find_peaks + mask_tf = distance_transform_edt(mask) # Overcome the shortcoming of find_peaks # in finding a marginal peak, by diff --git a/mne/preprocessing/bads.py b/mne/preprocessing/bads.py index 8fdf4219cf5..6c8fde10a5c 100644 --- a/mne/preprocessing/bads.py +++ b/mne/preprocessing/bads.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.stats import zscore def _find_outliers(X, threshold=3.0, max_iter=2, tail=0): @@ -30,6 +29,8 @@ def _find_outliers(X, threshold=3.0, max_iter=2, tail=0): bad_idx : np.ndarray of int, shape (n_features) The outlier indices. """ + from scipy.stats import zscore + my_mask = np.zeros(len(X), dtype=bool) for _ in range(max_iter): X = np.ma.masked_array(X, my_mask) diff --git a/mne/preprocessing/ctps_.py b/mne/preprocessing/ctps_.py index 7a53243999a..9a3271f8148 100644 --- a/mne/preprocessing/ctps_.py +++ b/mne/preprocessing/ctps_.py @@ -5,8 +5,6 @@ import math import numpy as np -from scipy.signal import hilbert -from scipy.special import logsumexp def _compute_normalized_phase(data): @@ -22,6 +20,8 @@ def _compute_normalized_phase(data): phase_angles : ndarray, shape (n_epochs, n_sources, n_times) The normalized phase angles. """ + from scipy.signal import hilbert + return (np.angle(hilbert(data)) + np.pi) / (2 * np.pi) @@ -143,6 +143,8 @@ def _prob_kuiper(d, n_eff, dtype="f8"): [2] Kuiper NH 1962. Proceedings of the Koninklijke Nederlands Akademie van Wetenschappen, ser Vol 63 pp 38-47 """ + from scipy.special import logsumexp + n_time_slices = np.size(d) # single value or vector n_points = 100 diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py index f35fe24c1ee..453d46ccd6a 100644 --- a/mne/preprocessing/ica.py +++ b/mne/preprocessing/ica.py @@ -18,9 +18,6 @@ from typing import Literal import numpy as np -from scipy import stats -from scipy.spatial import distance -from scipy.special import expit from .._fiff.constants import FIFF from .._fiff.meas_info import ContainsMixin, read_meas_info, write_meas_info @@ -142,6 +139,9 @@ def get_score_funcs(): score_funcs : dict The score functions. """ + from scipy import stats + from scipy.spatial import distance + score_funcs = Bunch() xy_arg_dist_funcs = [ (n, f) @@ -1993,6 +1993,9 @@ def find_bads_muscle( ----- .. versionadded:: 1.1 """ + from scipy.spatial.distance import pdist, squareform + from scipy.special import expit + _validate_type(threshold, "numeric", "threshold") slope_score, focus_score, smoothness_score = None, None, None @@ -2048,10 +2051,10 @@ def find_bads_muscle( # compute metric #3: smoothness smoothnesses = np.zeros((components.shape[1],)) - dists = distance.squareform(distance.pdist(pos)) + dists = squareform(pdist(pos)) dists = 1 - (dists / dists.max()) # invert for idx, comp in enumerate(components.T): - comp_dists = distance.squareform(distance.pdist(comp[:, np.newaxis])) + comp_dists = squareform(pdist(comp[:, np.newaxis])) comp_dists /= comp_dists.max() smoothnesses[idx] = np.multiply(dists, comp_dists).sum() diff --git a/mne/preprocessing/ieeg/_projection.py b/mne/preprocessing/ieeg/_projection.py index d71f9e67e4f..d67441c536c 100644 --- a/mne/preprocessing/ieeg/_projection.py +++ b/mne/preprocessing/ieeg/_projection.py @@ -5,7 +5,6 @@ from itertools import combinations import numpy as np -from scipy.spatial.distance import pdist, squareform from ..._fiff.pick import _picks_to_idx from ...channels import make_dig_montage @@ -65,6 +64,8 @@ def project_sensors_onto_brain( :ref:`mne watershed_bem` using the T1 or :ref:`mne flash_bem` using a FLASH scan. """ + from scipy.spatial.distance import pdist, squareform + n_neighbors = _ensure_int(n_neighbors, "n_neighbors") _validate_type(copy, bool, "copy") if copy: diff --git a/mne/preprocessing/infomax_.py b/mne/preprocessing/infomax_.py index b445ac7116c..7c0949f5068 100644 --- a/mne/preprocessing/infomax_.py +++ b/mne/preprocessing/infomax_.py @@ -5,8 +5,6 @@ import math import numpy as np -from scipy.special import expit -from scipy.stats import kurtosis from ..utils import check_random_state, logger, random_permutation, verbose @@ -116,6 +114,9 @@ def infomax( analysis using an extended infomax algorithm for mixed subgaussian and supergaussian sources. Neural Computation, 11(2), 417-441, 1999. """ + from scipy.special import expit + from scipy.stats import kurtosis + rng = check_random_state(random_state) # define some default parameters diff --git a/mne/preprocessing/interpolate.py b/mne/preprocessing/interpolate.py index e0152bbf2dc..169e53721dd 100644 --- a/mne/preprocessing/interpolate.py +++ b/mne/preprocessing/interpolate.py @@ -7,7 +7,6 @@ from itertools import chain import numpy as np -from scipy.sparse.csgraph import connected_components from .._fiff.meas_info import create_info from ..epochs import BaseEpochs, EpochsArray @@ -108,6 +107,8 @@ def interpolate_bridged_electrodes(inst, bridged_idx, bad_limit=4): -------- mne.preprocessing.compute_bridged_electrodes """ + from scipy.sparse.csgraph import connected_components + _validate_type(inst, (BaseRaw, BaseEpochs, Evoked)) bad_limit = _ensure_int(bad_limit, "bad_limit") if bad_limit <= 0: diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index 47c5b52e0c6..05225a9efcf 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -9,8 +9,6 @@ from pathlib import Path import numpy as np -from scipy import linalg -from scipy.special import lpmv from .. import __version__ from .._fiff.compensator import make_compensator @@ -1297,6 +1295,8 @@ def _get_decomp( mult, ): """Get a decomposition matrix and pseudoinverse matrices.""" + from scipy import linalg + # # Fine calibration processing (point-like magnetometers and calib. coeffs) # @@ -1953,6 +1953,8 @@ def _alegendre_deriv(order, degree, val): dPlm : float Associated Legendre function derivative """ + from scipy.special import lpmv + assert order >= 0 return ( order * val * lpmv(order, degree, val) @@ -2139,6 +2141,8 @@ def _orth_overwrite(A): def _overlap_projector(data_int, data_res, corr): """Calculate projector for removal of subspace intersection in tSSS.""" + from scipy import linalg + # corr necessary to deal with noise when finding identical signal # directions in the subspace. See the end of the Results section in # :footcite:`TauluSimola2006` diff --git a/mne/preprocessing/nirs/_beer_lambert_law.py b/mne/preprocessing/nirs/_beer_lambert_law.py index c17cf31110c..f08d7579666 100644 --- a/mne/preprocessing/nirs/_beer_lambert_law.py +++ b/mne/preprocessing/nirs/_beer_lambert_law.py @@ -5,8 +5,6 @@ import os.path as op import numpy as np -from scipy.interpolate import interp1d -from scipy.io import loadmat from ..._fiff.constants import FIFF from ...io import BaseRaw @@ -87,6 +85,9 @@ def beer_lambert_law(raw, ppf=6.0): def _load_absorption(freqs): """Load molar extinction coefficients.""" + from scipy.interpolate import interp1d + from scipy.io import loadmat + # Data from https://omlc.org/spectra/hemoglobin/summary.html # The text was copied to a text file. The text before and # after the table was deleted. The the following was run in diff --git a/mne/preprocessing/nirs/_tddr.py b/mne/preprocessing/nirs/_tddr.py index 9f323ae3db9..36fd9573360 100644 --- a/mne/preprocessing/nirs/_tddr.py +++ b/mne/preprocessing/nirs/_tddr.py @@ -4,7 +4,6 @@ import numpy as np -from scipy.signal import butter, filtfilt from ...io import BaseRaw from ...utils import _validate_type, verbose @@ -83,6 +82,8 @@ def _TDDR(signal, sample_rate): # Outputs: # signals_corrected: A [sample x channel] matrix of corrected optical # density data + from scipy.signal import butter, filtfilt + signal = np.array(signal) if len(signal.shape) != 1: for ch in range(signal.shape[1]): diff --git a/mne/preprocessing/realign.py b/mne/preprocessing/realign.py index 7f08937949e..85d27b58a06 100644 --- a/mne/preprocessing/realign.py +++ b/mne/preprocessing/realign.py @@ -4,7 +4,6 @@ import numpy as np from numpy.polynomial.polynomial import Polynomial -from scipy.stats import pearsonr from ..io import BaseRaw from ..utils import _validate_type, logger, verbose, warn @@ -53,6 +52,8 @@ def realign_raw(raw, other, t_raw, t_other, *, verbose=None): .. versionadded:: 0.22 """ + from scipy import stats + _validate_type(raw, BaseRaw, "raw") _validate_type(other, BaseRaw, "other") t_raw = np.array(t_raw, float) @@ -72,7 +73,7 @@ def realign_raw(raw, other, t_raw, t_other, *, verbose=None): logger.info( f"Zero order coefficient: {zero_ord} \nFirst order coefficient: {first_ord}" ) - r, p = pearsonr(t_other, t_raw) + r, p = stats.pearsonr(t_other, t_raw) msg = f"Linear correlation computed as R={r:0.3f} and p={p:0.2e}" if p > 0.05 or r <= 0: raise ValueError(msg + ", cannot resample safely") diff --git a/mne/preprocessing/stim.py b/mne/preprocessing/stim.py index a823820988b..0a2834bb647 100644 --- a/mne/preprocessing/stim.py +++ b/mne/preprocessing/stim.py @@ -3,8 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.interpolate import interp1d -from scipy.signal.windows import hann from .._fiff.pick import _picks_to_idx from ..epochs import BaseEpochs @@ -16,6 +14,8 @@ def _get_window(start, end): """Return window which has length as much as parameter start - end.""" + from scipy.signal.windows import hann + window = 1 - np.r_[hann(4)[:2], np.ones(np.abs(end - start) - 4), hann(4)[-2:]].T return window @@ -24,6 +24,8 @@ def _fix_artifact( data, window, picks, first_samp, last_samp, base_tmin, base_tmax, mode ): """Modify original data by using parameter data.""" + from scipy.interpolate import interp1d + if mode == "linear": x = np.array([first_samp, last_samp]) f = interp1d(x, data[:, (first_samp, last_samp)][picks]) diff --git a/mne/preprocessing/xdawn.py b/mne/preprocessing/xdawn.py index 606b49370df..69cf65f5b60 100644 --- a/mne/preprocessing/xdawn.py +++ b/mne/preprocessing/xdawn.py @@ -3,11 +3,11 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg +from sklearn.base import TransformerMixin from .._fiff.pick import _pick_data_channels, pick_info from ..cov import Covariance, _regularized_covariance -from ..decoding import BaseEstimator, TransformerMixin +from ..decoding import BaseEstimator from ..epochs import BaseEpochs from ..evoked import Evoked, EvokedArray from ..io import BaseRaw @@ -57,6 +57,8 @@ def _least_square_evoked(epochs_data, events, tmin, sfreq): toeplitz : array, shape (n_class * n_components, n_channels) An concatenated array of toeplitz matrix for each event type. """ + from scipy import linalg + n_epochs, n_channels, n_times = epochs_data.shape tmax = tmin + n_times / float(sfreq) @@ -151,6 +153,8 @@ def _fit_xdawn( evokeds : array, shape (n_class, n_components, n_times) The independent evoked responses per condition. """ + from scipy import linalg + if not isinstance(epochs_data, np.ndarray) or epochs_data.ndim != 3: raise ValueError("epochs_data must be 3D ndarray") diff --git a/mne/rank.py b/mne/rank.py index 22ae5068732..0918f3df15c 100644 --- a/mne/rank.py +++ b/mne/rank.py @@ -5,7 +5,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg from ._fiff.meas_info import Info, _simplify_info from ._fiff.pick import _picks_by_type, _picks_to_idx, pick_channels_cov, pick_info @@ -64,6 +63,8 @@ def estimate_rank( If return_singular is True, the singular values that were thresholded to determine the rank are also returned. """ + from scipy import linalg + if norm: data = data.copy() # operate on a copy norms = _compute_row_norms(data) diff --git a/mne/report/__init__.py b/mne/report/__init__.py index e3a0a07d6c8..8b7334f50bc 100644 --- a/mne/report/__init__.py +++ b/mne/report/__init__.py @@ -2,7 +2,4 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -"""Report-generation functions and classes.""" -import lazy_loader as lazy - -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .report import Report, open_report, _ReportScraper diff --git a/mne/report/__init__.pyi b/mne/report/__init__.pyi deleted file mode 100644 index fa99d6e3ea1..00000000000 --- a/mne/report/__init__.pyi +++ /dev/null @@ -1,2 +0,0 @@ -__all__ = ["Report", "_ReportScraper", "open_report"] -from .report import Report, _ReportScraper, open_report diff --git a/mne/simulation/__init__.py b/mne/simulation/__init__.py index 67360191ac9..43cefc5c501 100644 --- a/mne/simulation/__init__.py +++ b/mne/simulation/__init__.py @@ -3,6 +3,8 @@ # Copyright the MNE-Python contributors. """Data simulation code.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .evoked import simulate_evoked, add_noise +from .raw import simulate_raw, add_ecg, add_eog, add_chpi +from .source import select_source_in_label, simulate_stc, simulate_sparse_stc +from .source import SourceSimulator diff --git a/mne/simulation/__init__.pyi b/mne/simulation/__init__.pyi deleted file mode 100644 index 9e0612f53a8..00000000000 --- a/mne/simulation/__init__.pyi +++ /dev/null @@ -1,22 +0,0 @@ -__all__ = [ - "SourceSimulator", - "add_chpi", - "add_ecg", - "add_eog", - "add_noise", - "metrics", - "select_source_in_label", - "simulate_evoked", - "simulate_raw", - "simulate_sparse_stc", - "simulate_stc", -] -from . import metrics -from .evoked import add_noise, simulate_evoked -from .raw import add_chpi, add_ecg, add_eog, simulate_raw -from .source import ( - SourceSimulator, - select_source_in_label, - simulate_sparse_stc, - simulate_stc, -) diff --git a/mne/simulation/evoked.py b/mne/simulation/evoked.py index 2b225727dfe..b60ccdd23f9 100644 --- a/mne/simulation/evoked.py +++ b/mne/simulation/evoked.py @@ -5,7 +5,6 @@ import math import numpy as np -from scipy.signal import lfilter from .._fiff.pick import pick_info from ..cov import Covariance, compute_whitener @@ -172,6 +171,8 @@ def _generate_noise( info, cov, iir_filter, random_state, n_samples, zi=None, picks=None ): """Create spatially colored and temporally IIR-filtered noise.""" + from scipy.signal import lfilter + rng = check_random_state(random_state) _, _, colorer = compute_whitener( cov, info, pca=True, return_colorer=True, picks=picks, verbose=False diff --git a/mne/simulation/metrics/metrics.py b/mne/simulation/metrics/metrics.py index c9ac7c0d893..0bd0f82174a 100644 --- a/mne/simulation/metrics/metrics.py +++ b/mne/simulation/metrics/metrics.py @@ -5,7 +5,6 @@ from functools import partial import numpy as np -from scipy.spatial.distance import cdist from ...utils import _check_option, _validate_type, fill_doc @@ -193,6 +192,8 @@ def _abs_col_sum(x): def _dle(p, q, src, stc): """Aux function to compute dipole localization error.""" + from scipy.spatial.distance import cdist + p = _abs_col_sum(p) q = _abs_col_sum(q) idx1 = np.nonzero(p)[0] diff --git a/mne/source_estimate.py b/mne/source_estimate.py index deeb3a43ede..6275fe73d97 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -8,8 +8,6 @@ from types import GeneratorType import numpy as np -from scipy import sparse -from scipy.spatial.distance import cdist, pdist from ._fiff.constants import FIFF from ._fiff.meas_info import Info @@ -3219,6 +3217,8 @@ def spatio_temporal_dist_adjacency(src, n_times, dist, verbose=None): vertices are time 1, the nodes from 2 to 2N are the vertices during time 2, etc. """ + from scipy import sparse + if src[0]["dist"] is None: raise RuntimeError( "src must have distances included, consider using " @@ -3329,6 +3329,9 @@ def spatial_inter_hemi_adjacency(src, dist, verbose=None): existing intra-hemispheric adjacency matrix, e.g. computed using geodesic distances. """ + from scipy import sparse + from scipy.spatial.distance import cdist + src = _ensure_src(src, kind="surface") adj = cdist(src[0]["rr"][src[0]["vertno"]], src[1]["rr"][src[1]["vertno"]]) adj = sparse.csr_array(adj <= dist, dtype=int) @@ -3342,6 +3345,8 @@ def spatial_inter_hemi_adjacency(src, dist, verbose=None): @verbose def _get_adjacency_from_edges(edges, n_times, verbose=None): """Given edges sparse matrix, create adjacency matrix.""" + from scipy import sparse + n_vertices = edges.shape[0] logger.info("-- number of adjacent vertices : %d", n_vertices) nnz = edges.col.size @@ -3431,6 +3436,8 @@ def _prepare_label_extraction(stc, labels, src, mode, allow_empty, use_sparse): # of vol src space. # If stc=None (i.e. no activation time courses provided) and mode='mean', # only computes vertex indices and label_flip will be list of None. + from scipy import sparse + from .label import BiHemiLabel, Label, label_sign_flip # if source estimate provided in stc, get vertices from source space and @@ -3661,6 +3668,8 @@ def _gen_extract_label_time_course( verbose=None, ): # loop through source estimates and extract time series + from scipy import sparse + if src is None and mode in ["mean", "max"]: kind = "surface" else: @@ -3917,6 +3926,8 @@ def stc_near_sensors( .. versionadded:: 0.22 """ + from scipy.spatial.distance import cdist, pdist + from .evoked import Evoked _validate_type(evoked, Evoked, "evoked") diff --git a/mne/source_space/__init__.py b/mne/source_space/__init__.py index 46fb9e1fbff..48899d28641 100644 --- a/mne/source_space/__init__.py +++ b/mne/source_space/__init__.py @@ -3,6 +3,16 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._source_space import ( + SourceSpaces, + add_source_space_distances, + compute_distance_to_sensors, + get_decimated_surfaces, + get_volume_labels_from_src, + morph_source_spaces, + read_source_spaces, + setup_source_space, + setup_volume_source_space, + write_source_spaces, +) diff --git a/mne/source_space/__init__.pyi b/mne/source_space/__init__.pyi deleted file mode 100644 index aeb7657bd33..00000000000 --- a/mne/source_space/__init__.pyi +++ /dev/null @@ -1,22 +0,0 @@ -__all__ = [ - "SourceSpaces", - "_source_space", - "add_source_space_distances", - "compute_distance_to_sensors", - "get_decimated_surfaces", - "read_source_spaces", - "setup_source_space", - "setup_volume_source_space", - "write_source_spaces", -] -from . import _source_space -from ._source_space import ( - SourceSpaces, - add_source_space_distances, - compute_distance_to_sensors, - get_decimated_surfaces, - read_source_spaces, - setup_source_space, - setup_volume_source_space, - write_source_spaces, -) diff --git a/mne/source_space/_source_space.py b/mne/source_space/_source_space.py index d64989961cf..9b6a1fed15a 100644 --- a/mne/source_space/_source_space.py +++ b/mne/source_space/_source_space.py @@ -11,9 +11,6 @@ from functools import partial import numpy as np -from scipy.sparse import csr_array, triu -from scipy.sparse.csgraph import dijkstra -from scipy.spatial.distance import cdist from .._fiff.constants import FIFF from .._fiff.meas_info import Info, create_info @@ -1317,6 +1314,8 @@ def _write_source_spaces(fid, src): def _write_one_source_space(fid, this, verbose=None): """Write one source space.""" + from scipy.sparse import csr_array, triu + if this["type"] == "surf": src_type = FIFF.FIFFV_MNE_SPACE_SURFACE elif this["type"] == "vol": @@ -2335,6 +2334,8 @@ def _src_vol_dims(s): def _add_interpolator(sp): """Compute a sparse matrix to interpolate the data into an MRI volume.""" + from scipy.sparse import csr_array + # extract transformation information from mri mri_width, mri_height, mri_depth, nvox = _src_vol_dims(sp[0]) @@ -2398,6 +2399,8 @@ def _add_interpolator(sp): def _grid_interp(from_shape, to_shape, trans, order=1, inuse=None): """Compute a grid-to-grid linear or nearest interpolation given.""" + from scipy.sparse import csr_array + from_shape = np.array(from_shape, int) to_shape = np.array(to_shape, int) trans = np.array(trans, np.float64) # to -> from @@ -2706,6 +2709,9 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=None, *, verbose=N the source space to disk, as the computed distances will automatically be stored along with the source space data for future use. """ + from scipy.sparse import csr_array + from scipy.sparse.csgraph import dijkstra + src = _ensure_src(src) dist_limit = float(dist_limit) if dist_limit < 0: @@ -2775,6 +2781,8 @@ def add_source_space_distances(src, dist_limit=np.inf, n_jobs=None, *, verbose=N def _do_src_distances(con, vertno, run_inds, limit): """Compute source space distances in chunks.""" + from scipy.sparse.csgraph import dijkstra + func = partial(dijkstra, limit=limit) chunk_size = 20 # save memory by chunking (only a little slower) lims = np.r_[np.arange(0, len(run_inds), chunk_size), len(run_inds)] @@ -3093,6 +3101,7 @@ def _compare_source_spaces(src0, src1, mode="exact", nearest=True, dist_tol=1.5e assert_array_less, assert_equal, ) + from scipy.spatial.distance import cdist if mode != "exact" and "approx" not in mode: # 'nointerp' can be appended raise RuntimeError(f"unknown mode {mode}") @@ -3249,6 +3258,8 @@ def compute_distance_to_sensors(src, info, picks=None, trans=None, verbose=None) The Euclidean distances of source space vertices with respect to sensors. """ + from scipy.spatial.distance import cdist + assert isinstance(src, SourceSpaces) _validate_type(info, (Info,), "info") diff --git a/mne/stats/__init__.py b/mne/stats/__init__.py index 18946a20aca..e285bc11211 100644 --- a/mne/stats/__init__.py +++ b/mne/stats/__init__.py @@ -3,6 +3,24 @@ # Copyright the MNE-Python contributors. """Functions for statistical analysis.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .parametric import ( + f_threshold_mway_rm, + f_mway_rm, + f_oneway, + _parametric_ci, + ttest_1samp_no_p, + ttest_ind_no_p, +) +from .permutations import permutation_t_test, _ci, bootstrap_confidence_interval +from .cluster_level import ( + permutation_cluster_test, + permutation_cluster_1samp_test, + spatio_temporal_cluster_test, + spatio_temporal_cluster_1samp_test, + _st_mask_from_s_inds, + summarize_clusters_stc, +) +from .multi_comp import fdr_correction, bonferroni_correction +from .regression import linear_regression, linear_regression_raw +from ._adjacency import combine_adjacency diff --git a/mne/stats/__init__.pyi b/mne/stats/__init__.pyi deleted file mode 100644 index a206a608eab..00000000000 --- a/mne/stats/__init__.pyi +++ /dev/null @@ -1,44 +0,0 @@ -__all__ = [ - "_ci", - "_parametric_ci", - "_st_mask_from_s_inds", - "bonferroni_correction", - "bootstrap_confidence_interval", - "combine_adjacency", - "erp", - "f_mway_rm", - "f_oneway", - "f_threshold_mway_rm", - "fdr_correction", - "linear_regression", - "linear_regression_raw", - "permutation_cluster_1samp_test", - "permutation_cluster_test", - "permutation_t_test", - "spatio_temporal_cluster_1samp_test", - "spatio_temporal_cluster_test", - "summarize_clusters_stc", - "ttest_1samp_no_p", - "ttest_ind_no_p", -] -from . import erp -from ._adjacency import combine_adjacency -from .cluster_level import ( - _st_mask_from_s_inds, - permutation_cluster_1samp_test, - permutation_cluster_test, - spatio_temporal_cluster_1samp_test, - spatio_temporal_cluster_test, - summarize_clusters_stc, -) -from .multi_comp import bonferroni_correction, fdr_correction -from .parametric import ( - _parametric_ci, - f_mway_rm, - f_oneway, - f_threshold_mway_rm, - ttest_1samp_no_p, - ttest_ind_no_p, -) -from .permutations import _ci, bootstrap_confidence_interval, permutation_t_test -from .regression import linear_regression, linear_regression_raw diff --git a/mne/stats/_adjacency.py b/mne/stats/_adjacency.py index 919c07b0ba5..ca2b84c2aaf 100644 --- a/mne/stats/_adjacency.py +++ b/mne/stats/_adjacency.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import sparse from ..utils import _check_option, _validate_type from ..utils.check import int_like @@ -57,6 +56,8 @@ def combine_adjacency(*structure): <5600x5600 sparse array of type '' with 27076 stored elements in COOrdinate format> """ + from scipy import sparse + structure = list(structure) for di, dim in enumerate(structure): name = f"structure[{di}]" diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py index 7c4c8b40435..96243ecf6c3 100644 --- a/mne/stats/cluster_level.py +++ b/mne/stats/cluster_level.py @@ -5,10 +5,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import ndimage, sparse -from scipy.sparse.csgraph import connected_components -from scipy.stats import f as fstat -from scipy.stats import t as tstat from ..fixes import has_numba, jit from ..parallel import parallel_func @@ -288,6 +284,9 @@ def _get_clusters_st(x_in, neighbors, max_step=1): def _get_components(x_in, adjacency, return_list=True): """Get connected components from a mask and a adjacency matrix.""" + from scipy import sparse + from scipy.sparse.csgraph import connected_components + if adjacency is False: components = np.arange(len(x_in)) else: @@ -376,6 +375,8 @@ def _find_clusters( sums : array Sum of x values in clusters. """ + from scipy import ndimage + _check_option("tail", tail, [-1, 0, 1]) x = np.asanyarray(x) @@ -512,6 +513,8 @@ def _find_clusters_1dir_parts( def _find_clusters_1dir(x, x_in, adjacency, max_step, t_power, ndimage): """Actually call the clustering algorithm.""" + from scipy import ndimage, sparse + if adjacency is None: labels, n_labels = ndimage.label(x_in) @@ -613,6 +616,8 @@ def _pval_from_histogram(T, H0, tail): def _setup_adjacency(adjacency, n_tests, n_times): + from scipy import sparse + if not sparse.issparse(adjacency): raise ValueError( "If adjacency matrix is given, it must be a SciPy sparse matrix." @@ -1133,6 +1138,9 @@ def _permutation_cluster_test( def _check_fun(X, stat_fun, threshold, tail=0, kind="within"): """Check the stat_fun and threshold values.""" + from scipy.stats import f as fstat + from scipy.stats import t as tstat + if kind == "within": if threshold is None: if stat_fun is not None and stat_fun is not ttest_1samp_no_p: @@ -1605,6 +1613,8 @@ def _st_mask_from_s_inds(n_times, n_vertices, vertices, set_as=True): @verbose def _get_partitions_from_adjacency(adjacency, n_times, verbose=None): """Specify disjoint subsets (e.g., hemispheres) based on adjacency.""" + from scipy import sparse + if isinstance(adjacency, list): test = np.ones(len(adjacency)) test_adj = np.zeros((len(adjacency), len(adjacency)), dtype="bool") diff --git a/mne/stats/parametric.py b/mne/stats/parametric.py index 2cc0bff2ea1..98da4a67a72 100644 --- a/mne/stats/parametric.py +++ b/mne/stats/parametric.py @@ -6,8 +6,6 @@ from string import ascii_uppercase import numpy as np -from scipy import stats -from scipy.signal import detrend from ..utils import _check_option @@ -239,6 +237,8 @@ def _get_contrast_indices(effect_idx, n_factors): # noqa: D401 def _iter_contrasts(n_subjects, factor_levels, effect_picks): """Set up contrasts.""" + from scipy.signal import detrend + sc = [] n_factors = len(factor_levels) # prepare computation of Kronecker products @@ -298,11 +298,13 @@ def f_threshold_mway_rm(n_subjects, factor_levels, effects="A*B", pvalue=0.05): ----- .. versionadded:: 0.10 """ + from scipy.stats import f + effect_picks, _ = _map_effects(len(factor_levels), effects) F_threshold = [] for _, df1, df2 in _iter_contrasts(n_subjects, factor_levels, effect_picks): - F_threshold.append(stats.f(df1, df2).isf(pvalue)) + F_threshold.append(f(df1, df2).isf(pvalue)) return F_threshold if len(F_threshold) > 1 else F_threshold[0] @@ -363,6 +365,8 @@ def f_mway_rm(data, factor_levels, effects="all", correction=False, return_pvals ----- .. versionadded:: 0.10 """ + from scipy.stats import f + out_reshape = (-1,) if data.ndim == 2: # general purpose support, e.g. behavioural data data = data[:, :, np.newaxis] @@ -401,7 +405,7 @@ def f_mway_rm(data, factor_levels, effects="all", correction=False, return_pvals df1, df2 = (np.maximum(d[None, :] * eps, 1.0) for d in (df1, df2)) if return_pvals: - pvals = stats.f(df1, df2).sf(fvals) + pvals = f(df1, df2).sf(fvals) else: pvals = np.empty(0) pvalues.append(pvals) @@ -415,6 +419,8 @@ def f_mway_rm(data, factor_levels, effects="all", correction=False, return_pvals def _parametric_ci(arr, ci=0.95): """Calculate the `ci`% parametric confidence interval for `arr`.""" + from scipy import stats + mean = arr.mean(0) if len(arr) < 2: # can't compute standard error sigma = np.full_like(mean, np.nan) diff --git a/mne/stats/regression.py b/mne/stats/regression.py index d6343cfccba..ed431d17fb5 100644 --- a/mne/stats/regression.py +++ b/mne/stats/regression.py @@ -6,7 +6,6 @@ from inspect import isgenerator import numpy as np -from scipy import linalg, sparse, stats from .._fiff.pick import _picks_to_idx, pick_info, pick_types from ..epochs import BaseEpochs @@ -103,6 +102,8 @@ def linear_regression(inst, design_matrix, names=None): def _fit_lm(data, design_matrix, names): """Aux function.""" + from scipy import linalg, stats + n_samples = len(data) n_features = np.prod(data.shape[1:]) if design_matrix.ndim != 2: @@ -257,6 +258,8 @@ def linear_regression_raw( ---------- .. footbibliography:: """ + from scipy import linalg + if isinstance(solver, str): if solver not in {"cholesky"}: raise ValueError(f"No such solver: {solver}") @@ -344,6 +347,8 @@ def _prepare_rerp_preds( n_samples, sfreq, events, event_id=None, tmin=-0.1, tmax=1, covariates=None ): """Build predictor matrix and metadata (e.g. condition time windows).""" + from scipy import sparse + conds = list(event_id) if covariates is not None: conds += list(covariates) diff --git a/mne/surface.py b/mne/surface.py index 9e24147a080..3a5652b6e37 100644 --- a/mne/surface.py +++ b/mne/surface.py @@ -16,10 +16,6 @@ from pathlib import Path import numpy as np -from scipy.ndimage import binary_dilation -from scipy.sparse import coo_array, csr_array -from scipy.spatial import ConvexHull, Delaunay -from scipy.spatial.distance import cdist from ._fiff.constants import FIFF from ._fiff.pick import pick_types @@ -195,6 +191,8 @@ def get_meg_helmet_surf(info, trans=None, *, verbose=None): A built-in helmet is loaded if possible. If not, a helmet surface will be approximated based on the sensor locations. """ + from scipy.spatial import ConvexHull, Delaunay + from .bem import _fit_sphere, read_bem_surfaces from .channels.channels import _get_meg_system @@ -387,6 +385,8 @@ def _accumulate_normals(tris, tri_nn, npts): def _triangle_neighbors(tris, npts): """Efficiently compute vertex neighboring triangles.""" + from scipy.sparse import coo_array + # this code replaces the following, but is faster (vectorized): # neighbor_tri = [list() for _ in range(npts)] # for ti, tri in enumerate(tris): @@ -585,6 +585,8 @@ def __init__(self, xhs): self._xhs = xhs def query(self, rr): + from scipy.spatial.distance import cdist + nearest = list() dists = list() for r in rr: @@ -738,6 +740,8 @@ def __init__(self, surf, *, mode="old", verbose=None): ) def _init_old(self): + from scipy.spatial import Delaunay + self.inner_r = None self.cm = self.surf["rr"].mean(0) # We could use Delaunay or ConvexHull here, Delaunay is slightly slower @@ -1714,6 +1718,8 @@ def mesh_edges(tris): @lru_cache(maxsize=10) def _mesh_edges(tris=None): + from scipy.sparse import coo_array + if np.max(tris) > len(np.unique(tris)): raise ValueError("Cannot compute adjacency on a selection of triangles.") @@ -1747,6 +1753,8 @@ def mesh_dist(tris, vert): dist_matrix : scipy.sparse.csr_array Sparse matrix with distances between adjacent vertices. """ + from scipy.sparse import csr_array + edges = mesh_edges(tris).tocoo() # Euclidean distances between neighboring vertices @@ -1939,6 +1947,7 @@ def _marching_cubes(image, level, smooth=0, fill_hole_size=None, use_flying_edge # Also vtkDiscreteFlyingEdges3D should be faster. # If we ever want not-discrete (continuous/float) marching cubes, # we should probably use vtkFlyingEdges3D rather than vtkMarchingCubes. + from scipy.ndimage import binary_dilation from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy from vtkmodules.vtkCommonDataModel import vtkDataSetAttributes, vtkImageData from vtkmodules.vtkFiltersCore import vtkThreshold diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index 6904e9ff755..38f26484f59 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -4,15 +4,11 @@ import ast import glob -import os -import sys from pathlib import Path from types import ModuleType -import pytest - import mne -from mne.utils import _pl, logger, run_subprocess +from mne.utils import _pl, logger # To avoid circular import issues, we have a defined order of submodule # priority. A submodule should nest an import from another submodule if and @@ -116,10 +112,7 @@ def generic_visit(self, node): self.errors.append(err + ("non-relative mne import",)) if isinstance(node, ast.ImportFrom): # from if node.level != 0: # from . - # now we need to triage based on whether this is nested - if node.module is None: - self.errors.append(err + ("non-explicit relative import",)) - elif node.level == self.level: + if node.module is not None and node.level == self.level: module_name = node.module.split(".")[0] if node.col_offset: # nested if ( @@ -223,49 +216,3 @@ def generic_visit(self, node): raise AssertionError(f"{n} nesting error{_pl(n)}:\n" + "\n".join(all_errors)) # scheme obeys the above order - - -# This test ensures that modules are lazily loaded by lazy_loader. - -eager_import = os.getenv("EAGER_IMPORT", "") -run_script = """ -import sys -import mne - -out = set() - -# check scipy (Numba imports it to check the version) -ok_scipy_submodules = {'version'} -scipy_submodules = set(x.split('.')[1] for x in sys.modules.keys() - if x.startswith('scipy.') and '__' not in x and - not x.split('.')[1].startswith('_') - and sys.modules[x] is not None) -bad = scipy_submodules - ok_scipy_submodules -if len(bad) > 0: - out |= {'scipy submodules: %s' % list(bad)} - -# check sklearn and others -for x in sys.modules.keys(): - for key in ('sklearn', 'pandas', 'pyvista', 'matplotlib', - 'dipy', 'nibabel', 'cupy', 'picard', 'pyvistaqt', 'pooch', - 'tqdm', 'jinja2'): - if x.startswith(key): - x = '.'.join(x.split('.')[:2]) - out |= {x} -if len(out) > 0: - print('\\nFound un-nested import(s) for %s' % (sorted(out),), end='') -exit(len(out)) - -# but this should still work -mne.io.read_raw_fif -assert "scipy.signal" in sys.modules, "scipy.signal not in sys.modules" -""" - - -@pytest.mark.skipif(bool(eager_import), reason=f"EAGER_IMPORT={eager_import}") -def test_lazy_loading(): - """Test that module imports are properly nested.""" - stdout, stderr, code = run_subprocess( - [sys.executable, "-c", run_script], return_code=True - ) - assert code == 0, stdout + stderr diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 74cfd53ec71..8c3496e23d6 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -3,6 +3,46 @@ # Copyright the MNE-Python contributors. """Time frequency analysis tools.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .tfr import ( + AverageTFR, + AverageTFRArray, + BaseTFR, + EpochsTFR, + EpochsTFRArray, + RawTFR, + RawTFRArray, + combine_tfr, + fwhm, + morlet, + tfr_array_morlet, + tfr_morlet, + tfr_multitaper, + read_tfrs, + write_tfrs, +) +from .psd import psd_array_welch +from .csd import ( + CrossSpectralDensity, + csd_fourier, + csd_multitaper, + csd_morlet, + csd_array_fourier, + csd_array_multitaper, + csd_array_morlet, + read_csd, + pick_channels_csd, + csd_tfr, +) +from .ar import fit_iir_model_raw +from .multitaper import dpss_windows, psd_array_multitaper, tfr_array_multitaper +from .spectrum import ( + EpochsSpectrum, + EpochsSpectrumArray, + Spectrum, + SpectrumArray, + combine_spectrum, + read_spectrum, +) +from ._stft import stft, istft, stftfreq +from ._stockwell import tfr_stockwell, tfr_array_stockwell diff --git a/mne/time_frequency/__init__.pyi b/mne/time_frequency/__init__.pyi deleted file mode 100644 index 6b53c39a98b..00000000000 --- a/mne/time_frequency/__init__.pyi +++ /dev/null @@ -1,85 +0,0 @@ -__all__ = [ - "AverageTFR", - "AverageTFRArray", - "BaseTFR", - "CrossSpectralDensity", - "EpochsSpectrum", - "EpochsSpectrumArray", - "EpochsTFR", - "EpochsTFRArray", - "RawTFR", - "RawTFRArray", - "Spectrum", - "SpectrumArray", - "combine_spectrum", - "combine_tfr", - "csd_array_fourier", - "csd_array_morlet", - "csd_array_multitaper", - "csd_fourier", - "csd_morlet", - "csd_multitaper", - "csd_tfr", - "dpss_windows", - "fit_iir_model_raw", - "fwhm", - "istft", - "morlet", - "pick_channels_csd", - "psd_array_multitaper", - "psd_array_welch", - "read_csd", - "read_spectrum", - "read_tfrs", - "stft", - "stftfreq", - "tfr_array_morlet", - "tfr_array_multitaper", - "tfr_array_stockwell", - "tfr_morlet", - "tfr_multitaper", - "tfr_stockwell", - "write_tfrs", -] -from ._stft import istft, stft, stftfreq -from ._stockwell import tfr_array_stockwell, tfr_stockwell -from .ar import fit_iir_model_raw -from .csd import ( - CrossSpectralDensity, - csd_array_fourier, - csd_array_morlet, - csd_array_multitaper, - csd_fourier, - csd_morlet, - csd_multitaper, - csd_tfr, - pick_channels_csd, - read_csd, -) -from .multitaper import dpss_windows, psd_array_multitaper, tfr_array_multitaper -from .psd import psd_array_welch -from .spectrum import ( - EpochsSpectrum, - EpochsSpectrumArray, - Spectrum, - SpectrumArray, - combine_spectrum, - read_spectrum, -) -from .tfr import ( - AverageTFR, - AverageTFRArray, - BaseTFR, - EpochsTFR, - EpochsTFRArray, - RawTFR, - RawTFRArray, - combine_tfr, - fwhm, - morlet, - read_tfrs, - tfr_array_morlet, - tfr_morlet, - tfr_multitaper, - write_tfrs, -) diff --git a/mne/time_frequency/_stft.py b/mne/time_frequency/_stft.py index a6b6f23fff7..15bbfc588bc 100644 --- a/mne/time_frequency/_stft.py +++ b/mne/time_frequency/_stft.py @@ -5,7 +5,6 @@ from math import ceil import numpy as np -from scipy.fft import irfft, rfft, rfftfreq from ..utils import logger, verbose @@ -39,6 +38,8 @@ def stft(x, wsize, tstep=None, verbose=None): istft stftfreq """ + from scipy.fft import rfft + if not np.isrealobj(x): raise ValueError("x is not a real valued array") @@ -122,6 +123,8 @@ def istft(X, tstep=None, Tx=None): stft """ # Errors and warnings + from scipy.fft import irfft + X = np.asarray(X) if X.ndim < 2: raise ValueError(f"X must have ndim >= 2, got {X.ndim}") @@ -203,6 +206,8 @@ def stftfreq(wsize, sfreq=None): # noqa: D401 stft istft """ + from scipy.fft import rfftfreq + freqs = rfftfreq(wsize) if sfreq is not None: freqs *= float(sfreq) diff --git a/mne/time_frequency/_stockwell.py b/mne/time_frequency/_stockwell.py index 0f20aa30bdf..ce2da7c1cf6 100644 --- a/mne/time_frequency/_stockwell.py +++ b/mne/time_frequency/_stockwell.py @@ -5,7 +5,6 @@ from copy import deepcopy import numpy as np -from scipy.fft import fft, fftfreq, ifft from .._fiff.pick import _pick_data_channels, pick_info from ..parallel import parallel_func @@ -43,6 +42,8 @@ def _is_power_of_two(n): def _precompute_st_windows(n_samp, start_f, stop_f, sfreq, width): """Precompute stockwell Gaussian windows (in the freq domain).""" + from scipy.fft import fft, fftfreq + tw = fftfreq(n_samp, 1.0 / sfreq) / n_samp tw = np.r_[tw[:1], tw[1:][::-1]] @@ -78,6 +79,8 @@ def _st(x, start_f, windows): def _st_power_itc(x, start_f, compute_itc, zero_pad, decim, W): """Aux function.""" + from scipy.fft import fft, ifft + decim = _ensure_slice(decim) n_samp = x.shape[-1] decim_indices = decim.indices(n_samp - zero_pad) diff --git a/mne/time_frequency/ar.py b/mne/time_frequency/ar.py index 82b863a0807..499c3596eb7 100644 --- a/mne/time_frequency/ar.py +++ b/mne/time_frequency/ar.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg from .._fiff.pick import _picks_by_type, _picks_to_idx, pick_info from ..defaults import _handle_default @@ -15,6 +14,8 @@ def _yule_walker(X, order=1): Operates in-place. """ + from scipy import linalg + assert X.ndim == 2 denom = X.shape[-1] - np.arange(order + 1) r = np.zeros(order + 1, np.float64) diff --git a/mne/time_frequency/csd.py b/mne/time_frequency/csd.py index 4ddaa0ac6a3..71c9b962689 100644 --- a/mne/time_frequency/csd.py +++ b/mne/time_frequency/csd.py @@ -6,7 +6,6 @@ import numbers import numpy as np -from scipy.fft import rfftfreq from .._fiff.pick import _picks_to_idx, pick_channels from ..parallel import parallel_func @@ -792,6 +791,8 @@ def csd_array_fourier( csd_morlet csd_multitaper """ + from scipy.fft import rfftfreq + X, times, tmin, tmax, fmin, fmax = _prepare_csd_array( X, sfreq, t0, tmin, tmax, fmin, fmax ) @@ -991,6 +992,8 @@ def csd_array_multitaper( csd_morlet csd_multitaper """ + from scipy.fft import rfftfreq + X, times, tmin, tmax, fmin, fmax = _prepare_csd_array( X, sfreq, t0, tmin, tmax, fmin, fmax ) diff --git a/mne/time_frequency/multitaper.py b/mne/time_frequency/multitaper.py index 1c1a3baf238..c8c6241e121 100644 --- a/mne/time_frequency/multitaper.py +++ b/mne/time_frequency/multitaper.py @@ -5,10 +5,6 @@ # Parts of this code were copied from NiTime http://nipy.sourceforge.net/nitime import numpy as np -from scipy.fft import rfft, rfftfreq -from scipy.integrate import trapezoid -from scipy.signal import get_window -from scipy.signal.windows import dpss as sp_dpss from ..parallel import parallel_func from ..utils import _check_option, logger, verbose, warn @@ -63,6 +59,8 @@ def dpss_windows(N, half_nbw, Kmax, *, sym=True, norm=None, low_bias=True): ---------- .. footbibliography:: """ + from scipy.signal.windows import dpss as sp_dpss + # TODO VERSION can be removed with SciPy 1.16 is min, # workaround for https://github.com/scipy/scipy/pull/22344 if N <= 1: @@ -112,6 +110,8 @@ def _psd_from_mt_adaptive(x_mt, eigvals, freq_mask, max_iter=250, return_weights The weights to use for making the multitaper estimate, such that :math:`S_{mt} = \sum_{k} |w_k|^2S_k^{mt} / \sum_{k} |w_k|^2` """ + from scipy.integrate import trapezoid + n_signals, n_tapers, n_freqs = x_mt.shape if len(eigvals) != n_tapers: @@ -263,6 +263,8 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None, remove_dc=True): freqs : array, shape=(n_freqs,) The frequency points in Hz of the spectra """ + from scipy.fft import rfft, rfftfreq + if n_fft is None: n_fft = x.shape[-1] @@ -289,6 +291,8 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None, remove_dc=True): @verbose def _compute_mt_params(n_times, sfreq, bandwidth, low_bias, adaptive, verbose=None): """Triage windowing and multitaper parameters.""" + from scipy.signal import get_window + # Compute standardized half-bandwidth if isinstance(bandwidth, str): logger.info(f' Using standard spectrum estimation with "{bandwidth}" window') @@ -403,6 +407,8 @@ def psd_array_multitaper( ---------- .. footbibliography:: """ + from scipy.fft import rfftfreq + _check_option("normalization", normalization, ["length", "full"]) # Reshape data so its 2-D for parallelization diff --git a/mne/time_frequency/psd.py b/mne/time_frequency/psd.py index 19cced38305..30db23f5021 100644 --- a/mne/time_frequency/psd.py +++ b/mne/time_frequency/psd.py @@ -6,7 +6,6 @@ from functools import partial import numpy as np -from scipy.signal import spectrogram from ..parallel import parallel_func from ..utils import _check_option, _ensure_int, logger, verbose @@ -206,6 +205,8 @@ def psd_array_welch( f"{n_overlap} overlap and {window} window" ) + from scipy.signal import spectrogram + parallel, my_spect_func, n_jobs = parallel_func(_spect_func, n_jobs=n_jobs) _func = partial( spectrogram, diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 0c8bb0f4fb0..f8b02fd1982 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -13,8 +13,6 @@ import matplotlib.pyplot as plt import numpy as np -from scipy.fft import fft, ifft -from scipy.signal import argrelmax from .._fiff.meas_info import ContainsMixin, Info from .._fiff.pick import _picks_to_idx, pick_info @@ -369,6 +367,8 @@ def _cwt_gen(X, Ws, *, fsize=0, mode="same", decim=1, use_fft=True): out : array, shape (n_signals, n_freqs, n_time_decim) The time-frequency transform of the signals. """ + from scipy.fft import fft, ifft + _check_option("mode", mode, ["same", "valid", "full"]) decim = _ensure_slice(decim) X = np.asarray(X) @@ -4200,6 +4200,8 @@ def _get_timefreqs(tfr, timefreqs): # If None, automatic identification of max peak else: + from scipy.signal import argrelmax + order = max((1, tfr.data.shape[2] // 30)) peaks_idx = argrelmax(tfr.data, order=order, axis=2) if peaks_idx[0].size == 0: diff --git a/mne/transforms.py b/mne/transforms.py index b1d0d4d4668..36c992c8028 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -10,8 +10,6 @@ from pathlib import Path import numpy as np -from scipy import linalg -from scipy.spatial.distance import cdist from ._fiff.constants import FIFF from ._fiff.open import fiff_open @@ -987,6 +985,9 @@ class _TPSWarp: """ def fit(self, source, destination, reg=1e-3): + from scipy import linalg + from scipy.spatial.distance import cdist + assert source.shape[1] == destination.shape[1] == 3 assert source.shape[0] == destination.shape[0] # Forward warping, different from image warping, use |dist|**2 @@ -1017,6 +1018,8 @@ def transform(self, pts, verbose=None): dest : shape (n_transform, 3) The transformed points. """ + from scipy.spatial.distance import cdist + logger.info(f"Transforming {len(pts)} points") assert pts.shape[1] == 3 # for memory reasons, we should do this in ~100 MB chunks @@ -1125,6 +1128,8 @@ def fit( inst : instance of SphericalSurfaceWarp The warping object (for chaining). """ + from scipy import linalg + from .bem import _fit_sphere from .source_space._source_space import _check_spacing @@ -1500,6 +1505,8 @@ def _fit_matched_points(p, x, weights=None, scale=False): def _average_quats(quats, weights=None): """Average unit quaternions properly.""" + from scipy import linalg + assert quats.ndim == 2 and quats.shape[1] in (3, 4) if weights is None: weights = np.ones(quats.shape[0]) diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index dbfedccd82b..4c1242912cf 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -2,6 +2,218 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy +# # # WARNING # # # +# This list must also be updated in doc/_templates/autosummary/class.rst if it +# is changed here! +_doc_special_members = ( + "__contains__", + "__getitem__", + "__iter__", + "__len__", + "__add__", + "__sub__", + "__mul__", + "__div__", + "__neg__", +) -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from ._bunch import Bunch, BunchConst, BunchConstNamed +from .check import ( + check_fname, + check_version, + check_random_state, + _check_fname, + _check_subject, + _check_pandas_installed, + _check_pandas_index_arguments, + _check_event_id, + _check_ch_locs, + _check_compensation_grade, + _check_if_nan, + _is_numeric, + _ensure_int, + _check_integer_or_list, + _check_preload, + _validate_type, + _check_range, + _check_info_inv, + _check_channels_spatial_filter, + _check_one_ch_type, + _check_rank, + _check_option, + _check_depth, + _check_combine, + _path_like, + _check_src_normal, + _check_stc_units, + _check_qt_version, + _check_sphere, + _check_time_format, + _check_freesurfer_home, + _suggest, + _require_version, + _on_missing, + _check_on_missing, + int_like, + _safe_input, + _check_all_same_channel_names, + path_like, + _ensure_events, + _check_eeglabio_installed, + _check_pybv_installed, + _check_edfio_installed, + _to_rgb, + _soft_import, + _check_dict_keys, + _check_pymatreader_installed, + _import_h5py, + _import_h5io_funcs, + _import_nibabel, + _import_pymatreader_funcs, + _check_head_radius, + _check_method_kwargs, +) +from .config import ( + set_config, + get_config, + get_config_path, + set_cache_dir, + set_memmap_min_size, + get_subjects_dir, + _get_stim_channel, + sys_info, + _get_extra_data_path, + _get_root_dir, + _get_numpy_libs, +) +from .docs import ( + copy_function_doc_to_method_doc, + copy_doc, + linkcode_resolve, + open_docs, + deprecated, + fill_doc, + deprecated_alias, + legacy, + docdict as _docdict, +) +from .fetching import _url_to_local_path +from ._logging import ( + verbose, + logger, + set_log_level, + set_log_file, + use_log_level, + catch_logging, + warn, + filter_out_warnings, + wrapped_stdout, + _get_call_line, + _record_warnings, + ClosingStringIO, + _verbose_safe_false, + _parse_verbose, +) +from .misc import ( + _auto_weakref, + run_subprocess, + _pl, + _clean_names, + pformat, + _empty_hash, + _file_like, + _explain_exception, + _get_argvalues, + sizeof_fmt, + running_subprocess, + _DefaultEventParser, + _assert_no_instances, + _resource_path, + repr_html, +) +from .progressbar import ProgressBar +from ._testing import ( + run_command_if_main, + requires_mne, + requires_good_network, + ArgvSetter, + has_freesurfer, + has_mne_c, + _TempDir, + buggy_mkl_svd, + requires_freesurfer, + requires_mne_mark, + assert_object_equal, + assert_and_remove_boundary_annot, + _raw_annot, + assert_dig_allclose, + assert_meg_snr, + assert_snr, + assert_stcs_equal, + _click_ch_name, + requires_openmeeg_mark, +) +from .numerics import ( + hashfunc, + _compute_row_norms, + _reg_pinv, + random_permutation, + _reject_data_segments, + compute_corr, + _get_inst_data, + array_split_idx, + sum_squared, + split_list, + _gen_events, + create_slices, + _time_mask, + _freq_mask, + grand_average, + object_diff, + object_hash, + object_size, + _apply_scaling_cov, + _undo_scaling_cov, + _apply_scaling_array, + _undo_scaling_array, + _scaled_array, + _replace_md5, + _PCA, + _mask_to_onsets_offsets, + _array_equal_nan, + _dt_to_stamp, + _stamp_to_dt, + _check_dt, + _ReuseCycle, + _arange_div, + _hashable_ndarray, + _custom_lru_cache, + _array_repr, + _date_to_julian, + _julian_to_date, +) +from .mixin import ( + SizeMixin, + GetEpochsMixin, + TimeMixin, + ExtendedTimeMixin, + _prepare_read_metadata, + _prepare_write_metadata, + _check_decim, +) +from .linalg import ( + _svd_lwork, + _repeated_svd, + _sym_mat_pow, + sqrtm_sym, + eigh, + _get_blas_funcs, + pinv, + pinvh, +) +from .dataframe import ( + _set_pandas_dtype, + _scale_dataframe_data, + _convert_times, + _build_data_frame, +) diff --git a/mne/utils/__init__.pyi b/mne/utils/__init__.pyi deleted file mode 100644 index 46d272e972d..00000000000 --- a/mne/utils/__init__.pyi +++ /dev/null @@ -1,384 +0,0 @@ -__all__ = [ - "ArgvSetter", - "Bunch", - "BunchConst", - "BunchConstNamed", - "ClosingStringIO", - "ExtendedTimeMixin", - "GetEpochsMixin", - "ProgressBar", - "SizeMixin", - "TimeMixin", - "_DefaultEventParser", - "_PCA", - "_ReuseCycle", - "_TempDir", - "_apply_scaling_array", - "_apply_scaling_cov", - "_arange_div", - "_array_equal_nan", - "_array_repr", - "_assert_no_instances", - "_auto_weakref", - "_build_data_frame", - "_check_all_same_channel_names", - "_check_ch_locs", - "_check_channels_spatial_filter", - "_check_combine", - "_check_compensation_grade", - "_check_decim", - "_check_depth", - "_check_dict_keys", - "_check_dt", - "_check_edfio_installed", - "_check_eeglabio_installed", - "_check_event_id", - "_check_fname", - "_check_freesurfer_home", - "_check_head_radius", - "_check_if_nan", - "_check_info_inv", - "_check_integer_or_list", - "_check_method_kwargs", - "_check_on_missing", - "_check_one_ch_type", - "_check_option", - "_check_pandas_index_arguments", - "_check_pandas_installed", - "_check_preload", - "_check_pybv_installed", - "_check_pymatreader_installed", - "_check_qt_version", - "_check_range", - "_check_rank", - "_check_sphere", - "_check_src_normal", - "_check_stc_units", - "_check_subject", - "_check_time_format", - "_clean_names", - "_click_ch_name", - "_compute_row_norms", - "_convert_times", - "_custom_lru_cache", - "_doc_special_members", - "_date_to_julian", - "_dt_to_stamp", - "_empty_hash", - "_ensure_events", - "_ensure_int", - "_explain_exception", - "_file_like", - "_freq_mask", - "_gen_events", - "_get_argvalues", - "_get_blas_funcs", - "_get_call_line", - "_get_extra_data_path", - "_get_inst_data", - "_get_numpy_libs", - "_get_root_dir", - "_get_stim_channel", - "_hashable_ndarray", - "_import_h5io_funcs", - "_import_h5py", - "_import_nibabel", - "_import_pymatreader_funcs", - "_is_numeric", - "_julian_to_date", - "_mask_to_onsets_offsets", - "_on_missing", - "_parse_verbose", - "_path_like", - "_pl", - "_prepare_read_metadata", - "_prepare_write_metadata", - "_raw_annot", - "_record_warnings", - "_reg_pinv", - "_reject_data_segments", - "_repeated_svd", - "_replace_md5", - "_require_version", - "_resource_path", - "_safe_input", - "_scale_dataframe_data", - "_scaled_array", - "_set_pandas_dtype", - "_soft_import", - "_stamp_to_dt", - "_suggest", - "_svd_lwork", - "_sym_mat_pow", - "_time_mask", - "_to_rgb", - "_undo_scaling_array", - "_undo_scaling_cov", - "_url_to_local_path", - "_validate_type", - "_verbose_safe_false", - "array_split_idx", - "assert_and_remove_boundary_annot", - "assert_dig_allclose", - "assert_meg_snr", - "assert_object_equal", - "assert_snr", - "assert_stcs_equal", - "buggy_mkl_svd", - "catch_logging", - "check_fname", - "check_random_state", - "check_version", - "compute_corr", - "copy_doc", - "copy_function_doc_to_method_doc", - "create_slices", - "deprecated", - "deprecated_alias", - "eigh", - "fill_doc", - "filter_out_warnings", - "get_config", - "get_config_path", - "get_subjects_dir", - "grand_average", - "has_freesurfer", - "has_mne_c", - "hashfunc", - "int_like", - "legacy", - "linkcode_resolve", - "logger", - "object_diff", - "object_hash", - "object_size", - "open_docs", - "path_like", - "pformat", - "pinv", - "pinvh", - "random_permutation", - "repr_html", - "requires_freesurfer", - "requires_good_network", - "requires_mne", - "requires_mne_mark", - "requires_openmeeg_mark", - "run_command_if_main", - "run_subprocess", - "running_subprocess", - "set_cache_dir", - "set_config", - "set_log_file", - "set_log_level", - "set_memmap_min_size", - "sizeof_fmt", - "split_list", - "sqrtm_sym", - "sum_squared", - "sys_info", - "use_log_level", - "verbose", - "warn", - "wrapped_stdout", -] -from ._bunch import Bunch, BunchConst, BunchConstNamed -from ._logging import ( - ClosingStringIO, - _get_call_line, - _parse_verbose, - _record_warnings, - _verbose_safe_false, - catch_logging, - filter_out_warnings, - logger, - set_log_file, - set_log_level, - use_log_level, - verbose, - warn, - wrapped_stdout, -) -from ._testing import ( - ArgvSetter, - _click_ch_name, - _raw_annot, - _TempDir, - assert_and_remove_boundary_annot, - assert_dig_allclose, - assert_meg_snr, - assert_object_equal, - assert_snr, - assert_stcs_equal, - buggy_mkl_svd, - has_freesurfer, - has_mne_c, - requires_freesurfer, - requires_good_network, - requires_mne, - requires_mne_mark, - requires_openmeeg_mark, - run_command_if_main, -) -from .check import ( - _check_all_same_channel_names, - _check_ch_locs, - _check_channels_spatial_filter, - _check_combine, - _check_compensation_grade, - _check_depth, - _check_dict_keys, - _check_edfio_installed, - _check_eeglabio_installed, - _check_event_id, - _check_fname, - _check_freesurfer_home, - _check_head_radius, - _check_if_nan, - _check_info_inv, - _check_integer_or_list, - _check_method_kwargs, - _check_on_missing, - _check_one_ch_type, - _check_option, - _check_pandas_index_arguments, - _check_pandas_installed, - _check_preload, - _check_pybv_installed, - _check_pymatreader_installed, - _check_qt_version, - _check_range, - _check_rank, - _check_sphere, - _check_src_normal, - _check_stc_units, - _check_subject, - _check_time_format, - _ensure_events, - _ensure_int, - _import_h5io_funcs, - _import_h5py, - _import_nibabel, - _import_pymatreader_funcs, - _is_numeric, - _on_missing, - _path_like, - _require_version, - _safe_input, - _soft_import, - _suggest, - _to_rgb, - _validate_type, - check_fname, - check_random_state, - check_version, - int_like, - path_like, -) -from .config import ( - _get_extra_data_path, - _get_numpy_libs, - _get_root_dir, - _get_stim_channel, - get_config, - get_config_path, - get_subjects_dir, - set_cache_dir, - set_config, - set_memmap_min_size, - sys_info, -) -from .dataframe import ( - _build_data_frame, - _convert_times, - _scale_dataframe_data, - _set_pandas_dtype, -) -from .docs import ( - _doc_special_members, - copy_doc, - copy_function_doc_to_method_doc, - deprecated, - deprecated_alias, - fill_doc, - legacy, - linkcode_resolve, - open_docs, -) -from .fetching import _url_to_local_path -from .linalg import ( - _get_blas_funcs, - _repeated_svd, - _svd_lwork, - _sym_mat_pow, - eigh, - pinv, - pinvh, - sqrtm_sym, -) -from .misc import ( - _assert_no_instances, - _auto_weakref, - _clean_names, - _DefaultEventParser, - _empty_hash, - _explain_exception, - _file_like, - _get_argvalues, - _pl, - _resource_path, - pformat, - repr_html, - run_subprocess, - running_subprocess, - sizeof_fmt, -) -from .mixin import ( - ExtendedTimeMixin, - GetEpochsMixin, - SizeMixin, - TimeMixin, - _check_decim, - _prepare_read_metadata, - _prepare_write_metadata, -) -from .numerics import ( - _PCA, - _apply_scaling_array, - _apply_scaling_cov, - _arange_div, - _array_equal_nan, - _array_repr, - _check_dt, - _compute_row_norms, - _custom_lru_cache, - _date_to_julian, - _dt_to_stamp, - _freq_mask, - _gen_events, - _get_inst_data, - _hashable_ndarray, - _julian_to_date, - _mask_to_onsets_offsets, - _reg_pinv, - _reject_data_segments, - _replace_md5, - _ReuseCycle, - _scaled_array, - _stamp_to_dt, - _time_mask, - _undo_scaling_array, - _undo_scaling_cov, - array_split_idx, - compute_corr, - create_slices, - grand_average, - hashfunc, - object_diff, - object_hash, - object_size, - random_permutation, - split_list, - sum_squared, -) -from .progressbar import ProgressBar diff --git a/mne/utils/_testing.py b/mne/utils/_testing.py index 63e0d1036b9..386390a5021 100644 --- a/mne/utils/_testing.py +++ b/mne/utils/_testing.py @@ -15,7 +15,6 @@ import numpy as np from numpy.testing import assert_allclose, assert_array_equal -from scipy import linalg from ._logging import ClosingStringIO, warn from .check import check_version @@ -273,6 +272,8 @@ def assert_meg_snr( def assert_snr(actual, desired, tol): """Assert actual and desired arrays are within some SNR tolerance.""" + from scipy import linalg + with np.errstate(divide="ignore"): # allow infinite snr = linalg.norm(desired, ord="fro") / linalg.norm(desired - actual, ord="fro") assert snr >= tol, f"{snr} < {tol}" diff --git a/mne/utils/config.py b/mne/utils/config.py index c28373fcb93..3d0772b8136 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -790,7 +790,6 @@ def sys_info( "# Infrastructure", "decorator", "jinja2", - # "lazy-loader", "packaging", "pooch", "tqdm", diff --git a/mne/utils/linalg.py b/mne/utils/linalg.py index 9382aad50f2..82a3db107db 100644 --- a/mne/utils/linalg.py +++ b/mne/utils/linalg.py @@ -25,8 +25,6 @@ import functools import numpy as np -from scipy import linalg -from scipy._lib._util import _asarray_validated from ..fixes import _safe_svd @@ -36,11 +34,15 @@ @functools.lru_cache(None) def _get_blas_funcs(dtype, names): + from scipy import linalg + return linalg.get_blas_funcs(names, (np.empty(0, dtype),)) @functools.lru_cache(None) def _get_lapack_funcs(dtype, names): + from scipy import linalg + assert dtype in (np.float64, np.complex128) x = np.empty(0, dtype) return linalg.get_lapack_funcs(names, (x,)) @@ -52,6 +54,8 @@ def _get_lapack_funcs(dtype, names): def _svd_lwork(shape, dtype=np.float64): """Set up SVD calculations on identical-shape float64/complex128 arrays.""" + from scipy import linalg + try: ds = linalg._decomp_svd except AttributeError: # < 1.8.0 @@ -95,6 +99,8 @@ def _repeated_svd(x, lwork, overwrite_a=False): @functools.lru_cache(None) def _get_evd(dtype): + from scipy import linalg + x = np.empty(0, dtype) if dtype == np.float64: driver = "syevd" @@ -126,6 +132,9 @@ def eigh(a, overwrite_a=False, check_finite=True): The normalized eigenvector corresponding to the eigenvalue ``w[i]`` is the column ``v[:, i]``. """ + from scipy._lib._util import _asarray_validated + from scipy.linalg import LinAlgError + # We use SYEVD, see https://github.com/scipy/scipy/issues/9212 if check_finite: a = _asarray_validated(a, check_finite=check_finite) @@ -136,7 +145,7 @@ def eigh(a, overwrite_a=False, check_finite=True): if info < 0: raise ValueError(f"illegal value in argument {-info} of internal {driver}") else: - raise linalg.LinAlgError( + raise LinAlgError( "internal fortran routine failed to converge: " f"{info} off-diagonal elements of an " "intermediate tridiagonal form did not converge" diff --git a/mne/utils/numerics.py b/mne/utils/numerics.py index 11ba0ecb487..2e52bc33de9 100644 --- a/mne/utils/numerics.py +++ b/mne/utils/numerics.py @@ -17,7 +17,6 @@ from pathlib import Path import numpy as np -from scipy import sparse from ..fixes import ( _infer_dimension_, @@ -638,6 +637,8 @@ def object_hash(x, h=None): digest : int The digest resulting from the hash. """ + from scipy import sparse + if h is None: h = _empty_hash() if hasattr(x, "keys"): @@ -730,6 +731,8 @@ def object_size(x, memo=None): def _is_sparse_cs(x): + from scipy import sparse + return isinstance( x, sparse.csr_matrix | sparse.csc_matrix | sparse.csr_array | sparse.csc_array ) @@ -775,6 +778,8 @@ def object_diff(a, b, pre="", *, allclose=False): diffs : str A string representation of the differences. """ + from scipy import sparse + pd = _check_pandas_installed(strict=False) out = "" if type(a) is not type(b): diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 4a893b7c017..20eb6bab587 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -17,9 +17,6 @@ from pathlib import Path import numpy as np -from scipy.spatial import ConvexHull, Delaunay -from scipy.spatial.distance import cdist -from scipy.stats import rankdata from .._fiff.constants import FIFF from .._fiff.meas_info import Info, create_info, read_fiducials @@ -280,6 +277,8 @@ def plot_head_positions( # knowing it will generally be spherical, we can approximate # how far away we are along the axis line by taking the # point to the left and right with the smallest distance + from scipy.spatial.distance import cdist + dists = cdist(rrs[:, oidx], use_trans[:, oidx]) left = rrs[:, [ii]] < use_trans[:, ii] left_dists_all = dists.copy() @@ -1739,6 +1738,8 @@ def _make_tris_fan(n_vert): def _sensor_shape(coil): """Get the sensor shape vertices.""" + from scipy.spatial import ConvexHull, Delaunay + try: from scipy.spatial import QhullError except ImportError: # scipy < 1.8 @@ -2134,6 +2135,7 @@ def _plot_mpl_stc( import nibabel as nib from matplotlib.widgets import Slider from mpl_toolkits.mplot3d import Axes3D + from scipy.stats import rankdata from ..morph import _get_subject_sphere_tris from ..source_space._source_space import _check_spacing, _create_surf_spacing diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index ae043dc12ab..24b2b8da4d2 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -3,6 +3,93 @@ # Copyright the MNE-Python contributors. """Visualization routines.""" -import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +from .topomap import ( + plot_evoked_topomap, + plot_projs_topomap, + plot_arrowmap, + plot_ica_components, + plot_tfr_topomap, + plot_topomap, + plot_epochs_psd_topomap, + plot_layout, + plot_bridged_electrodes, + plot_ch_adjacency, + plot_regression_weights, +) +from .topo import plot_topo_image_epochs, iter_topography +from .utils import ( + mne_analyze_colormap, + compare_fiff, + ClickableImage, + add_background_image, + plot_sensors, + centers_to_edges, + concatenate_images, + _get_plot_ch_type, +) +from ._3d import ( + plot_sparse_source_estimates, + plot_source_estimates, + plot_vector_source_estimates, + plot_evoked_field, + plot_dipole_locations, + snapshot_brain_montage, + plot_head_positions, + plot_alignment, + plot_brain_colorbar, + plot_volume_source_estimates, + link_brains, + set_3d_options, +) +from .misc import ( + plot_cov, + plot_csd, + plot_bem, + plot_events, + plot_source_spectrogram, + _get_presser, + plot_dipole_amplitudes, + plot_ideal_filter, + plot_filter, + adjust_axes, + plot_chpi_snr, +) +from .evoked import ( + plot_evoked, + plot_evoked_image, + plot_evoked_white, + plot_snr_estimate, + plot_evoked_topo, + plot_evoked_joint, + plot_compare_evokeds, +) +from .circle import circular_layout, plot_channel_labels_circle +from .epochs import plot_drop_log, plot_epochs, plot_epochs_psd, plot_epochs_image +from .raw import plot_raw, plot_raw_psd, plot_raw_psd_topo, _RAW_CLIP_DEF +from .ica import ( + plot_ica_scores, + plot_ica_sources, + plot_ica_overlay, + _plot_sources, + plot_ica_properties, +) +from .montage import plot_montage +from .backends._abstract import Figure3D +from .backends.renderer import ( + set_3d_backend, + get_3d_backend, + use_3d_backend, + set_3d_view, + set_3d_title, + create_3d_figure, + close_3d_figure, + close_all_3d_figures, + get_brain_class, +) + +from ._brain import Brain +from ._figure import get_browser_backend, set_browser_backend, use_browser_backend +from ._proj import plot_projs_joint + +from .evoked_field import EvokedField diff --git a/mne/viz/__init__.pyi b/mne/viz/__init__.pyi deleted file mode 100644 index c58ad7d0e54..00000000000 --- a/mne/viz/__init__.pyi +++ /dev/null @@ -1,177 +0,0 @@ -__all__ = [ - "Brain", - "ClickableImage", - "EvokedField", - "Figure3D", - "_RAW_CLIP_DEF", - "_get_plot_ch_type", - "_get_presser", - "_plot_sources", - "_scraper", - "add_background_image", - "adjust_axes", - "backends", - "centers_to_edges", - "circular_layout", - "close_3d_figure", - "close_all_3d_figures", - "compare_fiff", - "concatenate_images", - "create_3d_figure", - "eyetracking", - "get_3d_backend", - "get_brain_class", - "get_browser_backend", - "iter_topography", - "link_brains", - "mne_analyze_colormap", - "plot_alignment", - "plot_arrowmap", - "plot_bem", - "plot_brain_colorbar", - "plot_bridged_electrodes", - "plot_ch_adjacency", - "plot_channel_labels_circle", - "plot_chpi_snr", - "plot_compare_evokeds", - "plot_cov", - "plot_csd", - "plot_dipole_amplitudes", - "plot_dipole_locations", - "plot_drop_log", - "plot_epochs", - "plot_epochs_image", - "plot_epochs_psd", - "plot_epochs_psd_topomap", - "plot_events", - "plot_evoked", - "plot_evoked_field", - "plot_evoked_image", - "plot_evoked_joint", - "plot_evoked_topo", - "plot_evoked_topomap", - "plot_evoked_white", - "plot_filter", - "plot_head_positions", - "plot_ica_components", - "plot_ica_overlay", - "plot_ica_properties", - "plot_ica_scores", - "plot_ica_sources", - "plot_ideal_filter", - "plot_layout", - "plot_montage", - "plot_projs_joint", - "plot_projs_topomap", - "plot_raw", - "plot_raw_psd", - "plot_raw_psd_topo", - "plot_regression_weights", - "plot_sensors", - "plot_snr_estimate", - "plot_source_estimates", - "plot_source_spectrogram", - "plot_sparse_source_estimates", - "plot_tfr_topomap", - "plot_topo_image_epochs", - "plot_topomap", - "plot_vector_source_estimates", - "plot_volume_source_estimates", - "set_3d_backend", - "set_3d_options", - "set_3d_title", - "set_3d_view", - "set_browser_backend", - "snapshot_brain_montage", - "ui_events", - "use_3d_backend", - "use_browser_backend", -] -from . import _scraper, backends, eyetracking, ui_events -from ._3d import ( - link_brains, - plot_alignment, - plot_brain_colorbar, - plot_dipole_locations, - plot_evoked_field, - plot_head_positions, - plot_source_estimates, - plot_sparse_source_estimates, - plot_vector_source_estimates, - plot_volume_source_estimates, - set_3d_options, - snapshot_brain_montage, -) -from ._brain import Brain -from ._figure import get_browser_backend, set_browser_backend, use_browser_backend -from ._proj import plot_projs_joint -from .backends._abstract import Figure3D -from .backends.renderer import ( - close_3d_figure, - close_all_3d_figures, - create_3d_figure, - get_3d_backend, - get_brain_class, - set_3d_backend, - set_3d_title, - set_3d_view, - use_3d_backend, -) -from .circle import circular_layout, plot_channel_labels_circle -from .epochs import plot_drop_log, plot_epochs, plot_epochs_image, plot_epochs_psd -from .evoked import ( - plot_compare_evokeds, - plot_evoked, - plot_evoked_image, - plot_evoked_joint, - plot_evoked_topo, - plot_evoked_white, - plot_snr_estimate, -) -from .evoked_field import EvokedField -from .ica import ( - _plot_sources, - plot_ica_overlay, - plot_ica_properties, - plot_ica_scores, - plot_ica_sources, -) -from .misc import ( - _get_presser, - adjust_axes, - plot_bem, - plot_chpi_snr, - plot_cov, - plot_csd, - plot_dipole_amplitudes, - plot_events, - plot_filter, - plot_ideal_filter, - plot_source_spectrogram, -) -from .montage import plot_montage -from .raw import _RAW_CLIP_DEF, plot_raw, plot_raw_psd, plot_raw_psd_topo -from .topo import iter_topography, plot_topo_image_epochs -from .topomap import ( - plot_arrowmap, - plot_bridged_electrodes, - plot_ch_adjacency, - plot_epochs_psd_topomap, - plot_evoked_topomap, - plot_ica_components, - plot_layout, - plot_projs_topomap, - plot_regression_weights, - plot_tfr_topomap, - plot_topomap, -) -from .utils import ( - ClickableImage, - _get_plot_ch_type, - add_background_image, - centers_to_edges, - compare_fiff, - concatenate_images, - mne_analyze_colormap, - plot_sensors, -) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 778700c99a7..65b873fee6e 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -12,9 +12,6 @@ from io import BytesIO import numpy as np -from scipy.interpolate import interp1d -from scipy.sparse import csr_array -from scipy.spatial.distance import cdist from ..._fiff.meas_info import Info from ..._fiff.pick import pick_types @@ -1066,6 +1063,8 @@ def _configure_vertex_time_course(self): publish(self, VertexSelect(hemi=hemi, vertex_id=vertex_id)) def _configure_picking(self): + from scipy.sparse import csr_array + # get data for each hemi for idx, hemi in enumerate(["vol", "lh", "rh"]): hemi_data = self._data.get(hemi) @@ -2639,6 +2638,8 @@ def add_foci( # Possibly map the foci coords through a surface if map_surface is not None: + from scipy.spatial.distance import cdist + foci_surf = _Surface( self._subject, hemi, @@ -4036,6 +4037,8 @@ def __hash__(self): def _safe_interp1d(x, y, kind="linear", axis=-1, assume_sorted=False): """Work around interp1d not liking singleton dimensions.""" + from scipy.interpolate import interp1d + if y.shape[axis] == 1: def func(x): diff --git a/mne/viz/_dipole.py b/mne/viz/_dipole.py index ffcc898becd..615596ef235 100644 --- a/mne/viz/_dipole.py +++ b/mne/viz/_dipole.py @@ -7,7 +7,6 @@ import os.path as op import numpy as np -from scipy.spatial import ConvexHull from .._freesurfer import _estimate_talxfm_rigid, _get_head_surface from ..surface import read_surface @@ -44,6 +43,7 @@ def _plot_dipole_mri_outlines( import matplotlib.pyplot as plt from matplotlib.collections import LineCollection, PatchCollection from matplotlib.patches import Circle + from scipy.spatial import ConvexHull extra = 'when mode is "outlines"' trans = _get_trans(trans, fro="head", to="mri")[0] diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index c691915f132..4ecc4bd414d 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -8,7 +8,6 @@ from copy import deepcopy import numpy as np -from scipy.ndimage import gaussian_filter1d from .._fiff.meas_info import create_info from .._fiff.pick import ( @@ -201,6 +200,8 @@ def plot_epochs_image( | | list of ch_names | callable | | +----------+----------------------------+------------+-------------------+ """ + from scipy.ndimage import gaussian_filter1d + from ..epochs import EpochsArray _validate_type(group_by, (dict, None), "group_by") diff --git a/mne/viz/evoked_field.py b/mne/viz/evoked_field.py index 839259ee117..678f28ae5f3 100644 --- a/mne/viz/evoked_field.py +++ b/mne/viz/evoked_field.py @@ -11,7 +11,6 @@ from functools import partial import numpy as np -from scipy.interpolate import interp1d from .._fiff.pick import pick_types from ..defaults import DEFAULTS @@ -252,6 +251,8 @@ def current_time_func(): def _prepare_surf_map(self, surf_map, color, alpha): """Compute all the data required to render a fieldlines map.""" + from scipy.interpolate import interp1d + if surf_map["kind"] == "eeg": pick = pick_types(self._evoked.info, meg=False, eeg=True) else: diff --git a/mne/viz/eyetracking/heatmap.py b/mne/viz/eyetracking/heatmap.py index 46aa34b7d31..8b1da0f1119 100644 --- a/mne/viz/eyetracking/heatmap.py +++ b/mne/viz/eyetracking/heatmap.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.ndimage import gaussian_filter from ..._fiff.constants import FIFF from ...utils import _validate_type, fill_doc, logger @@ -60,6 +59,8 @@ def plot_gaze( ----- .. versionadded:: 1.6 """ + from scipy.ndimage import gaussian_filter + from mne import BaseEpochs from mne._fiff.pick import _picks_to_idx diff --git a/mne/viz/ica.py b/mne/viz/ica.py index 59895fb52c3..8778b2b50bb 100644 --- a/mne/viz/ica.py +++ b/mne/viz/ica.py @@ -8,7 +8,6 @@ from functools import partial import numpy as np -from scipy.stats import gaussian_kde from .._fiff.meas_info import create_info from .._fiff.pick import _picks_to_idx, pick_types @@ -216,6 +215,7 @@ def _plot_ica_properties( ): """Plot ICA properties (helper).""" from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable + from scipy.stats import gaussian_kde topo_ax, image_ax, erp_ax, spec_ax, var_ax = axes diff --git a/mne/viz/misc.py b/mne/viz/misc.py index c83a4dfe717..e87007f8588 100644 --- a/mne/viz/misc.py +++ b/mne/viz/misc.py @@ -15,7 +15,6 @@ from pathlib import Path import numpy as np -from scipy.signal import filtfilt, freqz, group_delay, lfilter, sosfilt, sosfiltfilt from .._fiff.constants import FIFF from .._fiff.pick import ( @@ -1133,6 +1132,7 @@ def plot_filter( .. versionadded:: 0.14 """ import matplotlib.pyplot as plt + from scipy.signal import filtfilt, freqz, group_delay, lfilter, sosfilt, sosfiltfilt sfreq = float(sfreq) _check_option("fscale", fscale, ["log", "linear"]) diff --git a/mne/viz/montage.py b/mne/viz/montage.py index 221cc21f7a0..ddf9a3f9bd1 100644 --- a/mne/viz/montage.py +++ b/mne/viz/montage.py @@ -7,7 +7,6 @@ from copy import deepcopy import numpy as np -from scipy.spatial.distance import cdist from .._fiff._digitization import _get_fid_coords from .._fiff.meas_info import create_info @@ -55,6 +54,7 @@ def plot_montage( The figure object. """ import matplotlib.pyplot as plt + from scipy.spatial.distance import cdist from ..channels import DigMontage, make_dig_montage diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 5c43d4de48e..d88cd6a9ac5 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -8,7 +8,6 @@ from functools import partial import numpy as np -from scipy import ndimage from .._fiff.pick import _picks_to_idx, channel_type, pick_types from ..defaults import _handle_default @@ -776,6 +775,7 @@ def _erfimage_imshow( ): """Plot erfimage on sensor topography.""" import matplotlib.pyplot as plt + from scipy import ndimage this_data = data[:, ch_idx, :] if vlim_array is not None: @@ -832,6 +832,8 @@ def _erfimage_imshow_unified( vlim_array=None, ): """Plot erfimage topography using a single axis.""" + from scipy import ndimage + _compute_ax_scalings(bn, (tmin, tmax), (0, len(epochs.events))) ax = bn.ax data_lines = bn.data_lines diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index bb180a3f299..95330c2f00e 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -11,14 +11,6 @@ from numbers import Integral import numpy as np -from scipy.interpolate import ( - CloughTocher2DInterpolator, - LinearNDInterpolator, - NearestNDInterpolator, -) -from scipy.sparse import csr_array -from scipy.spatial import Delaunay, Voronoi -from scipy.spatial.distance import pdist, squareform from .._fiff.meas_info import Info, _simplify_info from .._fiff.pick import ( @@ -186,6 +178,8 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): + from scipy.spatial.distance import pdist, squareform + from ..channels.layout import _find_topomap_coords picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") @@ -684,6 +678,8 @@ def _draw_outlines(ax, outlines): def _get_extra_points(pos, extrapolate, origin, radii): """Get coordinates of additional interpolation points.""" + from scipy.spatial import Delaunay + radii = np.array(radii, float) assert radii.shape == (2,) x, y = origin @@ -820,6 +816,12 @@ class _GridData: """ def __init__(self, pos, image_interp, extrapolate, origin, radii, border): + from scipy.interpolate import ( + CloughTocher2DInterpolator, + LinearNDInterpolator, + NearestNDInterpolator, + ) + # in principle this works in N dimensions, not just 2 assert pos.ndim == 2 and pos.shape[1] == 2, pos.shape _validate_type(border, ("numeric", str), "border") @@ -1089,6 +1091,8 @@ def _setup_interp(pos, res, image_interp, extrapolate, outlines, border): def _voronoi_topomap(data, pos, outlines, ax, cmap, norm, extent, res): """Make a Voronoi diagram on a topomap.""" + from scipy.spatial import Voronoi + # we need an image axis object so first empty image to plot over im = ax.imshow( np.zeros((res, res)) * np.nan, @@ -3789,6 +3793,7 @@ def plot_ch_adjacency(info, adjacency, ch_names, kind="2d", edit=False): """ import matplotlib as mpl import matplotlib.pyplot as plt + from scipy.sparse import csr_array _validate_type(info, Info, "info") _validate_type(adjacency, (np.ndarray, csr_array), "adjacency") diff --git a/mne/viz/utils.py b/mne/viz/utils.py index b9b844b321a..e07ea6bc622 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -18,7 +18,6 @@ import numpy as np from decorator import decorator -from scipy.signal import argrelmax from .._fiff.constants import FIFF from .._fiff.meas_info import Info @@ -877,6 +876,8 @@ def _find_peaks(evoked, npeaks): Returns ``npeaks`` biggest peaks as a list of time points. """ + from scipy.signal import argrelmax + gfp = evoked.data.std(axis=0) order = len(evoked.times) // 30 if order < 1: diff --git a/pyproject.toml b/pyproject.toml index f20c495a2bc..620d95a8d1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ classifiers = [ dependencies = [ "decorator", "jinja2", - "lazy_loader >= 0.3", "matplotlib >= 3.7", # 2023/02/13 "numpy >= 1.25,<3", # 2023/06/17 "packaging", diff --git a/tools/dev/generate_pyi_files.py b/tools/dev/generate_pyi_files.py deleted file mode 100644 index c6ae36e7dd4..00000000000 --- a/tools/dev/generate_pyi_files.py +++ /dev/null @@ -1,81 +0,0 @@ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import os -import sys -from importlib import import_module -from pathlib import Path - -import ast_comments as ast -import black - -import mne - - -class RewriteAssign(ast.NodeTransformer): - """NodeTransformer to replace lazy attach with attach_stub.""" - - def visit_Assign(self, node): - """Replace lazy attach assignment with stub assignment.""" - if not hasattr(node.targets[0], "dims"): - return node - - ids = [name.id for name in node.targets[0].dims] - if ids == ["__getattr__", "__dir__", "__all__"]: - return ast.parse( - "__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)\n" - ) - return node - - -pyi_mode = black.Mode(is_pyi=True) -root = Path(mne.__file__).parent -inits = root.rglob("__init__.py") - -for init in inits: - # skip init files that don't lazy load (e.g., tests) - code = init.read_text("utf-8") - if "import lazy_loader as lazy" not in code: - continue - # get the AST - tree = ast.parse(code) - nodes = [node for node in tree.body if isinstance(node, ast.Assign)] - assert len(nodes) == 1 - node = nodes[0] - keywords = node.value.keywords - # get submodules - import_lines = list() - assert keywords[0].arg == "submodules" - # for submod in keywords[0].value.elts: - # import_lines.append(f"import {submod.value}") - submods = [submod.value for submod in keywords[0].value.elts] - if len(submods): - import_lines.append(f"from . import {', '.join(submods)}") - # get attrs - assert keywords[1].arg == "submod_attrs" - _dict = keywords[1].value - for key, vals in zip(_dict.keys, _dict.values): - attrs = [attr.value for attr in vals.elts] - import_lines.append(f"from .{key.value} import {', '.join(attrs)}") - # format - import_lines = black.format_str("\n".join(import_lines), mode=pyi_mode) - # get __all__ - import_path = str(init.parent.relative_to(root.parent)).replace(os.sep, ".") - import_module(import_path) - _all = black.format_str( - f"__all__ = {repr(sys.modules[import_path].__all__)}\n", - mode=pyi_mode, - ) - # write __init__.pyi - outfile = init.with_suffix(".pyi") - with open(outfile, "w") as fid: - fid.write(_all) - fid.write(import_lines) - # rewrite __init__.py - new_tree = RewriteAssign().visit(tree) - new_tree = ast.fix_missing_locations(new_tree) - new_code = ast.unparse(new_tree) - formatted_code = black.format_str(new_code, mode=black.Mode()) - with open(init, "w") as fid: - fid.write(formatted_code) diff --git a/tools/environment_minimal.yml b/tools/environment_minimal.yml index f67decbe0d2..147e938d73b 100644 --- a/tools/environment_minimal.yml +++ b/tools/environment_minimal.yml @@ -12,4 +12,3 @@ dependencies: - decorator - packaging - jinja2 - - lazy_loader diff --git a/tools/environment_old.yml b/tools/environment_old.yml index 46826bdbcf4..ddb1b51a231 100644 --- a/tools/environment_old.yml +++ b/tools/environment_old.yml @@ -15,4 +15,3 @@ dependencies: - decorator - packaging - jinja2 - - lazy_loader =0.3 diff --git a/tools/hooks/sync_dependencies.py b/tools/hooks/sync_dependencies.py index 1ff6d7f8712..aa8d5a6b7fb 100755 --- a/tools/hooks/sync_dependencies.py +++ b/tools/hooks/sync_dependencies.py @@ -28,7 +28,6 @@ "tqdm": "https://tqdm.github.io", "Jinja2": "https://palletsprojects.com/p/jinja/", "decorator": "https://github.com/micheles/decorator", - "lazy-loader": "https://pypi.org/project/lazy_loader", "packaging": "https://packaging.pypa.io/en/stable/", } @@ -52,7 +51,10 @@ def _prettify_pin(pin): # get the dependency info py_pin = metadata("mne").get("Requires-Python") all_deps = metadata("mne").get_all("Requires-Dist") -core_deps = [f"python{py_pin}", *[dep for dep in all_deps if "extra ==" not in dep]] +core_deps = [ + f"python{py_pin}", + *[dep for dep in all_deps if "extra ==" not in dep and dep != "lazy-loader>=0.3"], +] pattern = re.compile(r"(?P[A-Za-z_\-\d]+)(?P[<>=]+.*)?") core_deps_pins = { dep["name"]: _prettify_pin(dep["pin"]) for dep in map(pattern.match, core_deps)