From 0c504b7a83f873378172cff92d0dfeb8173432b8 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Sun, 23 Feb 2025 10:18:55 +0100 Subject: [PATCH 01/46] Revert "handle lazy loading through .pyi type stubs (#12072)" This reverts commit 81b7ddfe3fc33f83ba7d472a6a67e714850d7c73. --- mne/__init__.py | 1 + mne/__init__.pyi | 430 ------------------ mne/beamformer/__init__.py | 33 +- mne/beamformer/__init__.pyi | 34 -- mne/channels/__init__.py | 47 +- mne/channels/__init__.pyi | 78 ---- mne/commands/__init__.py | 7 +- mne/commands/__init__.pyi | 2 - mne/datasets/__init__.py | 43 +- mne/datasets/__init__.pyi | 78 ---- mne/decoding/__init__.py | 24 +- mne/decoding/__init__.pyi | 45 -- mne/export/__init__.py | 10 +- mne/export/__init__.pyi | 3 - mne/forward/__init__.py | 55 ++- mne/forward/__init__.pyi | 86 ---- mne/gui/__init__.py | 7 +- mne/gui/__init__.pyi | 2 - mne/html_templates/__init__.py | 7 +- mne/html_templates/__init__.pyi | 2 - mne/inverse_sparse/__init__.py | 9 +- mne/inverse_sparse/__init__.pyi | 3 - mne/io/__init__.py | 51 ++- mne/io/__init__.pyi | 91 ---- mne/minimum_norm/__init__.py | 35 +- mne/minimum_norm/__init__.pyi | 50 -- mne/preprocessing/__init__.py | 50 +- mne/preprocessing/__init__.pyi | 93 ---- mne/report/__init__.py | 9 +- mne/report/__init__.pyi | 2 - mne/simulation/__init__.py | 16 +- mne/simulation/__init__.pyi | 22 - mne/source_space/__init__.py | 20 +- mne/source_space/__init__.pyi | 22 - mne/stats/__init__.py | 32 +- mne/stats/__init__.pyi | 44 -- mne/time_frequency/__init__.py | 55 ++- mne/time_frequency/__init__.pyi | 85 ---- mne/utils/__init__.py | 207 ++++++++- mne/utils/__init__.pyi | 384 ---------------- mne/viz/__init__.py | 107 ++++- mne/viz/__init__.pyi | 177 ------- tools/dev/ensure_headers.py | 196 -------- tools/dev/gen_css_for_mne.py | 57 --- tools/dev/generate_pyi_files.py | 81 ---- tools/dev/unacknowledged-issues.jq | 32 -- tools/dev/update_credit_json.py | 94 ---- tools/{dev => dev_reports}/Makefile | 0 .../check_steering_committee.py | 0 49 files changed, 804 insertions(+), 2214 deletions(-) delete mode 100644 mne/__init__.pyi delete mode 100644 mne/beamformer/__init__.pyi delete mode 100644 mne/channels/__init__.pyi delete mode 100644 mne/commands/__init__.pyi delete mode 100644 mne/datasets/__init__.pyi delete mode 100644 mne/decoding/__init__.pyi delete mode 100644 mne/export/__init__.pyi delete mode 100644 mne/forward/__init__.pyi delete mode 100644 mne/gui/__init__.pyi delete mode 100644 mne/html_templates/__init__.pyi delete mode 100644 mne/inverse_sparse/__init__.pyi delete mode 100644 mne/io/__init__.pyi delete mode 100644 mne/minimum_norm/__init__.pyi delete mode 100644 mne/preprocessing/__init__.pyi delete mode 100644 mne/report/__init__.pyi delete mode 100644 mne/simulation/__init__.pyi delete mode 100644 mne/source_space/__init__.pyi delete mode 100644 mne/stats/__init__.pyi delete mode 100644 mne/time_frequency/__init__.pyi delete mode 100644 mne/utils/__init__.pyi delete mode 100644 mne/viz/__init__.pyi delete mode 100644 tools/dev/ensure_headers.py delete mode 100644 tools/dev/gen_css_for_mne.py delete mode 100644 tools/dev/generate_pyi_files.py delete mode 100644 tools/dev/unacknowledged-issues.jq delete mode 100644 tools/dev/update_credit_json.py rename tools/{dev => dev_reports}/Makefile (100%) rename tools/{dev => dev_reports}/check_steering_committee.py (100%) diff --git a/mne/__init__.py b/mne/__init__.py index 6abe2bccb62..f59504172d0 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/ # 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/beamformer/__init__.py b/mne/beamformer/__init__.py index 16a5b363eb6..193cb2e2be8 100644 --- a/mne/beamformer/__init__.py +++ b/mne/beamformer/__init__.py @@ -3,6 +3,37 @@ # Copyright the MNE-Python contributors. """Beamformers for source localization.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "_lcmv": [ + "make_lcmv", + "apply_lcmv", + "apply_lcmv_epochs", + "apply_lcmv_raw", + "apply_lcmv_cov", + ], + "_dics": [ + "make_dics", + "apply_dics", + "apply_dics_epochs", + "apply_dics_tfr_epochs", + "apply_dics_csd", + ], + "_rap_music": [ + "rap_music", + "trap_music", + ], + "_compute_beamformer": [ + "Beamformer", + "read_beamformer", + ], + "resolution_matrix": [ + "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/channels/__init__.py b/mne/channels/__init__.py index 901a237da24..676fa47c09b 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -9,4 +9,49 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "channels": [ + "equalize_channels", + "rename_channels", + "fix_mag_coil_types", + "read_ch_adjacency", + "find_ch_adjacency", + "make_1020_channel_selections", + "combine_channels", + "read_vectorview_selection", + "_SELECTIONS", + "_EEG_SELECTIONS", + "_divide_to_regions", + "get_builtin_ch_adjacencies", + "unify_bad_channels", + ], + "layout": [ + "Layout", + "make_eeg_layout", + "make_grid_layout", + "read_layout", + "find_layout", + "generate_2d_layout", + ], + "montage": [ + "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", + "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/commands/__init__.py b/mne/commands/__init__.py index 1c7134ea78b..0d50003f7c4 100644 --- a/mne/commands/__init__.py +++ b/mne/commands/__init__.py @@ -3,6 +3,11 @@ # Copyright the MNE-Python contributors. """Command-line utilities.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["utils"], + submod_attrs={}, +) 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/datasets/__init__.py b/mne/datasets/__init__.py index 700dab0608e..3831c722719 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -9,4 +9,45 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[ + "fieldtrip_cmc", + "brainstorm", + "visual_92_categories", + "kiloword", + "eegbci", + "hf_sef", + "misc", + "mtrf", + "sample", + "somato", + "multimodal", + "fnirs_motor", + "opm", + "spm_face", + "testing", + "_fake", + "phantom_4dbti", + "sleep_physionet", + "limo", + "refmeg_noise", + "ssvep", + "erp_core", + "epilepsy_ecog", + "eyelink", + "ucl_opm_auditory", + ], + submod_attrs={ + "_fetch": ["fetch_dataset"], + "_fsaverage.base": ["fetch_fsaverage"], + "_infant.base": ["fetch_infant_template"], + "_phantom.base": ["fetch_phantom"], + "utils": [ + "_download_all_example_data", + "fetch_hcp_mmp_parcellation", + "fetch_aparc_sub_parcellation", + "has_dataset", + ], + }, +) 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/decoding/__init__.py b/mne/decoding/__init__.py index b0dc90e1048..8ff92748aca 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -5,4 +5,26 @@ """Decoding and encoding, including machine learning and receptive fields.""" import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "base": ["BaseEstimator", "LinearModel", "cross_val_multiscore", "get_coef"], + "csp": ["CSP", "SPoC"], + "ems": ["EMS", "compute_ems"], + "mixin": ["TransformerMixin"], + "receptive_field": ["ReceptiveField"], + "search_light": ["GeneralizingEstimator", "SlidingEstimator"], + "ssd": ["SSD"], + "time_delaying_ridge": ["TimeDelayingRidge"], + "time_frequency": ["TimeFrequency"], + "transformer": [ + "FilterEstimator", + "PSDEstimator", + "Scaler", + "TemporalFilter", + "UnsupervisedSpatialFilter", + "Vectorizer", + ], + }, +) 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/export/__init__.py b/mne/export/__init__.py index 7a07df656d6..4ee7ca57feb 100644 --- a/mne/export/__init__.py +++ b/mne/export/__init__.py @@ -3,6 +3,14 @@ # 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__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "_export": ["export_raw", "export_epochs", "export_evokeds"], + "_egimff": ["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/forward/__init__.py b/mne/forward/__init__.py index 3dcafb625de..ecc43e47afa 100644 --- a/mne/forward/__init__.py +++ b/mne/forward/__init__.py @@ -3,6 +3,57 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" -import lazy_loader as lazy # for testing purposes -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +import lazy_loader as lazy + +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["_lead_dots"], # for testing purposes + submod_attrs={ + "forward": [ + "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", + ], + "_compute_forward": [ + "_magnetic_dipole_field_vec", + "_compute_forwards", + "_concatenate_coils", + ], + "_field_interpolation": [ + "_make_surface_mapping", + "make_field_map", + "_as_meg_type_inst", + "_map_meg_or_eeg_channels", + ], + "_make_forward": [ + "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", + ], + }, +) 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/gui/__init__.py b/mne/gui/__init__.py index c06c8dc9939..05ca2357ad5 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -3,6 +3,11 @@ # Copyright the MNE-Python contributors. """Convenience functions for opening GUIs.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={"_gui": ["coregistration", "_GUIScraper"]}, +) 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..8dc30b105ab 100644 --- a/mne/html_templates/__init__.py +++ b/mne/html_templates/__init__.py @@ -3,6 +3,11 @@ # Copyright the MNE-Python contributors. """Jinja2 HTML templates.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={"_templates": ["_get_html_template"]}, +) 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..67f19e84c44 100644 --- a/mne/inverse_sparse/__init__.py +++ b/mne/inverse_sparse/__init__.py @@ -6,4 +6,11 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "mxne_inverse": ["mixed_norm", "tf_mixed_norm", "make_stc_from_dipoles"], + "_gamma_map": ["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..574df48980a 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -6,4 +6,53 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[ + "constants", + "pick", + ], + submod_attrs={ + "base": ["BaseRaw", "concatenate_raws", "match_channel_orders"], + "array": ["RawArray"], + "besa": ["read_evoked_besa"], + "brainvision": ["read_raw_brainvision"], + "bti": ["read_raw_bti"], + "cnt": ["read_raw_cnt"], + "ctf": ["read_raw_ctf"], + "curry": ["read_raw_curry"], + "edf": ["read_raw_edf", "read_raw_bdf", "read_raw_gdf"], + "egi": ["read_raw_egi", "read_evokeds_mff"], + "kit": ["read_raw_kit", "read_epochs_kit"], + "fiff": ["read_raw_fif", "Raw"], + "fil": ["read_raw_fil"], + "nedf": ["read_raw_nedf"], + "nicolet": ["read_raw_nicolet"], + "artemis123": ["read_raw_artemis123"], + "eeglab": ["read_raw_eeglab", "read_epochs_eeglab"], + "eximia": ["read_raw_eximia"], + "hitachi": ["read_raw_hitachi"], + "nirx": ["read_raw_nirx"], + "boxy": ["read_raw_boxy"], + "snirf": ["read_raw_snirf"], + "persyst": ["read_raw_persyst"], + "fieldtrip": [ + "read_raw_fieldtrip", + "read_epochs_fieldtrip", + "read_evoked_fieldtrip", + ], + "nihon": ["read_raw_nihon"], + "nsx": ["read_raw_nsx"], + "_read_raw": ["read_raw"], + "eyelink": ["read_raw_eyelink"], + "_fiff_wrap": [ + "read_info", + "write_info", + "anonymize_info", + "read_fiducials", + "write_fiducials", + "show_fiff", + "get_channel_type_constants", + ], + }, +) 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/minimum_norm/__init__.py b/mne/minimum_norm/__init__.py index 975eb5ed3e5..f583d035be4 100644 --- a/mne/minimum_norm/__init__.py +++ b/mne/minimum_norm/__init__.py @@ -3,6 +3,39 @@ # 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__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "inverse": [ + "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", + ], + "time_frequency": [ + "source_band_induced_power", + "source_induced_power", + "compute_source_psd", + "compute_source_psd_epochs", + ], + "resolution_matrix": [ + "make_inverse_resolution_matrix", + "get_point_spread", + "get_cross_talk", + ], + "spatial_resolution": ["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/preprocessing/__init__.py b/mne/preprocessing/__init__.py index dac4012b6ef..dbb77f99f23 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -6,4 +6,52 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["eyetracking", "ieeg", "nirs"], + submod_attrs={ + "_annotate_amplitude": ["annotate_amplitude"], + "maxfilter": ["apply_maxfilter"], + "ssp": ["compute_proj_ecg", "compute_proj_eog"], + "eog": ["find_eog_events", "create_eog_epochs"], + "ecg": ["find_ecg_events", "create_ecg_epochs"], + "ica": [ + "ICA", + "ica_find_eog_events", + "ica_find_ecg_events", + "get_score_funcs", + "read_ica", + "corrmap", + "read_ica_eeglab", + ], + "otp": ["oversampled_temporal_projection"], + "_peak_finder": ["peak_finder"], + "infomax_": ["infomax"], + "stim": ["fix_stim_artifact"], + "maxwell": [ + "maxwell_filter", + "find_bad_channels_maxwell", + "compute_maxwell_basis", + "maxwell_filter_prepare_emptyroom", + ], + "realign": ["realign_raw"], + "xdawn": ["Xdawn"], + "_csd": ["compute_current_source_density", "compute_bridged_electrodes"], + "artifact_detection": [ + "annotate_movement", + "compute_average_dev_head_t", + "annotate_muscle_zscore", + "annotate_break", + ], + "_regress": ["regress_artifact", "EOGRegression", "read_eog_regression"], + "_fine_cal": [ + "compute_fine_calibration", + "read_fine_calibration", + "write_fine_calibration", + ], + "_annotate_nan": ["annotate_nan"], + "interpolate": ["equalize_bads", "interpolate_bridged_electrodes"], + "_css": ["cortical_signal_suppression"], + "hfc": ["compute_proj_hfc"], + }, +) 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/report/__init__.py b/mne/report/__init__.py index e3a0a07d6c8..bed989a9f80 100644 --- a/mne/report/__init__.py +++ b/mne/report/__init__.py @@ -3,6 +3,13 @@ # Copyright the MNE-Python contributors. """Report-generation functions and classes.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "report": ["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..b716443ee0f 100644 --- a/mne/simulation/__init__.py +++ b/mne/simulation/__init__.py @@ -3,6 +3,20 @@ # Copyright the MNE-Python contributors. """Data simulation code.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["metrics"], + submod_attrs={ + "evoked": ["simulate_evoked", "add_noise"], + "raw": ["simulate_raw", "add_ecg", "add_eog", "add_chpi"], + "source": [ + "select_source_in_label", + "simulate_stc", + "simulate_sparse_stc", + "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/source_space/__init__.py b/mne/source_space/__init__.py index 46fb9e1fbff..b55af4072e8 100644 --- a/mne/source_space/__init__.py +++ b/mne/source_space/__init__.py @@ -3,6 +3,24 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["_source_space"], + submod_attrs={ + "_source_space": [ + "compute_distance_to_sensors", + "get_decimated_surfaces", + # These are documented in the MNE namespace but it doesn't hurt to + # keep them here as well + "SourceSpaces", + "read_source_spaces", + "write_source_spaces", + "setup_source_space", + "setup_volume_source_space", + "add_source_space_distances", + ], + }, +) 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/stats/__init__.py b/mne/stats/__init__.py index 18946a20aca..9ec167e4eb4 100644 --- a/mne/stats/__init__.py +++ b/mne/stats/__init__.py @@ -3,6 +3,36 @@ # Copyright the MNE-Python contributors. """Functions for statistical analysis.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "parametric": [ + "f_threshold_mway_rm", + "f_mway_rm", + "f_oneway", + "_parametric_ci", + "ttest_1samp_no_p", + "ttest_ind_no_p", + ], + "permutations": [ + "permutation_t_test", + "_ci", + "bootstrap_confidence_interval", + ], + "cluster_level": [ + "permutation_cluster_test", + "permutation_cluster_1samp_test", + "spatio_temporal_cluster_test", + "spatio_temporal_cluster_1samp_test", + "_st_mask_from_s_inds", + "summarize_clusters_stc", + ], + "multi_comp": ["fdr_correction", "bonferroni_correction"], + "regression": ["linear_regression", "linear_regression_raw"], + "_adjacency": ["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/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 74cfd53ec71..5539f183fa2 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -3,6 +3,59 @@ # Copyright the MNE-Python contributors. """Time frequency analysis tools.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "_stft": [ + "istft", + "stft", + "stftfreq", + ], + "_stockwell": [ + "tfr_array_stockwell", + "tfr_stockwell", + ], + "ar": ["fit_iir_model_raw"], + "csd": [ + "CrossSpectralDensity", + "csd_array_fourier", + "csd_array_morlet", + "csd_array_multitaper", + "csd_fourier", + "csd_morlet", + "csd_multitaper", + "csd_tfr", + "pick_channels_csd", + "read_csd", + ], + "multitaper": [ + "dpss_windows", + "psd_array_multitaper", + "tfr_array_multitaper", + ], + "psd": ["psd_array_welch"], + "spectrum": [ + "EpochsSpectrum", + "EpochsSpectrumArray", + "Spectrum", + "SpectrumArray", + "read_spectrum", + ], + "tfr": [ + "_BaseTFR", + "AverageTFR", + "EpochsTFR", + "fwhm", + "morlet", + "read_tfrs", + "tfr_array_morlet", + "tfr_morlet", + "tfr_multitaper", + "write_tfrs", + ], + }, +) 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/utils/__init__.py b/mne/utils/__init__.py index dbfedccd82b..600c74720e4 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -4,4 +4,209 @@ import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=[], + submod_attrs={ + "_bunch": ["Bunch", "BunchConst", "BunchConstNamed"], + "check": [ + "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_edflib_installed", + "_to_rgb", + "_soft_import", + "_check_dict_keys", + "_check_pymatreader_installed", + "_import_h5py", + "_import_h5io_funcs", + "_import_nibabel", + "_import_pymatreader_funcs", + "_check_head_radius", + ], + "config": [ + "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", + ], + "docs": [ + "copy_function_doc_to_method_doc", + "copy_doc", + "linkcode_resolve", + "open_docs", + "deprecated", + "fill_doc", + "deprecated_alias", + "legacy", + "copy_base_doc_to_subclass_doc", + "_doc_special_members", + ], + "fetching": ["_url_to_local_path"], + "_logging": [ + "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", + ], + "misc": [ + "run_subprocess", + "_pl", + "_clean_names", + "pformat", + "_file_like", + "_empty_hash", + "_explain_exception", + "_get_argvalues", + "sizeof_fmt", + "running_subprocess", + "_DefaultEventParser", + "_assert_no_instances", + "_resource_path", + "repr_html", + "_auto_weakref", + ], + "progressbar": ["ProgressBar"], + "_testing": [ + "run_command_if_main", + "requires_mne", + "requires_good_network", + "ArgvSetter", + "SilenceStdout", + "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", + ], + "numerics": [ + "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", + "_julian_to_cal", + "_cal_to_julian", + "_dt_to_julian", + "_julian_to_dt", + "_dt_to_stamp", + "_stamp_to_dt", + "_check_dt", + "_ReuseCycle", + "_arange_div", + "_hashable_ndarray", + "_custom_lru_cache", + ], + "mixin": [ + "SizeMixin", + "GetEpochsMixin", + "TimeMixin", + "ExtendedTimeMixin", + "_prepare_read_metadata", + "_prepare_write_metadata", + "_check_decim", + ], + "linalg": [ + "_svd_lwork", + "_repeated_svd", + "_sym_mat_pow", + "sqrtm_sym", + "eigh", + "_get_blas_funcs", + ], + "dataframe": [ + "_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/viz/__init__.py b/mne/viz/__init__.py index ae043dc12ab..8b118cc8c5d 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -3,6 +3,111 @@ # Copyright the MNE-Python contributors. """Visualization routines.""" + import lazy_loader as lazy -(__getattr__, __dir__, __all__) = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach( + __name__, + submodules=["backends", "_scraper", "ui_events"], + submod_attrs={ + "backends._abstract": ["Figure3D"], + "backends.renderer": [ + "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", + ], + "circle": ["circular_layout", "plot_channel_labels_circle"], + "epochs": [ + "plot_drop_log", + "plot_epochs", + "plot_epochs_psd", + "plot_epochs_image", + ], + "evoked": [ + "plot_evoked", + "plot_evoked_image", + "plot_evoked_white", + "plot_snr_estimate", + "plot_evoked_topo", + "plot_evoked_joint", + "plot_compare_evokeds", + ], + "evoked_field": [ + "EvokedField", + ], + "ica": [ + "plot_ica_scores", + "plot_ica_sources", + "plot_ica_overlay", + "_plot_sources", + "plot_ica_properties", + ], + "misc": [ + "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", + ], + "montage": ["plot_montage"], + "raw": ["plot_raw", "plot_raw_psd", "plot_raw_psd_topo", "_RAW_CLIP_DEF"], + "topo": ["plot_topo_image_epochs", "iter_topography"], + "topomap": [ + "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", + ], + "utils": [ + "tight_layout", + "mne_analyze_colormap", + "compare_fiff", + "ClickableImage", + "add_background_image", + "plot_sensors", + "centers_to_edges", + "concatenate_images", + "_get_plot_ch_type", + ], + "_3d": [ + "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", + ], + "_brain": ["Brain"], + "_figure": [ + "get_browser_backend", + "set_browser_backend", + "use_browser_backend", + ], + "_proj": ["plot_projs_joint"], + }, +) 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/tools/dev/ensure_headers.py b/tools/dev/ensure_headers.py deleted file mode 100644 index a4095d82b42..00000000000 --- a/tools/dev/ensure_headers.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Ensure license and copyright statements are in source files. - -From https://www.bestpractices.dev/en/projects/7783?criteria_level=2: - - The project MUST include a copyright statement in each source file, identifying the - copyright holder (e.g., the [project name] contributors). [copyright_per_file] - This MAY be done by including the following inside a comment near the beginning of - each file: "Copyright the [project name] contributors.". - -And: - - The project MUST include a license statement in each source file. - -This script ensures that we use consistent license naming in consistent locations -toward the top of each file. -""" - -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import re -from pathlib import Path - -import numpy as np -from git import Repo - -repo = Repo(Path(__file__).parents[2]) - -AUTHOR_LINE = "# Authors: The MNE-Python contributors." -LICENSE_LINE = "# License: BSD-3-Clause" -COPYRIGHT_LINE = "# Copyright the MNE-Python contributors." - -# Cover how lines can start (regex or tuple to be used with startswith) -AUTHOR_RE = re.compile(r"^# (A|@a)uthors? ?: .*$") -LICENSE_STARTS = ("# License: ", "# SPDX-License-Identifier: ") -COPYRIGHT_STARTS = ("# Copyright ",) - - -def get_paths_from_tree(root, level=0): - """Get paths from a GitPython tree.""" - for entry in root: - if entry.type == "tree": - yield from get_paths_from_tree(entry, level + 1) - else: - yield Path(entry.path) # entry.type - - -def first_commentable_line(lines): - """Find the first line where we can add a comment.""" - max_len = 100 - if lines[0].startswith(('"""', 'r"""')): - if lines[0].count('"""') == 2: - return 1 - for insert in range(1, min(max_len, len(lines))): - if '"""' in lines[insert]: - return insert + 1 - else: - raise RuntimeError( - f"Failed to find end of file docstring within {max_len} lines" - ) - if lines[0].startswith("#!"): - return 1 - else: - return 0 - - -def path_multi_author(path): - """Check if a file allows multi-author comments.""" - return path.parts[0] in ("examples", "tutorials") - - -def get_author_idx(path, lines): - """Get the index of the author line, if available.""" - author_idx = np.where([AUTHOR_RE.match(line) is not None for line in lines])[0] - assert len(author_idx) <= 1, f"{len(author_idx)=} for {path=}" - return author_idx[0] if len(author_idx) else None - - -def get_license_idx(path, lines): - """Get the license index.""" - license_idx = np.where([line.startswith(LICENSE_STARTS) for line in lines])[0] - assert len(license_idx) <= 1, f"{len(license_idx)=} for {path=}" - return license_idx[0] if len(license_idx) else None - - -def _ensure_author(lines, path): - author_idx = get_author_idx(path, lines) - license_idx = get_license_idx(path, lines) - first_idx = first_commentable_line(lines) - # 1. Keep existing - if author_idx is not None: - # We have to be careful here -- examples and tutorials are allowed multiple - # authors - if path_multi_author(path): - # Just assume it's correct and return - return - assert license_idx is not None, f"{license_idx=} for {path=}" - for _ in range(license_idx - author_idx - 1): - lines.pop(author_idx + 1) - assert lines[author_idx + 1].startswith(LICENSE_STARTS), lines[license_idx + 1] - del license_idx - lines[author_idx] = AUTHOR_LINE - elif license_idx is not None: - # 2. Before license line if present - lines.insert(license_idx, AUTHOR_LINE) - else: - # 3. First line after docstring - lines.insert(first_idx, AUTHOR_LINE) - # Now make sure it's in the right spot - author_idx = get_author_idx(path, lines) - if author_idx != 0: - if author_idx == first_idx: - # Insert a blank line - lines.insert(author_idx, "") - author_idx += 1 - first_idx += 1 - if author_idx != first_idx: - raise RuntimeError( - "\nLine should have comments as docstring or author line needs to be moved " - "manually to be one blank line after the docstring:\n" - f"{path}: {author_idx=} != {first_idx=}" - ) - - -def _ensure_license(lines, path): - # 1. Keep/replace existing - insert = get_license_idx(path, lines) - - # 2. After author line(s) - if insert is None: - author_idx = get_author_idx(path, lines) - assert author_idx is not None, f"{author_idx=} for {path=}" - insert = author_idx + 1 - if path_multi_author: - # Figure out where to insert the license: - for insert, line in enumerate(lines[author_idx + 1 :], insert): - if not line.startswith("# "): - break - if lines[insert].startswith(LICENSE_STARTS): - lines[insert] = LICENSE_LINE - else: - lines.insert(insert, LICENSE_LINE) - assert lines.count(LICENSE_LINE) == 1, f"{lines.count(LICENSE_LINE)=} for {path=}" - - -def _ensure_copyright(lines, path): - n_expected = { - "mne/preprocessing/_csd.py": 2, - "mne/transforms.py": 2, - } - n_copyright = sum(line.startswith(COPYRIGHT_STARTS) for line in lines) - assert n_copyright <= n_expected.get(str(path), 1), n_copyright - insert = lines.index(LICENSE_LINE) + 1 - if lines[insert].startswith(COPYRIGHT_STARTS): - lines[insert] = COPYRIGHT_LINE - else: - lines.insert(insert, COPYRIGHT_LINE) - assert lines.count(COPYRIGHT_LINE) == 1, ( - f"{lines.count(COPYRIGHT_LINE)=} for {path=}" - ) - - -def _ensure_blank(lines, path): - assert lines.count(COPYRIGHT_LINE) == 1, ( - f"{lines.count(COPYRIGHT_LINE)=} for {path=}" - ) - insert = lines.index(COPYRIGHT_LINE) + 1 - if lines[insert].strip(): # actually has content - lines.insert(insert, "") - - -for path in get_paths_from_tree(repo.tree()): - if not path.suffix == ".py": - continue - lines = path.read_text("utf-8").split("\n") - # Remove the UTF-8 file coding stuff - orig_lines = list(lines) - if lines[0] in ("# -*- coding: utf-8 -*-", "# -*- coding: UTF-8 -*-"): - lines = lines[1:] - if lines[0] == "": - lines = lines[1:] - # We had these with mne/commands without an executable bit, and don't really - # need them executable, so let's get rid of the line. - if lines[0].startswith("#!/usr/bin/env python") and path.parts[:2] == ( - "mne", - "commands", - ): - lines = lines[1:] - _ensure_author(lines, path) - _ensure_license(lines, path) - _ensure_copyright(lines, path) - _ensure_blank(lines, path) - if lines != orig_lines: - print(path) - path.write_text("\n".join(lines), "utf-8") diff --git a/tools/dev/gen_css_for_mne.py b/tools/dev/gen_css_for_mne.py deleted file mode 100644 index 2d277857348..00000000000 --- a/tools/dev/gen_css_for_mne.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Generate a bootstrap-icons CSS file with embedded font. - -- Install rcssmin (for CSS minification) -- Download and extract bootstrap-icons -- Copy to this directory: - - bootstrap-icons.css - - bootstrap-icons.woff2 -- Run this script. It will generate bootstrap-icons.mne.css and - bootstrap-icons.mne.min.css -""" - -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import base64 -from pathlib import Path - -import rcssmin - -import mne - -base_dir = Path(mne.__file__).parent / "report" / "js_and_css" / "bootstrap-icons" -css_path_in = base_dir / "bootstrap-icons.css" -css_path_out = base_dir / "bootstrap-icons.mne.css" -css_minified_path_out = base_dir / "bootstrap-icons.mne.min.css" -font_path = base_dir / "bootstrap-icons.woff2" - - -def main(): - """Start the CSS modification.""" - css_in = css_path_in.read_text(encoding="utf-8") - font_binary = font_path.read_bytes() - font_b64 = base64.b64encode(font_binary).decode("utf-8") - - css_out = [] - for css in css_in.split("\n"): - if "src: url(" in css: - css = ( - f" src: url(data:font/woff2;charset=utf-8;" - f'base64,{font_b64}) format("woff2");' - ) - elif "url(" in css: - continue - - css_out.append(css) - - css_out = "\n".join(css_out) - css_minified_out = rcssmin.cssmin(style=css_out) - - css_path_out.write_text(data=css_out, encoding="utf-8") - css_minified_path_out.write_text(data=css_minified_out, encoding="utf-8") - - -if __name__ == "__main__": - main() 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/dev/unacknowledged-issues.jq b/tools/dev/unacknowledged-issues.jq deleted file mode 100644 index b91f5fc2e78..00000000000 --- a/tools/dev/unacknowledged-issues.jq +++ /dev/null @@ -1,32 +0,0 @@ -# Processor for `gh issue list` output that displays unacknowledged issues -# that are 2-12 months old. The date range is specific to OpenSSF best practices. - -# `now` is in seconds since the unix epoch -def one_year_ago: now - (365 * 24 * 60 * 60); - -def sixty_days_ago: now - (60 * 24 * 60 * 60); - -def date_fmt: "%Y/%m/%d"; - -def make_pretty_date_range: - (one_year_ago | strftime(date_fmt)) + " - " + (sixty_days_ago | strftime(date_fmt)); - -def make_issue_url: "https://github.com/mne-tools/mne-python/issues/\(.number)"; - -def get_dev_comments: .comments | map(select(.authorAssociation == "MEMBER")); - - -# main routine -map( - select( - (.createdAt > (one_year_ago | todate)) and - (.createdAt < (sixty_days_ago | todate)) - ) += - { "devComments": . | get_dev_comments | length } -) | -{ - "range": make_pretty_date_range, - "has_dev_comments": map(select(.devComments > 0)) | length, - "no_dev_comments": map(select(.devComments == 0) and .state == "OPEN") | length, - "unaddressed": map(select(.devComments == 0) | make_issue_url), -} diff --git a/tools/dev/update_credit_json.py b/tools/dev/update_credit_json.py deleted file mode 100644 index de96c040604..00000000000 --- a/tools/dev/update_credit_json.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Collect credit information for PRs. - -The initial run takes a long time (hours!) due to GitHub rate limits, even with -a personal GITHUB_TOKEN. -""" - -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import json -import os -import re -from pathlib import Path - -from github import Auth, Github -from tqdm import tqdm - -auth = Auth.Token(os.environ["GITHUB_TOKEN"]) -g = Github(auth=auth, per_page=100) -out_path = Path(__file__).parents[2] / "doc" / "sphinxext" / "prs" -out_path.mkdir(exist_ok=True) -# manually update this when the oldest open PR changes to speed things up -# (don't need to look any farther back than this) -oldest_pr = 9176 - -# JSON formatting -json_kwargs = dict(indent=2, ensure_ascii=False, sort_keys=False) -# If the above arguments are changed, existing JSON should also be reformatted with -# something like: -# for fname in sorted(glob.glob("doc/sphinxext/prs/*.json")): -# fname = Path(fname).resolve(strict=True) -# fname.write_text(json.dumps(json.loads(fname.read_text("utf-8")), **json_kwargs), "utf-8") # noqa: E501 - -repo = g.get_repo("mne-tools/mne-python") -co_re = re.compile("Co-authored-by: ([^<>]+) <([^()>]+)>") -# We go in descending order of updates and `break` when we encounter a PR we have -# already committed a file for. -pulls_iter = repo.get_pulls(state="closed", sort="created", direction="desc") -iter_ = tqdm(pulls_iter, unit="pr", desc="Traversing") -last = 0 -n_added = 0 -for pull in iter_: - fname_out = out_path / f"{pull.number}.json" - if pull.number < oldest_pr: - iter_.close() - print( - f"After checking {iter_.n + 1} and adding {n_added} PR(s), " - f"found PR number less than oldest existing file {fname_out}, stopping" - ) - break - if fname_out.is_file(): - continue - - # PR diff credit - if not pull.merged: - continue - out = dict() - # One option is to do a git diff between pull.base and pull.head, - # but let's see if we can stay pythonic - out["merge_commit_sha"] = pull.merge_commit_sha - # Prefer the GitHub username information because it should be most up to date - name, email = pull.user.name, pull.user.email - if name is None and email is None: - # no usable GitHub user information, pull it from the first commit - author = pull.get_commits()[0].commit.author - name, email = author.name, author.email - out["authors"] = [dict(n=name, e=email)] - # For PR 54 for example this is empty for some reason! - if out["merge_commit_sha"]: - try: - merge_commit = repo.get_commit(out["merge_commit_sha"]) - except Exception: - pass # this happens on a lot of old PRs for some reason - else: - msg = merge_commit.commit.message.replace("\r", "") - for n, e in co_re.findall(msg): - # sometimes commit messages like for 9754 contain all - # commit messages and include some repeated co-authorship messages - if n not in {a["n"] for a in out["authors"]}: - out["authors"].append(dict(n=n, e=e)) - out["changes"] = dict() - for file in pull.get_files(): - out["changes"][file.filename] = { - k[0]: getattr(file, k) for k in ("additions", "deletions") - } - n_added += 1 - fname_out.write_text(json.dumps(out, **json_kwargs), encoding="utf-8") - - # TODO: Should add: - # pull.get_comments() - # pull.get_review_comments() - -g.close() diff --git a/tools/dev/Makefile b/tools/dev_reports/Makefile similarity index 100% rename from tools/dev/Makefile rename to tools/dev_reports/Makefile diff --git a/tools/dev/check_steering_committee.py b/tools/dev_reports/check_steering_committee.py similarity index 100% rename from tools/dev/check_steering_committee.py rename to tools/dev_reports/check_steering_committee.py From 1f00b19ec65d8a1c8f6f8f561e757dfc7f200b9d Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Sun, 23 Feb 2025 11:21:20 +0100 Subject: [PATCH 02/46] Revert "ENH: use lazy loading (#11838)" This reverts commit dea8f8728eef1832ba6dc750e447fc0799f46f88. --- mne/__init__.py | 228 +++++++++- mne/_fiff/__init__.py | 19 +- mne/_fiff/meas_info.py | 58 ++- mne/_fiff/open.py | 2 + mne/_fiff/tag.py | 3 + mne/_fiff/write.py | 2 + mne/_ola.py | 4 +- mne/annotations.py | 4 +- mne/beamformer/__init__.py | 49 +- mne/beamformer/_rap_music.py | 3 +- mne/bem.py | 6 +- mne/channels/__init__.py | 125 ++++-- mne/channels/channels.py | 6 + mne/channels/interpolation.py | 4 +- mne/channels/layout.py | 4 +- mne/chpi.py | 12 +- mne/commands/__init__.py | 8 +- mne/coreg.py | 6 +- mne/cov.py | 8 +- mne/cuda.py | 6 +- mne/datasets/__init__.py | 116 +++-- mne/datasets/limo/limo.py | 4 +- mne/decoding/__init__.py | 39 +- mne/decoding/csp.py | 4 +- mne/decoding/receptive_field.py | 6 +- mne/decoding/ssd.py | 4 +- mne/decoding/time_delaying_ridge.py | 7 + mne/dipole.py | 8 +- mne/epochs.py | 3 +- mne/evoked.py | 6 +- mne/export/__init__.py | 18 +- mne/filter.py | 79 ++-- mne/fixes.py | 50 ++- mne/forward/__init__.py | 97 ++-- mne/forward/_field_interpolation.py | 3 +- mne/forward/forward.py | 4 + mne/gui/__init__.py | 289 +++++++++++- mne/gui/_coreg.py | 8 +- mne/gui/_gui.py | 222 --------- mne/html_templates/__init__.py | 8 +- mne/inverse_sparse/__init__.py | 12 +- mne/io/__init__.py | 114 +++++ mne/io/_fiff_wrap.py | 20 - mne/io/_read_raw.py | 85 +++- mne/io/base.py | 9 +- mne/io/edf/edf.py | 4 +- mne/io/eeglab/_eeglab.py | 19 +- mne/io/fiff/__init__.py | 2 - mne/io/nirx/nirx.py | 1 - mne/label.py | 9 +- mne/minimum_norm/__init__.py | 60 ++- mne/minimum_norm/inverse.py | 10 +- mne/morph.py | 11 +- mne/morph_map.py | 2 + mne/preprocessing/_csd.py | 3 + mne/preprocessing/_fine_cal.py | 2 + mne/preprocessing/_regress.py | 1 + ...ate_amplitude.py => annotate_amplitude.py} | 0 mne/preprocessing/artifact_detection.py | 6 + mne/preprocessing/bads.py | 4 +- mne/preprocessing/ctps_.py | 7 +- mne/preprocessing/ica.py | 16 +- mne/preprocessing/ieeg/_projection.py | 3 +- mne/preprocessing/infomax_.py | 5 +- mne/preprocessing/interpolate.py | 3 +- mne/preprocessing/maxwell.py | 8 + mne/preprocessing/nirs/_beer_lambert_law.py | 5 + mne/preprocessing/nirs/_tddr.py | 3 +- mne/preprocessing/realign.py | 5 +- mne/preprocessing/stim.py | 4 + mne/preprocessing/xdawn.py | 5 +- mne/rank.py | 3 +- mne/report/__init__.py | 16 +- mne/report/report.py | 8 +- mne/simulation/__init__.py | 21 +- mne/simulation/evoked.py | 3 +- mne/simulation/metrics/metrics.py | 2 + mne/source_estimate.py | 14 +- mne/source_space/_source_space.py | 10 + mne/stats/__init__.py | 50 +-- mne/stats/_adjacency.py | 3 +- mne/stats/cluster_level.py | 18 +- mne/stats/parametric.py | 13 +- mne/stats/regression.py | 7 +- mne/surface.py | 11 + mne/tests/test_import_nesting.py | 7 +- mne/time_frequency/__init__.py | 83 ++-- mne/time_frequency/_stft.py | 8 +- mne/time_frequency/_stockwell.py | 7 +- mne/time_frequency/ar.py | 3 +- mne/time_frequency/csd.py | 5 +- mne/time_frequency/multitaper.py | 6 + mne/time_frequency/psd.py | 3 +- mne/time_frequency/tfr.py | 4 + mne/transforms.py | 8 + mne/utils/__init__.py | 421 +++++++++--------- mne/utils/_testing.py | 2 + mne/utils/linalg.py | 16 +- mne/utils/numerics.py | 6 + mne/viz/_3d.py | 5 + mne/viz/__init__.py | 193 ++++---- mne/viz/_brain/_brain.py | 6 + mne/viz/_dipole.py | 3 +- mne/viz/_figure.py | 3 +- mne/viz/epochs.py | 2 +- mne/viz/evoked.py | 3 + mne/viz/ica.py | 2 +- mne/viz/misc.py | 1 + mne/viz/montage.py | 1 - mne/viz/raw.py | 5 +- mne/viz/topo.py | 7 +- mne/viz/topomap.py | 24 +- mne/viz/utils.py | 4 + 113 files changed, 1862 insertions(+), 1125 deletions(-) delete mode 100644 mne/gui/_gui.py delete mode 100644 mne/io/_fiff_wrap.py rename mne/preprocessing/{_annotate_amplitude.py => annotate_amplitude.py} (100%) diff --git a/mne/__init__.py b/mne/__init__.py index f59504172d0..d0732a00353 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -19,7 +19,6 @@ # # Dev branch marker is: 'X.Y.devN' where N is an integer. # -import lazy_loader as lazy try: from importlib.metadata import version @@ -28,10 +27,231 @@ 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 .source_space import ( + read_source_spaces, + write_source_spaces, + setup_source_space, + setup_volume_source_space, + SourceSpaces, + add_source_space_distances, + morph_source_spaces, + get_volume_labels_from_src, +) +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 . import beamformer +from . import channels +from . import chpi +from . import commands +from . import coreg +from . import cuda +from . import datasets +from . import dipole +from . import epochs +from . import event +from . import io +from . import filter +from . import gui +from . import inverse_sparse +from . import minimum_norm +from . import preprocessing +from . import simulation +from . import stats +from . import surface +from . import time_frequency +from . import viz +from . import decoding +from . import export +# initialize logging set_log_level(None, False) set_log_file() diff --git a/mne/_fiff/__init__.py b/mne/_fiff/__init__.py index 48a5871c58b..c9e95cab776 100644 --- a/mne/_fiff/__init__.py +++ b/mne/_fiff/__init__.py @@ -5,4 +5,21 @@ # 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 + + +_dep_msg = ( + "is deprecated will be removed in 1.6, use documented public API instead. " + "If no appropriate public API exists, please open an issue on GitHub." +) + + +# Helper for keeping some attributes en mne/io/*.py +def _io_dep_getattr(name, mod): + import importlib + from ..utils import warn + + fiff_mod = importlib.import_module(f"mne._fiff.{mod}") + obj = getattr(fiff_mod, name) + warn(f"mne.io.{mod}.{name} {_dep_msg}", FutureWarning) + return obj diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index 76d78782ac8..286fb552076 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -16,8 +16,62 @@ import numpy as np -from ..defaults import _handle_default -from ..html_templates import _get_html_template +from mne.defaults import _handle_default +from mne.html_templates import _get_html_template +from .pick import ( + channel_type, + _get_channel_types, + get_channel_type_constants, + pick_types, + _picks_to_idx, + _contains_ch_type, +) +from .constants import FIFF, _coord_frame_named, _ch_unit_mul_named +from .open import fiff_open +from .tree import dir_tree_find +from .tag import ( + read_tag, + find_tag, + _ch_coord_dict, + _update_ch_info_named, + _rename_list, + _int_item, + _float_item, +) +from .proj import ( + _read_proj, + _write_proj, + _uniquify_projs, + _normalize_proj, + _proj_equal, + Projection, +) +from .ctf_comp import _read_ctf_comp, write_ctf_comp +from .write import ( + start_and_end_file, + start_block, + end_block, + write_string, + write_dig_points, + write_float, + write_int, + write_coord_trans, + write_ch_info, + write_julian, + write_float_matrix, + write_id, + DATE_NONE, + _safe_name_list, + write_name_list_sanitized, +) +from .proc_history import _read_proc_history, _write_proc_history +from ..transforms import ( + invert_transform, + Transform, + _coord_frame_name, + _ensure_trans, + _frame_to_str, +) from ..utils import ( _check_fname, _check_on_missing, diff --git a/mne/_fiff/open.py b/mne/_fiff/open.py index 1d99bd8ddc2..36d6b3a2ad2 100644 --- a/mne/_fiff/open.py +++ b/mne/_fiff/open.py @@ -295,6 +295,8 @@ def _show_tree( show_bytes=False, ): """Show FIFF tree.""" + from scipy import sparse + 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..4084e56a426 100644 --- a/mne/_fiff/tag.py +++ b/mne/_fiff/tag.py @@ -24,6 +24,7 @@ _dig_kind_named, ) + ############################################################################## # HELPERS @@ -146,6 +147,8 @@ def _read_tag_header(fid, pos): def _read_matrix(fid, tag, shape, rlims): """Read a matrix (dense or sparse) tag.""" + from scipy import sparse + # 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..7cad2287c95 100644 --- a/mne/_fiff/write.py +++ b/mne/_fiff/write.py @@ -422,6 +422,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 import sparse + 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 135ff835da3..23ed618c296 100644 --- a/mne/_ola.py +++ b/mne/_ola.py @@ -3,10 +3,10 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy.signal import get_window from .utils import _ensure_int, logger, verbose + ############################################################################### # Class for interpolation between adjacent points @@ -278,6 +278,8 @@ def __init__( *, 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..021c00b8fb5 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,11 +1298,12 @@ def _read_brainstorm_annotations(fname, orig_time=None): annot : instance of Annotations | None The annotations. """ + from scipy import io def get_duration_from_times(t): return t[1] - t[0] if t.shape[0] == 2 else np.zeros(len(t[0])) - annot_data = loadmat(fname) + annot_data = io.loadmat(fname) onsets, durations, descriptions = (list(), list(), list()) for label, _, _, _, times, _, _ in annot_data["events"][0]: onsets.append(times[0]) diff --git a/mne/beamformer/__init__.py b/mne/beamformer/__init__.py index 193cb2e2be8..5493ced6959 100644 --- a/mne/beamformer/__init__.py +++ b/mne/beamformer/__init__.py @@ -4,36 +4,21 @@ """Beamformers for source localization.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "_lcmv": [ - "make_lcmv", - "apply_lcmv", - "apply_lcmv_epochs", - "apply_lcmv_raw", - "apply_lcmv_cov", - ], - "_dics": [ - "make_dics", - "apply_dics", - "apply_dics_epochs", - "apply_dics_tfr_epochs", - "apply_dics_csd", - ], - "_rap_music": [ - "rap_music", - "trap_music", - ], - "_compute_beamformer": [ - "Beamformer", - "read_beamformer", - ], - "resolution_matrix": [ - "make_lcmv_resolution_matrix", - ], - }, +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/_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..075c6ec8b30 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 @@ -71,6 +70,7 @@ ) from .viz.misc import plot_bem + # ############################################################################ # Compute BEM solution @@ -791,6 +791,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 +1109,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 676fa47c09b..b3706c0d21f 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -7,51 +7,82 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "channels": [ - "equalize_channels", - "rename_channels", - "fix_mag_coil_types", - "read_ch_adjacency", - "find_ch_adjacency", - "make_1020_channel_selections", - "combine_channels", - "read_vectorview_selection", - "_SELECTIONS", - "_EEG_SELECTIONS", - "_divide_to_regions", - "get_builtin_ch_adjacencies", - "unify_bad_channels", - ], - "layout": [ - "Layout", - "make_eeg_layout", - "make_grid_layout", - "read_layout", - "find_layout", - "generate_2d_layout", - ], - "montage": [ - "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", - "compute_native_head_t", - ], - }, +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, + compute_native_head_t, ) +from .channels import ( + equalize_channels, + rename_channels, + fix_mag_coil_types, + read_ch_adjacency, + _get_ch_type, + find_ch_adjacency, + make_1020_channel_selections, + combine_channels, + read_vectorview_selection, + _SELECTIONS, + _EEG_SELECTIONS, + _divide_to_regions, + get_builtin_ch_adjacencies, +) + +__all__ = [ + # Data Structures + "DigMontage", + "Layout", + # 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", + # Other + "compute_dev_head_t", + "compute_native_head_t", +] diff --git a/mne/channels/channels.py b/mne/channels/channels.py index d0e57eecb5f..2e36d5caf37 100644 --- a/mne/channels/channels.py +++ b/mne/channels/channels.py @@ -1520,6 +1520,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 +1585,8 @@ def _ch_neighbor_adjacency(ch_names, neighbors): ch_adjacency : scipy.sparse.spmatrix The adjacency matrix. """ + from scipy import sparse + 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} @@ -2161,6 +2165,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..353252892c0 100644 --- a/mne/channels/interpolation.py +++ b/mne/channels/interpolation.py @@ -85,6 +85,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 import linalg + pos_from = pos_from.copy() pos_to = pos_to.copy() n_from = pos_from.shape[0] @@ -111,7 +113,7 @@ def _make_interpolation_matrix(pos_from, pos_to, alpha=1e-5): np.hstack([np.ones((1, n_from)), [[0]]]), ] ) - C_inv = pinv(C) + C_inv = linalg.pinv(C) interpolation = np.hstack([G_to_from, np.ones((n_to, 1))]) @ C_inv[:, :-1] assert interpolation.shape == (n_to, n_from) 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 d04081779c9..14f2c4c4c72 100644 --- a/mne/chpi.py +++ b/mne/chpi.py @@ -24,9 +24,7 @@ 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 +import itertools from ._fiff.constants import FIFF from ._fiff.meas_info import Info, _simplify_info @@ -538,6 +536,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 +722,8 @@ def _reorder_inv_model(inv_model, n_freqs): def _setup_ext_proj(info, ext_order): + from scipy import linalg + 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( @@ -733,7 +735,7 @@ def _setup_ext_proj(info, ext_order): ).T out_removes = _regularize_out(0, 1, mag_or_fine, []) ext = ext[~np.isin(np.arange(len(ext)), out_removes)] - ext = orth(ext.T).T + ext = linalg.orth(ext.T).T assert ext.shape[1] == len(meg_picks) proj = Projection( kind=FIFF.FIFFV_PROJ_ITEM_HOMOG_FIELD, @@ -1548,6 +1550,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 0d50003f7c4..bb2c3c0311e 100644 --- a/mne/commands/__init__.py +++ b/mne/commands/__init__.py @@ -4,10 +4,4 @@ """Command-line utilities.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["utils"], - submod_attrs={}, -) +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..72b757e740e 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 @@ -299,7 +298,7 @@ def __iadd__(self, cov): return self @verbose - @copy_function_doc_to_method_doc(viz.plot_cov) + @copy_function_doc_to_method_doc(viz.misc.plot_cov) def plot( self, info, @@ -310,7 +309,7 @@ def plot( show=True, verbose=None, ): - return viz.plot_cov( + return viz.misc.plot_cov( self, info, exclude, colorbar, proj, show_svd, show, verbose ) @@ -2343,6 +2342,7 @@ 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 import sparse from ._fiff.write import _safe_name_list covs = dir_tree_find(node, FIFF.FIFFB_MNE_COV) @@ -2407,7 +2407,7 @@ def _read_cov(fid, node, cov_kind, limited=False, verbose=None): ) else: - if not issparse(tag.data): + if not sparse.issparse(tag.data): # Lower diagonal is stored vals = tag.data data = np.zeros((dim, dim)) diff --git a/mne/cuda.py b/mne/cuda.py index f44dc653a1e..18ea5b6e537 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, @@ -16,6 +15,7 @@ warn, ) + _cuda_capable = False @@ -166,6 +166,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 rfft, irfft + 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 +263,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 rfft, irfft + 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 3831c722719..1549fa21f8f 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -3,51 +3,73 @@ See :ref:`datasets` for more information. """ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[ - "fieldtrip_cmc", - "brainstorm", - "visual_92_categories", - "kiloword", - "eegbci", - "hf_sef", - "misc", - "mtrf", - "sample", - "somato", - "multimodal", - "fnirs_motor", - "opm", - "spm_face", - "testing", - "_fake", - "phantom_4dbti", - "sleep_physionet", - "limo", - "refmeg_noise", - "ssvep", - "erp_core", - "epilepsy_ecog", - "eyelink", - "ucl_opm_auditory", - ], - submod_attrs={ - "_fetch": ["fetch_dataset"], - "_fsaverage.base": ["fetch_fsaverage"], - "_infant.base": ["fetch_infant_template"], - "_phantom.base": ["fetch_phantom"], - "utils": [ - "_download_all_example_data", - "fetch_hcp_mmp_parcellation", - "fetch_aparc_sub_parcellation", - "has_dataset", - ], - }, +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 +from ._fetch import fetch_dataset +from .utils import ( + _download_all_example_data, + fetch_hcp_mmp_parcellation, + fetch_aparc_sub_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", + "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", +] diff --git a/mne/datasets/limo/limo.py b/mne/datasets/limo/limo.py index f0696a78b1e..cf7a5c78d92 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 @@ -15,6 +14,7 @@ from ...utils import _check_pandas_installed, logger, verbose from ..utils import _do_path_update, _downloader_params, _get_path, _log_time_size + # root url for LIMO files root_url = "https://files.de-1.osf.io/v1/resources/52rea/providers/osfstorage/" @@ -290,6 +290,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 8ff92748aca..6fa09000f0b 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -3,28 +3,21 @@ # Copyright the MNE-Python contributors. """Decoding and encoding, including machine learning and receptive fields.""" -import lazy_loader as lazy -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "base": ["BaseEstimator", "LinearModel", "cross_val_multiscore", "get_coef"], - "csp": ["CSP", "SPoC"], - "ems": ["EMS", "compute_ems"], - "mixin": ["TransformerMixin"], - "receptive_field": ["ReceptiveField"], - "search_light": ["GeneralizingEstimator", "SlidingEstimator"], - "ssd": ["SSD"], - "time_delaying_ridge": ["TimeDelayingRidge"], - "time_frequency": ["TimeFrequency"], - "transformer": [ - "FilterEstimator", - "PSDEstimator", - "Scaler", - "TemporalFilter", - "UnsupervisedSpatialFilter", - "Vectorizer", - ], - }, +from .transformer import ( + PSDEstimator, + Vectorizer, + UnsupervisedSpatialFilter, + TemporalFilter, + Scaler, + FilterEstimator, ) +from .mixin import 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/csp.py b/mne/decoding/csp.py index ea38fd58ca3..3eed6656c88 100644 --- a/mne/decoding/csp.py +++ b/mne/decoding/csp.py @@ -621,6 +621,8 @@ def _epoch_cov(self, x_class, *, cov_kind, log_rank): return cov, weight def _decompose_covs(self, covs, sample_weights): + from scipy import linalg + n_classes = len(covs) n_channels = covs[0].shape[0] assert self._rank is not None # should happen in _compute_covariance_matrices @@ -637,7 +639,7 @@ def _decompose_covs(self, covs, sample_weights): covs = np.array([sub_vec @ cov @ sub_vec.T for cov in covs], float) assert covs[0].shape == (mask.sum(),) * 2 if n_classes == 2: - eigen_values, eigen_vectors = eigh(covs[0], covs.sum(0)) + eigen_values, eigen_vectors = linalg.eigh(covs[0], covs.sum(0)) else: # The multiclass case is adapted from # http://github.com/alexandrebarachant/pyRiemann diff --git a/mne/decoding/receptive_field.py b/mne/decoding/receptive_field.py index 99412cf56b7..fb65a96082b 100644 --- a/mne/decoding/receptive_field.py +++ b/mne/decoding/receptive_field.py @@ -184,6 +184,8 @@ def fit(self, X, y): self : instance The instance so you can chain operations. """ + from scipy import linalg + if self.scoring not in _SCORERS.keys(): raise ValueError( f"scoring must be one of {sorted(_SCORERS.keys())}, got {self.scoring} " @@ -270,7 +272,7 @@ def fit(self, X, y): # Inverse output covariance if y.ndim == 2 and y.shape[1] != 1: y = y - y.mean(0, keepdims=True) - inv_Y = pinv(np.cov(y.T)) + inv_Y = linalg.pinv(np.cov(y.T)) else: inv_Y = 1.0 / float(n_times * n_epochs - 1) del y @@ -505,6 +507,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..cd02c0f07c8 100644 --- a/mne/decoding/ssd.py +++ b/mne/decoding/ssd.py @@ -371,6 +371,8 @@ def apply(self, X): def _dimensionality_reduction(cov_signal, cov_noise, info, rank): """Perform dimensionality reduction on the covariance matrices.""" + from scipy import linalg + n_channels = cov_signal.shape[0] # find ranks of covariance matrices @@ -407,7 +409,7 @@ def _dimensionality_reduction(cov_signal, cov_noise, info, rank): rank = np.min([rank_signal, rank_noise]) # should be identical if rank < n_channels: - eigvals, eigvects = eigh(cov_signal) + eigvals, eigvects = linalg.eigh(cov_signal) # sort in descending order ix = np.argsort(eigvals)[::-1] eigvals = eigvals[ix] diff --git a/mne/decoding/time_delaying_ridge.py b/mne/decoding/time_delaying_ridge.py index b08f997e4f4..4f13e262509 100644 --- a/mne/decoding/time_delaying_ridge.py +++ b/mne/decoding/time_delaying_ridge.py @@ -153,6 +153,9 @@ 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 + from scipy.sparse.csgraph import laplacian + known_types = ("ridge", "laplacian") if isinstance(reg_type, str): reg_type = (reg_type,) * 2 @@ -205,6 +208,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.""" # do the regularized solving + from scipy import linalg + n_ch_out = x_y.shape[1] assert x_y.shape[0] % n_ch_x == 0 n_delays = x_y.shape[0] // n_ch_x @@ -375,6 +380,8 @@ def predict(self, X): X : ndarray The predicted response. """ + from scipy.signal import fftconvolve + if X.ndim == 2: X = X[:, np.newaxis, :] singleton = True diff --git a/mne/dipole.py b/mne/dipole.py index a40e9708db2..906daba9c06 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 import linalg + direction = np.empty((3, 3)) # The coordinate system has the x axis aligned with the dipole orientation, direction[0] = ori @@ -1190,7 +1192,7 @@ def _fit_confidence(*, rd, Q, ori, whitener, fwd_data, sensors): 4 * np.pi / 3.0 - * np.sqrt(476.379541 * np.prod(eigh(C[:3, :3], eigvals_only=True))) + * np.sqrt(476.379541 * np.prod(linalg.eigh(C[:3, :3], eigvals_only=True))) ) conf = np.concatenate([conf, [vol_conf]]) # Now we reorder and subselect the proper columns: diff --git a/mne/epochs.py b/mne/epochs.py index ee8921d3990..32c8baef7a0 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..919fb518ce6 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -45,6 +45,7 @@ from .parallel import parallel_func from .time_frequency.spectrum import Spectrum, SpectrumMixin, _validate_method from .time_frequency.tfr import AverageTFR +from .io.base import _check_maxshield, _get_ch_factors from .utils import ( ExtendedTimeMixin, SizeMixin, @@ -251,9 +252,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=()) start, stop = self._handle_tmin_tmax(tmin, tmax) @@ -1529,8 +1527,6 @@ def _get_entries(fid, evoked_node, allow_maxshield=False): def _get_aspect(evoked, allow_maxshield): """Get Evoked data aspect.""" - from .io.base import _check_maxshield - is_maxshield = False aspect = dir_tree_find(evoked, FIFF.FIFFB_ASPECT) if len(aspect) == 0: diff --git a/mne/export/__init__.py b/mne/export/__init__.py index 4ee7ca57feb..9d7abae0aff 100644 --- a/mne/export/__init__.py +++ b/mne/export/__init__.py @@ -1,16 +1,2 @@ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -"""Functions for exporting data to non-FIF formats.""" - -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "_export": ["export_raw", "export_epochs", "export_evokeds"], - "_egimff": ["export_evokeds_mff"], - }, -) +from ._export import export_raw, export_epochs, export_evokeds +from ._egimff import export_evokeds_mff diff --git a/mne/filter.py b/mne/filter.py index a7d7c883e2f..2079b7662bf 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -10,8 +10,7 @@ from math import gcd import numpy as np -from scipy import fft, signal -from scipy.stats import f as fstat +from scipy import signal from ._fiff.pick import _picks_to_idx from ._ola import _COLA @@ -387,7 +386,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 +421,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 +447,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 +478,12 @@ 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) + from scipy.signal import minimum_phase + # issue a warning if attenuation is less than this min_att_db = 12 if phase == "minimum-half" else 20 @@ -526,9 +531,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 +549,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, sosfiltfilt, lfilter, sosfilt + 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 +597,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 +778,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 iirfilter, iirdesign, freqz, sosfreqz + known_filters = ( "bessel", "butter", @@ -842,7 +851,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 +866,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 +883,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 +1591,7 @@ 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 +1600,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 @@ -1853,10 +1863,23 @@ def resample( important consequences, and the default choices should work well for most natural signals. """ + from scipy.signal import get_window + from scipy.fft import ifftshift, fftfreq + _validate_type(method, str, "method") _validate_type(pad, str, "pad") _check_option("method", method, ("fft", "polyphase")) + # check explicitly for backwards compatibility + if not isinstance(axis, int): + err = ( + "The axis parameter needs to be an integer (got %s). " + "The axis parameter was missing from this function for a " + "period of time, you might be intending to specify the " + "subsequent window parameter." % repr(axis) + ) + raise TypeError(err) + # make sure our arithmetic will work x = _check_filterable(x, "resampled", "resample") ratio, final_len = _resamp_ratio_len(up, down, x.shape[axis]) @@ -2063,6 +2086,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: @@ -2072,7 +2097,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 @@ -2812,8 +2837,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 @@ -2860,6 +2887,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) @@ -2899,7 +2928,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..b5b77cd21f3 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -1,4 +1,4 @@ -"""Compatibility fixes for older versions of libraries. +"""Compatibility fixes for older versions of libraries If you add content to this file, please give the version of the package at which the fix is no longer needed. @@ -24,6 +24,7 @@ import numpy as np from packaging.version import parse + ############################################################################### # distutils LooseVersion removed in Python 3.12 @@ -49,9 +50,7 @@ def _compare_version(version_a, operator, version_b): mapping = {"<": "lt", "<=": "le", "==": "eq", "!=": "ne", ">=": "ge", ">": "gt"} with warnings.catch_warnings(record=True): warnings.simplefilter("ignore") - ver_a = parse(version_a) - ver_b = parse(version_b) - return getattr(operator_module, mapping[operator])(ver_a, ver_b) + return eval(f'parse("{version_a}") {operator} parse("{version_b}")') ############################################################################### @@ -75,7 +74,7 @@ def _median_complex(data, axis): def _safe_svd(A, **kwargs): - """Get around the SVD did not converge error of death.""" + """Wrapper to get around 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 @@ -116,6 +115,19 @@ def rng_uniform(rng): return getattr(rng, "integers", getattr(rng, "randint", None)) +def _validate_sos(sos): + """Helper to validate a SOS input""" + sos = np.atleast_2d(sos) + if sos.ndim != 2: + raise ValueError("sos array must be 2D") + n_sections, m = sos.shape + if m != 6: + raise ValueError("sos array must be shape (n_sections, 6)") + if not (sos[:, 3] == 1).all(): + raise ValueError("sos[:, 3] should be all ones") + return sos, n_sections + + ############################################################################### # Misc utilities @@ -133,7 +145,8 @@ def _get_img_fdata(img): def empirical_covariance(X, assume_centered=False): - """Compute the Maximum likelihood covariance estimator. + """Computes the Maximum likelihood covariance estimator + Parameters ---------- @@ -150,6 +163,7 @@ def empirical_covariance(X, assume_centered=False): ------- covariance : 2D ndarray, shape (n_features, n_features) Empirical covariance (Maximum Likelihood Estimator). + """ X = np.asarray(X) if X.ndim == 1: @@ -253,6 +267,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): @@ -260,7 +275,7 @@ def __init__(self, store_precision=True, assume_centered=False): self.assume_centered = assume_centered def _set_covariance(self, covariance): - """Save the covariance and precision estimates. + """Saves the covariance and precision estimates Storage is done accordingly to `self.store_precision`. Precision stored only if invertible. @@ -270,6 +285,7 @@ def _set_covariance(self, covariance): covariance : 2D ndarray, shape (n_features, n_features) Estimated covariance matrix to be stored, and from which precision is computed. + """ from scipy import linalg @@ -354,7 +370,7 @@ def score(self, X_test, y=None): return res def error_norm(self, comp_cov, norm="frobenius", scaling=True, squared=True): - """Compute the Mean Squared Error between two covariance estimators. + """Computes the Mean Squared Error between two covariance estimators. Parameters ---------- @@ -403,7 +419,7 @@ def error_norm(self, comp_cov, norm="frobenius", scaling=True, squared=True): return result def mahalanobis(self, observations): - """Compute the squared Mahalanobis distances of given observations. + """Computes the squared Mahalanobis distances of given observations. Parameters ---------- @@ -416,6 +432,7 @@ def mahalanobis(self, observations): ------- mahalanobis_distance : array, shape = [n_observations,] Squared Mahalanobis distances of the observations. + """ precision = self.get_precision() # compute mahalanobis distances @@ -426,7 +443,7 @@ def mahalanobis(self, observations): def log_likelihood(emp_cov, precision): - """Compute the sample mean of the log_likelihood under a covariance model. + """Computes the sample mean of the log_likelihood under a covariance model computes the empirical expected log-likelihood (accounting for the normalization terms and scaling), allowing for universal comparison (beyond @@ -466,8 +483,7 @@ def _logdet(A): def _infer_dimension_(spectrum, n_samples, n_features): - """Infer the dimension of a dataset of shape (n_samples, n_features). - + """Infers the dimension of a dataset of shape (n_samples, n_features) The dataset is described by its spectrum `spectrum`. """ n_spectrum = len(spectrum) @@ -514,7 +530,7 @@ 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): if u_based_decision: # columns of u, rows of v max_abs_cols = np.argmax(np.abs(u), axis=0) @@ -531,7 +547,7 @@ def svd_flip(u, v, u_based_decision=True): # noqa: D103 def stable_cumsum(arr, axis=None, rtol=1e-05, atol=1e-08): - """Use high precision for cumsum and check that final value matches sum. + """Use high precision for cumsum and check that final value matches sum Parameters ---------- @@ -565,10 +581,12 @@ def stable_cumsum(arr, axis=None, rtol=1e-05, atol=1e-08): def _crop_colorbar(cbar, cbar_vmin, cbar_vmax): - """Crop a colorbar to show from cbar_vmin to cbar_vmax. - + """ + crop a colorbar to show from cbar_vmin to cbar_vmax Used when symmetric_cbar=False is used. """ + import matplotlib + if (cbar_vmin is None) and (cbar_vmax is None): return cbar_tick_locs = cbar.locator.locs diff --git a/mne/forward/__init__.py b/mne/forward/__init__.py index ecc43e47afa..2ae65536a52 100644 --- a/mne/forward/__init__.py +++ b/mne/forward/__init__.py @@ -4,56 +4,49 @@ """Forward modeling code.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["_lead_dots"], # for testing purposes - submod_attrs={ - "forward": [ - "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", - ], - "_compute_forward": [ - "_magnetic_dipole_field_vec", - "_compute_forwards", - "_concatenate_coils", - ], - "_field_interpolation": [ - "_make_surface_mapping", - "make_field_map", - "_as_meg_type_inst", - "_map_meg_or_eeg_channels", - ], - "_make_forward": [ - "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 .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, ) +from . import _lead_dots # for testing purposes 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..eb1340576ca 100644 --- a/mne/forward/forward.py +++ b/mne/forward/forward.py @@ -293,6 +293,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 +751,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 05ca2357ad5..7cd0519d0a5 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -4,10 +4,287 @@ """Convenience functions for opening GUIs.""" -import lazy_loader as lazy +# Authors: Christian Brodbeck +# +# License: BSD-3-Clause + +from ..utils import verbose, get_config, warn + + +@verbose +def coregistration( + tabbed=False, + split=True, + width=None, + inst=None, + subject=None, + subjects_dir=None, + guess_mri_subject=None, + height=None, + head_opacity=None, + head_high_res=None, + trans=None, + scrollable=True, + *, + orient_to_surface=True, + scale_by_distance=True, + mark_inside=True, + interaction=None, + scale=None, + advanced_rendering=None, + head_inside=True, + fullscreen=None, + show=True, + block=False, + verbose=None, +): + """Coregister an MRI with a subject's head shape. + + The GUI can be launched through the command line interface: + + .. code-block:: bash + + $ mne coreg + + or using a python interpreter as shown in :ref:`tut-source-alignment`. + + Parameters + ---------- + tabbed : bool + Combine the data source panel and the coregistration panel into a + single panel with tabs. + split : bool + Split the main panels with a movable splitter (good for QT4 but + unnecessary for wx backend). + width : int | None + Specify the width for window (in logical pixels). + Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value + (which defaults to 800). + inst : None | str + Path to an instance file containing the digitizer data. Compatible for + Raw, Epochs, and Evoked files. + subject : None | str + Name of the mri subject. + %(subjects_dir)s + guess_mri_subject : bool + When selecting a new head shape file, guess the subject's name based + on the filename and change the MRI subject accordingly (default True). + height : int | None + Specify a height for window (in logical pixels). + Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value + (which defaults to 400). + head_opacity : float | None + The opacity of the head surface in the range [0., 1.]. + Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value + (which defaults to 1.). + head_high_res : bool | None + Use a high resolution head surface. + Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value + (which defaults to True). + trans : path-like | None + The transform file to use. + scrollable : bool + Make the coregistration panel vertically scrollable (default True). + orient_to_surface : bool | None + If True (default), orient EEG electrode and head shape points + to the head surface. + + .. versionadded:: 0.16 + scale_by_distance : bool | None + If True (default), scale the digitization points by their + distance from the scalp surface. + + .. versionadded:: 0.16 + mark_inside : bool | None + If True (default), mark points inside the head surface in a + different color. + + .. versionadded:: 0.16 + %(interaction_scene_none)s + Defaults to ``'terrain'``. + + .. versionadded:: 0.16 + .. versionchanged:: 1.0 + Default interaction mode if ``None`` and no config setting found + changed from ``'trackball'`` to ``'terrain'``. + scale : float | None + The scaling for the scene. + + .. versionadded:: 0.16 + advanced_rendering : bool + Use advanced OpenGL rendering techniques (default True). + For some renderers (such as MESA software) this can cause rendering + bugs. + + .. versionadded:: 0.18 + head_inside : bool + If True (default), add opaque inner scalp head surface to help occlude + points behind the head. + + .. versionadded:: 0.23 + %(fullscreen)s + Default is None, which uses ``MNE_COREG_FULLSCREEN`` config value + (which defaults to False). + + .. versionadded:: 1.1 + show : bool + Show the GUI if True. + block : bool + Whether to halt program execution until the figure is closed. + %(verbose)s + + Returns + ------- + frame : instance of CoregistrationUI + The coregistration frame. + + Notes + ----- + Many parameters (e.g., ``head_opacity``) take None as a parameter, + which means that the default will be read from the MNE-Python + configuration file (which gets saved when exiting). + + Step by step instructions for the coregistrations are shown below: + + .. youtube:: ALV5qqMHLlQ + """ + unsupported_params = { + "tabbed": (tabbed, False), + "split": (split, True), + "scrollable": (scrollable, True), + "head_inside": (head_inside, True), + "guess_mri_subject": guess_mri_subject, + "scale": scale, + "advanced_rendering": advanced_rendering, + } + for key, val in unsupported_params.items(): + if isinstance(val, tuple): + to_raise = val[0] != val[1] + else: + to_raise = val is not None + if to_raise: + warn( + f"The parameter {key} is not supported with" + " the pyvistaqt 3d backend. It will be ignored." + ) + config = get_config() + if guess_mri_subject is None: + guess_mri_subject = config.get("MNE_COREG_GUESS_MRI_SUBJECT", "true") == "true" + if head_high_res is None: + head_high_res = config.get("MNE_COREG_HEAD_HIGH_RES", "true") == "true" + if advanced_rendering is None: + advanced_rendering = ( + config.get("MNE_COREG_ADVANCED_RENDERING", "true") == "true" + ) + if head_opacity is None: + head_opacity = config.get("MNE_COREG_HEAD_OPACITY", 0.8) + if head_inside is None: + head_inside = config.get("MNE_COREG_HEAD_INSIDE", "true").lower() == "true" + if width is None: + width = config.get("MNE_COREG_WINDOW_WIDTH", 800) + if height is None: + height = config.get("MNE_COREG_WINDOW_HEIGHT", 600) + if subjects_dir is None: + if "SUBJECTS_DIR" in config: + subjects_dir = config["SUBJECTS_DIR"] + elif "MNE_COREG_SUBJECTS_DIR" in config: + subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] + if orient_to_surface is None: + orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "") == "true" + if scale_by_distance is None: + scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "") == "true" + if interaction is None: + interaction = config.get("MNE_COREG_INTERACTION", "terrain") + if mark_inside is None: + mark_inside = config.get("MNE_COREG_MARK_INSIDE", "") == "true" + if scale is None: + scale = config.get("MNE_COREG_SCENE_SCALE", 0.16) + if fullscreen is None: + fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" + head_opacity = float(head_opacity) + head_inside = bool(head_inside) + width = int(width) + height = int(height) + scale = float(scale) + + from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING + from ._coreg import CoregistrationUI + + if MNE_3D_BACKEND_TESTING: + show = block = False + return CoregistrationUI( + info_file=inst, + subject=subject, + subjects_dir=subjects_dir, + head_resolution=head_high_res, + head_opacity=head_opacity, + orient_glyphs=orient_to_surface, + scale_by_distance=scale_by_distance, + mark_inside=mark_inside, + trans=trans, + size=(width, height), + show=show, + block=block, + interaction=interaction, + fullscreen=fullscreen, + verbose=verbose, + ) + + +class _GUIScraper: + """Scrape GUI outputs.""" + + def __repr__(self): + return "" + + def __call__(self, block, block_vars, gallery_conf): + from ._coreg import CoregistrationUI + + gui_classes = (CoregistrationUI,) + try: + from mne_gui_addons._ieeg_locate import ( + IntracranialElectrodeLocator, + ) # noqa: E501 + except Exception: + pass + else: + gui_classes = gui_classes + (IntracranialElectrodeLocator,) + from sphinx_gallery.scrapers import figure_rst + from qtpy import QtGui -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={"_gui": ["coregistration", "_GUIScraper"]}, -) + for gui in block_vars["example_globals"].values(): + if ( + isinstance(gui, gui_classes) + and not getattr(gui, "_scraped", False) + and gallery_conf["builder_name"] == "html" + ): + gui._scraped = True # monkey-patch but it's easy enough + img_fname = next(block_vars["image_path_iterator"]) + # TODO fix in window refactor + window = gui if hasattr(gui, "grab") else gui._renderer._window + # window is QWindow + # https://doc.qt.io/qt-5/qwidget.html#grab + pixmap = window.grab() + if hasattr(gui, "_renderer"): # if no renderer, no need + # Now the tricky part: we need to get the 3D renderer, + # extract the image from it, and put it in the correct + # place in the pixmap. The easiest way to do this is + # actually to save the 3D image first, then load it + # using QPixmap and Qt geometry. + plotter = gui._renderer.plotter + plotter.screenshot(img_fname) + sub_pixmap = QtGui.QPixmap(img_fname) + # https://doc.qt.io/qt-5/qwidget.html#mapTo + # https://doc.qt.io/qt-5/qpainter.html#drawPixmap-1 + QtGui.QPainter(pixmap).drawPixmap( + plotter.mapTo(window, plotter.rect().topLeft()), sub_pixmap + ) + # https://doc.qt.io/qt-5/qpixmap.html#save + pixmap.save(img_fname) + try: # for compatibility with both GUIs, will be refactored + gui._renderer.close() # TODO should be triggered by close + except Exception: + pass + gui.close() + return figure_rst([img_fname], gallery_conf["src_dir"], "GUI") + return "" diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index b365a2eed5a..f2fcbf96748 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -17,6 +17,9 @@ import numpy as np from traitlets import Bool, Float, HasTraits, Instance, Unicode, observe +from ..io import read_raw +from ..io._read_raw import supported as raw_supported_types +from ..defaults import DEFAULTS from .._fiff.constants import FIFF from .._fiff.meas_info import _empty_info, read_fiducials, read_info, write_fiducials from .._fiff.open import dir_tree_find, fiff_open @@ -449,13 +452,12 @@ def _set_info_file(self, fname): return # info file can be anything supported by read_raw - supported = _get_supported() try: check_fname( fname, "info", - tuple(supported), - endings_err=tuple(supported), + tuple(raw_supported_types.keys()), + endings_err=tuple(raw_supported_types.keys()), ) fname = Path(fname) # ctf ds `files` are actually directories diff --git a/mne/gui/_gui.py b/mne/gui/_gui.py deleted file mode 100644 index b8898d8b7c2..00000000000 --- a/mne/gui/_gui.py +++ /dev/null @@ -1,222 +0,0 @@ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -from ..utils import get_config, verbose - - -@verbose -def coregistration( - *, - width=None, - height=None, - inst=None, - subject=None, - subjects_dir=None, - head_opacity=None, - head_high_res=None, - trans=None, - orient_to_surface=None, - scale_by_distance=None, - mark_inside=None, - interaction=None, - fullscreen=None, - show=True, - block=False, - verbose=None, -): - """Coregister an MRI with a subject's head shape. - - The GUI can be launched through the command line interface: - - .. code-block:: bash - - $ mne coreg - - or using a python interpreter as shown in :ref:`tut-source-alignment`. - - Parameters - ---------- - width : int | None - Specify the width for window (in logical pixels). - Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to ``800``). - height : int | None - Specify a height for window (in logical pixels). - Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to ``400``). - inst : None | path-like - Path to an instance file containing the digitizer data. Compatible for - Raw, Epochs, and Evoked files. - subject : None | str - Name of the mri subject. - %(subjects_dir)s - head_opacity : float | None - The opacity of the head surface in the range ``[0., 1.]``. - Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value - (which defaults to ``1.``). - head_high_res : bool | None - Use a high resolution head surface. - Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value - (which defaults to True). - trans : path-like | Transform | None - The Head<->MRI transform or the path to its FIF file (``"-trans.fif"``). - orient_to_surface : bool | None - If True (default), orient EEG electrode and head shape points to the head - surface. - - .. versionadded:: 0.16 - scale_by_distance : bool | None - If True (default), scale the digitization points by their distance from the - scalp surface. - - .. versionadded:: 0.16 - mark_inside : bool | None - If True (default), mark points inside the head surface in a - different color. - - .. versionadded:: 0.16 - %(interaction_scene_none)s - Defaults to ``'terrain'``. - - .. versionadded:: 0.16 - .. versionchanged:: 1.0 - Default interaction mode if ``None`` and no config setting found - changed from ``'trackball'`` to ``'terrain'``. - %(fullscreen)s - Default is ``None``, which uses ``MNE_COREG_FULLSCREEN`` config value - (which defaults to ``False``). - - .. versionadded:: 1.1 - show : bool - Show the GUI if True. - block : bool - Whether to halt program execution until the figure is closed. - %(verbose)s - - Returns - ------- - frame : instance of CoregistrationUI - The coregistration frame. - - Notes - ----- - Many parameters (e.g., ``head_opacity``) take None as a parameter, - which means that the default will be read from the MNE-Python - configuration file (which gets saved when exiting). - - Step by step instructions for the coregistrations are shown below: - - .. youtube:: ALV5qqMHLlQ - """ - config = get_config() - if head_high_res is None: - head_high_res = config.get("MNE_COREG_HEAD_HIGH_RES", "true") == "true" - if head_opacity is None: - head_opacity = config.get("MNE_COREG_HEAD_OPACITY", 0.8) - if width is None: - width = config.get("MNE_COREG_WINDOW_WIDTH", 800) - if height is None: - height = config.get("MNE_COREG_WINDOW_HEIGHT", 600) - if subjects_dir is None: - if "SUBJECTS_DIR" in config: - subjects_dir = config["SUBJECTS_DIR"] - elif "MNE_COREG_SUBJECTS_DIR" in config: - subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] - false_like = ("false", "0") - if orient_to_surface is None: - orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "true").lower() - orient_to_surface = orient_to_surface not in false_like - if scale_by_distance is None: - scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "true").lower() - scale_by_distance = scale_by_distance not in false_like - if interaction is None: - interaction = config.get("MNE_COREG_INTERACTION", "terrain") - if mark_inside is None: - mark_inside = config.get("MNE_COREG_MARK_INSIDE", "true").lower() - mark_inside = mark_inside not in false_like - if fullscreen is None: - fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" - head_opacity = float(head_opacity) - width = int(width) - height = int(height) - - from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING - from ._coreg import CoregistrationUI - - if MNE_3D_BACKEND_TESTING: - show = block = False - return CoregistrationUI( - info_file=inst, - subject=subject, - subjects_dir=subjects_dir, - head_resolution=head_high_res, - head_opacity=head_opacity, - orient_glyphs=orient_to_surface, - scale_by_distance=scale_by_distance, - mark_inside=mark_inside, - trans=trans, - size=(width, height), - show=show, - block=block, - interaction=interaction, - fullscreen=fullscreen, - verbose=verbose, - ) - - -class _GUIScraper: - """Scrape GUI outputs.""" - - def __repr__(self): - return "" - - def __call__(self, block, block_vars, gallery_conf): - from ._coreg import CoregistrationUI - - gui_classes = (CoregistrationUI,) - try: - from mne_gui_addons._ieeg_locate import IntracranialElectrodeLocator - except Exception: - pass - else: - gui_classes = gui_classes + (IntracranialElectrodeLocator,) - from qtpy import QtGui - from sphinx_gallery.scrapers import figure_rst - - for gui in block_vars["example_globals"].values(): - if ( - isinstance(gui, gui_classes) - and not getattr(gui, "_scraped", False) - and gallery_conf["builder_name"] == "html" - ): - gui._scraped = True # monkey-patch but it's easy enough - img_fname = next(block_vars["image_path_iterator"]) - # TODO fix in window refactor - window = gui if hasattr(gui, "grab") else gui._renderer._window - # window is QWindow - # https://doc.qt.io/qt-5/qwidget.html#grab - pixmap = window.grab() - if hasattr(gui, "_renderer"): # if no renderer, no need - # Now the tricky part: we need to get the 3D renderer, - # extract the image from it, and put it in the correct - # place in the pixmap. The easiest way to do this is - # actually to save the 3D image first, then load it - # using QPixmap and Qt geometry. - plotter = gui._renderer.plotter - plotter.screenshot(img_fname) - sub_pixmap = QtGui.QPixmap(img_fname) - # https://doc.qt.io/qt-5/qwidget.html#mapTo - # https://doc.qt.io/qt-5/qpainter.html#drawPixmap-1 - QtGui.QPainter(pixmap).drawPixmap( - plotter.mapTo(window, plotter.rect().topLeft()), sub_pixmap - ) - # https://doc.qt.io/qt-5/qpixmap.html#save - pixmap.save(img_fname) - try: # for compatibility with both GUIs, will be refactored - gui._renderer.close() # TODO should be triggered by close - except Exception: - pass - gui.close() - return figure_rst([img_fname], gallery_conf["src_dir"], "GUI") - return "" diff --git a/mne/html_templates/__init__.py b/mne/html_templates/__init__.py index 8dc30b105ab..39eeb9d0747 100644 --- a/mne/html_templates/__init__.py +++ b/mne/html_templates/__init__.py @@ -4,10 +4,4 @@ """Jinja2 HTML templates.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={"_templates": ["_get_html_template"]}, -) +from ._templates import repr_templates_env, report_templates_env diff --git a/mne/inverse_sparse/__init__.py b/mne/inverse_sparse/__init__.py index 67f19e84c44..8d45653f79d 100644 --- a/mne/inverse_sparse/__init__.py +++ b/mne/inverse_sparse/__init__.py @@ -4,13 +4,5 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "mxne_inverse": ["mixed_norm", "tf_mixed_norm", "make_stc_from_dipoles"], - "_gamma_map": ["gamma_map"], - }, -) +from .mxne_inverse import mixed_norm, tf_mixed_norm, make_stc_from_dipoles +from ._gamma_map import gamma_map diff --git a/mne/io/__init__.py b/mne/io/__init__.py index 574df48980a..29eb130eac8 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -4,6 +4,73 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import sys as _sys + +from .base import BaseRaw, concatenate_raws, match_channel_orders + +from . import array +from . import base +from . import brainvision +from . import bti +from . import cnt +from . import ctf +from . import edf +from . import egi +from . import fiff +from . import fil +from . import kit +from . import nicolet +from . import nirx +from . import boxy +from . import persyst +from . import eeglab +from . import nihon +from . import nsx + +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 .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, + anonymize_info, + read_fiducials, + write_fiducials, + _empty_info, + Info as _info, +) +from .._fiff.open import show_fiff +from .._fiff.pick import get_channel_type_constants # moved up a level + +# These we will remove in 1.6 +from .._fiff import ( + _dep_msg, import lazy_loader as lazy __getattr__, __dir__, __all__ = lazy.attach( @@ -56,3 +123,50 @@ ], }, ) + +from . import constants +from . import pick + +# These three we will remove in 1.6 +from . import proj +from . import meas_info +from . import reference + + +def __getattr__(name): + """Try getting attribute from fiff submodule.""" + from ..utils import warn + + 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": + from .._fiff.meas_info import 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/_fiff_wrap.py b/mne/io/_fiff_wrap.py deleted file mode 100644 index e4566d61a40..00000000000 --- a/mne/io/_fiff_wrap.py +++ /dev/null @@ -1,20 +0,0 @@ -# ruff: noqa: F401 -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -# 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 ( - Info as _info, -) -from .._fiff.meas_info import ( - _empty_info, - anonymize_info, - read_fiducials, - read_info, - write_fiducials, - write_info, -) -from .._fiff.open import show_fiff -from .._fiff.pick import get_channel_type_constants # moved up a level diff --git a/mne/io/_read_raw.py b/mne/io/_read_raw.py index 28c86c00ee6..ef023f2067b 100644 --- a/mne/io/_read_raw.py +++ b/mne/io/_read_raw.py @@ -7,6 +7,79 @@ from functools import partial from pathlib import Path +from . import ( + read_raw_edf, + read_raw_bdf, + read_raw_gdf, + read_raw_brainvision, + read_raw_fif, + read_raw_eeglab, + read_raw_cnt, + read_raw_egi, + read_raw_eximia, + read_raw_nirx, + read_raw_fieldtrip, + read_raw_artemis123, + read_raw_nicolet, + read_raw_kit, + read_raw_ctf, + read_raw_boxy, + read_raw_snirf, + read_raw_fil, + read_raw_nihon, + read_raw_curry, + read_raw_nedf, +) +from ..utils import fill_doc + + +def _read_unsupported(fname, **kwargs): + ext = "".join(Path(fname).suffixes) + msg = f"Unsupported file type ({ext})." + suggest = kwargs.get("suggest") + if suggest is not None: + msg += f" Try reading a {suggest} file instead." + msg += " Consider using a dedicated reader function for more options." + raise ValueError(msg) + + +# supported read file formats +supported = { + ".edf": dict(EDF=read_raw_edf), + ".eeg": dict(NihonKoden=read_raw_nihon), + ".bdf": dict(BDF=read_raw_bdf), + ".gdf": dict(GDF=read_raw_gdf), + ".vhdr": dict(brainvision=read_raw_brainvision), + ".ahdr": dict(brainvision=read_raw_brainvision), + ".fif": dict(FIF=read_raw_fif), + ".fif.gz": dict(FIF=read_raw_fif), + ".set": dict(EEGLAB=read_raw_eeglab), + ".cnt": dict(CNT=read_raw_cnt), + ".mff": dict(EGI=read_raw_egi), + ".nxe": dict(eximia=read_raw_eximia), + ".hdr": dict(NIRx=read_raw_nirx), + ".snirf": dict(SNIRF=read_raw_snirf), + ".mat": dict(fieldtrip=read_raw_fieldtrip), + ".bin": { + "ARTEMIS": read_raw_artemis123, + "UCL FIL OPM": read_raw_fil, + }, + ".data": dict(Nicolet=read_raw_nicolet), + ".sqd": dict(KIT=read_raw_kit), + ".con": dict(KIT=read_raw_kit), + ".ds": dict(CTF=read_raw_ctf), + ".txt": dict(BOXY=read_raw_boxy), + # Curry + ".dat": dict(CURRY=read_raw_curry), + ".dap": dict(CURRY=read_raw_curry), + ".rs3": dict(CURRY=read_raw_curry), + ".cdt": dict(CURRY=read_raw_curry), + ".cdt.dpa": dict(CURRY=read_raw_curry), + ".cdt.cef": dict(CURRY=read_raw_curry), + ".cef": dict(CURRY=read_raw_curry), + # NEDF + ".nedf": dict(NEDF=read_raw_nedf), +} from ..utils import fill_doc from .base import BaseRaw @@ -92,26 +165,23 @@ def _get_supported(): ".lay": dict(Persyst=read_raw_persyst), } - # known but unsupported file formats -_suggested = { +suggested = { ".vmrk": dict(brainvision=partial(_read_unsupported, suggest=".vhdr")), ".amrk": dict(brainvision=partial(_read_unsupported, suggest=".ahdr")), } - # all known file formats -def _get_readers(): - return {**_get_supported(), **_suggested} +readers = {**supported, **suggested} def split_name_ext(fname): """Return name and supported file extension.""" - maxsuffixes = max(ext.count(".") for ext in _get_supported()) + maxsuffixes = max(ext.count(".") for ext in supported) suffixes = Path(fname).suffixes for si in range(-maxsuffixes, 0): ext = "".join(suffixes[si:]).lower() - if ext in _get_readers(): + if ext in readers: return Path(fname).name[: -len(ext)], ext return fname, None # unknown file extension @@ -171,7 +241,6 @@ def read_raw(fname, *, preload=False, verbose=None, **kwargs) -> BaseRaw: _, ext = split_name_ext(fname) kwargs["verbose"] = verbose kwargs["preload"] = preload - readers = _get_readers() if ext not in readers: _read_unsupported(fname) these_readers = list(readers[ext].values()) diff --git a/mne/io/base.py b/mne/io/base.py index b3052b80aff..381f7391453 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -14,6 +14,7 @@ import numpy as np +from mne.html_templates import _get_html_template from .._fiff.compensator import make_compensator, set_current_comp from .._fiff.constants import FIFF from .._fiff.meas_info import ( @@ -47,6 +48,7 @@ write_int, write_string, ) + from ..annotations import ( Annotations, _annotations_starts_stops, @@ -67,7 +69,6 @@ notch_filter, resample, ) -from ..html_templates import _get_html_template from ..parallel import parallel_func from ..time_frequency.spectrum import Spectrum, SpectrumMixin, _validate_method from ..time_frequency.tfr import RawTFR @@ -2496,6 +2497,8 @@ def describe(self, data_frame=False): If data_frame=False, returns None. If data_frame=True, returns results in a pandas.DataFrame (requires pandas). """ + from scipy.stats import scoreatpercentile as q + nchan = self.info["nchan"] # describe each channel @@ -2507,9 +2510,9 @@ def describe(self, data_frame=False): cols["type"].append(channel_type(self.info, i)) cols["unit"].append(_unit2human[ch["unit"]]) cols["min"].append(np.min(data)) - cols["Q1"].append(np.percentile(data, 25)) + cols["Q1"].append(q(data, 25)) cols["median"].append(np.median(data)) - cols["Q3"].append(np.percentile(data, 75)) + cols["Q3"].append(q(data, 75)) cols["max"].append(np.max(data)) if data_frame: # return data frame diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 763ef4f91eb..1ac29de0e74 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 @@ -20,6 +19,7 @@ from ...utils import _validate_type, fill_doc, logger, verbose, warn from ..base import BaseRaw, _get_scaling + # common channel type names mapped to internal ch types CH_TYPE_MAPPING = { "EEG": FIFF.FIFFV_EEG_CH, @@ -352,6 +352,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/fiff/__init__.py b/mne/io/fiff/__init__.py index 3d83b2c7a7b..24f2baff615 100644 --- a/mne/io/fiff/__init__.py +++ b/mne/io/fiff/__init__.py @@ -6,5 +6,3 @@ from .raw import Raw from .raw import read_raw_fif - -RawFIF = Raw diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index 5d9b79b57cc..caa21653388 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 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 f583d035be4..e7fff3b1496 100644 --- a/mne/minimum_norm/__init__.py +++ b/mne/minimum_norm/__init__.py @@ -4,38 +4,30 @@ """Linear inverse solvers based on L2 Minimum Norm Estimates (MNE).""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "inverse": [ - "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", - ], - "time_frequency": [ - "source_band_induced_power", - "source_induced_power", - "compute_source_psd", - "compute_source_psd_epochs", - ], - "resolution_matrix": [ - "make_inverse_resolution_matrix", - "get_point_spread", - "get_cross_talk", - ], - "spatial_resolution": ["resolution_metrics"], - }, +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/inverse.py b/mne/minimum_norm/inverse.py index 7c789503ac1..59e81b97c3d 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 ( @@ -130,10 +128,12 @@ def __repr__(self): # noqa: D105 @repr_html def _repr_html_(self): + from ..html_templates import repr_templates_env + repr_info = self._get_chs_and_src_info_for_repr() n_chs_meg, n_chs_eeg, src_space_descr, src_ori = repr_info - t = _get_html_template("repr", "inverse_operator.html.jinja") + t = repr_templates_env.get_template("inverse_operator.html.jinja") html = t.render( channels=f"{n_chs_meg} MEG, {n_chs_eeg} EEG", source_space_descr=src_space_descr, @@ -635,6 +635,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 +2202,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..5d4183285dd 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 @@ -603,6 +602,7 @@ def compute_vol_morph_mat(self, *, verbose=None): return self def _morph_vols(self, vols, mesg, subselect=True): + from scipy import sparse from dipy.align.reslice import reslice interp = self.src_data["interpolator"].tocsc()[ @@ -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,9 @@ 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 + from scipy.sparse.csgraph import dijkstra + # Vertices can be out of order, so sort them to start ... order = np.argsort(vertices) vertices = vertices[order] @@ -1371,6 +1379,7 @@ 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..2923efd9313 100644 --- a/mne/morph_map.py +++ b/mne/morph_map.py @@ -206,6 +206,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_matrix, eye as speye + # 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/_csd.py b/mne/preprocessing/_csd.py index 35a32e90245..01e0d00b198 100644 --- a/mne/preprocessing/_csd.py +++ b/mne/preprocessing/_csd.py @@ -261,6 +261,9 @@ def compute_bridged_electrodes( ---------- .. footbibliography:: """ + from scipy.stats import gaussian_kde + from scipy.optimize import minimize_scalar + _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..8a49d06548a 100644 --- a/mne/preprocessing/_fine_cal.py +++ b/mne/preprocessing/_fine_cal.py @@ -284,6 +284,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 fmin_cobyla + # 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/_regress.py b/mne/preprocessing/_regress.py index 6a87649f138..6c79989a511 100644 --- a/mne/preprocessing/_regress.py +++ b/mne/preprocessing/_regress.py @@ -21,6 +21,7 @@ verbose, ) from ..viz import plot_regression_weights +from ..evoked import Evoked @verbose diff --git a/mne/preprocessing/_annotate_amplitude.py b/mne/preprocessing/annotate_amplitude.py similarity index 100% rename from mne/preprocessing/_annotate_amplitude.py rename to mne/preprocessing/annotate_amplitude.py diff --git a/mne/preprocessing/artifact_detection.py b/mne/preprocessing/artifact_detection.py index 8674d6e22b3..0e6cb487825 100644 --- a/mne/preprocessing/artifact_detection.py +++ b/mne/preprocessing/artifact_detection.py @@ -89,6 +89,9 @@ def annotate_muscle_zscore( ---------- .. footbibliography:: """ + from scipy.stats import zscore + from scipy.ndimage import label + raw_copy = raw.copy() if ch_type is None: @@ -406,6 +409,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..a7c0b7d0204 100644 --- a/mne/preprocessing/bads.py +++ b/mne/preprocessing/bads.py @@ -2,8 +2,8 @@ # License: BSD-3-Clause # 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 +30,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..4c5cc7e54fd 100644 --- a/mne/preprocessing/ctps_.py +++ b/mne/preprocessing/ctps_.py @@ -3,10 +3,7 @@ # Copyright the MNE-Python contributors. import math - import numpy as np -from scipy.signal import hilbert -from scipy.special import logsumexp def _compute_normalized_phase(data): @@ -22,6 +19,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 +142,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..de8f026870c 100644 --- a/mne/preprocessing/ica.py +++ b/mne/preprocessing/ica.py @@ -142,6 +142,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) @@ -576,8 +579,10 @@ def __repr__(self): @repr_html def _repr_html_(self): + from ..html_templates import repr_templates_env + infos = self._get_infos_for_repr() - t = _get_html_template("repr", "ica.html.jinja") + t = repr_templates_env.get_template("ica.html.jinja") html = t.render( fit_on=infos.fit_on, method=infos.fit_method, @@ -1993,6 +1998,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 +2056,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() @@ -3498,6 +3506,8 @@ def read_ica_eeglab(fname, *, montage_units="auto", verbose=None): ica : instance of ICA An ICA object based on the information contained in the input file. """ + from scipy import linalg + eeg = _check_load_mat(fname, None) info, eeg_montage, _ = _get_info(eeg, eog=(), montage_units=montage_units) info.set_montage(eeg_montage) 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..b4bec37a908 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.stats import kurtosis + from scipy.special import expit + 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 8c9c0a93957..ca946e0b63a 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -1184,6 +1184,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) # @@ -1449,6 +1451,8 @@ def _get_mag_mask(coils): def _sss_basis_basic(exp, coils, mag_scale=100.0, method="standard"): """Compute SSS basis using non-optimized (but more readable) algorithms.""" + from scipy.special import sph_harm + int_order, ext_order = exp["int_order"], exp["ext_order"] origin = exp["origin"] assert "extended_proj" not in exp # advanced option not supported @@ -1839,6 +1843,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) @@ -2040,6 +2046,8 @@ def _overlap_projector(data_int, data_res, corr): # computation # we use np.linalg.norm instead of sp.linalg.norm here: ~2x faster! + from scipy import linalg + n = np.linalg.norm(data_int) n = 1.0 if n == 0 else n # all-zero data should gracefully continue data_int = _orth_overwrite((data_int / n).T) diff --git a/mne/preprocessing/nirs/_beer_lambert_law.py b/mne/preprocessing/nirs/_beer_lambert_law.py index c17cf31110c..276862a488a 100644 --- a/mne/preprocessing/nirs/_beer_lambert_law.py +++ b/mne/preprocessing/nirs/_beer_lambert_law.py @@ -32,6 +32,8 @@ def beer_lambert_law(raw, ppf=6.0): raw : instance of Raw The modified raw instance. """ + from scipy import linalg + raw = raw.copy().load_data() _validate_type(raw, BaseRaw, "raw") _validate_type(ppf, ("numeric", "array-like"), "ppf") @@ -96,6 +98,9 @@ def _load_absorption(freqs): # # Returns data as [[HbO2(freq1), Hb(freq1)], # [HbO2(freq2), Hb(freq2)]] + from scipy.io import loadmat + from scipy.interpolate import interp1d + extinction_fname = op.join( op.dirname(__file__), "..", "..", "data", "extinction_coef.mat" ) 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..ca673e8cf7c 100644 --- a/mne/preprocessing/stim.py +++ b/mne/preprocessing/stim.py @@ -16,6 +16,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 +26,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..b54ffda80a2 100644 --- a/mne/preprocessing/xdawn.py +++ b/mne/preprocessing/xdawn.py @@ -3,7 +3,6 @@ # Copyright the MNE-Python contributors. import numpy as np -from scipy import linalg from .._fiff.pick import _pick_data_channels, pick_info from ..cov import Covariance, _regularized_covariance @@ -57,6 +56,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 +152,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 bed989a9f80..02ee6610095 100644 --- a/mne/report/__init__.py +++ b/mne/report/__init__.py @@ -1,15 +1 @@ -# Authors: The MNE-Python contributors. -# License: BSD-3-Clause -# Copyright the MNE-Python contributors. - -"""Report-generation functions and classes.""" - -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "report": ["Report", "open_report", "_ReportScraper"], - }, -) +from .report import Report, open_report, _ReportScraper diff --git a/mne/report/report.py b/mne/report/report.py index 852feebc638..4c7bac5309b 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -37,6 +37,7 @@ from ..html_templates import _get_html_template from ..io import BaseRaw, read_raw from ..io._read_raw import _get_supported as _get_extension_reader_map +from ..io._read_raw import supported as extension_reader_map from ..minimum_norm import InverseOperator, read_inverse_operator from ..parallel import parallel_func from ..preprocessing.ica import read_ica @@ -89,7 +90,7 @@ # For raw files, we want to support different suffixes + extensions for all # supported file formats -SUPPORTED_READ_RAW_EXTENSIONS = tuple(_get_extension_reader_map()) +SUPPORTED_READ_RAW_EXTENSIONS = tuple(extension_reader_map.keys()) RAW_EXTENSIONS = [] for ext in SUPPORTED_READ_RAW_EXTENSIONS: RAW_EXTENSIONS.append(f"raw{ext}") @@ -161,7 +162,9 @@ def _id_sanitize(title): def _renderer(kind): - return _get_html_template("report", kind).render + from ..html_templates import report_templates_env + + return report_templates_env.get_template(kind).render ############################################################################### @@ -405,6 +408,7 @@ def _fig_to_img( # check instead if fig.__class__.__name__ in ("MNEQtBrowser", "PyQtGraphBrowser"): img = _mne_qt_browser_screenshot(fig, return_type="ndarray") + print(img.shape, img.max(), img.min(), img.mean()) elif isinstance(fig, Figure3D): from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING, backend diff --git a/mne/simulation/__init__.py b/mne/simulation/__init__.py index b716443ee0f..cde16108f92 100644 --- a/mne/simulation/__init__.py +++ b/mne/simulation/__init__.py @@ -4,19 +4,8 @@ """Data simulation code.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["metrics"], - submod_attrs={ - "evoked": ["simulate_evoked", "add_noise"], - "raw": ["simulate_raw", "add_ecg", "add_eog", "add_chpi"], - "source": [ - "select_source_in_label", - "simulate_stc", - "simulate_sparse_stc", - "SourceSimulator", - ], - }, -) +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 +from . import metrics 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..41ec807f840 100644 --- a/mne/simulation/metrics/metrics.py +++ b/mne/simulation/metrics/metrics.py @@ -193,6 +193,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..bbe646ad798 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -3183,6 +3183,8 @@ def spatio_temporal_tris_adjacency(tris, n_times, remap_vertices=False, verbose= vertices are time 1, the nodes from 2 to 2N are the vertices during time 2, etc. """ + from scipy import sparse + if remap_vertices: logger.info("Reassigning vertex indices.") tris = np.searchsorted(np.unique(tris), tris) @@ -3219,6 +3221,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.sparse import block_diag as sparse_block_diag + if src[0]["dist"] is None: raise RuntimeError( "src must have distances included, consider using " @@ -3231,7 +3235,7 @@ def spatio_temporal_dist_adjacency(src, n_times, dist, verbose=None): block[block == 0] = -np.inf else: block.data[block.data == 0] == -1 - edges = sparse.block_diag(blocks) + edges = sparse_block_diag(blocks) edges.data[:] = np.less_equal(edges.data, dist) # clean it up and put it in coo format edges = edges.tocsr() @@ -3329,6 +3333,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 +3349,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.sparse import coo_matrix + n_vertices = edges.shape[0] logger.info("-- number of adjacent vertices : %d", n_vertices) nnz = edges.col.size @@ -3661,6 +3670,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 +3928,7 @@ 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/_source_space.py b/mne/source_space/_source_space.py index d64989961cf..62e1cd009b9 100644 --- a/mne/source_space/_source_space.py +++ b/mne/source_space/_source_space.py @@ -2336,6 +2336,8 @@ def _src_vol_dims(s): def _add_interpolator(sp): """Compute a sparse matrix to interpolate the data into an MRI volume.""" # extract transformation information from mri + from scipy import sparse + mri_width, mri_height, mri_depth, nvox = _src_vol_dims(sp[0]) # @@ -2398,6 +2400,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 import sparse + from_shape = np.array(from_shape, int) to_shape = np.array(to_shape, int) trans = np.array(trans, np.float64) # to -> from @@ -2706,6 +2710,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_matrix + from scipy.sparse.csgraph import dijkstra + src = _ensure_src(src) dist_limit = float(dist_limit) if dist_limit < 0: @@ -2775,6 +2782,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 +3102,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}") diff --git a/mne/stats/__init__.py b/mne/stats/__init__.py index 9ec167e4eb4..e285bc11211 100644 --- a/mne/stats/__init__.py +++ b/mne/stats/__init__.py @@ -4,35 +4,23 @@ """Functions for statistical analysis.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "parametric": [ - "f_threshold_mway_rm", - "f_mway_rm", - "f_oneway", - "_parametric_ci", - "ttest_1samp_no_p", - "ttest_ind_no_p", - ], - "permutations": [ - "permutation_t_test", - "_ci", - "bootstrap_confidence_interval", - ], - "cluster_level": [ - "permutation_cluster_test", - "permutation_cluster_1samp_test", - "spatio_temporal_cluster_test", - "spatio_temporal_cluster_1samp_test", - "_st_mask_from_s_inds", - "summarize_clusters_stc", - ], - "multi_comp": ["fdr_correction", "bonferroni_correction"], - "regression": ["linear_regression", "linear_regression_raw"], - "_adjacency": ["combine_adjacency"], - }, +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/_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..f710ffe1f4a 100644 --- a/mne/stats/cluster_level.py +++ b/mne/stats/cluster_level.py @@ -288,9 +288,13 @@ 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 + if adjacency is False: components = np.arange(len(x_in)) else: + from scipy.sparse.csgraph import connected_components + mask = np.logical_and(x_in[adjacency.row], x_in[adjacency.col]) data = adjacency.data[mask] row = adjacency.row[mask] @@ -376,6 +380,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 +518,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 sparse + if adjacency is None: labels, n_labels = ndimage.label(x_in) @@ -613,6 +621,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,7 +1143,10 @@ def _permutation_cluster_test( def _check_fun(X, stat_fun, threshold, tail=0, kind="within"): """Check the stat_fun and threshold values.""" + from scipy import stats + if kind == "within": + ppf = stats.t.ppf if threshold is None: if stat_fun is not None and stat_fun is not ttest_1samp_no_p: warn( @@ -1142,13 +1155,14 @@ def _check_fun(X, stat_fun, threshold, tail=0, kind="within"): ) p_thresh = 0.05 / (1 + (tail == 0)) n_samples = len(X) - threshold = -tstat.ppf(p_thresh, n_samples - 1) + threshold = -ppf(p_thresh, n_samples - 1) if np.sign(tail) < 0: threshold = -threshold logger.info(f"Using a threshold of {threshold:.6f}") stat_fun = ttest_1samp_no_p if stat_fun is None else stat_fun else: assert kind == "between" + ppf = stats.f.ppf if threshold is None: if stat_fun is not None and stat_fun is not f_oneway: warn( @@ -1605,6 +1619,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..677f645aaa2 100644 --- a/mne/stats/parametric.py +++ b/mne/stats/parametric.py @@ -2,6 +2,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import numpy as np from functools import reduce from string import ascii_uppercase @@ -239,6 +240,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 +301,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 +368,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 +408,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) @@ -419,5 +426,7 @@ def _parametric_ci(arr, ci=0.95): if len(arr) < 2: # can't compute standard error sigma = np.full_like(mean, np.nan) return mean, sigma + from scipy import stats + sigma = stats.sem(arr, 0) return stats.t.interval(ci, loc=mean, scale=sigma, df=arr.shape[0]) diff --git a/mne/stats/regression.py b/mne/stats/regression.py index d6343cfccba..e9a61fca3a3 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 stats, linalg + 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..06cea178f3f 100644 --- a/mne/surface.py +++ b/mne/surface.py @@ -392,6 +392,8 @@ def _triangle_neighbors(tris, npts): # for ti, tri in enumerate(tris): # for t in tri: # neighbor_tri[t].append(ti) + from scipy.sparse import coo_matrix + rows = tris.ravel() cols = np.repeat(np.arange(len(tris)), 3) data = np.ones(len(cols)) @@ -585,6 +587,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 +742,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 +1720,8 @@ def mesh_edges(tris): @lru_cache(maxsize=10) def _mesh_edges(tris=None): + from scipy.sparse import coo_matrix + if np.max(tris) > len(np.unique(tris)): raise ValueError("Cannot compute adjacency on a selection of triangles.") @@ -1747,6 +1755,8 @@ def mesh_dist(tris, vert): dist_matrix : scipy.sparse.csr_array Sparse matrix with distances between adjacent vertices. """ + from scipy.sparse import csr_matrix + edges = mesh_edges(tris).tocoo() # Euclidean distances between neighboring vertices @@ -2090,6 +2100,7 @@ def get_montage_volume_labels(montage, subject, subjects_dir=None, aseg="auto", """ from ._freesurfer import _get_aseg, read_freesurfer_lut from .channels import DigMontage + from ._freesurfer import read_freesurfer_lut, _get_aseg _validate_type(montage, DigMontage, "montage") _validate_type(dist, (int, float), "dist") diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index 6904e9ff755..ce44f8d058b 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -255,15 +255,10 @@ def generic_visit(self, node): 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(): +def test_module_nesting(): """Test that module imports are properly nested.""" stdout, stderr, code = run_subprocess( [sys.executable, "-c", run_script], return_code=True diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 5539f183fa2..4a3a5c140b5 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -4,58 +4,33 @@ """Time frequency analysis tools.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[], - submod_attrs={ - "_stft": [ - "istft", - "stft", - "stftfreq", - ], - "_stockwell": [ - "tfr_array_stockwell", - "tfr_stockwell", - ], - "ar": ["fit_iir_model_raw"], - "csd": [ - "CrossSpectralDensity", - "csd_array_fourier", - "csd_array_morlet", - "csd_array_multitaper", - "csd_fourier", - "csd_morlet", - "csd_multitaper", - "csd_tfr", - "pick_channels_csd", - "read_csd", - ], - "multitaper": [ - "dpss_windows", - "psd_array_multitaper", - "tfr_array_multitaper", - ], - "psd": ["psd_array_welch"], - "spectrum": [ - "EpochsSpectrum", - "EpochsSpectrumArray", - "Spectrum", - "SpectrumArray", - "read_spectrum", - ], - "tfr": [ - "_BaseTFR", - "AverageTFR", - "EpochsTFR", - "fwhm", - "morlet", - "read_tfrs", - "tfr_array_morlet", - "tfr_morlet", - "tfr_multitaper", - "write_tfrs", - ], - }, +from .tfr import ( + morlet, + tfr_morlet, + AverageTFR, + tfr_multitaper, + _BaseTFR, + read_tfrs, + write_tfrs, + EpochsTFR, + tfr_array_morlet, + fwhm, +) +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, Spectrum, read_spectrum +from ._stft import stft, istft, stftfreq +from ._stockwell import tfr_stockwell, tfr_array_stockwell diff --git a/mne/time_frequency/_stft.py b/mne/time_frequency/_stft.py index a6b6f23fff7..0a7d7792292 100644 --- a/mne/time_frequency/_stft.py +++ b/mne/time_frequency/_stft.py @@ -3,9 +3,7 @@ # Copyright the MNE-Python contributors. from math import ceil - import numpy as np -from scipy.fft import irfft, rfft, rfftfreq from ..utils import logger, verbose @@ -39,6 +37,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 +122,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 +205,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..51e24766301 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) @@ -183,6 +186,8 @@ def tfr_array_stockwell( ---------- .. footbibliography:: """ + from scipy.fft import fftfreq + _validate_type(data, np.ndarray, "data") if data.ndim != 3: raise ValueError( 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..9e43aa4698c 100644 --- a/mne/time_frequency/multitaper.py +++ b/mne/time_frequency/multitaper.py @@ -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] @@ -290,6 +292,8 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None, remove_dc=True): def _compute_mt_params(n_times, sfreq, bandwidth, low_bias, adaptive, verbose=None): """Triage windowing and multitaper parameters.""" # Compute standardized half-bandwidth + from scipy.signal import get_window + if isinstance(bandwidth, str): logger.info(f' Using standard spectrum estimation with "{bandwidth}" window') window_fun = get_window(bandwidth, n_times)[np.newaxis] @@ -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..f411cc0e0ea 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -369,6 +369,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 +4202,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 7072ea25124..4c4b6130f6d 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -921,6 +921,8 @@ def _sh_real_to_complex(shs, order): def _compute_sph_harm(order, az, pol): """Compute complex spherical harmonics of spherical coordinates.""" + from scipy.special import sph_harm + out = np.empty((len(az), _get_n_moments(order) + 1)) # _deg_ord_idx(0, 0) = -1 so we're actually okay to use it here for degree in range(order + 1): @@ -984,6 +986,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 @@ -1122,6 +1127,7 @@ 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 @@ -1497,6 +1503,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 600c74720e4..6c2c705d10c 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -1,212 +1,213 @@ -# Authors: The MNE-Python contributors. -# 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( - __name__, - submodules=[], - submod_attrs={ - "_bunch": ["Bunch", "BunchConst", "BunchConstNamed"], - "check": [ - "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_edflib_installed", - "_to_rgb", - "_soft_import", - "_check_dict_keys", - "_check_pymatreader_installed", - "_import_h5py", - "_import_h5io_funcs", - "_import_nibabel", - "_import_pymatreader_funcs", - "_check_head_radius", - ], - "config": [ - "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", - ], - "docs": [ - "copy_function_doc_to_method_doc", - "copy_doc", - "linkcode_resolve", - "open_docs", - "deprecated", - "fill_doc", - "deprecated_alias", - "legacy", - "copy_base_doc_to_subclass_doc", - "_doc_special_members", - ], - "fetching": ["_url_to_local_path"], - "_logging": [ - "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", - ], - "misc": [ - "run_subprocess", - "_pl", - "_clean_names", - "pformat", - "_file_like", - "_empty_hash", - "_explain_exception", - "_get_argvalues", - "sizeof_fmt", - "running_subprocess", - "_DefaultEventParser", - "_assert_no_instances", - "_resource_path", - "repr_html", - "_auto_weakref", - ], - "progressbar": ["ProgressBar"], - "_testing": [ - "run_command_if_main", - "requires_mne", - "requires_good_network", - "ArgvSetter", - "SilenceStdout", - "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", - ], - "numerics": [ - "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", - "_julian_to_cal", - "_cal_to_julian", - "_dt_to_julian", - "_julian_to_dt", - "_dt_to_stamp", - "_stamp_to_dt", - "_check_dt", - "_ReuseCycle", - "_arange_div", - "_hashable_ndarray", - "_custom_lru_cache", - ], - "mixin": [ - "SizeMixin", - "GetEpochsMixin", - "TimeMixin", - "ExtendedTimeMixin", - "_prepare_read_metadata", - "_prepare_write_metadata", - "_check_decim", - ], - "linalg": [ - "_svd_lwork", - "_repeated_svd", - "_sym_mat_pow", - "sqrtm_sym", - "eigh", - "_get_blas_funcs", - ], - "dataframe": [ - "_set_pandas_dtype", - "_scale_dataframe_data", - "_convert_times", - "_build_data_frame", - ], - }, +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_edflib_installed, + _to_rgb, + _soft_import, + _check_dict_keys, + _check_pymatreader_installed, + _import_h5py, + _import_h5io_funcs, + _import_nibabel, + _import_pymatreader_funcs, + _check_head_radius, +) +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, + copy_base_doc_to_subclass_doc, + 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 ( + run_subprocess, + _pl, + _clean_names, + pformat, + _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, + SilenceStdout, + 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, + _julian_to_cal, + _cal_to_julian, + _dt_to_julian, + _julian_to_dt, + _dt_to_stamp, + _stamp_to_dt, + _check_dt, + _ReuseCycle, + _arange_div, + _hashable_ndarray, + _custom_lru_cache, +) +from .mixin import ( + SizeMixin, + GetEpochsMixin, + TimeMixin, + ExtendedTimeMixin, + _prepare_read_metadata, + _prepare_write_metadata, + _FakeNoPandas, +) +from .linalg import ( + _svd_lwork, + _repeated_svd, + _sym_mat_pow, + sqrtm_sym, + eigh, + _get_blas_funcs, +) +from .dataframe import ( + _set_pandas_dtype, + _scale_dataframe_data, + _convert_times, + _build_data_frame, ) diff --git a/mne/utils/_testing.py b/mne/utils/_testing.py index 63e0d1036b9..5ae04ced01f 100644 --- a/mne/utils/_testing.py +++ b/mne/utils/_testing.py @@ -273,6 +273,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/linalg.py b/mne/utils/linalg.py index 9382aad50f2..545030cd508 100644 --- a/mne/utils/linalg.py +++ b/mne/utils/linalg.py @@ -25,8 +25,7 @@ import functools import numpy as np -from scipy import linalg -from scipy._lib._util import _asarray_validated + from ..fixes import _safe_svd @@ -36,11 +35,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 +55,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 +100,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 +133,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.linalg import LinAlgError + from scipy._lib._util import _asarray_validated + # We use SYEVD, see https://github.com/scipy/scipy/issues/9212 if check_finite: a = _asarray_validated(a, check_finite=check_finite) @@ -136,7 +146,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..cc4f91f83ac 100644 --- a/mne/utils/numerics.py +++ b/mne/utils/numerics.py @@ -638,6 +638,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"): @@ -693,6 +695,8 @@ def object_size(x, memo=None): size : int The estimated size in bytes of the object. """ + from scipy import sparse + # Note: this will not process object arrays properly (since those only) # hold references if memo is None: @@ -775,6 +779,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 9c558d32a51..a36662376cb 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -90,6 +90,7 @@ figure_nobar, plt_show, ) +from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces verbose_dec = verbose FIDUCIAL_ORDER = (FIFF.FIFFV_POINT_LPA, FIFF.FIFFV_POINT_NASION, FIFF.FIFFV_POINT_RPA) @@ -280,6 +281,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 +1742,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 diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index 8b118cc8c5d..c8b5af3940b 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -4,110 +4,91 @@ """Visualization routines.""" -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["backends", "_scraper", "ui_events"], - submod_attrs={ - "backends._abstract": ["Figure3D"], - "backends.renderer": [ - "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", - ], - "circle": ["circular_layout", "plot_channel_labels_circle"], - "epochs": [ - "plot_drop_log", - "plot_epochs", - "plot_epochs_psd", - "plot_epochs_image", - ], - "evoked": [ - "plot_evoked", - "plot_evoked_image", - "plot_evoked_white", - "plot_snr_estimate", - "plot_evoked_topo", - "plot_evoked_joint", - "plot_compare_evokeds", - ], - "evoked_field": [ - "EvokedField", - ], - "ica": [ - "plot_ica_scores", - "plot_ica_sources", - "plot_ica_overlay", - "_plot_sources", - "plot_ica_properties", - ], - "misc": [ - "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", - ], - "montage": ["plot_montage"], - "raw": ["plot_raw", "plot_raw_psd", "plot_raw_psd_topo", "_RAW_CLIP_DEF"], - "topo": ["plot_topo_image_epochs", "iter_topography"], - "topomap": [ - "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", - ], - "utils": [ - "tight_layout", - "mne_analyze_colormap", - "compare_fiff", - "ClickableImage", - "add_background_image", - "plot_sensors", - "centers_to_edges", - "concatenate_images", - "_get_plot_ch_type", - ], - "_3d": [ - "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", - ], - "_brain": ["Brain"], - "_figure": [ - "get_browser_backend", - "set_browser_backend", - "use_browser_backend", - ], - "_proj": ["plot_projs_joint"], - }, +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 ( + tight_layout, + mne_analyze_colormap, + compare_fiff, + ClickableImage, + add_background_image, + plot_sensors, + centers_to_edges, + concatenate_images, +) +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 . import backends, _scraper +from ._brain import Brain +from ._figure import get_browser_backend, set_browser_backend, use_browser_backend +from ._proj import plot_projs_joint +from . import ui_events diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 778700c99a7..b444fad77c7 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1067,6 +1067,8 @@ def _configure_vertex_time_course(self): def _configure_picking(self): # get data for each hemi + from scipy import sparse + for idx, hemi in enumerate(["vol", "lh", "rh"]): hemi_data = self._data.get(hemi) if hemi_data is not None: @@ -2639,6 +2641,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 +4040,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..d22fdf4298e 100644 --- a/mne/viz/_dipole.py +++ b/mne/viz/_dipole.py @@ -5,9 +5,7 @@ # Copyright the MNE-Python contributors. 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 +42,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/_figure.py b/mne/viz/_figure.py index 090c661f633..022b306ea08 100644 --- a/mne/viz/_figure.py +++ b/mne/viz/_figure.py @@ -26,6 +26,7 @@ set_config, verbose, ) +from ..annotations import _sync_onset from .backends._utils import VALID_BROWSE_BACKENDS from .utils import _get_color_list, _setup_plot_projector, _show_browser @@ -185,8 +186,6 @@ def _setup_annotation_colors(self): def _update_annotation_segments(self): """Update the array of annotation start/end times.""" - from ..annotations import _sync_onset - self.mne.annotation_segments = np.array([]) if len(self.mne.inst.annotations): annot_start = _sync_onset(self.mne.inst, self.mne.inst.annotations.onset) diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index c691915f132..99b1551ed11 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,7 @@ 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.py b/mne/viz/evoked.py index 96ee0684e6e..36e6a971365 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -44,6 +44,8 @@ _prepare_topomap, _prepare_topomap_plot, _set_contour_locator, + _check_sphere, + _make_head_outlines, plot_topomap, ) from .utils import ( @@ -70,6 +72,7 @@ _validate_if_list_of_axes, plt_show, ) +from ..channels.layout import _pair_grad_sensors, find_layout def _butterfly_onpick(event, params): 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..86e08300cff 100644 --- a/mne/viz/misc.py +++ b/mne/viz/misc.py @@ -1132,6 +1132,7 @@ def plot_filter( ----- .. versionadded:: 0.14 """ + from scipy.signal import freqz, group_delay, lfilter, filtfilt, sosfilt, sosfiltfilt import matplotlib.pyplot as plt sfreq = float(sfreq) diff --git a/mne/viz/montage.py b/mne/viz/montage.py index 221cc21f7a0..0ee23363aca 100644 --- a/mne/viz/montage.py +++ b/mne/viz/montage.py @@ -5,7 +5,6 @@ """Functions to plot EEG sensor montages or digitizer montages.""" from copy import deepcopy - import numpy as np from scipy.spatial.distance import cdist diff --git a/mne/viz/raw.py b/mne/viz/raw.py index 856e7eda35e..ffa46d0e4a1 100644 --- a/mne/viz/raw.py +++ b/mne/viz/raw.py @@ -11,6 +11,7 @@ from .._fiff.pick import _picks_to_idx, pick_channels, pick_types from ..defaults import _handle_default from ..filter import create_filter +from ..time_frequency import Spectrum from ..utils import _check_option, _get_stim_channel, _validate_type, legacy, verbose from ..utils.spectrum import _split_psd_kwargs from .utils import ( @@ -488,8 +489,6 @@ def plot_raw_psd( ----- %(notes_plot_*_psd_func)s """ - from ..time_frequency import Spectrum - init_kw, plot_kw = _split_psd_kwargs(plot_fun=Spectrum.plot) return raw.compute_psd(**init_kw).plot(**plot_kw) @@ -557,8 +556,6 @@ def plot_raw_psd_topo( fig : instance of matplotlib.figure.Figure Figure distributing one image per channel across sensor topography. """ - from ..time_frequency import Spectrum - init_kw, plot_kw = _split_psd_kwargs(plot_fun=Spectrum.plot_topo) return raw.compute_psd(**init_kw).plot_topo(**plot_kw) diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 5c43d4de48e..ffb1bb8535f 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -8,9 +8,9 @@ from functools import partial import numpy as np -from scipy import ndimage from .._fiff.pick import _picks_to_idx, channel_type, pick_types +from ..channels.layout import _merge_ch_data, _pair_grad_sensors, find_layout from ..defaults import _handle_default from ..utils import Bunch, _check_option, _clean_names, _is_numeric, _to_rgb, fill_doc from .ui_events import ChannelsSelect, publish, subscribe @@ -775,6 +775,7 @@ def _erfimage_imshow( vlim_array=None, ): """Plot erfimage on sensor topography.""" + from scipy import ndimage import matplotlib.pyplot as plt this_data = data[:, ch_idx, :] @@ -832,6 +833,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 @@ -1284,8 +1287,6 @@ def plot_topo_image_epochs( will always have a colorbar even when the topo plot does not (because it shows multiple sensor types). """ - from ..channels.layout import find_layout - scalings = _handle_default("scalings", scalings) # make a copy because we discard non-data channels and scale the data diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index bb180a3f299..a3dbcbf6153 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -21,6 +21,13 @@ from scipy.spatial.distance import pdist, squareform from .._fiff.meas_info import Info, _simplify_info +from ..channels.channels import _get_ch_type +from ..channels.layout import ( + _find_topomap_coords, + find_layout, + _pair_grad_sensors, + _merge_ch_data, +) from .._fiff.pick import ( _MEG_CH_TYPES_SPLIT, _pick_data_channels, @@ -186,7 +193,7 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): - from ..channels.layout import _find_topomap_coords + from scipy.spatial.distance import pdist, squareform picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") chs = [info["chs"][i] for i in picks] @@ -241,8 +248,6 @@ def _average_fnirs_overlaps(info, ch_type, sphere): def _plot_update_evoked_topomap(params, bools): """Update topomaps.""" - from ..channels.layout import _merge_ch_data - projs = [ proj for ii, proj in enumerate(params["projs"]) if ii in np.where(bools)[0] ] @@ -684,6 +689,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 +827,12 @@ class _GridData: """ def __init__(self, pos, image_interp, extrapolate, origin, radii, border): + from scipy.interpolate import ( + CloughTocher2DInterpolator, + NearestNDInterpolator, + LinearNDInterpolator, + ) + # 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 +1102,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, @@ -3395,8 +3410,6 @@ def _plot_corrmap( show_names=False, ): """Customize ica.plot_components for corrmap.""" - from ..channels.layout import _merge_ch_data - if not template: title = "Detected components" if label is not None: @@ -3787,6 +3800,7 @@ def plot_ch_adjacency(info, adjacency, ch_names, kind="2d", edit=False): ----- .. versionadded:: 1.1 """ + from scipy import sparse import matplotlib as mpl import matplotlib.pyplot as plt diff --git a/mne/viz/utils.py b/mne/viz/utils.py index b9b844b321a..abb4d5004ae 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -23,6 +23,8 @@ from .._fiff.constants import FIFF from .._fiff.meas_info import Info from .._fiff.open import show_fiff +from .._fiff.meas_info import Info +from .._fiff.constants import FIFF from .._fiff.pick import ( _DATA_CH_TYPES_ORDER_DEFAULT, _DATA_CH_TYPES_SPLIT, @@ -877,6 +879,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: From 7bafe87715dbcf20cb552961722b3022e29f8600 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Sun, 23 Feb 2025 11:23:46 +0100 Subject: [PATCH 03/46] ruff --- mne/_fiff/meas_info.py | 58 +-------------------- mne/_fiff/open.py | 2 - mne/_fiff/tag.py | 3 -- mne/_fiff/write.py | 2 - mne/_ola.py | 1 - mne/bem.py | 1 - mne/channels/channels.py | 6 --- mne/channels/interpolation.py | 1 - mne/chpi.py | 1 - mne/cov.py | 1 + mne/cuda.py | 5 +- mne/datasets/limo/limo.py | 1 - mne/decoding/receptive_field.py | 3 +- mne/decoding/time_delaying_ridge.py | 6 --- mne/evoked.py | 2 +- mne/filter.py | 9 ++-- mne/fixes.py | 8 +-- mne/forward/forward.py | 4 -- mne/gui/_coreg.py | 7 ++- mne/io/_read_raw.py | 30 +++++------ mne/io/base.py | 2 +- mne/io/edf/edf.py | 1 - mne/minimum_norm/inverse.py | 1 - mne/morph.py | 4 +- mne/morph_map.py | 2 - mne/preprocessing/_csd.py | 3 -- mne/preprocessing/_fine_cal.py | 2 - mne/preprocessing/_regress.py | 1 - mne/preprocessing/artifact_detection.py | 6 --- mne/preprocessing/ctps_.py | 1 + mne/preprocessing/ica.py | 7 --- mne/preprocessing/infomax_.py | 2 +- mne/preprocessing/maxwell.py | 7 --- mne/preprocessing/nirs/_beer_lambert_law.py | 4 -- mne/preprocessing/stim.py | 4 -- mne/report/report.py | 2 - mne/simulation/metrics/metrics.py | 2 - mne/source_estimate.py | 6 --- mne/source_space/_source_space.py | 8 --- mne/stats/cluster_level.py | 11 ---- mne/stats/parametric.py | 4 -- mne/stats/regression.py | 2 +- mne/surface.py | 6 --- mne/tests/test_import_nesting.py | 2 - mne/time_frequency/_stft.py | 1 + mne/time_frequency/_stockwell.py | 2 - mne/time_frequency/multitaper.py | 5 -- mne/time_frequency/tfr.py | 3 -- mne/transforms.py | 6 --- mne/utils/_testing.py | 2 - mne/utils/linalg.py | 3 +- mne/utils/numerics.py | 2 - mne/viz/_3d.py | 6 +-- mne/viz/_brain/_brain.py | 4 -- mne/viz/_dipole.py | 1 + mne/viz/_figure.py | 2 +- mne/viz/epochs.py | 1 + mne/viz/evoked.py | 5 +- mne/viz/misc.py | 1 - mne/viz/montage.py | 1 + mne/viz/topo.py | 4 +- mne/viz/topomap.py | 28 +++------- mne/viz/utils.py | 4 -- 63 files changed, 52 insertions(+), 270 deletions(-) diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index 286fb552076..b5415008e22 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -18,59 +18,13 @@ from mne.defaults import _handle_default from mne.html_templates import _get_html_template -from .pick import ( - channel_type, - _get_channel_types, - get_channel_type_constants, - pick_types, - _picks_to_idx, - _contains_ch_type, -) -from .constants import FIFF, _coord_frame_named, _ch_unit_mul_named -from .open import fiff_open -from .tree import dir_tree_find -from .tag import ( - read_tag, - find_tag, - _ch_coord_dict, - _update_ch_info_named, - _rename_list, - _int_item, - _float_item, -) -from .proj import ( - _read_proj, - _write_proj, - _uniquify_projs, - _normalize_proj, - _proj_equal, - Projection, -) -from .ctf_comp import _read_ctf_comp, write_ctf_comp -from .write import ( - start_and_end_file, - start_block, - end_block, - write_string, - write_dig_points, - write_float, - write_int, - write_coord_trans, - write_ch_info, - write_julian, - write_float_matrix, - write_id, - DATE_NONE, - _safe_name_list, - write_name_list_sanitized, -) -from .proc_history import _read_proc_history, _write_proc_history + from ..transforms import ( - invert_transform, Transform, _coord_frame_name, _ensure_trans, _frame_to_str, + invert_transform, ) from ..utils import ( _check_fname, @@ -377,7 +331,6 @@ def get_montage(self): A copy of the channel positions, if available, otherwise ``None``. """ from ..channels.montage import make_dig_montage - from ..transforms import _frame_to_str info = self if isinstance(self, Info) else self.info if info["dig"] is None: @@ -1131,7 +1084,6 @@ def __init__(self, initial): def _format_trans(obj, key): - from ..transforms import Transform try: t = obj[key] @@ -1202,7 +1154,6 @@ def _check_bads(bads, *, info): def _check_dev_head_t(dev_head_t, *, info): - from ..transforms import Transform, _ensure_trans _validate_type(dev_head_t, (Transform, None), "info['dev_head_t']") if dev_head_t is not None: @@ -1782,7 +1733,6 @@ def normalize_proj(self): def __repr__(self): """Summarize info instead of printing all.""" from ..io.kit.constants import KIT_SYSNAMES - from ..transforms import Transform, _coord_frame_name MAX_WIDTH = 68 strs = ["= 0 return ( order * val * lpmv(order, degree, val) @@ -2046,7 +2040,6 @@ def _overlap_projector(data_int, data_res, corr): # computation # we use np.linalg.norm instead of sp.linalg.norm here: ~2x faster! - from scipy import linalg n = np.linalg.norm(data_int) n = 1.0 if n == 0 else n # all-zero data should gracefully continue diff --git a/mne/preprocessing/nirs/_beer_lambert_law.py b/mne/preprocessing/nirs/_beer_lambert_law.py index 276862a488a..6049e444ba3 100644 --- a/mne/preprocessing/nirs/_beer_lambert_law.py +++ b/mne/preprocessing/nirs/_beer_lambert_law.py @@ -32,8 +32,6 @@ def beer_lambert_law(raw, ppf=6.0): raw : instance of Raw The modified raw instance. """ - from scipy import linalg - raw = raw.copy().load_data() _validate_type(raw, BaseRaw, "raw") _validate_type(ppf, ("numeric", "array-like"), "ppf") @@ -98,8 +96,6 @@ def _load_absorption(freqs): # # Returns data as [[HbO2(freq1), Hb(freq1)], # [HbO2(freq2), Hb(freq2)]] - from scipy.io import loadmat - from scipy.interpolate import interp1d extinction_fname = op.join( op.dirname(__file__), "..", "..", "data", "extinction_coef.mat" diff --git a/mne/preprocessing/stim.py b/mne/preprocessing/stim.py index ca673e8cf7c..a823820988b 100644 --- a/mne/preprocessing/stim.py +++ b/mne/preprocessing/stim.py @@ -16,8 +16,6 @@ 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 @@ -26,8 +24,6 @@ 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/report/report.py b/mne/report/report.py index 4c7bac5309b..91901186a5b 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -34,9 +34,7 @@ from ..event import read_events from ..evoked import Evoked, read_evokeds from ..forward import Forward, read_forward_solution -from ..html_templates import _get_html_template from ..io import BaseRaw, read_raw -from ..io._read_raw import _get_supported as _get_extension_reader_map from ..io._read_raw import supported as extension_reader_map from ..minimum_norm import InverseOperator, read_inverse_operator from ..parallel import parallel_func diff --git a/mne/simulation/metrics/metrics.py b/mne/simulation/metrics/metrics.py index 41ec807f840..c9ac7c0d893 100644 --- a/mne/simulation/metrics/metrics.py +++ b/mne/simulation/metrics/metrics.py @@ -193,8 +193,6 @@ 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 bbe646ad798..a076c9661b3 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -3183,8 +3183,6 @@ def spatio_temporal_tris_adjacency(tris, n_times, remap_vertices=False, verbose= vertices are time 1, the nodes from 2 to 2N are the vertices during time 2, etc. """ - from scipy import sparse - if remap_vertices: logger.info("Reassigning vertex indices.") tris = np.searchsorted(np.unique(tris), tris) @@ -3334,7 +3332,6 @@ def spatial_inter_hemi_adjacency(src, dist, verbose=None): 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"]]) @@ -3349,8 +3346,6 @@ 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.sparse import coo_matrix - n_vertices = edges.shape[0] logger.info("-- number of adjacent vertices : %d", n_vertices) nnz = edges.col.size @@ -3928,7 +3923,6 @@ 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/_source_space.py b/mne/source_space/_source_space.py index 62e1cd009b9..daef13ef4bf 100644 --- a/mne/source_space/_source_space.py +++ b/mne/source_space/_source_space.py @@ -2336,7 +2336,6 @@ def _src_vol_dims(s): def _add_interpolator(sp): """Compute a sparse matrix to interpolate the data into an MRI volume.""" # extract transformation information from mri - from scipy import sparse mri_width, mri_height, mri_depth, nvox = _src_vol_dims(sp[0]) @@ -2400,8 +2399,6 @@ 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 import sparse - from_shape = np.array(from_shape, int) to_shape = np.array(to_shape, int) trans = np.array(trans, np.float64) # to -> from @@ -2710,9 +2707,6 @@ 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_matrix - from scipy.sparse.csgraph import dijkstra - src = _ensure_src(src) dist_limit = float(dist_limit) if dist_limit < 0: @@ -2782,8 +2776,6 @@ 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)] diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py index f710ffe1f4a..8bc2cdc18eb 100644 --- a/mne/stats/cluster_level.py +++ b/mne/stats/cluster_level.py @@ -6,9 +6,7 @@ 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,8 +286,6 @@ 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 - if adjacency is False: components = np.arange(len(x_in)) else: @@ -380,8 +376,6 @@ 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) @@ -518,8 +512,6 @@ 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 sparse - if adjacency is None: labels, n_labels = ndimage.label(x_in) @@ -621,7 +613,6 @@ 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( @@ -1619,8 +1610,6 @@ 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 677f645aaa2..d807ff8a1f2 100644 --- a/mne/stats/parametric.py +++ b/mne/stats/parametric.py @@ -2,7 +2,6 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import numpy as np from functools import reduce from string import ascii_uppercase @@ -240,8 +239,6 @@ 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 @@ -426,7 +423,6 @@ def _parametric_ci(arr, ci=0.95): if len(arr) < 2: # can't compute standard error sigma = np.full_like(mean, np.nan) return mean, sigma - from scipy import stats sigma = stats.sem(arr, 0) return stats.t.interval(ci, loc=mean, scale=sigma, df=arr.shape[0]) diff --git a/mne/stats/regression.py b/mne/stats/regression.py index e9a61fca3a3..ed431d17fb5 100644 --- a/mne/stats/regression.py +++ b/mne/stats/regression.py @@ -102,7 +102,7 @@ def linear_regression(inst, design_matrix, names=None): def _fit_lm(data, design_matrix, names): """Aux function.""" - from scipy import stats, linalg + from scipy import linalg, stats n_samples = len(data) n_features = np.prod(data.shape[1:]) diff --git a/mne/surface.py b/mne/surface.py index 06cea178f3f..3d9c9f6ae6e 100644 --- a/mne/surface.py +++ b/mne/surface.py @@ -392,7 +392,6 @@ def _triangle_neighbors(tris, npts): # for ti, tri in enumerate(tris): # for t in tri: # neighbor_tri[t].append(ti) - from scipy.sparse import coo_matrix rows = tris.ravel() cols = np.repeat(np.arange(len(tris)), 3) @@ -587,7 +586,6 @@ def __init__(self, xhs): self._xhs = xhs def query(self, rr): - from scipy.spatial.distance import cdist nearest = list() dists = list() @@ -1720,7 +1718,6 @@ def mesh_edges(tris): @lru_cache(maxsize=10) def _mesh_edges(tris=None): - from scipy.sparse import coo_matrix if np.max(tris) > len(np.unique(tris)): raise ValueError("Cannot compute adjacency on a selection of triangles.") @@ -1755,8 +1752,6 @@ def mesh_dist(tris, vert): dist_matrix : scipy.sparse.csr_array Sparse matrix with distances between adjacent vertices. """ - from scipy.sparse import csr_matrix - edges = mesh_edges(tris).tocoo() # Euclidean distances between neighboring vertices @@ -2100,7 +2095,6 @@ def get_montage_volume_labels(montage, subject, subjects_dir=None, aseg="auto", """ from ._freesurfer import _get_aseg, read_freesurfer_lut from .channels import DigMontage - from ._freesurfer import read_freesurfer_lut, _get_aseg _validate_type(montage, DigMontage, "montage") _validate_type(dist, (int, float), "dist") diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index ce44f8d058b..493ffccd680 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -9,8 +9,6 @@ from pathlib import Path from types import ModuleType -import pytest - import mne from mne.utils import _pl, logger, run_subprocess diff --git a/mne/time_frequency/_stft.py b/mne/time_frequency/_stft.py index 0a7d7792292..15bbfc588bc 100644 --- a/mne/time_frequency/_stft.py +++ b/mne/time_frequency/_stft.py @@ -3,6 +3,7 @@ # Copyright the MNE-Python contributors. from math import ceil + import numpy as np from ..utils import logger, verbose diff --git a/mne/time_frequency/_stockwell.py b/mne/time_frequency/_stockwell.py index 51e24766301..ce2da7c1cf6 100644 --- a/mne/time_frequency/_stockwell.py +++ b/mne/time_frequency/_stockwell.py @@ -186,8 +186,6 @@ def tfr_array_stockwell( ---------- .. footbibliography:: """ - from scipy.fft import fftfreq - _validate_type(data, np.ndarray, "data") if data.ndim != 3: raise ValueError( diff --git a/mne/time_frequency/multitaper.py b/mne/time_frequency/multitaper.py index 9e43aa4698c..5b54628d959 100644 --- a/mne/time_frequency/multitaper.py +++ b/mne/time_frequency/multitaper.py @@ -263,8 +263,6 @@ 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] @@ -292,7 +290,6 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None, remove_dc=True): def _compute_mt_params(n_times, sfreq, bandwidth, low_bias, adaptive, verbose=None): """Triage windowing and multitaper parameters.""" # Compute standardized half-bandwidth - from scipy.signal import get_window if isinstance(bandwidth, str): logger.info(f' Using standard spectrum estimation with "{bandwidth}" window') @@ -407,8 +404,6 @@ 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/tfr.py b/mne/time_frequency/tfr.py index f411cc0e0ea..dd1474c7b0e 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -14,7 +14,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,8 +368,6 @@ 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) diff --git a/mne/transforms.py b/mne/transforms.py index 4c4b6130f6d..73bf36d0069 100644 --- a/mne/transforms.py +++ b/mne/transforms.py @@ -921,8 +921,6 @@ def _sh_real_to_complex(shs, order): def _compute_sph_harm(order, az, pol): """Compute complex spherical harmonics of spherical coordinates.""" - from scipy.special import sph_harm - out = np.empty((len(az), _get_n_moments(order) + 1)) # _deg_ord_idx(0, 0) = -1 so we're actually okay to use it here for degree in range(order + 1): @@ -986,7 +984,6 @@ 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 @@ -1127,7 +1124,6 @@ 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 @@ -1503,8 +1499,6 @@ 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/_testing.py b/mne/utils/_testing.py index 5ae04ced01f..63e0d1036b9 100644 --- a/mne/utils/_testing.py +++ b/mne/utils/_testing.py @@ -273,8 +273,6 @@ 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/linalg.py b/mne/utils/linalg.py index 545030cd508..82a3db107db 100644 --- a/mne/utils/linalg.py +++ b/mne/utils/linalg.py @@ -26,7 +26,6 @@ import numpy as np - from ..fixes import _safe_svd # For efficiency, names should be str or tuple of str, dtype a builtin @@ -133,8 +132,8 @@ 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.linalg import LinAlgError 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: diff --git a/mne/utils/numerics.py b/mne/utils/numerics.py index cc4f91f83ac..a9188e85adb 100644 --- a/mne/utils/numerics.py +++ b/mne/utils/numerics.py @@ -695,8 +695,6 @@ def object_size(x, memo=None): size : int The estimated size in bytes of the object. """ - from scipy import sparse - # Note: this will not process object arrays properly (since those only) # hold references if memo is None: diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index a36662376cb..a391279aabd 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -18,7 +18,6 @@ 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 @@ -38,6 +37,7 @@ _read_mri_info, read_freesurfer_lut, ) +from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces from ..defaults import DEFAULTS from ..fixes import _crop_colorbar, _get_img_fdata from ..surface import ( @@ -90,7 +90,6 @@ figure_nobar, plt_show, ) -from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces verbose_dec = verbose FIDUCIAL_ORDER = (FIFF.FIFFV_POINT_LPA, FIFF.FIFFV_POINT_NASION, FIFF.FIFFV_POINT_RPA) @@ -675,7 +674,6 @@ def plot_alignment( .. versionadded:: 0.15 """ # Update the backend - from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces from ..source_space._source_space import _ensure_src from .backends.renderer import _get_renderer @@ -1742,8 +1740,6 @@ 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 diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index b444fad77c7..5a163b827bb 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -14,7 +14,6 @@ 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 @@ -1067,7 +1066,6 @@ def _configure_vertex_time_course(self): def _configure_picking(self): # get data for each hemi - from scipy import sparse for idx, hemi in enumerate(["vol", "lh", "rh"]): hemi_data = self._data.get(hemi) @@ -4040,8 +4038,6 @@ 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 d22fdf4298e..615596ef235 100644 --- a/mne/viz/_dipole.py +++ b/mne/viz/_dipole.py @@ -5,6 +5,7 @@ # Copyright the MNE-Python contributors. import os.path as op + import numpy as np from .._freesurfer import _estimate_talxfm_rigid, _get_head_surface diff --git a/mne/viz/_figure.py b/mne/viz/_figure.py index 022b306ea08..913211735cb 100644 --- a/mne/viz/_figure.py +++ b/mne/viz/_figure.py @@ -14,6 +14,7 @@ import numpy as np from .._fiff.pick import _DATA_CH_TYPES_SPLIT +from ..annotations import _sync_onset from ..defaults import _handle_default from ..filter import _iir_filter, _overlap_add_filter from ..fixes import _compare_version @@ -26,7 +27,6 @@ set_config, verbose, ) -from ..annotations import _sync_onset from .backends._utils import VALID_BROWSE_BACKENDS from .utils import _get_color_list, _setup_plot_projector, _show_browser diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index 99b1551ed11..4ecc4bd414d 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -201,6 +201,7 @@ def plot_epochs_image( +----------+----------------------------+------------+-------------------+ """ from scipy.ndimage import gaussian_filter1d + from ..epochs import EpochsArray _validate_type(group_by, (dict, None), "group_by") diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 36e6a971365..d7fde5d6c28 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -20,6 +20,7 @@ channel_type, pick_info, ) +from ..channels.layout import _pair_grad_sensors from ..defaults import _handle_default from ..utils import ( _check_ch_locs, @@ -44,8 +45,6 @@ _prepare_topomap, _prepare_topomap_plot, _set_contour_locator, - _check_sphere, - _make_head_outlines, plot_topomap, ) from .utils import ( @@ -72,7 +71,6 @@ _validate_if_list_of_axes, plt_show, ) -from ..channels.layout import _pair_grad_sensors, find_layout def _butterfly_onpick(event, params): @@ -130,7 +128,6 @@ def _line_plot_onselect( """Draw topomaps from the selected area.""" import matplotlib.pyplot as plt - from ..channels.layout import _pair_grad_sensors ch_types = [type_ for type_ in ch_types if type_ in ("eeg", "grad", "mag")] if len(ch_types) == 0: diff --git a/mne/viz/misc.py b/mne/viz/misc.py index 86e08300cff..c83a4dfe717 100644 --- a/mne/viz/misc.py +++ b/mne/viz/misc.py @@ -1132,7 +1132,6 @@ def plot_filter( ----- .. versionadded:: 0.14 """ - from scipy.signal import freqz, group_delay, lfilter, filtfilt, sosfilt, sosfiltfilt import matplotlib.pyplot as plt sfreq = float(sfreq) diff --git a/mne/viz/montage.py b/mne/viz/montage.py index 0ee23363aca..221cc21f7a0 100644 --- a/mne/viz/montage.py +++ b/mne/viz/montage.py @@ -5,6 +5,7 @@ """Functions to plot EEG sensor montages or digitizer montages.""" from copy import deepcopy + import numpy as np from scipy.spatial.distance import cdist diff --git a/mne/viz/topo.py b/mne/viz/topo.py index ffb1bb8535f..096aee9fbf0 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -775,8 +775,8 @@ def _erfimage_imshow( vlim_array=None, ): """Plot erfimage on sensor topography.""" - from scipy import ndimage import matplotlib.pyplot as plt + from scipy import ndimage this_data = data[:, ch_idx, :] if vlim_array is not None: @@ -981,7 +981,7 @@ def _plot_evoked_topo( """ import matplotlib.pyplot as plt - from ..channels.layout import _merge_ch_data, _pair_grad_sensors, find_layout + from ..channels.layout import find_layout from ..cov import whiten_evoked if type(evoked) not in (tuple, list): diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index a3dbcbf6153..dcc42ec1797 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -21,13 +21,6 @@ from scipy.spatial.distance import pdist, squareform from .._fiff.meas_info import Info, _simplify_info -from ..channels.channels import _get_ch_type -from ..channels.layout import ( - _find_topomap_coords, - find_layout, - _pair_grad_sensors, - _merge_ch_data, -) from .._fiff.pick import ( _MEG_CH_TYPES_SPLIT, _pick_data_channels, @@ -38,6 +31,12 @@ pick_types, ) from ..baseline import rescale +from ..channels.layout import ( + _find_topomap_coords, + _merge_ch_data, + _pair_grad_sensors, + find_layout, +) from ..defaults import ( _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, @@ -116,7 +115,7 @@ def _adjust_meg_sphere(sphere, info, ch_type): def _prepare_topomap_plot(inst, ch_type, sphere=None): """Prepare topo plot.""" - from ..channels.layout import _find_topomap_coords, _pair_grad_sensors, find_layout + from ..channels.layout import _find_topomap_coords info = copy.deepcopy(inst if isinstance(inst, Info) else inst.info) sphere, clip_origin = _adjust_meg_sphere(sphere, info, ch_type) @@ -193,7 +192,6 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): - from scipy.spatial.distance import pdist, squareform picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") chs = [info["chs"][i] for i in picks] @@ -689,8 +687,6 @@ 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 @@ -827,11 +823,6 @@ class _GridData: """ def __init__(self, pos, image_interp, extrapolate, origin, radii, border): - from scipy.interpolate import ( - CloughTocher2DInterpolator, - NearestNDInterpolator, - LinearNDInterpolator, - ) # in principle this works in N dimensions, not just 2 assert pos.ndim == 2 and pos.shape[1] == 2, pos.shape @@ -1102,8 +1093,6 @@ 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, @@ -1207,7 +1196,6 @@ def _plot_topomap( from ..channels.layout import ( _find_topomap_coords, _merge_ch_data, - _pair_grad_sensors, ) data = np.asarray(data) @@ -2981,7 +2969,6 @@ def _onselect( import matplotlib.pyplot as plt from matplotlib.collections import PathCollection - from ..channels.layout import _pair_grad_sensors ax = eclick.inaxes xmin = min(eclick.xdata, erelease.xdata) @@ -3800,7 +3787,6 @@ def plot_ch_adjacency(info, adjacency, ch_names, kind="2d", edit=False): ----- .. versionadded:: 1.1 """ - from scipy import sparse import matplotlib as mpl import matplotlib.pyplot as plt diff --git a/mne/viz/utils.py b/mne/viz/utils.py index abb4d5004ae..b9b844b321a 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -23,8 +23,6 @@ from .._fiff.constants import FIFF from .._fiff.meas_info import Info from .._fiff.open import show_fiff -from .._fiff.meas_info import Info -from .._fiff.constants import FIFF from .._fiff.pick import ( _DATA_CH_TYPES_ORDER_DEFAULT, _DATA_CH_TYPES_SPLIT, @@ -879,8 +877,6 @@ 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: From b4426cc04334eb0fcd0ff3d657a1b445edc57c4d Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Sun, 23 Feb 2025 11:30:34 +0100 Subject: [PATCH 04/46] Style --- mne/filter.py | 8 ++++---- mne/fixes.py | 22 +++++++++++----------- mne/gui/_coreg.py | 1 - mne/io/_read_raw.py | 2 +- mne/io/nirx/nirx.py | 2 ++ 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/mne/filter.py b/mne/filter.py index 1e297b99281..e1da91447ab 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -1870,10 +1870,10 @@ def resample( # check explicitly for backwards compatibility if not isinstance(axis, int): err = ( - "The axis parameter needs to be an integer (got %s). " + f"The axis parameter needs to be an integer (got {repr(axis)}). " "The axis parameter was missing from this function for a " "period of time, you might be intending to specify the " - "subsequent window parameter." % repr(axis) + "subsequent window parameter." ) raise TypeError(err) @@ -1976,11 +1976,11 @@ def _resample_fft(x_flat, *, ratio, final_len, pad, window, npad, n_jobs): # figure out windowing function if callable(window): - W = window(fft.fftfreq(orig_len)) + W = window(signal.fft.fftfreq(orig_len)) elif isinstance(window, np.ndarray) and window.shape == (orig_len,): W = window else: - W = fft.ifftshift(signal.get_window(window, orig_len)) + W = signal.fft.ifftshift(signal.get_window(window, orig_len)) W *= float(new_len) / float(orig_len) # figure out if we should use CUDA diff --git a/mne/fixes.py b/mne/fixes.py index 60d83c47e26..1a3d7c41ff7 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -44,7 +44,6 @@ def _compare_version(version_a, operator, version_b): bool The result of the version comparison. """ - mapping = {"<": "lt", "<=": "le", "==": "eq", "!=": "ne", ">=": "ge", ">": "gt"} with warnings.catch_warnings(record=True): warnings.simplefilter("ignore") return eval(f'parse("{version_a}") {operator} parse("{version_b}")') @@ -113,7 +112,7 @@ def rng_uniform(rng): def _validate_sos(sos): - """Helper to validate a SOS input""" + """Helper to validate a SOS input.""" sos = np.atleast_2d(sos) if sos.ndim != 2: raise ValueError("sos array must be 2D") @@ -142,7 +141,7 @@ def _get_img_fdata(img): def empirical_covariance(X, assume_centered=False): - """Computes the Maximum likelihood covariance estimator + """Compute the Maximum likelihood covariance estimator. Parameters ---------- @@ -271,7 +270,7 @@ def __init__(self, store_precision=True, assume_centered=False): self.assume_centered = assume_centered def _set_covariance(self, covariance): - """Saves the covariance and precision estimates + """Save the covariance and precision estimates. Storage is done accordingly to `self.store_precision`. Precision stored only if invertible. @@ -366,7 +365,7 @@ def score(self, X_test, y=None): return res def error_norm(self, comp_cov, norm="frobenius", scaling=True, squared=True): - """Computes the Mean Squared Error between two covariance estimators. + """Compute the Mean Squared Error between two covariance estimators. Parameters ---------- @@ -415,7 +414,7 @@ def error_norm(self, comp_cov, norm="frobenius", scaling=True, squared=True): return result def mahalanobis(self, observations): - """Computes the squared Mahalanobis distances of given observations. + """Compute the squared Mahalanobis distances of given observations. Parameters ---------- @@ -439,7 +438,7 @@ def mahalanobis(self, observations): def log_likelihood(emp_cov, precision): - """Computes the sample mean of the log_likelihood under a covariance model + """Compute the sample mean of the log_likelihood under a covariance model. computes the empirical expected log-likelihood (accounting for the normalization terms and scaling), allowing for universal comparison (beyond @@ -479,7 +478,8 @@ def _logdet(A): def _infer_dimension_(spectrum, n_samples, n_features): - """Infers the dimension of a dataset of shape (n_samples, n_features) + """Infers the dimension of a dataset of shape (n_samples, n_features). + The dataset is described by its spectrum `spectrum`. """ n_spectrum = len(spectrum) @@ -543,7 +543,7 @@ def svd_flip(u, v, u_based_decision=True): def stable_cumsum(arr, axis=None, rtol=1e-05, atol=1e-08): - """Use high precision for cumsum and check that final value matches sum + """Use high precision for cumsum and check that final value matches sum. Parameters ---------- @@ -577,8 +577,8 @@ def stable_cumsum(arr, axis=None, rtol=1e-05, atol=1e-08): def _crop_colorbar(cbar, cbar_vmin, cbar_vmax): - """ - Crop a colorbar to show from cbar_vmin to cbar_vmax + """Crop a colorbar to show from cbar_vmin to cbar_vmax. + Used when symmetric_cbar=False is used. """ if (cbar_vmin is None) and (cbar_vmax is None): diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 9037ea079bb..8ba8c80d8d7 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -35,7 +35,6 @@ ) from ..defaults import DEFAULTS from ..io import read_raw -from ..io._read_raw import read_raw from ..io._read_raw import supported as raw_supported_types from ..surface import _CheckInside, _DistanceQuery from ..transforms import ( diff --git a/mne/io/_read_raw.py b/mne/io/_read_raw.py index 3422f9ef81e..a2c512ae9be 100644 --- a/mne/io/_read_raw.py +++ b/mne/io/_read_raw.py @@ -31,6 +31,7 @@ read_raw_nirx, read_raw_snirf, ) +from .base import BaseRaw def _read_unsupported(fname, **kwargs): @@ -80,7 +81,6 @@ def _read_unsupported(fname, **kwargs): # NEDF ".nedf": dict(NEDF=read_raw_nedf), } -from .base import BaseRaw def _read_unsupported(fname, **kwargs): diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index caa21653388..138ffc32ba5 100644 --- a/mne/io/nirx/nirx.py +++ b/mne/io/nirx/nirx.py @@ -93,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") From fb2676e4ac42801c40c990384b9c92d2d3c24e71 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Sun, 23 Feb 2025 11:56:14 +0100 Subject: [PATCH 05/46] Various fixes --- mne/__init__.py | 11 ------ mne/channels/__init__.py | 1 - mne/cov.py | 1 - mne/dipole.py | 1 - mne/html_templates/__init__.py | 2 +- mne/io/__init__.py | 61 ---------------------------------- mne/report/report.py | 1 - mne/time_frequency/__init__.py | 1 - mne/time_frequency/spectrum.py | 4 ++- mne/time_frequency/tfr.py | 8 ++++- mne/utils/__init__.py | 13 +++----- mne/viz/__init__.py | 1 - mne/viz/evoked.py | 3 +- mne/viz/topo.py | 3 +- mne/viz/topomap.py | 15 ++++----- 15 files changed, 26 insertions(+), 100 deletions(-) diff --git a/mne/__init__.py b/mne/__init__.py index d0732a00353..d933da32bd9 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -153,16 +153,6 @@ grade_to_vertices, compute_source_morph, ) -from .source_space import ( - read_source_spaces, - write_source_spaces, - setup_source_space, - setup_volume_source_space, - SourceSpaces, - add_source_space_distances, - morph_source_spaces, - get_volume_labels_from_src, -) from .annotations import ( Annotations, read_annotations, @@ -249,7 +239,6 @@ from . import surface from . import time_frequency from . import viz -from . import decoding from . import export # initialize logging diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py index b3706c0d21f..6e843618f85 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -38,7 +38,6 @@ rename_channels, fix_mag_coil_types, read_ch_adjacency, - _get_ch_type, find_ch_adjacency, make_1020_channel_selections, combine_channels, diff --git a/mne/cov.py b/mne/cov.py index a27dbeea5ab..802dd52548a 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -53,7 +53,6 @@ ) from .rank import _compute_rank from .utils import ( - _array_repr, _check_fname, _check_on_missing, _check_option, diff --git a/mne/dipole.py b/mne/dipole.py index 906daba9c06..6f92fc59a26 100644 --- a/mne/dipole.py +++ b/mne/dipole.py @@ -46,7 +46,6 @@ copy_function_doc_to_method_doc, fill_doc, logger, - pinvh, verbose, warn, ) diff --git a/mne/html_templates/__init__.py b/mne/html_templates/__init__.py index 39eeb9d0747..0d4be5f51de 100644 --- a/mne/html_templates/__init__.py +++ b/mne/html_templates/__init__.py @@ -4,4 +4,4 @@ """Jinja2 HTML templates.""" -from ._templates import repr_templates_env, report_templates_env +from ._templates import _get_html_template, _get_html_templates_env diff --git a/mne/io/__init__.py b/mne/io/__init__.py index 29eb130eac8..286f5edd540 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -68,70 +68,9 @@ from .._fiff.open import show_fiff from .._fiff.pick import get_channel_type_constants # moved up a level -# These we will remove in 1.6 -from .._fiff import ( - _dep_msg, -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=[ - "constants", - "pick", - ], - submod_attrs={ - "base": ["BaseRaw", "concatenate_raws", "match_channel_orders"], - "array": ["RawArray"], - "besa": ["read_evoked_besa"], - "brainvision": ["read_raw_brainvision"], - "bti": ["read_raw_bti"], - "cnt": ["read_raw_cnt"], - "ctf": ["read_raw_ctf"], - "curry": ["read_raw_curry"], - "edf": ["read_raw_edf", "read_raw_bdf", "read_raw_gdf"], - "egi": ["read_raw_egi", "read_evokeds_mff"], - "kit": ["read_raw_kit", "read_epochs_kit"], - "fiff": ["read_raw_fif", "Raw"], - "fil": ["read_raw_fil"], - "nedf": ["read_raw_nedf"], - "nicolet": ["read_raw_nicolet"], - "artemis123": ["read_raw_artemis123"], - "eeglab": ["read_raw_eeglab", "read_epochs_eeglab"], - "eximia": ["read_raw_eximia"], - "hitachi": ["read_raw_hitachi"], - "nirx": ["read_raw_nirx"], - "boxy": ["read_raw_boxy"], - "snirf": ["read_raw_snirf"], - "persyst": ["read_raw_persyst"], - "fieldtrip": [ - "read_raw_fieldtrip", - "read_epochs_fieldtrip", - "read_evoked_fieldtrip", - ], - "nihon": ["read_raw_nihon"], - "nsx": ["read_raw_nsx"], - "_read_raw": ["read_raw"], - "eyelink": ["read_raw_eyelink"], - "_fiff_wrap": [ - "read_info", - "write_info", - "anonymize_info", - "read_fiducials", - "write_fiducials", - "show_fiff", - "get_channel_type_constants", - ], - }, -) - from . import constants from . import pick -# These three we will remove in 1.6 -from . import proj -from . import meas_info -from . import reference - def __getattr__(name): """Try getting attribute from fiff submodule.""" diff --git a/mne/report/report.py b/mne/report/report.py index 91901186a5b..52ad97b9f3e 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -66,7 +66,6 @@ from ..utils.spectrum import _split_psd_kwargs from ..viz import ( Figure3D, - _get_plot_ch_type, create_3d_figure, get_3d_backend, plot_alignment, diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 4a3a5c140b5..7096d146264 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -9,7 +9,6 @@ tfr_morlet, AverageTFR, tfr_multitaper, - _BaseTFR, read_tfrs, write_tfrs, EpochsTFR, diff --git a/mne/time_frequency/spectrum.py b/mne/time_frequency/spectrum.py index 03a57010061..876ee6da0ff 100644 --- a/mne/time_frequency/spectrum.py +++ b/mne/time_frequency/spectrum.py @@ -13,7 +13,6 @@ from .._fiff.meas_info import ContainsMixin, Info from .._fiff.pick import _pick_data_channels, _picks_to_idx, pick_info from ..channels.channels import UpdateChannelsMixin -from ..channels.layout import _merge_ch_data, find_layout from ..defaults import ( _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, @@ -735,6 +734,7 @@ def plot_topo( fig : instance of matplotlib.figure.Figure Figure distributing one image per channel across sensor topography. """ + from ..channels.layout import find_layout if layout is None: layout = find_layout(self.info) @@ -833,6 +833,8 @@ def plot_topomap( fig : instance of Figure Figure showing one scalp topography per frequency band. """ + from ..channels.layout import _merge_ch_data + ch_type = _get_plot_ch_type(self, ch_type) if units is None: units = _handle_default("units", None) diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index dd1474c7b0e..029419aaf37 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -19,7 +19,6 @@ from .._fiff.pick import _picks_to_idx, pick_info from ..baseline import _check_baseline, rescale from ..channels.channels import UpdateChannelsMixin -from ..channels.layout import _find_topomap_coords, _merge_ch_data, _pair_grad_sensors from ..defaults import _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, _INTERPOLATION_DEFAULT from ..filter import next_fast_len from ..parallel import parallel_func @@ -1596,6 +1595,8 @@ def _onselect( verbose=None, ): """Respond to rectangle selector in TFR image plots with a topomap plot.""" + from ..channels.layout import _pair_grad_sensors + if abs(eclick.x - erelease.x) < 0.1 or abs(eclick.y - erelease.y) < 0.1: return t_range = (min(eclick.xdata, erelease.xdata), max(eclick.xdata, erelease.xdata)) @@ -4231,6 +4232,11 @@ def _check_tfr_complex(tfr, reason="source space estimation"): def _merge_if_grads(data, info, ch_type, sphere, combine=None): + from ..channels.layout import ( + _find_topomap_coords, + _merge_ch_data, + _pair_grad_sensors, + ) if ch_type == "grad": grad_picks = _pair_grad_sensors(info, topomap_coords=False) pos = _find_topomap_coords(info, picks=grad_picks[::2], sphere=sphere) diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 6c2c705d10c..3674058a139 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -57,7 +57,7 @@ _ensure_events, _check_eeglabio_installed, _check_pybv_installed, - _check_edflib_installed, + _check_edfio_installed, _to_rgb, _soft_import, _check_dict_keys, @@ -67,6 +67,7 @@ _import_nibabel, _import_pymatreader_funcs, _check_head_radius, + _check_method_kwargs, ) from .config import ( set_config, @@ -90,7 +91,6 @@ fill_doc, deprecated_alias, legacy, - copy_base_doc_to_subclass_doc, docdict as _docdict, ) from .fetching import _url_to_local_path @@ -111,6 +111,7 @@ _parse_verbose, ) from .misc import ( + _auto_weakref, run_subprocess, _pl, _clean_names, @@ -131,7 +132,6 @@ requires_mne, requires_good_network, ArgvSetter, - SilenceStdout, has_freesurfer, has_mne_c, _TempDir, @@ -176,10 +176,6 @@ _PCA, _mask_to_onsets_offsets, _array_equal_nan, - _julian_to_cal, - _cal_to_julian, - _dt_to_julian, - _julian_to_dt, _dt_to_stamp, _stamp_to_dt, _check_dt, @@ -195,7 +191,7 @@ ExtendedTimeMixin, _prepare_read_metadata, _prepare_write_metadata, - _FakeNoPandas, + _check_decim, ) from .linalg import ( _svd_lwork, @@ -204,6 +200,7 @@ sqrtm_sym, eigh, _get_blas_funcs, + pinv, ) from .dataframe import ( _set_pandas_dtype, diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index c8b5af3940b..85ac9ec429d 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -19,7 +19,6 @@ ) from .topo import plot_topo_image_epochs, iter_topography from .utils import ( - tight_layout, mne_analyze_colormap, compare_fiff, ClickableImage, diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index d7fde5d6c28..aa38d5d7750 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -20,7 +20,6 @@ channel_type, pick_info, ) -from ..channels.layout import _pair_grad_sensors from ..defaults import _handle_default from ..utils import ( _check_ch_locs, @@ -128,6 +127,8 @@ def _line_plot_onselect( """Draw topomaps from the selected area.""" import matplotlib.pyplot as plt + from mne.channels.layout import _pair_grad_sensors + ch_types = [type_ for type_ in ch_types if type_ in ("eeg", "grad", "mag")] if len(ch_types) == 0: diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 096aee9fbf0..5e351bf8d97 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -10,7 +10,6 @@ import numpy as np from .._fiff.pick import _picks_to_idx, channel_type, pick_types -from ..channels.layout import _merge_ch_data, _pair_grad_sensors, find_layout from ..defaults import _handle_default from ..utils import Bunch, _check_option, _clean_names, _is_numeric, _to_rgb, fill_doc from .ui_events import ChannelsSelect, publish, subscribe @@ -981,7 +980,7 @@ def _plot_evoked_topo( """ import matplotlib.pyplot as plt - from ..channels.layout import find_layout + from ..channels.layout import _merge_ch_data, _pair_grad_sensors, find_layout from ..cov import whiten_evoked if type(evoked) not in (tuple, list): diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index dcc42ec1797..a6582404a96 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -31,12 +31,6 @@ pick_types, ) from ..baseline import rescale -from ..channels.layout import ( - _find_topomap_coords, - _merge_ch_data, - _pair_grad_sensors, - find_layout, -) from ..defaults import ( _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, @@ -115,7 +109,7 @@ def _adjust_meg_sphere(sphere, info, ch_type): def _prepare_topomap_plot(inst, ch_type, sphere=None): """Prepare topo plot.""" - from ..channels.layout import _find_topomap_coords + from ..channels.layout import _find_topomap_coords, _pair_grad_sensors, find_layout info = copy.deepcopy(inst if isinstance(inst, Info) else inst.info) sphere, clip_origin = _adjust_meg_sphere(sphere, info, ch_type) @@ -192,7 +186,7 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): - + from mne.channels.layout import _find_topomap_coords picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") chs = [info["chs"][i] for i in picks] locs3d = np.array([ch["loc"][:3] for ch in chs]) @@ -246,6 +240,8 @@ def _average_fnirs_overlaps(info, ch_type, sphere): def _plot_update_evoked_topomap(params, bools): """Update topomaps.""" + from mne.channels.layout import _merge_ch_data + projs = [ proj for ii, proj in enumerate(params["projs"]) if ii in np.where(bools)[0] ] @@ -1196,6 +1192,7 @@ def _plot_topomap( from ..channels.layout import ( _find_topomap_coords, _merge_ch_data, + _pair_grad_sensors, ) data = np.asarray(data) @@ -2969,6 +2966,7 @@ def _onselect( import matplotlib.pyplot as plt from matplotlib.collections import PathCollection + from mne.channels.layout import _pair_grad_sensors ax = eclick.inaxes xmin = min(eclick.xdata, erelease.xdata) @@ -3397,6 +3395,7 @@ def _plot_corrmap( show_names=False, ): """Customize ica.plot_components for corrmap.""" + from mne.channels.layout import _merge_ch_data if not template: title = "Detected components" if label is not None: From 269e0d3a03ce944e0f552b78dca504b98354f4de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:29:50 +0000 Subject: [PATCH 06/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/_fiff/meas_info.py | 2 -- mne/decoding/receptive_field.py | 1 - mne/stats/cluster_level.py | 1 - mne/surface.py | 2 -- mne/time_frequency/spectrum.py | 1 + mne/time_frequency/tfr.py | 1 + mne/viz/evoked.py | 1 - mne/viz/topomap.py | 3 ++- 8 files changed, 4 insertions(+), 8 deletions(-) diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index b5415008e22..f3f0fb4669e 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -1084,7 +1084,6 @@ def __init__(self, initial): def _format_trans(obj, key): - try: t = obj[key] except KeyError: @@ -1154,7 +1153,6 @@ def _check_bads(bads, *, info): def _check_dev_head_t(dev_head_t, *, info): - _validate_type(dev_head_t, (Transform, None), "info['dev_head_t']") if dev_head_t is not None: dev_head_t = _ensure_trans(dev_head_t, "meg", "head") diff --git a/mne/decoding/receptive_field.py b/mne/decoding/receptive_field.py index 534b52f5085..5396f128581 100644 --- a/mne/decoding/receptive_field.py +++ b/mne/decoding/receptive_field.py @@ -507,7 +507,6 @@ def _reshape_for_est(X_del): # Create a correlation scikit-learn-style scorer def _corr_score(y_true, y, multioutput=None): - assert multioutput == "raw_values" for this_y in (y_true, y): if this_y.ndim != 2: diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py index 8bc2cdc18eb..c12ca7547d6 100644 --- a/mne/stats/cluster_level.py +++ b/mne/stats/cluster_level.py @@ -613,7 +613,6 @@ def _pval_from_histogram(T, H0, tail): def _setup_adjacency(adjacency, n_tests, n_times): - if not sparse.issparse(adjacency): raise ValueError( "If adjacency matrix is given, it must be a SciPy sparse matrix." diff --git a/mne/surface.py b/mne/surface.py index 3d9c9f6ae6e..ccfcaae8583 100644 --- a/mne/surface.py +++ b/mne/surface.py @@ -586,7 +586,6 @@ def __init__(self, xhs): self._xhs = xhs def query(self, rr): - nearest = list() dists = list() for r in rr: @@ -1718,7 +1717,6 @@ def mesh_edges(tris): @lru_cache(maxsize=10) def _mesh_edges(tris=None): - if np.max(tris) > len(np.unique(tris)): raise ValueError("Cannot compute adjacency on a selection of triangles.") diff --git a/mne/time_frequency/spectrum.py b/mne/time_frequency/spectrum.py index 876ee6da0ff..a89ffd02e23 100644 --- a/mne/time_frequency/spectrum.py +++ b/mne/time_frequency/spectrum.py @@ -735,6 +735,7 @@ def plot_topo( Figure distributing one image per channel across sensor topography. """ from ..channels.layout import find_layout + if layout is None: layout = find_layout(self.info) diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 029419aaf37..86bd886444d 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -4237,6 +4237,7 @@ def _merge_if_grads(data, info, ch_type, sphere, combine=None): _merge_ch_data, _pair_grad_sensors, ) + if ch_type == "grad": grad_picks = _pair_grad_sensors(info, topomap_coords=False) pos = _find_topomap_coords(info, picks=grad_picks[::2], sphere=sphere) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index aa38d5d7750..73502c593c4 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -129,7 +129,6 @@ def _line_plot_onselect( from mne.channels.layout import _pair_grad_sensors - ch_types = [type_ for type_ in ch_types if type_ in ("eeg", "grad", "mag")] if len(ch_types) == 0: raise ValueError("Interactive topomaps only allowed for EEG and MEG channels.") diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index a6582404a96..146e54572b4 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -187,6 +187,7 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): from mne.channels.layout import _find_topomap_coords + picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") chs = [info["chs"][i] for i in picks] locs3d = np.array([ch["loc"][:3] for ch in chs]) @@ -819,7 +820,6 @@ class _GridData: """ def __init__(self, pos, image_interp, extrapolate, origin, radii, border): - # 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") @@ -3396,6 +3396,7 @@ def _plot_corrmap( ): """Customize ica.plot_components for corrmap.""" from mne.channels.layout import _merge_ch_data + if not template: title = "Detected components" if label is not None: From d8b963f623ccbdf206a6a1ad09133fb539ea0b72 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 09:47:38 +0100 Subject: [PATCH 07/46] Fix ruff errors --- mne/cov.py | 1 + mne/dipole.py | 1 + mne/fixes.py | 7 ++++--- mne/report/report.py | 2 +- mne/utils/__init__.py | 2 ++ mne/viz/topo.py | 1 + 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mne/cov.py b/mne/cov.py index 802dd52548a..a27dbeea5ab 100644 --- a/mne/cov.py +++ b/mne/cov.py @@ -53,6 +53,7 @@ ) from .rank import _compute_rank from .utils import ( + _array_repr, _check_fname, _check_on_missing, _check_option, diff --git a/mne/dipole.py b/mne/dipole.py index 6f92fc59a26..906daba9c06 100644 --- a/mne/dipole.py +++ b/mne/dipole.py @@ -46,6 +46,7 @@ copy_function_doc_to_method_doc, fill_doc, logger, + pinvh, verbose, warn, ) diff --git a/mne/fixes.py b/mne/fixes.py index 1a3d7c41ff7..1b7f57228c8 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -1,4 +1,4 @@ -"""Compatibility fixes for older versions of libraries +"""Compatibility fixes for older versions of libraries. If you add content to this file, please give the version of the package at which the fix is no longer needed. @@ -70,7 +70,7 @@ def _median_complex(data, axis): def _safe_svd(A, **kwargs): - """Wrapper to 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 @@ -112,7 +112,7 @@ def rng_uniform(rng): def _validate_sos(sos): - """Helper to validate a SOS input.""" + """Help validate an SOS input.""" sos = np.atleast_2d(sos) if sos.ndim != 2: raise ValueError("sos array must be 2D") @@ -527,6 +527,7 @@ def _assess_dimension_(spectrum, rank, n_samples, n_features): 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/report/report.py b/mne/report/report.py index 52ad97b9f3e..0fa7aa9e87c 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -80,7 +80,7 @@ from ..viz._brain.view import views_dicts from ..viz._scraper import _mne_qt_browser_screenshot from ..viz.misc import _get_bem_plotting_surfaces, _plot_mri_contours -from ..viz.utils import _ndarray_to_fig +from ..viz.utils import _get_plot_ch_type, _ndarray_to_fig _BEM_VIEWS = ("axial", "sagittal", "coronal") diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 3674058a139..63ea264a98f 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -183,6 +183,7 @@ _arange_div, _hashable_ndarray, _custom_lru_cache, + _array_repr, ) from .mixin import ( SizeMixin, @@ -201,6 +202,7 @@ eigh, _get_blas_funcs, pinv, + pinvh, ) from .dataframe import ( _set_pandas_dtype, diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 5e351bf8d97..1bbaeeb5e96 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -10,6 +10,7 @@ import numpy as np from .._fiff.pick import _picks_to_idx, channel_type, pick_types +from ..channels import find_layout from ..defaults import _handle_default from ..utils import Bunch, _check_option, _clean_names, _is_numeric, _to_rgb, fill_doc from .ui_events import ChannelsSelect, publish, subscribe From 0f82b9c641c5f656981d3905fd91acb24281ed06 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 10:02:17 +0100 Subject: [PATCH 08/46] More fixes --- README.rst | 1 - codemeta.json | 1 - environment.yml | 1 - mne/_fiff/__init__.py | 19 +----------- mne/preprocessing/__init__.py | 51 -------------------------------- mne/source_space/__init__.py | 21 ------------- mne/tests/test_import_nesting.py | 33 --------------------- mne/utils/config.py | 1 - pyproject.toml | 1 - tools/environment_minimal.yml | 1 - tools/environment_old.yml | 1 - tools/hooks/sync_dependencies.py | 1 - 12 files changed, 1 insertion(+), 131 deletions(-) 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/_fiff/__init__.py b/mne/_fiff/__init__.py index c9e95cab776..6d14d005fd4 100644 --- a/mne/_fiff/__init__.py +++ b/mne/_fiff/__init__.py @@ -5,21 +5,4 @@ # Copyright the MNE-Python contributors. # All imports should be done directly to submodules, so we don't import -# anything here - - -_dep_msg = ( - "is deprecated will be removed in 1.6, use documented public API instead. " - "If no appropriate public API exists, please open an issue on GitHub." -) - - -# Helper for keeping some attributes en mne/io/*.py -def _io_dep_getattr(name, mod): - import importlib - from ..utils import warn - - fiff_mod = importlib.import_module(f"mne._fiff.{mod}") - obj = getattr(fiff_mod, name) - warn(f"mne.io.{mod}.{name} {_dep_msg}", FutureWarning) - return obj +# anything here. diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index dbb77f99f23..cb58ea7ce76 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -4,54 +4,3 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["eyetracking", "ieeg", "nirs"], - submod_attrs={ - "_annotate_amplitude": ["annotate_amplitude"], - "maxfilter": ["apply_maxfilter"], - "ssp": ["compute_proj_ecg", "compute_proj_eog"], - "eog": ["find_eog_events", "create_eog_epochs"], - "ecg": ["find_ecg_events", "create_ecg_epochs"], - "ica": [ - "ICA", - "ica_find_eog_events", - "ica_find_ecg_events", - "get_score_funcs", - "read_ica", - "corrmap", - "read_ica_eeglab", - ], - "otp": ["oversampled_temporal_projection"], - "_peak_finder": ["peak_finder"], - "infomax_": ["infomax"], - "stim": ["fix_stim_artifact"], - "maxwell": [ - "maxwell_filter", - "find_bad_channels_maxwell", - "compute_maxwell_basis", - "maxwell_filter_prepare_emptyroom", - ], - "realign": ["realign_raw"], - "xdawn": ["Xdawn"], - "_csd": ["compute_current_source_density", "compute_bridged_electrodes"], - "artifact_detection": [ - "annotate_movement", - "compute_average_dev_head_t", - "annotate_muscle_zscore", - "annotate_break", - ], - "_regress": ["regress_artifact", "EOGRegression", "read_eog_regression"], - "_fine_cal": [ - "compute_fine_calibration", - "read_fine_calibration", - "write_fine_calibration", - ], - "_annotate_nan": ["annotate_nan"], - "interpolate": ["equalize_bads", "interpolate_bridged_electrodes"], - "_css": ["cortical_signal_suppression"], - "hfc": ["compute_proj_hfc"], - }, -) diff --git a/mne/source_space/__init__.py b/mne/source_space/__init__.py index b55af4072e8..61c38ef4a44 100644 --- a/mne/source_space/__init__.py +++ b/mne/source_space/__init__.py @@ -3,24 +3,3 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" - -import lazy_loader as lazy - -__getattr__, __dir__, __all__ = lazy.attach( - __name__, - submodules=["_source_space"], - submod_attrs={ - "_source_space": [ - "compute_distance_to_sensors", - "get_decimated_surfaces", - # These are documented in the MNE namespace but it doesn't hurt to - # keep them here as well - "SourceSpaces", - "read_source_spaces", - "write_source_spaces", - "setup_source_space", - "setup_volume_source_space", - "add_source_space_distances", - ], - }, -) diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index 493ffccd680..39484e09b3b 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -223,39 +223,6 @@ def generic_visit(self, node): # 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)) -""" - - def test_module_nesting(): """Test that module imports are properly nested.""" stdout, stderr, code = run_subprocess( 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/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/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..db14274893e 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/", } From bb7ac085ba88bd7980c413ff9c92dcb49175f9b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 09:07:18 +0000 Subject: [PATCH 09/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/tests/test_import_nesting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index 39484e09b3b..4fb1a85aa2b 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -4,7 +4,6 @@ import ast import glob -import os import sys from pathlib import Path from types import ModuleType From c4222a96ba54abeba12fbc1a230c4ebc1c232cc3 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 12:24:06 +0100 Subject: [PATCH 10/46] More fixes --- mne/_fiff/meas_info.py | 22 ++- mne/annotations.py | 4 +- mne/channels/interpolation.py | 4 +- mne/chpi.py | 4 +- mne/cov.py | 8 +- mne/decoding/csp.py | 4 +- mne/decoding/receptive_field.py | 6 +- mne/decoding/ssd.py | 4 +- mne/decoding/time_delaying_ridge.py | 1 - mne/dipole.py | 4 +- mne/evoked.py | 5 +- mne/export/__init__.py | 6 + mne/filter.py | 30 ++- mne/fixes.py | 26 +-- mne/gui/__init__.py | 286 +--------------------------- mne/gui/_coreg.py | 8 +- mne/gui/_gui.py | 280 +++++++++++++++++++++++++++ mne/io/_read_raw.py | 83 +------- 18 files changed, 353 insertions(+), 432 deletions(-) create mode 100644 mne/gui/_gui.py diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index f3f0fb4669e..76d78782ac8 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -16,16 +16,8 @@ import numpy as np -from mne.defaults import _handle_default -from mne.html_templates import _get_html_template - -from ..transforms import ( - Transform, - _coord_frame_name, - _ensure_trans, - _frame_to_str, - invert_transform, -) +from ..defaults import _handle_default +from ..html_templates import _get_html_template from ..utils import ( _check_fname, _check_on_missing, @@ -331,6 +323,7 @@ def get_montage(self): A copy of the channel positions, if available, otherwise ``None``. """ from ..channels.montage import make_dig_montage + from ..transforms import _frame_to_str info = self if isinstance(self, Info) else self.info if info["dig"] is None: @@ -1084,6 +1077,8 @@ def __init__(self, initial): def _format_trans(obj, key): + from ..transforms import Transform + try: t = obj[key] except KeyError: @@ -1153,6 +1148,8 @@ def _check_bads(bads, *, info): def _check_dev_head_t(dev_head_t, *, info): + from ..transforms import Transform, _ensure_trans + _validate_type(dev_head_t, (Transform, None), "info['dev_head_t']") if dev_head_t is not None: dev_head_t = _ensure_trans(dev_head_t, "meg", "head") @@ -1731,6 +1728,7 @@ def normalize_proj(self): def __repr__(self): """Summarize info instead of printing all.""" from ..io.kit.constants import KIT_SYSNAMES + from ..transforms import Transform, _coord_frame_name MAX_WIDTH = 68 strs = [">> 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") @@ -2456,7 +2452,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 diff --git a/mne/fixes.py b/mne/fixes.py index 1b7f57228c8..b3647e19fba 100644 --- a/mne/fixes.py +++ b/mne/fixes.py @@ -16,11 +16,13 @@ # because this module is imported many places (but not always used)! import inspect +import operator as operator_module import os import warnings from math import log import numpy as np +from packaging.version import parse ############################################################################### # distutils LooseVersion removed in Python 3.12 @@ -44,9 +46,12 @@ def _compare_version(version_a, operator, version_b): bool The result of the version comparison. """ + mapping = {"<": "lt", "<=": "le", "==": "eq", "!=": "ne", ">=": "ge", ">": "gt"} with warnings.catch_warnings(record=True): warnings.simplefilter("ignore") - return eval(f'parse("{version_a}") {operator} parse("{version_b}")') + ver_a = parse(version_a) + ver_b = parse(version_b) + return getattr(operator_module, mapping[operator])(ver_a, ver_b) ############################################################################### @@ -111,19 +116,6 @@ def rng_uniform(rng): return getattr(rng, "integers", getattr(rng, "randint", None)) -def _validate_sos(sos): - """Help validate an SOS input.""" - sos = np.atleast_2d(sos) - if sos.ndim != 2: - raise ValueError("sos array must be 2D") - n_sections, m = sos.shape - if m != 6: - raise ValueError("sos array must be shape (n_sections, 6)") - if not (sos[:, 3] == 1).all(): - raise ValueError("sos[:, 3] should be all ones") - return sos, n_sections - - ############################################################################### # Misc utilities @@ -158,7 +150,6 @@ def empirical_covariance(X, assume_centered=False): ------- covariance : 2D ndarray, shape (n_features, n_features) Empirical covariance (Maximum Likelihood Estimator). - """ X = np.asarray(X) if X.ndim == 1: @@ -280,7 +271,6 @@ def _set_covariance(self, covariance): covariance : 2D ndarray, shape (n_features, n_features) Estimated covariance matrix to be stored, and from which precision is computed. - """ from scipy import linalg @@ -300,7 +290,6 @@ def get_precision(self): ------- precision_ : array-like, The precision matrix associated to the current covariance object. - """ from scipy import linalg @@ -427,7 +416,6 @@ def mahalanobis(self, observations): ------- mahalanobis_distance : array, shape = [n_observations,] Squared Mahalanobis distances of the observations. - """ precision = self.get_precision() # compute mahalanobis distances @@ -478,7 +466,7 @@ def _logdet(A): def _infer_dimension_(spectrum, n_samples, n_features): - """Infers the dimension of a dataset of shape (n_samples, n_features). + """Infer the dimension of a dataset of shape (n_samples, n_features). The dataset is described by its spectrum `spectrum`. """ diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 7cd0519d0a5..ddc40dc4c0a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -4,287 +4,5 @@ """Convenience functions for opening GUIs.""" -# Authors: Christian Brodbeck -# -# License: BSD-3-Clause - -from ..utils import verbose, get_config, warn - - -@verbose -def coregistration( - tabbed=False, - split=True, - width=None, - inst=None, - subject=None, - subjects_dir=None, - guess_mri_subject=None, - height=None, - head_opacity=None, - head_high_res=None, - trans=None, - scrollable=True, - *, - orient_to_surface=True, - scale_by_distance=True, - mark_inside=True, - interaction=None, - scale=None, - advanced_rendering=None, - head_inside=True, - fullscreen=None, - show=True, - block=False, - verbose=None, -): - """Coregister an MRI with a subject's head shape. - - The GUI can be launched through the command line interface: - - .. code-block:: bash - - $ mne coreg - - or using a python interpreter as shown in :ref:`tut-source-alignment`. - - Parameters - ---------- - tabbed : bool - Combine the data source panel and the coregistration panel into a - single panel with tabs. - split : bool - Split the main panels with a movable splitter (good for QT4 but - unnecessary for wx backend). - width : int | None - Specify the width for window (in logical pixels). - Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to 800). - inst : None | str - Path to an instance file containing the digitizer data. Compatible for - Raw, Epochs, and Evoked files. - subject : None | str - Name of the mri subject. - %(subjects_dir)s - guess_mri_subject : bool - When selecting a new head shape file, guess the subject's name based - on the filename and change the MRI subject accordingly (default True). - height : int | None - Specify a height for window (in logical pixels). - Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to 400). - head_opacity : float | None - The opacity of the head surface in the range [0., 1.]. - Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value - (which defaults to 1.). - head_high_res : bool | None - Use a high resolution head surface. - Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value - (which defaults to True). - trans : path-like | None - The transform file to use. - scrollable : bool - Make the coregistration panel vertically scrollable (default True). - orient_to_surface : bool | None - If True (default), orient EEG electrode and head shape points - to the head surface. - - .. versionadded:: 0.16 - scale_by_distance : bool | None - If True (default), scale the digitization points by their - distance from the scalp surface. - - .. versionadded:: 0.16 - mark_inside : bool | None - If True (default), mark points inside the head surface in a - different color. - - .. versionadded:: 0.16 - %(interaction_scene_none)s - Defaults to ``'terrain'``. - - .. versionadded:: 0.16 - .. versionchanged:: 1.0 - Default interaction mode if ``None`` and no config setting found - changed from ``'trackball'`` to ``'terrain'``. - scale : float | None - The scaling for the scene. - - .. versionadded:: 0.16 - advanced_rendering : bool - Use advanced OpenGL rendering techniques (default True). - For some renderers (such as MESA software) this can cause rendering - bugs. - - .. versionadded:: 0.18 - head_inside : bool - If True (default), add opaque inner scalp head surface to help occlude - points behind the head. - - .. versionadded:: 0.23 - %(fullscreen)s - Default is None, which uses ``MNE_COREG_FULLSCREEN`` config value - (which defaults to False). - - .. versionadded:: 1.1 - show : bool - Show the GUI if True. - block : bool - Whether to halt program execution until the figure is closed. - %(verbose)s - - Returns - ------- - frame : instance of CoregistrationUI - The coregistration frame. - - Notes - ----- - Many parameters (e.g., ``head_opacity``) take None as a parameter, - which means that the default will be read from the MNE-Python - configuration file (which gets saved when exiting). - - Step by step instructions for the coregistrations are shown below: - - .. youtube:: ALV5qqMHLlQ - """ - unsupported_params = { - "tabbed": (tabbed, False), - "split": (split, True), - "scrollable": (scrollable, True), - "head_inside": (head_inside, True), - "guess_mri_subject": guess_mri_subject, - "scale": scale, - "advanced_rendering": advanced_rendering, - } - for key, val in unsupported_params.items(): - if isinstance(val, tuple): - to_raise = val[0] != val[1] - else: - to_raise = val is not None - if to_raise: - warn( - f"The parameter {key} is not supported with" - " the pyvistaqt 3d backend. It will be ignored." - ) - config = get_config() - if guess_mri_subject is None: - guess_mri_subject = config.get("MNE_COREG_GUESS_MRI_SUBJECT", "true") == "true" - if head_high_res is None: - head_high_res = config.get("MNE_COREG_HEAD_HIGH_RES", "true") == "true" - if advanced_rendering is None: - advanced_rendering = ( - config.get("MNE_COREG_ADVANCED_RENDERING", "true") == "true" - ) - if head_opacity is None: - head_opacity = config.get("MNE_COREG_HEAD_OPACITY", 0.8) - if head_inside is None: - head_inside = config.get("MNE_COREG_HEAD_INSIDE", "true").lower() == "true" - if width is None: - width = config.get("MNE_COREG_WINDOW_WIDTH", 800) - if height is None: - height = config.get("MNE_COREG_WINDOW_HEIGHT", 600) - if subjects_dir is None: - if "SUBJECTS_DIR" in config: - subjects_dir = config["SUBJECTS_DIR"] - elif "MNE_COREG_SUBJECTS_DIR" in config: - subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] - if orient_to_surface is None: - orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "") == "true" - if scale_by_distance is None: - scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "") == "true" - if interaction is None: - interaction = config.get("MNE_COREG_INTERACTION", "terrain") - if mark_inside is None: - mark_inside = config.get("MNE_COREG_MARK_INSIDE", "") == "true" - if scale is None: - scale = config.get("MNE_COREG_SCENE_SCALE", 0.16) - if fullscreen is None: - fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" - head_opacity = float(head_opacity) - head_inside = bool(head_inside) - width = int(width) - height = int(height) - scale = float(scale) - - from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING - from ._coreg import CoregistrationUI - - if MNE_3D_BACKEND_TESTING: - show = block = False - return CoregistrationUI( - info_file=inst, - subject=subject, - subjects_dir=subjects_dir, - head_resolution=head_high_res, - head_opacity=head_opacity, - orient_glyphs=orient_to_surface, - scale_by_distance=scale_by_distance, - mark_inside=mark_inside, - trans=trans, - size=(width, height), - show=show, - block=block, - interaction=interaction, - fullscreen=fullscreen, - verbose=verbose, - ) - - -class _GUIScraper: - """Scrape GUI outputs.""" - - def __repr__(self): - return "" - - def __call__(self, block, block_vars, gallery_conf): - from ._coreg import CoregistrationUI - - gui_classes = (CoregistrationUI,) - try: - from mne_gui_addons._ieeg_locate import ( - IntracranialElectrodeLocator, - ) # noqa: E501 - except Exception: - pass - else: - gui_classes = gui_classes + (IntracranialElectrodeLocator,) - from sphinx_gallery.scrapers import figure_rst - from qtpy import QtGui - - for gui in block_vars["example_globals"].values(): - if ( - isinstance(gui, gui_classes) - and not getattr(gui, "_scraped", False) - and gallery_conf["builder_name"] == "html" - ): - gui._scraped = True # monkey-patch but it's easy enough - img_fname = next(block_vars["image_path_iterator"]) - # TODO fix in window refactor - window = gui if hasattr(gui, "grab") else gui._renderer._window - # window is QWindow - # https://doc.qt.io/qt-5/qwidget.html#grab - pixmap = window.grab() - if hasattr(gui, "_renderer"): # if no renderer, no need - # Now the tricky part: we need to get the 3D renderer, - # extract the image from it, and put it in the correct - # place in the pixmap. The easiest way to do this is - # actually to save the 3D image first, then load it - # using QPixmap and Qt geometry. - plotter = gui._renderer.plotter - plotter.screenshot(img_fname) - sub_pixmap = QtGui.QPixmap(img_fname) - # https://doc.qt.io/qt-5/qwidget.html#mapTo - # https://doc.qt.io/qt-5/qpainter.html#drawPixmap-1 - QtGui.QPainter(pixmap).drawPixmap( - plotter.mapTo(window, plotter.rect().topLeft()), sub_pixmap - ) - # https://doc.qt.io/qt-5/qpixmap.html#save - pixmap.save(img_fname) - try: # for compatibility with both GUIs, will be refactored - gui._renderer.close() # TODO should be triggered by close - except Exception: - pass - gui.close() - return figure_rst([img_fname], gallery_conf["src_dir"], "GUI") - return "" +__all__ = ["_GUIScraper", "coregistration"] +from ._gui import _GUIScraper, coregistration diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 8ba8c80d8d7..b365a2eed5a 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -34,8 +34,7 @@ scale_mri, ) from ..defaults import DEFAULTS -from ..io import read_raw -from ..io._read_raw import supported as raw_supported_types +from ..io._read_raw import _get_supported, read_raw from ..surface import _CheckInside, _DistanceQuery from ..transforms import ( Transform, @@ -450,12 +449,13 @@ def _set_info_file(self, fname): return # info file can be anything supported by read_raw + supported = _get_supported() try: check_fname( fname, "info", - tuple(raw_supported_types.keys()), - endings_err=tuple(raw_supported_types.keys()), + tuple(supported), + endings_err=tuple(supported), ) fname = Path(fname) # ctf ds `files` are actually directories diff --git a/mne/gui/_gui.py b/mne/gui/_gui.py new file mode 100644 index 00000000000..fa72101ad32 --- /dev/null +++ b/mne/gui/_gui.py @@ -0,0 +1,280 @@ +from ..utils import get_config, verbose, warn + + +@verbose +def coregistration( + tabbed=False, + split=True, + width=None, + inst=None, + subject=None, + subjects_dir=None, + guess_mri_subject=None, + height=None, + head_opacity=None, + head_high_res=None, + trans=None, + scrollable=True, + *, + orient_to_surface=True, + scale_by_distance=True, + mark_inside=True, + interaction=None, + scale=None, + advanced_rendering=None, + head_inside=True, + fullscreen=None, + show=True, + block=False, + verbose=None, +): + """Coregister an MRI with a subject's head shape. + + The GUI can be launched through the command line interface: + + .. code-block:: bash + + $ mne coreg + + or using a python interpreter as shown in :ref:`tut-source-alignment`. + + Parameters + ---------- + tabbed : bool + Combine the data source panel and the coregistration panel into a + single panel with tabs. + split : bool + Split the main panels with a movable splitter (good for QT4 but + unnecessary for wx backend). + width : int | None + Specify the width for window (in logical pixels). + Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value + (which defaults to 800). + inst : None | str + Path to an instance file containing the digitizer data. Compatible for + Raw, Epochs, and Evoked files. + subject : None | str + Name of the mri subject. + %(subjects_dir)s + guess_mri_subject : bool + When selecting a new head shape file, guess the subject's name based + on the filename and change the MRI subject accordingly (default True). + height : int | None + Specify a height for window (in logical pixels). + Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value + (which defaults to 400). + head_opacity : float | None + The opacity of the head surface in the range [0., 1.]. + Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value + (which defaults to 1.). + head_high_res : bool | None + Use a high resolution head surface. + Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value + (which defaults to True). + trans : path-like | None + The transform file to use. + scrollable : bool + Make the coregistration panel vertically scrollable (default True). + orient_to_surface : bool | None + If True (default), orient EEG electrode and head shape points + to the head surface. + + .. versionadded:: 0.16 + scale_by_distance : bool | None + If True (default), scale the digitization points by their + distance from the scalp surface. + + .. versionadded:: 0.16 + mark_inside : bool | None + If True (default), mark points inside the head surface in a + different color. + + .. versionadded:: 0.16 + %(interaction_scene_none)s + Defaults to ``'terrain'``. + + .. versionadded:: 0.16 + .. versionchanged:: 1.0 + Default interaction mode if ``None`` and no config setting found + changed from ``'trackball'`` to ``'terrain'``. + scale : float | None + The scaling for the scene. + + .. versionadded:: 0.16 + advanced_rendering : bool + Use advanced OpenGL rendering techniques (default True). + For some renderers (such as MESA software) this can cause rendering + bugs. + + .. versionadded:: 0.18 + head_inside : bool + If True (default), add opaque inner scalp head surface to help occlude + points behind the head. + + .. versionadded:: 0.23 + %(fullscreen)s + Default is None, which uses ``MNE_COREG_FULLSCREEN`` config value + (which defaults to False). + + .. versionadded:: 1.1 + show : bool + Show the GUI if True. + block : bool + Whether to halt program execution until the figure is closed. + %(verbose)s + + Returns + ------- + frame : instance of CoregistrationUI + The coregistration frame. + + Notes + ----- + Many parameters (e.g., ``head_opacity``) take None as a parameter, + which means that the default will be read from the MNE-Python + configuration file (which gets saved when exiting). + + Step by step instructions for the coregistrations are shown below: + + .. youtube:: ALV5qqMHLlQ + """ + unsupported_params = { + "tabbed": (tabbed, False), + "split": (split, True), + "scrollable": (scrollable, True), + "head_inside": (head_inside, True), + "guess_mri_subject": guess_mri_subject, + "scale": scale, + "advanced_rendering": advanced_rendering, + } + for key, val in unsupported_params.items(): + if isinstance(val, tuple): + to_raise = val[0] != val[1] + else: + to_raise = val is not None + if to_raise: + warn( + f"The parameter {key} is not supported with" + " the pyvistaqt 3d backend. It will be ignored." + ) + config = get_config() + if guess_mri_subject is None: + guess_mri_subject = config.get("MNE_COREG_GUESS_MRI_SUBJECT", "true") == "true" + if head_high_res is None: + head_high_res = config.get("MNE_COREG_HEAD_HIGH_RES", "true") == "true" + if advanced_rendering is None: + advanced_rendering = ( + config.get("MNE_COREG_ADVANCED_RENDERING", "true") == "true" + ) + if head_opacity is None: + head_opacity = config.get("MNE_COREG_HEAD_OPACITY", 0.8) + if head_inside is None: + head_inside = config.get("MNE_COREG_HEAD_INSIDE", "true").lower() == "true" + if width is None: + width = config.get("MNE_COREG_WINDOW_WIDTH", 800) + if height is None: + height = config.get("MNE_COREG_WINDOW_HEIGHT", 600) + if subjects_dir is None: + if "SUBJECTS_DIR" in config: + subjects_dir = config["SUBJECTS_DIR"] + elif "MNE_COREG_SUBJECTS_DIR" in config: + subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] + if orient_to_surface is None: + orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "") == "true" + if scale_by_distance is None: + scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "") == "true" + if interaction is None: + interaction = config.get("MNE_COREG_INTERACTION", "terrain") + if mark_inside is None: + mark_inside = config.get("MNE_COREG_MARK_INSIDE", "") == "true" + if scale is None: + scale = config.get("MNE_COREG_SCENE_SCALE", 0.16) + if fullscreen is None: + fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" + head_opacity = float(head_opacity) + head_inside = bool(head_inside) + width = int(width) + height = int(height) + scale = float(scale) + + from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING + from ._coreg import CoregistrationUI + + if MNE_3D_BACKEND_TESTING: + show = block = False + return CoregistrationUI( + info_file=inst, + subject=subject, + subjects_dir=subjects_dir, + head_resolution=head_high_res, + head_opacity=head_opacity, + orient_glyphs=orient_to_surface, + scale_by_distance=scale_by_distance, + mark_inside=mark_inside, + trans=trans, + size=(width, height), + show=show, + block=block, + interaction=interaction, + fullscreen=fullscreen, + verbose=verbose, + ) + + +class _GUIScraper: + """Scrape GUI outputs.""" + + def __repr__(self): + return "" + + def __call__(self, block, block_vars, gallery_conf): + from ._coreg import CoregistrationUI + + gui_classes = (CoregistrationUI,) + try: + from mne_gui_addons._ieeg_locate import ( + IntracranialElectrodeLocator, + ) # noqa: E501 + except Exception: + pass + else: + gui_classes = gui_classes + (IntracranialElectrodeLocator,) + from qtpy import QtGui + from sphinx_gallery.scrapers import figure_rst + + for gui in block_vars["example_globals"].values(): + if ( + isinstance(gui, gui_classes) + and not getattr(gui, "_scraped", False) + and gallery_conf["builder_name"] == "html" + ): + gui._scraped = True # monkey-patch but it's easy enough + img_fname = next(block_vars["image_path_iterator"]) + # TODO fix in window refactor + window = gui if hasattr(gui, "grab") else gui._renderer._window + # window is QWindow + # https://doc.qt.io/qt-5/qwidget.html#grab + pixmap = window.grab() + if hasattr(gui, "_renderer"): # if no renderer, no need + # Now the tricky part: we need to get the 3D renderer, + # extract the image from it, and put it in the correct + # place in the pixmap. The easiest way to do this is + # actually to save the 3D image first, then load it + # using QPixmap and Qt geometry. + plotter = gui._renderer.plotter + plotter.screenshot(img_fname) + sub_pixmap = QtGui.QPixmap(img_fname) + # https://doc.qt.io/qt-5/qwidget.html#mapTo + # https://doc.qt.io/qt-5/qpainter.html#drawPixmap-1 + QtGui.QPainter(pixmap).drawPixmap( + plotter.mapTo(window, plotter.rect().topLeft()), sub_pixmap + ) + # https://doc.qt.io/qt-5/qpixmap.html#save + pixmap.save(img_fname) + try: # for compatibility with both GUIs, will be refactored + gui._renderer.close() # TODO should be triggered by close + except Exception: + pass + gui.close() + return figure_rst([img_fname], gallery_conf["src_dir"], "GUI") + return "" diff --git a/mne/io/_read_raw.py b/mne/io/_read_raw.py index a2c512ae9be..28c86c00ee6 100644 --- a/mne/io/_read_raw.py +++ b/mne/io/_read_raw.py @@ -8,81 +8,9 @@ from pathlib import Path from ..utils import fill_doc -from . import ( - read_raw_artemis123, - read_raw_bdf, - read_raw_boxy, - read_raw_brainvision, - read_raw_cnt, - read_raw_ctf, - read_raw_curry, - read_raw_edf, - read_raw_eeglab, - read_raw_egi, - read_raw_eximia, - read_raw_fieldtrip, - read_raw_fif, - read_raw_fil, - read_raw_gdf, - read_raw_kit, - read_raw_nedf, - read_raw_nicolet, - read_raw_nihon, - read_raw_nirx, - read_raw_snirf, -) from .base import BaseRaw -def _read_unsupported(fname, **kwargs): - ext = "".join(Path(fname).suffixes) - msg = f"Unsupported file type ({ext})." - suggest = kwargs.get("suggest") - if suggest is not None: - msg += f" Try reading a {suggest} file instead." - msg += " Consider using a dedicated reader function for more options." - raise ValueError(msg) - - -# supported read file formats -supported = { - ".edf": dict(EDF=read_raw_edf), - ".eeg": dict(NihonKoden=read_raw_nihon), - ".bdf": dict(BDF=read_raw_bdf), - ".gdf": dict(GDF=read_raw_gdf), - ".vhdr": dict(brainvision=read_raw_brainvision), - ".ahdr": dict(brainvision=read_raw_brainvision), - ".fif": dict(FIF=read_raw_fif), - ".fif.gz": dict(FIF=read_raw_fif), - ".set": dict(EEGLAB=read_raw_eeglab), - ".cnt": dict(CNT=read_raw_cnt), - ".mff": dict(EGI=read_raw_egi), - ".nxe": dict(eximia=read_raw_eximia), - ".hdr": dict(NIRx=read_raw_nirx), - ".snirf": dict(SNIRF=read_raw_snirf), - ".mat": dict(fieldtrip=read_raw_fieldtrip), - ".bin": { - "ARTEMIS": read_raw_artemis123, - "UCL FIL OPM": read_raw_fil, - }, - ".data": dict(Nicolet=read_raw_nicolet), - ".sqd": dict(KIT=read_raw_kit), - ".con": dict(KIT=read_raw_kit), - ".ds": dict(CTF=read_raw_ctf), - ".txt": dict(BOXY=read_raw_boxy), - # Curry - ".dat": dict(CURRY=read_raw_curry), - ".dap": dict(CURRY=read_raw_curry), - ".rs3": dict(CURRY=read_raw_curry), - ".cdt": dict(CURRY=read_raw_curry), - ".cdt.dpa": dict(CURRY=read_raw_curry), - ".cdt.cef": dict(CURRY=read_raw_curry), - ".cef": dict(CURRY=read_raw_curry), - # NEDF - ".nedf": dict(NEDF=read_raw_nedf), -} - - def _read_unsupported(fname, **kwargs): ext = "".join(Path(fname).suffixes) msg = f"Unsupported file type ({ext})." @@ -166,22 +94,24 @@ def _get_supported(): # known but unsupported file formats -suggested = { +_suggested = { ".vmrk": dict(brainvision=partial(_read_unsupported, suggest=".vhdr")), ".amrk": dict(brainvision=partial(_read_unsupported, suggest=".ahdr")), } + # all known file formats -readers = {**supported, **suggested} +def _get_readers(): + return {**_get_supported(), **_suggested} def split_name_ext(fname): """Return name and supported file extension.""" - maxsuffixes = max(ext.count(".") for ext in supported) + maxsuffixes = max(ext.count(".") for ext in _get_supported()) suffixes = Path(fname).suffixes for si in range(-maxsuffixes, 0): ext = "".join(suffixes[si:]).lower() - if ext in readers: + if ext in _get_readers(): return Path(fname).name[: -len(ext)], ext return fname, None # unknown file extension @@ -241,6 +171,7 @@ def read_raw(fname, *, preload=False, verbose=None, **kwargs) -> BaseRaw: _, ext = split_name_ext(fname) kwargs["verbose"] = verbose kwargs["preload"] = preload + readers = _get_readers() if ext not in readers: _read_unsupported(fname) these_readers = list(readers[ext].values()) From edafe194db61ff2fa7510873f8c06d83ad43fd02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:25:11 +0000 Subject: [PATCH 11/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/filter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/filter.py b/mne/filter.py index f52c9bebb14..0643bc5b8fe 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -1926,6 +1926,7 @@ def _prep_polyphase(ratio, x_len, final_len, 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) From 70f8d34f709c77f494436fd27f2581d3b1f2de52 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 12:33:58 +0100 Subject: [PATCH 12/46] gui fixes --- mne/__init__.py | 1 - mne/gui/_gui.py | 115 ++++++++++++------------------------------------ 2 files changed, 27 insertions(+), 89 deletions(-) diff --git a/mne/__init__.py b/mne/__init__.py index d933da32bd9..ec4a8fddb4c 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -18,7 +18,6 @@ # X.Y # Final release # # Dev branch marker is: 'X.Y.devN' where N is an integer. -# try: from importlib.metadata import version diff --git a/mne/gui/_gui.py b/mne/gui/_gui.py index fa72101ad32..723b236f430 100644 --- a/mne/gui/_gui.py +++ b/mne/gui/_gui.py @@ -1,28 +1,25 @@ -from ..utils import get_config, verbose, warn +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +from ..utils import get_config, verbose @verbose def coregistration( - tabbed=False, - split=True, + *, width=None, + height=None, inst=None, subject=None, subjects_dir=None, - guess_mri_subject=None, - height=None, head_opacity=None, head_high_res=None, trans=None, - scrollable=True, - *, - orient_to_surface=True, - scale_by_distance=True, - mark_inside=True, + orient_to_surface=None, + scale_by_distance=None, + mark_inside=None, interaction=None, - scale=None, - advanced_rendering=None, - head_inside=True, fullscreen=None, show=True, block=False, @@ -40,49 +37,38 @@ def coregistration( Parameters ---------- - tabbed : bool - Combine the data source panel and the coregistration panel into a - single panel with tabs. - split : bool - Split the main panels with a movable splitter (good for QT4 but - unnecessary for wx backend). width : int | None Specify the width for window (in logical pixels). Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to 800). + (which defaults to ``800``). + height : int | None + Specify a height for window (in logical pixels). + Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value + (which defaults to ``400``). inst : None | str Path to an instance file containing the digitizer data. Compatible for Raw, Epochs, and Evoked files. subject : None | str Name of the mri subject. %(subjects_dir)s - guess_mri_subject : bool - When selecting a new head shape file, guess the subject's name based - on the filename and change the MRI subject accordingly (default True). - height : int | None - Specify a height for window (in logical pixels). - Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value - (which defaults to 400). head_opacity : float | None - The opacity of the head surface in the range [0., 1.]. + The opacity of the head surface in the range ``[0., 1.]``. Default is None, which uses ``MNE_COREG_HEAD_OPACITY`` config value - (which defaults to 1.). + (which defaults to ``1.``). head_high_res : bool | None Use a high resolution head surface. Default is None, which uses ``MNE_COREG_HEAD_HIGH_RES`` config value (which defaults to True). - trans : path-like | None - The transform file to use. - scrollable : bool - Make the coregistration panel vertically scrollable (default True). + trans : path-like | Transform | None + The Head<->MRI transform or the path to its FIF file (``"-trans.fif"``). orient_to_surface : bool | None - If True (default), orient EEG electrode and head shape points - to the head surface. + If True (default), orient EEG electrode and head shape points to the head + surface. .. versionadded:: 0.16 scale_by_distance : bool | None - If True (default), scale the digitization points by their - distance from the scalp surface. + If True (default), scale the digitization points by their distance from the + scalp surface. .. versionadded:: 0.16 mark_inside : bool | None @@ -97,24 +83,9 @@ def coregistration( .. versionchanged:: 1.0 Default interaction mode if ``None`` and no config setting found changed from ``'trackball'`` to ``'terrain'``. - scale : float | None - The scaling for the scene. - - .. versionadded:: 0.16 - advanced_rendering : bool - Use advanced OpenGL rendering techniques (default True). - For some renderers (such as MESA software) this can cause rendering - bugs. - - .. versionadded:: 0.18 - head_inside : bool - If True (default), add opaque inner scalp head surface to help occlude - points behind the head. - - .. versionadded:: 0.23 %(fullscreen)s - Default is None, which uses ``MNE_COREG_FULLSCREEN`` config value - (which defaults to False). + Default is ``None``, which uses ``MNE_COREG_FULLSCREEN`` config value + (which defaults to ``False``). .. versionadded:: 1.1 show : bool @@ -138,38 +109,11 @@ def coregistration( .. youtube:: ALV5qqMHLlQ """ - unsupported_params = { - "tabbed": (tabbed, False), - "split": (split, True), - "scrollable": (scrollable, True), - "head_inside": (head_inside, True), - "guess_mri_subject": guess_mri_subject, - "scale": scale, - "advanced_rendering": advanced_rendering, - } - for key, val in unsupported_params.items(): - if isinstance(val, tuple): - to_raise = val[0] != val[1] - else: - to_raise = val is not None - if to_raise: - warn( - f"The parameter {key} is not supported with" - " the pyvistaqt 3d backend. It will be ignored." - ) config = get_config() - if guess_mri_subject is None: - guess_mri_subject = config.get("MNE_COREG_GUESS_MRI_SUBJECT", "true") == "true" if head_high_res is None: head_high_res = config.get("MNE_COREG_HEAD_HIGH_RES", "true") == "true" - if advanced_rendering is None: - advanced_rendering = ( - config.get("MNE_COREG_ADVANCED_RENDERING", "true") == "true" - ) if head_opacity is None: head_opacity = config.get("MNE_COREG_HEAD_OPACITY", 0.8) - if head_inside is None: - head_inside = config.get("MNE_COREG_HEAD_INSIDE", "true").lower() == "true" if width is None: width = config.get("MNE_COREG_WINDOW_WIDTH", 800) if height is None: @@ -179,6 +123,7 @@ def coregistration( subjects_dir = config["SUBJECTS_DIR"] elif "MNE_COREG_SUBJECTS_DIR" in config: subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] + false_like = ("false", "0") if orient_to_surface is None: orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "") == "true" if scale_by_distance is None: @@ -187,15 +132,11 @@ def coregistration( interaction = config.get("MNE_COREG_INTERACTION", "terrain") if mark_inside is None: mark_inside = config.get("MNE_COREG_MARK_INSIDE", "") == "true" - if scale is None: - scale = config.get("MNE_COREG_SCENE_SCALE", 0.16) if fullscreen is None: fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" head_opacity = float(head_opacity) - head_inside = bool(head_inside) width = int(width) height = int(height) - scale = float(scale) from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING from ._coreg import CoregistrationUI @@ -232,9 +173,7 @@ def __call__(self, block, block_vars, gallery_conf): gui_classes = (CoregistrationUI,) try: - from mne_gui_addons._ieeg_locate import ( - IntracranialElectrodeLocator, - ) # noqa: E501 + from mne_gui_addons._ieeg_locate import IntracranialElectrodeLocator except Exception: pass else: From 255531aabdc5d74b66bf3bd21db6c76b64e7c282 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 12:36:22 +0100 Subject: [PATCH 13/46] More gui --- mne/gui/_gui.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mne/gui/_gui.py b/mne/gui/_gui.py index 723b236f430..b8898d8b7c2 100644 --- a/mne/gui/_gui.py +++ b/mne/gui/_gui.py @@ -45,7 +45,7 @@ def coregistration( Specify a height for window (in logical pixels). Default is None, which uses ``MNE_COREG_WINDOW_WIDTH`` config value (which defaults to ``400``). - inst : None | str + inst : None | path-like Path to an instance file containing the digitizer data. Compatible for Raw, Epochs, and Evoked files. subject : None | str @@ -125,13 +125,16 @@ def coregistration( subjects_dir = config["MNE_COREG_SUBJECTS_DIR"] false_like = ("false", "0") if orient_to_surface is None: - orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "") == "true" + orient_to_surface = config.get("MNE_COREG_ORIENT_TO_SURFACE", "true").lower() + orient_to_surface = orient_to_surface not in false_like if scale_by_distance is None: - scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "") == "true" + scale_by_distance = config.get("MNE_COREG_SCALE_BY_DISTANCE", "true").lower() + scale_by_distance = scale_by_distance not in false_like if interaction is None: interaction = config.get("MNE_COREG_INTERACTION", "terrain") if mark_inside is None: - mark_inside = config.get("MNE_COREG_MARK_INSIDE", "") == "true" + mark_inside = config.get("MNE_COREG_MARK_INSIDE", "true").lower() + mark_inside = mark_inside not in false_like if fullscreen is None: fullscreen = config.get("MNE_COREG_FULLSCREEN", "") == "true" head_opacity = float(head_opacity) From 0c5ed992722287a99285d53fdc1500d9204e2f09 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 12:38:18 +0100 Subject: [PATCH 14/46] Add missing file --- mne/io/_fiff_wrap.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 mne/io/_fiff_wrap.py diff --git a/mne/io/_fiff_wrap.py b/mne/io/_fiff_wrap.py new file mode 100644 index 00000000000..e4566d61a40 --- /dev/null +++ b/mne/io/_fiff_wrap.py @@ -0,0 +1,20 @@ +# ruff: noqa: F401 +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +# 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 ( + Info as _info, +) +from .._fiff.meas_info import ( + _empty_info, + anonymize_info, + read_fiducials, + read_info, + write_fiducials, + write_info, +) +from .._fiff.open import show_fiff +from .._fiff.pick import get_channel_type_constants # moved up a level From 2c0026f0f3796bf12cb1932263b2159178627f7c Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 14:05:47 +0100 Subject: [PATCH 15/46] More fixes --- mne/io/base.py | 9 ++-- mne/io/fiff/__init__.py | 2 + mne/minimum_norm/inverse.py | 5 +- mne/morph_map.py | 8 ++-- mne/preprocessing/__init__.py | 46 +++++++++++++++++++ ...te_amplitude.py => _annotate_amplitude.py} | 0 mne/preprocessing/bads.py | 1 - mne/preprocessing/ica.py | 5 +- mne/preprocessing/maxwell.py | 1 - mne/preprocessing/nirs/_beer_lambert_law.py | 1 - mne/report/report.py | 12 ++--- mne/source_space/_source_space.py | 1 - mne/stats/cluster_level.py | 10 ++-- mne/stats/parametric.py | 1 - mne/surface.py | 1 - 15 files changed, 68 insertions(+), 35 deletions(-) rename mne/preprocessing/{annotate_amplitude.py => _annotate_amplitude.py} (100%) diff --git a/mne/io/base.py b/mne/io/base.py index 03298fa801d..b3052b80aff 100644 --- a/mne/io/base.py +++ b/mne/io/base.py @@ -14,8 +14,6 @@ import numpy as np -from mne.html_templates import _get_html_template - from .._fiff.compensator import make_compensator, set_current_comp from .._fiff.constants import FIFF from .._fiff.meas_info import ( @@ -69,6 +67,7 @@ notch_filter, resample, ) +from ..html_templates import _get_html_template from ..parallel import parallel_func from ..time_frequency.spectrum import Spectrum, SpectrumMixin, _validate_method from ..time_frequency.tfr import RawTFR @@ -2497,8 +2496,6 @@ def describe(self, data_frame=False): If data_frame=False, returns None. If data_frame=True, returns results in a pandas.DataFrame (requires pandas). """ - from scipy.stats import scoreatpercentile as q - nchan = self.info["nchan"] # describe each channel @@ -2510,9 +2507,9 @@ def describe(self, data_frame=False): cols["type"].append(channel_type(self.info, i)) cols["unit"].append(_unit2human[ch["unit"]]) cols["min"].append(np.min(data)) - cols["Q1"].append(q(data, 25)) + cols["Q1"].append(np.percentile(data, 25)) cols["median"].append(np.median(data)) - cols["Q3"].append(q(data, 75)) + cols["Q3"].append(np.percentile(data, 75)) cols["max"].append(np.max(data)) if data_frame: # return data frame diff --git a/mne/io/fiff/__init__.py b/mne/io/fiff/__init__.py index 24f2baff615..3d83b2c7a7b 100644 --- a/mne/io/fiff/__init__.py +++ b/mne/io/fiff/__init__.py @@ -6,3 +6,5 @@ from .raw import Raw from .raw import read_raw_fif + +RawFIF = Raw diff --git a/mne/minimum_norm/inverse.py b/mne/minimum_norm/inverse.py index bef4daa3e6d..e0e73fba886 100644 --- a/mne/minimum_norm/inverse.py +++ b/mne/minimum_norm/inverse.py @@ -47,6 +47,7 @@ is_fixed_orient, ) from ..forward.forward import _triage_loose, write_forward_meas_info +from ..html_templates import _get_html_template from ..io import BaseRaw from ..source_estimate import _get_src_type, _make_stc from ..source_space._source_space import ( @@ -127,12 +128,10 @@ def __repr__(self): # noqa: D105 @repr_html def _repr_html_(self): - from ..html_templates import repr_templates_env - repr_info = self._get_chs_and_src_info_for_repr() n_chs_meg, n_chs_eeg, src_space_descr, src_ori = repr_info - t = repr_templates_env.get_template("inverse_operator.html.jinja") + t = _get_html_template("repr", "inverse_operator.html.jinja") html = t.render( channels=f"{n_chs_meg} MEG, {n_chs_eeg} EEG", source_space_descr=src_space_descr, diff --git a/mne/morph_map.py b/mne/morph_map.py index d2fccd3c075..e7af5a769d0 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,11 +205,14 @@ 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_matrix + from scipy.sparse import eye as speye + # 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 n_pts = len(read_surface(fname, verbose=False)[0]) - return _eye_array(n_pts, format="csr") + return speye(n_pts, format="csr") # load surfaces and normalize points to be on unit sphere fname = subjects_dir / subject_from / "surf" / reg_from @@ -241,7 +243,7 @@ def _make_morph_map_hemi(subject_from, subject_to, subjects_dir, reg_from, reg_t weights = np.array(weights) row_ind = np.repeat(np.arange(len(to_rr)), 3) - this_map = csr_array( + this_map = csr_matrix( (weights.ravel(), (row_ind, nn_idx.ravel())), shape=(len(to_rr), len(from_rr)) ) return this_map diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index cb58ea7ce76..6bb9664297f 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -4,3 +4,49 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +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 .xdawn import Xdawn +from ._csd import compute_current_source_density, compute_bridged_electrodes +from . import nirs +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 . import ieeg +from ._css import cortical_signal_suppression +from .hfc import compute_proj_hfc + +from . import eyetracking diff --git a/mne/preprocessing/annotate_amplitude.py b/mne/preprocessing/_annotate_amplitude.py similarity index 100% rename from mne/preprocessing/annotate_amplitude.py rename to mne/preprocessing/_annotate_amplitude.py diff --git a/mne/preprocessing/bads.py b/mne/preprocessing/bads.py index a7c0b7d0204..6c8fde10a5c 100644 --- a/mne/preprocessing/bads.py +++ b/mne/preprocessing/bads.py @@ -2,7 +2,6 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. - import numpy as np diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py index 9e0aeb4e957..b726e12dce3 100644 --- a/mne/preprocessing/ica.py +++ b/mne/preprocessing/ica.py @@ -55,6 +55,7 @@ from ..evoked import Evoked from ..filter import filter_data from ..fixes import _safe_svd +from ..html_templates import _get_html_template from ..io import BaseRaw from ..io.eeglab.eeglab import _check_load_mat, _get_info from ..utils import ( @@ -575,10 +576,8 @@ def __repr__(self): @repr_html def _repr_html_(self): - from ..html_templates import repr_templates_env - infos = self._get_infos_for_repr() - t = repr_templates_env.get_template("ica.html.jinja") + t = _get_html_template("repr", "ica.html.jinja") html = t.render( fit_on=infos.fit_on, method=infos.fit_method, diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index 0ab7d3f2298..8c9c0a93957 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -2040,7 +2040,6 @@ def _overlap_projector(data_int, data_res, corr): # computation # we use np.linalg.norm instead of sp.linalg.norm here: ~2x faster! - n = np.linalg.norm(data_int) n = 1.0 if n == 0 else n # all-zero data should gracefully continue data_int = _orth_overwrite((data_int / n).T) diff --git a/mne/preprocessing/nirs/_beer_lambert_law.py b/mne/preprocessing/nirs/_beer_lambert_law.py index 6049e444ba3..c17cf31110c 100644 --- a/mne/preprocessing/nirs/_beer_lambert_law.py +++ b/mne/preprocessing/nirs/_beer_lambert_law.py @@ -96,7 +96,6 @@ def _load_absorption(freqs): # # Returns data as [[HbO2(freq1), Hb(freq1)], # [HbO2(freq2), Hb(freq2)]] - extinction_fname = op.join( op.dirname(__file__), "..", "..", "data", "extinction_coef.mat" ) diff --git a/mne/report/report.py b/mne/report/report.py index 0fa7aa9e87c..64d149c1b65 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -34,8 +34,9 @@ from ..event import read_events from ..evoked import Evoked, read_evokeds from ..forward import Forward, read_forward_solution +from ..html_templates import _get_html_template from ..io import BaseRaw, read_raw -from ..io._read_raw import supported as extension_reader_map +from ..io._read_raw import _get_supported as _get_extension_reader_map from ..minimum_norm import InverseOperator, read_inverse_operator from ..parallel import parallel_func from ..preprocessing.ica import read_ica @@ -80,14 +81,14 @@ from ..viz._brain.view import views_dicts from ..viz._scraper import _mne_qt_browser_screenshot from ..viz.misc import _get_bem_plotting_surfaces, _plot_mri_contours -from ..viz.utils import _get_plot_ch_type, _ndarray_to_fig +from ..viz.utils import _ndarray_to_fig _BEM_VIEWS = ("axial", "sagittal", "coronal") # For raw files, we want to support different suffixes + extensions for all # supported file formats -SUPPORTED_READ_RAW_EXTENSIONS = tuple(extension_reader_map.keys()) +SUPPORTED_READ_RAW_EXTENSIONS = tuple(_get_extension_reader_map()) RAW_EXTENSIONS = [] for ext in SUPPORTED_READ_RAW_EXTENSIONS: RAW_EXTENSIONS.append(f"raw{ext}") @@ -159,9 +160,7 @@ def _id_sanitize(title): def _renderer(kind): - from ..html_templates import report_templates_env - - return report_templates_env.get_template(kind).render + return _get_html_template("report", kind).render ############################################################################### @@ -405,7 +404,6 @@ def _fig_to_img( # check instead if fig.__class__.__name__ in ("MNEQtBrowser", "PyQtGraphBrowser"): img = _mne_qt_browser_screenshot(fig, return_type="ndarray") - print(img.shape, img.max(), img.min(), img.mean()) elif isinstance(fig, Figure3D): from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING, backend diff --git a/mne/source_space/_source_space.py b/mne/source_space/_source_space.py index daef13ef4bf..8dcc279ecef 100644 --- a/mne/source_space/_source_space.py +++ b/mne/source_space/_source_space.py @@ -2336,7 +2336,6 @@ def _src_vol_dims(s): def _add_interpolator(sp): """Compute a sparse matrix to interpolate the data into an MRI volume.""" # extract transformation information from mri - mri_width, mri_height, mri_depth, nvox = _src_vol_dims(sp[0]) # diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py index c12ca7547d6..7c4c8b40435 100644 --- a/mne/stats/cluster_level.py +++ b/mne/stats/cluster_level.py @@ -6,7 +6,9 @@ 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 @@ -289,8 +291,6 @@ def _get_components(x_in, adjacency, return_list=True): if adjacency is False: components = np.arange(len(x_in)) else: - from scipy.sparse.csgraph import connected_components - mask = np.logical_and(x_in[adjacency.row], x_in[adjacency.col]) data = adjacency.data[mask] row = adjacency.row[mask] @@ -1133,10 +1133,7 @@ def _permutation_cluster_test( def _check_fun(X, stat_fun, threshold, tail=0, kind="within"): """Check the stat_fun and threshold values.""" - from scipy import stats - if kind == "within": - ppf = stats.t.ppf if threshold is None: if stat_fun is not None and stat_fun is not ttest_1samp_no_p: warn( @@ -1145,14 +1142,13 @@ def _check_fun(X, stat_fun, threshold, tail=0, kind="within"): ) p_thresh = 0.05 / (1 + (tail == 0)) n_samples = len(X) - threshold = -ppf(p_thresh, n_samples - 1) + threshold = -tstat.ppf(p_thresh, n_samples - 1) if np.sign(tail) < 0: threshold = -threshold logger.info(f"Using a threshold of {threshold:.6f}") stat_fun = ttest_1samp_no_p if stat_fun is None else stat_fun else: assert kind == "between" - ppf = stats.f.ppf if threshold is None: if stat_fun is not None and stat_fun is not f_oneway: warn( diff --git a/mne/stats/parametric.py b/mne/stats/parametric.py index d807ff8a1f2..1043731a599 100644 --- a/mne/stats/parametric.py +++ b/mne/stats/parametric.py @@ -423,6 +423,5 @@ def _parametric_ci(arr, ci=0.95): if len(arr) < 2: # can't compute standard error sigma = np.full_like(mean, np.nan) return mean, sigma - sigma = stats.sem(arr, 0) return stats.t.interval(ci, loc=mean, scale=sigma, df=arr.shape[0]) diff --git a/mne/surface.py b/mne/surface.py index ccfcaae8583..6d6ad0f19ae 100644 --- a/mne/surface.py +++ b/mne/surface.py @@ -392,7 +392,6 @@ def _triangle_neighbors(tris, npts): # for ti, tri in enumerate(tris): # for t in tri: # neighbor_tri[t].append(ti) - rows = tris.ravel() cols = np.repeat(np.arange(len(tris)), 3) data = np.ones(len(cols)) From ed67948321dccd9431f86b7664dc05e64c9b584f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:06:12 +0000 Subject: [PATCH 16/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/morph_map.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/morph_map.py b/mne/morph_map.py index e7af5a769d0..9d8b5b0f15e 100644 --- a/mne/morph_map.py +++ b/mne/morph_map.py @@ -21,7 +21,6 @@ write_int, write_string, ) -from .fixes import _eye_array from .surface import ( _compute_nearest, _find_nearest_tri_pts, From 4b55795e02f7b553e3f8b15f2c9cc51cfb07a767 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 14:27:25 +0100 Subject: [PATCH 17/46] Even more --- mne/report/report.py | 1 + mne/source_estimate.py | 4 ++-- mne/tests/test_import_nesting.py | 11 +---------- mne/time_frequency/multitaper.py | 1 - mne/time_frequency/spectrum.py | 5 +---- mne/time_frequency/tfr.py | 9 +-------- mne/utils/__init__.py | 4 ++++ mne/viz/_3d.py | 2 +- mne/viz/__init__.py | 1 + mne/viz/_brain/_brain.py | 1 - mne/viz/evoked.py | 2 +- mne/viz/raw.py | 5 ++++- mne/viz/topo.py | 3 ++- mne/viz/topomap.py | 8 ++++---- 14 files changed, 23 insertions(+), 34 deletions(-) diff --git a/mne/report/report.py b/mne/report/report.py index 64d149c1b65..852feebc638 100644 --- a/mne/report/report.py +++ b/mne/report/report.py @@ -67,6 +67,7 @@ from ..utils.spectrum import _split_psd_kwargs from ..viz import ( Figure3D, + _get_plot_ch_type, create_3d_figure, get_3d_backend, plot_alignment, diff --git a/mne/source_estimate.py b/mne/source_estimate.py index a076c9661b3..24621d4defa 100644 --- a/mne/source_estimate.py +++ b/mne/source_estimate.py @@ -3219,7 +3219,7 @@ 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.sparse import block_diag as sparse_block_diag + from scipy import sparse if src[0]["dist"] is None: raise RuntimeError( @@ -3233,7 +3233,7 @@ def spatio_temporal_dist_adjacency(src, n_times, dist, verbose=None): block[block == 0] = -np.inf else: block.data[block.data == 0] == -1 - edges = sparse_block_diag(blocks) + edges = sparse.block_diag(blocks) edges.data[:] = np.less_equal(edges.data, dist) # clean it up and put it in coo format edges = edges.tocsr() diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index 4fb1a85aa2b..b90e387cb02 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -4,12 +4,11 @@ import ast import glob -import sys from pathlib import Path from types import ModuleType 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 @@ -220,11 +219,3 @@ def generic_visit(self, node): raise AssertionError(f"{n} nesting error{_pl(n)}:\n" + "\n".join(all_errors)) # scheme obeys the above order - - -def test_module_nesting(): - """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/multitaper.py b/mne/time_frequency/multitaper.py index 5b54628d959..1c1a3baf238 100644 --- a/mne/time_frequency/multitaper.py +++ b/mne/time_frequency/multitaper.py @@ -290,7 +290,6 @@ def _mt_spectra(x, dpss, sfreq, n_fft=None, remove_dc=True): def _compute_mt_params(n_times, sfreq, bandwidth, low_bias, adaptive, verbose=None): """Triage windowing and multitaper parameters.""" # Compute standardized half-bandwidth - if isinstance(bandwidth, str): logger.info(f' Using standard spectrum estimation with "{bandwidth}" window') window_fun = get_window(bandwidth, n_times)[np.newaxis] diff --git a/mne/time_frequency/spectrum.py b/mne/time_frequency/spectrum.py index a89ffd02e23..03a57010061 100644 --- a/mne/time_frequency/spectrum.py +++ b/mne/time_frequency/spectrum.py @@ -13,6 +13,7 @@ from .._fiff.meas_info import ContainsMixin, Info from .._fiff.pick import _pick_data_channels, _picks_to_idx, pick_info from ..channels.channels import UpdateChannelsMixin +from ..channels.layout import _merge_ch_data, find_layout from ..defaults import ( _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, @@ -734,8 +735,6 @@ def plot_topo( fig : instance of matplotlib.figure.Figure Figure distributing one image per channel across sensor topography. """ - from ..channels.layout import find_layout - if layout is None: layout = find_layout(self.info) @@ -834,8 +833,6 @@ def plot_topomap( fig : instance of Figure Figure showing one scalp topography per frequency band. """ - from ..channels.layout import _merge_ch_data - ch_type = _get_plot_ch_type(self, ch_type) if units is None: units = _handle_default("units", None) diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 86bd886444d..dd1474c7b0e 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -19,6 +19,7 @@ from .._fiff.pick import _picks_to_idx, pick_info from ..baseline import _check_baseline, rescale from ..channels.channels import UpdateChannelsMixin +from ..channels.layout import _find_topomap_coords, _merge_ch_data, _pair_grad_sensors from ..defaults import _BORDER_DEFAULT, _EXTRAPOLATE_DEFAULT, _INTERPOLATION_DEFAULT from ..filter import next_fast_len from ..parallel import parallel_func @@ -1595,8 +1596,6 @@ def _onselect( verbose=None, ): """Respond to rectangle selector in TFR image plots with a topomap plot.""" - from ..channels.layout import _pair_grad_sensors - if abs(eclick.x - erelease.x) < 0.1 or abs(eclick.y - erelease.y) < 0.1: return t_range = (min(eclick.xdata, erelease.xdata), max(eclick.xdata, erelease.xdata)) @@ -4232,12 +4231,6 @@ def _check_tfr_complex(tfr, reason="source space estimation"): def _merge_if_grads(data, info, ch_type, sphere, combine=None): - from ..channels.layout import ( - _find_topomap_coords, - _merge_ch_data, - _pair_grad_sensors, - ) - if ch_type == "grad": grad_picks = _pair_grad_sensors(info, topomap_coords=False) pos = _find_topomap_coords(info, picks=grad_picks[::2], sphere=sphere) diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 63ea264a98f..89d6b7a4d02 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -1,3 +1,7 @@ +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + # # # WARNING # # # # This list must also be updated in doc/_templates/autosummary/class.rst if it # is changed here! diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index a391279aabd..0ba7a08ec82 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -37,7 +37,6 @@ _read_mri_info, read_freesurfer_lut, ) -from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces from ..defaults import DEFAULTS from ..fixes import _crop_colorbar, _get_img_fdata from ..surface import ( @@ -674,6 +673,7 @@ def plot_alignment( .. versionadded:: 0.15 """ # Update the backend + from ..bem import ConductorModel, _bem_find_surface, _ensure_bem_surfaces from ..source_space._source_space import _ensure_src from .backends.renderer import _get_renderer diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index 85ac9ec429d..522189f1326 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -26,6 +26,7 @@ plot_sensors, centers_to_edges, concatenate_images, + _get_plot_ch_type, ) from ._3d import ( plot_sparse_source_estimates, diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 5a163b827bb..dc3e1f4851f 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1066,7 +1066,6 @@ def _configure_vertex_time_course(self): def _configure_picking(self): # get data for each hemi - for idx, hemi in enumerate(["vol", "lh", "rh"]): hemi_data = self._data.get(hemi) if hemi_data is not None: diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 73502c593c4..96ee0684e6e 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -127,7 +127,7 @@ def _line_plot_onselect( """Draw topomaps from the selected area.""" import matplotlib.pyplot as plt - from mne.channels.layout import _pair_grad_sensors + from ..channels.layout import _pair_grad_sensors ch_types = [type_ for type_ in ch_types if type_ in ("eeg", "grad", "mag")] if len(ch_types) == 0: diff --git a/mne/viz/raw.py b/mne/viz/raw.py index ffa46d0e4a1..856e7eda35e 100644 --- a/mne/viz/raw.py +++ b/mne/viz/raw.py @@ -11,7 +11,6 @@ from .._fiff.pick import _picks_to_idx, pick_channels, pick_types from ..defaults import _handle_default from ..filter import create_filter -from ..time_frequency import Spectrum from ..utils import _check_option, _get_stim_channel, _validate_type, legacy, verbose from ..utils.spectrum import _split_psd_kwargs from .utils import ( @@ -489,6 +488,8 @@ def plot_raw_psd( ----- %(notes_plot_*_psd_func)s """ + from ..time_frequency import Spectrum + init_kw, plot_kw = _split_psd_kwargs(plot_fun=Spectrum.plot) return raw.compute_psd(**init_kw).plot(**plot_kw) @@ -556,6 +557,8 @@ def plot_raw_psd_topo( fig : instance of matplotlib.figure.Figure Figure distributing one image per channel across sensor topography. """ + from ..time_frequency import Spectrum + init_kw, plot_kw = _split_psd_kwargs(plot_fun=Spectrum.plot_topo) return raw.compute_psd(**init_kw).plot_topo(**plot_kw) diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 1bbaeeb5e96..d88cd6a9ac5 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -10,7 +10,6 @@ import numpy as np from .._fiff.pick import _picks_to_idx, channel_type, pick_types -from ..channels import find_layout from ..defaults import _handle_default from ..utils import Bunch, _check_option, _clean_names, _is_numeric, _to_rgb, fill_doc from .ui_events import ChannelsSelect, publish, subscribe @@ -1287,6 +1286,8 @@ def plot_topo_image_epochs( will always have a colorbar even when the topo plot does not (because it shows multiple sensor types). """ + from ..channels.layout import find_layout + scalings = _handle_default("scalings", scalings) # make a copy because we discard non-data channels and scale the data diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 146e54572b4..bb180a3f299 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -186,7 +186,7 @@ def _prepare_topomap_plot(inst, ch_type, sphere=None): def _average_fnirs_overlaps(info, ch_type, sphere): - from mne.channels.layout import _find_topomap_coords + from ..channels.layout import _find_topomap_coords picks = pick_types(info, meg=False, ref_meg=False, fnirs=ch_type, exclude="bads") chs = [info["chs"][i] for i in picks] @@ -241,7 +241,7 @@ def _average_fnirs_overlaps(info, ch_type, sphere): def _plot_update_evoked_topomap(params, bools): """Update topomaps.""" - from mne.channels.layout import _merge_ch_data + from ..channels.layout import _merge_ch_data projs = [ proj for ii, proj in enumerate(params["projs"]) if ii in np.where(bools)[0] @@ -2966,7 +2966,7 @@ def _onselect( import matplotlib.pyplot as plt from matplotlib.collections import PathCollection - from mne.channels.layout import _pair_grad_sensors + from ..channels.layout import _pair_grad_sensors ax = eclick.inaxes xmin = min(eclick.xdata, erelease.xdata) @@ -3395,7 +3395,7 @@ def _plot_corrmap( show_names=False, ): """Customize ica.plot_components for corrmap.""" - from mne.channels.layout import _merge_ch_data + from ..channels.layout import _merge_ch_data if not template: title = "Detected components" From e84e5e3ce226e2c207f54d5ea8919f7e218146f0 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 14:53:15 +0100 Subject: [PATCH 18/46] Revert --- tools/{dev_reports => dev}/Makefile | 0 tools/{dev_reports => dev}/check_steering_committee.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tools/{dev_reports => dev}/Makefile (100%) rename tools/{dev_reports => dev}/check_steering_committee.py (100%) diff --git a/tools/dev_reports/Makefile b/tools/dev/Makefile similarity index 100% rename from tools/dev_reports/Makefile rename to tools/dev/Makefile diff --git a/tools/dev_reports/check_steering_committee.py b/tools/dev/check_steering_committee.py similarity index 100% rename from tools/dev_reports/check_steering_committee.py rename to tools/dev/check_steering_committee.py From 305cd63edb4de4d9f08d9297daaab8b37bbde94f Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 14:58:14 +0100 Subject: [PATCH 19/46] Undelete --- tools/dev/ensure_headers.py | 196 +++++++++++++++++++++++++++++ tools/dev/gen_css_for_mne.py | 57 +++++++++ tools/dev/generate_pyi_files.py | 81 ++++++++++++ tools/dev/unacknowledged-issues.jq | 32 +++++ tools/dev/update_credit_json.py | 94 ++++++++++++++ 5 files changed, 460 insertions(+) create mode 100644 tools/dev/ensure_headers.py create mode 100644 tools/dev/gen_css_for_mne.py create mode 100644 tools/dev/generate_pyi_files.py create mode 100644 tools/dev/unacknowledged-issues.jq create mode 100644 tools/dev/update_credit_json.py diff --git a/tools/dev/ensure_headers.py b/tools/dev/ensure_headers.py new file mode 100644 index 00000000000..b5b425b5900 --- /dev/null +++ b/tools/dev/ensure_headers.py @@ -0,0 +1,196 @@ +"""Ensure license and copyright statements are in source files. + +From https://www.bestpractices.dev/en/projects/7783?criteria_level=2: + + The project MUST include a copyright statement in each source file, identifying the + copyright holder (e.g., the [project name] contributors). [copyright_per_file] + This MAY be done by including the following inside a comment near the beginning of + each file: "Copyright the [project name] contributors.". + +And: + + The project MUST include a license statement in each source file. + +This script ensures that we use consistent license naming in consistent locations +toward the top of each file. +""" + +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +import re +from pathlib import Path + +import numpy as np +from git import Repo + +repo = Repo(Path(__file__).parents[2]) + +AUTHOR_LINE = "# Authors: The MNE-Python contributors." +LICENSE_LINE = "# License: BSD-3-Clause" +COPYRIGHT_LINE = "# Copyright the MNE-Python contributors." + +# Cover how lines can start (regex or tuple to be used with startswith) +AUTHOR_RE = re.compile(r"^# (A|@a)uthors? ?: .*$") +LICENSE_STARTS = ("# License: ", "# SPDX-License-Identifier: ") +COPYRIGHT_STARTS = ("# Copyright ",) + + +def get_paths_from_tree(root, level=0): + """Get paths from a GitPython tree.""" + for entry in root: + if entry.type == "tree": + yield from get_paths_from_tree(entry, level + 1) + else: + yield Path(entry.path) # entry.type + + +def first_commentable_line(lines): + """Find the first line where we can add a comment.""" + max_len = 100 + if lines[0].startswith(('"""', 'r"""')): + if lines[0].count('"""') == 2: + return 1 + for insert in range(1, min(max_len, len(lines))): + if '"""' in lines[insert]: + return insert + 1 + else: + raise RuntimeError( + f"Failed to find end of file docstring within {max_len} lines" + ) + if lines[0].startswith("#!"): + return 1 + else: + return 0 + + +def path_multi_author(path): + """Check if a file allows multi-author comments.""" + return path.parts[0] in ("examples", "tutorials") + + +def get_author_idx(path, lines): + """Get the index of the author line, if available.""" + author_idx = np.where([AUTHOR_RE.match(line) is not None for line in lines])[0] + assert len(author_idx) <= 1, f"{len(author_idx)=} for {path=}" + return author_idx[0] if len(author_idx) else None + + +def get_license_idx(path, lines): + """Get the license index.""" + license_idx = np.where([line.startswith(LICENSE_STARTS) for line in lines])[0] + assert len(license_idx) <= 1, f"{len(license_idx)=} for {path=}" + return license_idx[0] if len(license_idx) else None + + +def _ensure_author(lines, path): + author_idx = get_author_idx(path, lines) + license_idx = get_license_idx(path, lines) + first_idx = first_commentable_line(lines) + # 1. Keep existing + if author_idx is not None: + # We have to be careful here -- examples and tutorials are allowed multiple + # authors + if path_multi_author(path): + # Just assume it's correct and return + return + assert license_idx is not None, f"{license_idx=} for {path=}" + for _ in range(license_idx - author_idx - 1): + lines.pop(author_idx + 1) + assert lines[author_idx + 1].startswith(LICENSE_STARTS), lines[license_idx + 1] + del license_idx + lines[author_idx] = AUTHOR_LINE + elif license_idx is not None: + # 2. Before license line if present + lines.insert(license_idx, AUTHOR_LINE) + else: + # 3. First line after docstring + lines.insert(first_idx, AUTHOR_LINE) + # Now make sure it's in the right spot + author_idx = get_author_idx(path, lines) + if author_idx != 0: + if author_idx == first_idx: + # Insert a blank line + lines.insert(author_idx, "") + author_idx += 1 + first_idx += 1 + if author_idx != first_idx: + raise RuntimeError( + "\nLine should have comments as docstring or author line needs to be moved " + "manually to be one blank line after the docstring:\n" + f"{path}: {author_idx=} != {first_idx=}" + ) + + +def _ensure_license(lines, path): + # 1. Keep/replace existing + insert = get_license_idx(path, lines) + + # 2. After author line(s) + if insert is None: + author_idx = get_author_idx(path, lines) + assert author_idx is not None, f"{author_idx=} for {path=}" + insert = author_idx + 1 + if path_multi_author: + # Figure out where to insert the license: + for insert, line in enumerate(lines[author_idx + 1 :], insert): + if not line.startswith("# "): + break + if lines[insert].startswith(LICENSE_STARTS): + lines[insert] = LICENSE_LINE + else: + lines.insert(insert, LICENSE_LINE) + assert lines.count(LICENSE_LINE) == 1, f"{lines.count(LICENSE_LINE)=} for {path=}" + + +def _ensure_copyright(lines, path): + n_expected = { + "mne/preprocessing/_csd.py": 2, + "mne/transforms.py": 2, + } + n_copyright = sum(line.startswith(COPYRIGHT_STARTS) for line in lines) + assert n_copyright <= n_expected.get(str(path), 1), n_copyright + insert = lines.index(LICENSE_LINE) + 1 + if lines[insert].startswith(COPYRIGHT_STARTS): + lines[insert] = COPYRIGHT_LINE + else: + lines.insert(insert, COPYRIGHT_LINE) + assert ( + lines.count(COPYRIGHT_LINE) == 1 + ), f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + + +def _ensure_blank(lines, path): + assert ( + lines.count(COPYRIGHT_LINE) == 1 + ), f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + insert = lines.index(COPYRIGHT_LINE) + 1 + if lines[insert].strip(): # actually has content + lines.insert(insert, "") + + +for path in get_paths_from_tree(repo.tree()): + if not path.suffix == ".py": + continue + lines = path.read_text("utf-8").split("\n") + # Remove the UTF-8 file coding stuff + orig_lines = list(lines) + if lines[0] in ("# -*- coding: utf-8 -*-", "# -*- coding: UTF-8 -*-"): + lines = lines[1:] + if lines[0] == "": + lines = lines[1:] + # We had these with mne/commands without an executable bit, and don't really + # need them executable, so let's get rid of the line. + if lines[0].startswith("#!/usr/bin/env python") and path.parts[:2] == ( + "mne", + "commands", + ): + lines = lines[1:] + _ensure_author(lines, path) + _ensure_license(lines, path) + _ensure_copyright(lines, path) + _ensure_blank(lines, path) + if lines != orig_lines: + print(path) + path.write_text("\n".join(lines), "utf-8") diff --git a/tools/dev/gen_css_for_mne.py b/tools/dev/gen_css_for_mne.py new file mode 100644 index 00000000000..2d277857348 --- /dev/null +++ b/tools/dev/gen_css_for_mne.py @@ -0,0 +1,57 @@ +""" +Generate a bootstrap-icons CSS file with embedded font. + +- Install rcssmin (for CSS minification) +- Download and extract bootstrap-icons +- Copy to this directory: + - bootstrap-icons.css + - bootstrap-icons.woff2 +- Run this script. It will generate bootstrap-icons.mne.css and + bootstrap-icons.mne.min.css +""" + +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +import base64 +from pathlib import Path + +import rcssmin + +import mne + +base_dir = Path(mne.__file__).parent / "report" / "js_and_css" / "bootstrap-icons" +css_path_in = base_dir / "bootstrap-icons.css" +css_path_out = base_dir / "bootstrap-icons.mne.css" +css_minified_path_out = base_dir / "bootstrap-icons.mne.min.css" +font_path = base_dir / "bootstrap-icons.woff2" + + +def main(): + """Start the CSS modification.""" + css_in = css_path_in.read_text(encoding="utf-8") + font_binary = font_path.read_bytes() + font_b64 = base64.b64encode(font_binary).decode("utf-8") + + css_out = [] + for css in css_in.split("\n"): + if "src: url(" in css: + css = ( + f" src: url(data:font/woff2;charset=utf-8;" + f'base64,{font_b64}) format("woff2");' + ) + elif "url(" in css: + continue + + css_out.append(css) + + css_out = "\n".join(css_out) + css_minified_out = rcssmin.cssmin(style=css_out) + + css_path_out.write_text(data=css_out, encoding="utf-8") + css_minified_path_out.write_text(data=css_minified_out, encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/tools/dev/generate_pyi_files.py b/tools/dev/generate_pyi_files.py new file mode 100644 index 00000000000..c6ae36e7dd4 --- /dev/null +++ b/tools/dev/generate_pyi_files.py @@ -0,0 +1,81 @@ +# 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/dev/unacknowledged-issues.jq b/tools/dev/unacknowledged-issues.jq new file mode 100644 index 00000000000..b91f5fc2e78 --- /dev/null +++ b/tools/dev/unacknowledged-issues.jq @@ -0,0 +1,32 @@ +# Processor for `gh issue list` output that displays unacknowledged issues +# that are 2-12 months old. The date range is specific to OpenSSF best practices. + +# `now` is in seconds since the unix epoch +def one_year_ago: now - (365 * 24 * 60 * 60); + +def sixty_days_ago: now - (60 * 24 * 60 * 60); + +def date_fmt: "%Y/%m/%d"; + +def make_pretty_date_range: + (one_year_ago | strftime(date_fmt)) + " - " + (sixty_days_ago | strftime(date_fmt)); + +def make_issue_url: "https://github.com/mne-tools/mne-python/issues/\(.number)"; + +def get_dev_comments: .comments | map(select(.authorAssociation == "MEMBER")); + + +# main routine +map( + select( + (.createdAt > (one_year_ago | todate)) and + (.createdAt < (sixty_days_ago | todate)) + ) += + { "devComments": . | get_dev_comments | length } +) | +{ + "range": make_pretty_date_range, + "has_dev_comments": map(select(.devComments > 0)) | length, + "no_dev_comments": map(select(.devComments == 0) and .state == "OPEN") | length, + "unaddressed": map(select(.devComments == 0) | make_issue_url), +} diff --git a/tools/dev/update_credit_json.py b/tools/dev/update_credit_json.py new file mode 100644 index 00000000000..de96c040604 --- /dev/null +++ b/tools/dev/update_credit_json.py @@ -0,0 +1,94 @@ +"""Collect credit information for PRs. + +The initial run takes a long time (hours!) due to GitHub rate limits, even with +a personal GITHUB_TOKEN. +""" + +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + +import json +import os +import re +from pathlib import Path + +from github import Auth, Github +from tqdm import tqdm + +auth = Auth.Token(os.environ["GITHUB_TOKEN"]) +g = Github(auth=auth, per_page=100) +out_path = Path(__file__).parents[2] / "doc" / "sphinxext" / "prs" +out_path.mkdir(exist_ok=True) +# manually update this when the oldest open PR changes to speed things up +# (don't need to look any farther back than this) +oldest_pr = 9176 + +# JSON formatting +json_kwargs = dict(indent=2, ensure_ascii=False, sort_keys=False) +# If the above arguments are changed, existing JSON should also be reformatted with +# something like: +# for fname in sorted(glob.glob("doc/sphinxext/prs/*.json")): +# fname = Path(fname).resolve(strict=True) +# fname.write_text(json.dumps(json.loads(fname.read_text("utf-8")), **json_kwargs), "utf-8") # noqa: E501 + +repo = g.get_repo("mne-tools/mne-python") +co_re = re.compile("Co-authored-by: ([^<>]+) <([^()>]+)>") +# We go in descending order of updates and `break` when we encounter a PR we have +# already committed a file for. +pulls_iter = repo.get_pulls(state="closed", sort="created", direction="desc") +iter_ = tqdm(pulls_iter, unit="pr", desc="Traversing") +last = 0 +n_added = 0 +for pull in iter_: + fname_out = out_path / f"{pull.number}.json" + if pull.number < oldest_pr: + iter_.close() + print( + f"After checking {iter_.n + 1} and adding {n_added} PR(s), " + f"found PR number less than oldest existing file {fname_out}, stopping" + ) + break + if fname_out.is_file(): + continue + + # PR diff credit + if not pull.merged: + continue + out = dict() + # One option is to do a git diff between pull.base and pull.head, + # but let's see if we can stay pythonic + out["merge_commit_sha"] = pull.merge_commit_sha + # Prefer the GitHub username information because it should be most up to date + name, email = pull.user.name, pull.user.email + if name is None and email is None: + # no usable GitHub user information, pull it from the first commit + author = pull.get_commits()[0].commit.author + name, email = author.name, author.email + out["authors"] = [dict(n=name, e=email)] + # For PR 54 for example this is empty for some reason! + if out["merge_commit_sha"]: + try: + merge_commit = repo.get_commit(out["merge_commit_sha"]) + except Exception: + pass # this happens on a lot of old PRs for some reason + else: + msg = merge_commit.commit.message.replace("\r", "") + for n, e in co_re.findall(msg): + # sometimes commit messages like for 9754 contain all + # commit messages and include some repeated co-authorship messages + if n not in {a["n"] for a in out["authors"]}: + out["authors"].append(dict(n=n, e=e)) + out["changes"] = dict() + for file in pull.get_files(): + out["changes"][file.filename] = { + k[0]: getattr(file, k) for k in ("additions", "deletions") + } + n_added += 1 + fname_out.write_text(json.dumps(out, **json_kwargs), encoding="utf-8") + + # TODO: Should add: + # pull.get_comments() + # pull.get_review_comments() + +g.close() From a992cd20d25ef3b4b03dfd8fc5ce4a5582bcb23c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:58:51 +0000 Subject: [PATCH 20/46] [autofix.ci] apply automated fixes --- mne/datasets/__init__.py | 4 ++++ mne/report/__init__.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py index 1549fa21f8f..e9861bdeddb 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -3,6 +3,10 @@ See :ref:`datasets` for more information. """ +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + from . import fieldtrip_cmc from . import brainstorm from . import visual_92_categories diff --git a/mne/report/__init__.py b/mne/report/__init__.py index 02ee6610095..8b7334f50bc 100644 --- a/mne/report/__init__.py +++ b/mne/report/__init__.py @@ -1 +1,5 @@ +# Authors: The MNE-Python contributors. +# License: BSD-3-Clause +# Copyright the MNE-Python contributors. + from .report import Report, open_report, _ReportScraper From 3402942fb902d560043569fe552b626ce291ec51 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 14:59:52 +0100 Subject: [PATCH 21/46] Remove script --- tools/dev/generate_pyi_files.py | 81 --------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 tools/dev/generate_pyi_files.py 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) From e77340d99052a8a9e24c54f8d06c20be0fe434c1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:01:26 +0000 Subject: [PATCH 22/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tools/dev/ensure_headers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/dev/ensure_headers.py b/tools/dev/ensure_headers.py index b5b425b5900..a4095d82b42 100644 --- a/tools/dev/ensure_headers.py +++ b/tools/dev/ensure_headers.py @@ -156,15 +156,15 @@ def _ensure_copyright(lines, path): lines[insert] = COPYRIGHT_LINE else: lines.insert(insert, COPYRIGHT_LINE) - assert ( - lines.count(COPYRIGHT_LINE) == 1 - ), f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + assert lines.count(COPYRIGHT_LINE) == 1, ( + f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + ) def _ensure_blank(lines, path): - assert ( - lines.count(COPYRIGHT_LINE) == 1 - ), f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + assert lines.count(COPYRIGHT_LINE) == 1, ( + f"{lines.count(COPYRIGHT_LINE)=} for {path=}" + ) insert = lines.index(COPYRIGHT_LINE) + 1 if lines[insert].strip(): # actually has content lines.insert(insert, "") From 325213a0b43538cbf31df9e547a0684254d3db19 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 15:22:05 +0100 Subject: [PATCH 23/46] Add missing imports --- mne/source_space/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mne/source_space/__init__.py b/mne/source_space/__init__.py index 61c38ef4a44..8d7962fa2b7 100644 --- a/mne/source_space/__init__.py +++ b/mne/source_space/__init__.py @@ -3,3 +3,18 @@ # Copyright the MNE-Python contributors. """Forward modeling code.""" + +from ._source_space import ( + SourceSpaces, + add_source_space_distances, + compute_distance_to_sensors, + find_source_space_hemi, + get_decimated_surfaces, + get_volume_labels_from_src, + label_src_vertno_sel, + morph_source_spaces, + read_source_spaces, + setup_source_space, + setup_volume_source_space, + write_source_spaces, +) From 6599745c2ec8d2e202261a77666dbbeb75e55322 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 15:58:58 +0100 Subject: [PATCH 24/46] Fix imports --- mne/decoding/__init__.py | 1 - mne/io/__init__.py | 1 + mne/preprocessing/xdawn.py | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/decoding/__init__.py b/mne/decoding/__init__.py index 6fa09000f0b..72b59b7be18 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -12,7 +12,6 @@ Scaler, FilterEstimator, ) -from .mixin import TransformerMixin from .base import BaseEstimator, LinearModel, get_coef, cross_val_multiscore from .csp import CSP, SPoC from .ssd import SSD diff --git a/mne/io/__init__.py b/mne/io/__init__.py index 286f5edd540..86a730ebab4 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -27,6 +27,7 @@ from . import nihon from . import nsx +from .ant import read_raw_ant from .array import RawArray from .besa import read_evoked_besa from .brainvision import read_raw_brainvision diff --git a/mne/preprocessing/xdawn.py b/mne/preprocessing/xdawn.py index b54ffda80a2..69cf65f5b60 100644 --- a/mne/preprocessing/xdawn.py +++ b/mne/preprocessing/xdawn.py @@ -3,10 +3,11 @@ # Copyright the MNE-Python contributors. import numpy as np +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 From 107948c29b255be510c204f9cd18e5a951c928e7 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Mon, 24 Feb 2025 16:37:02 +0100 Subject: [PATCH 25/46] Nest SciPy --- mne/_fiff/open.py | 3 ++- mne/_fiff/tag.py | 3 ++- mne/_fiff/write.py | 3 ++- mne/channels/channels.py | 13 +++++++++---- mne/channels/interpolation.py | 7 +++++-- mne/decoding/csp.py | 3 ++- mne/decoding/receptive_field.py | 3 ++- mne/decoding/ssd.py | 3 ++- mne/decoding/time_delaying_ridge.py | 5 ++++- mne/forward/forward.py | 5 ++++- mne/io/artemis123/artemis123.py | 3 ++- mne/preprocessing/_csd.py | 5 +++-- mne/preprocessing/_fine_cal.py | 3 ++- mne/preprocessing/_pca_obs.py | 7 +++++-- mne/preprocessing/artifact_detection.py | 9 ++++++--- mne/preprocessing/ica.py | 7 ++++--- mne/preprocessing/maxwell.py | 8 ++++++-- mne/preprocessing/nirs/_beer_lambert_law.py | 5 +++-- mne/preprocessing/stim.py | 6 ++++-- mne/simulation/metrics/metrics.py | 3 ++- mne/source_estimate.py | 9 +++++++-- mne/source_space/_source_space.py | 16 +++++++++++++--- mne/stats/cluster_level.py | 18 ++++++++++++++---- mne/stats/parametric.py | 6 ++++-- mne/surface.py | 15 +++++++++++---- mne/time_frequency/multitaper.py | 14 ++++++++++---- mne/time_frequency/tfr.py | 3 ++- mne/transforms.py | 9 +++++++-- mne/utils/_testing.py | 3 ++- mne/utils/numerics.py | 3 ++- mne/viz/_3d.py | 4 ++-- mne/viz/_brain/_brain.py | 6 ++++-- mne/viz/evoked_field.py | 3 ++- mne/viz/eyetracking/heatmap.py | 3 ++- mne/viz/misc.py | 2 +- mne/viz/montage.py | 2 +- mne/viz/topomap.py | 21 +++++++++++++-------- mne/viz/utils.py | 3 ++- 38 files changed, 170 insertions(+), 74 deletions(-) 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/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 e7136b9beef..1bb804a1a53 100644 --- a/mne/channels/interpolation.py +++ b/mne/channels/interpolation.py @@ -4,8 +4,6 @@ import numpy as np from numpy.polynomial.legendre import legval -from scipy.interpolate import RectBivariateSpline -from scipy.spatial.distance import pdist, squareform from .._fiff.meas_info import _simplify_info from .._fiff.pick import pick_channels, pick_info, pick_types @@ -254,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: @@ -292,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 @@ -362,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/decoding/csp.py b/mne/decoding/csp.py index 102b9bdb637..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 @@ -880,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 eed15d032be..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): 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/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/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/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..50594a5dd04 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.signal import detrend + from scipy.interpolate import pchip + # 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/ica.py b/mne/preprocessing/ica.py index b726e12dce3..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) @@ -1994,6 +1994,7 @@ def find_bads_muscle( .. versionadded:: 1.1 """ from scipy.spatial.distance import pdist, squareform + from scipy.special import expit _validate_type(threshold, "numeric", "threshold") diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index 8c9c0a93957..7b9ce1ce1c4 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 @@ -1184,6 +1182,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) # @@ -1839,6 +1839,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) @@ -2025,6 +2027,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/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/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 24621d4defa..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 @@ -3332,6 +3330,7 @@ def spatial_inter_hemi_adjacency(src, dist, verbose=None): 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"]]) @@ -3346,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 @@ -3435,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 @@ -3923,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/_source_space.py b/mne/source_space/_source_space.py index 8dcc279ecef..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)] @@ -3250,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/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 1043731a599..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 @@ -419,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/surface.py b/mne/surface.py index 6d6ad0f19ae..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: @@ -1716,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.") @@ -1749,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 @@ -1941,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/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/tfr.py b/mne/time_frequency/tfr.py index dd1474c7b0e..f8b02fd1982 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -13,7 +13,6 @@ import matplotlib.pyplot as plt import numpy as np -from scipy.fft import fft, ifft from .._fiff.meas_info import ContainsMixin, Info from .._fiff.pick import _picks_to_idx, pick_info @@ -368,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) diff --git a/mne/transforms.py b/mne/transforms.py index 73bf36d0069..c6c4cfad42c 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 @@ -984,6 +982,7 @@ 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 @@ -1016,6 +1015,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 @@ -1124,6 +1125,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 @@ -1499,6 +1502,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/_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/numerics.py b/mne/utils/numerics.py index a9188e85adb..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_, @@ -732,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 ) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 0ba7a08ec82..4d47dab3da3 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -17,8 +17,6 @@ from pathlib import Path import numpy as np -from scipy.spatial import ConvexHull, Delaunay -from scipy.stats import rankdata from .._fiff.constants import FIFF from .._fiff.meas_info import Info, create_info, read_fiducials @@ -1740,6 +1738,7 @@ 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 @@ -2135,6 +2134,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/_brain/_brain.py b/mne/viz/_brain/_brain.py index dc3e1f4851f..65b873fee6e 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -12,8 +12,6 @@ from io import BytesIO import numpy as np -from scipy.interpolate import interp1d -from scipy.sparse import csr_array from ..._fiff.meas_info import Info from ..._fiff.pick import pick_types @@ -1065,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) @@ -4037,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/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/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/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: From 7799c20a9f888c50dce9812541bb9c971da16e91 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:38:51 +0000 Subject: [PATCH 26/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/preprocessing/_pca_obs.py | 2 +- mne/viz/_3d.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/preprocessing/_pca_obs.py b/mne/preprocessing/_pca_obs.py index 50594a5dd04..d78b6a39305 100755 --- a/mne/preprocessing/_pca_obs.py +++ b/mne/preprocessing/_pca_obs.py @@ -276,8 +276,8 @@ def _fit_ecg_template( ------- tuple[np.ndarray, int]: the fitted artifact and the next peak index """ - from scipy.signal import detrend 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 diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 4d47dab3da3..67e350d1027 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1739,6 +1739,7 @@ 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 From 71c345e097266fa37573e89b6764b0bfce55ff3f Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 08:11:10 +0100 Subject: [PATCH 27/46] Remove lazy-loader core dep (temporary fix) --- tools/hooks/sync_dependencies.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/hooks/sync_dependencies.py b/tools/hooks/sync_dependencies.py index db14274893e..aa8d5a6b7fb 100755 --- a/tools/hooks/sync_dependencies.py +++ b/tools/hooks/sync_dependencies.py @@ -51,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) From 1a007bbd886e475c28ed2d2285f45b2a5d5492e7 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 09:01:10 +0100 Subject: [PATCH 28/46] Do not export xdawn --- mne/preprocessing/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index 6bb9664297f..9d373b33e15 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -28,7 +28,7 @@ maxwell_filter_prepare_emptyroom, ) from .realign import realign_raw -from .xdawn import Xdawn +# from .xdawn import Xdawn from ._csd import compute_current_source_density, compute_bridged_electrodes from . import nirs from .artifact_detection import ( From a27941071ae5fb0b5f3a51cd8672cae10bcee329 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 09:13:11 +0100 Subject: [PATCH 29/46] Export source_space --- mne/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/__init__.py b/mne/__init__.py index ec4a8fddb4c..559afcccf3a 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -234,6 +234,7 @@ from . import minimum_norm from . import preprocessing from . import simulation +from . import source_space from . import stats from . import surface from . import time_frequency From 753f92a98c9d2752ab75f82747c810fe801408e5 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 09:48:49 +0100 Subject: [PATCH 30/46] Explicit source_space exports --- mne/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mne/__init__.py b/mne/__init__.py index 559afcccf3a..f1f497f1f92 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -217,6 +217,13 @@ ) from .rank import compute_rank +from .source_space import ( + SourceSpaces, + add_source_space_distances, + read_source_spaces, + setup_volume_source_space, +) + from . import beamformer from . import channels from . import chpi From 5bd359d7a1bfe27158fc76ed71e8b379fc7b4ba0 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 10:06:48 +0100 Subject: [PATCH 31/46] More explicit exports --- mne/channels/__init__.py | 1 + mne/decoding/__init__.py | 37 +++++++++++++++++++--------------- mne/io/__init__.py | 2 ++ mne/preprocessing/__init__.py | 3 ++- mne/time_frequency/__init__.py | 2 ++ mne/utils/__init__.py | 2 ++ mne/viz/__init__.py | 1 + 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py index 6e843618f85..d71ae72cf58 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -42,6 +42,7 @@ make_1020_channel_selections, combine_channels, read_vectorview_selection, + unify_bad_channels, _SELECTIONS, _EEG_SELECTIONS, _divide_to_regions, diff --git a/mne/decoding/__init__.py b/mne/decoding/__init__.py index 72b59b7be18..f59afeeafdb 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -4,19 +4,24 @@ """Decoding and encoding, including machine learning and receptive fields.""" -from .transformer import ( - PSDEstimator, - Vectorizer, - UnsupervisedSpatialFilter, - TemporalFilter, - Scaler, - FilterEstimator, -) -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 +try: + import sklearn +except ImportError: + pass +else: + from .transformer import ( + PSDEstimator, + Vectorizer, + UnsupervisedSpatialFilter, + TemporalFilter, + Scaler, + FilterEstimator, + ) + 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/io/__init__.py b/mne/io/__init__.py index 86a730ebab4..dc4656e0294 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -24,6 +24,7 @@ from . import boxy from . import persyst from . import eeglab +from . import neuralynx from . import nihon from . import nsx @@ -51,6 +52,7 @@ 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 diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index 9d373b33e15..48aed8b8724 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -48,5 +48,6 @@ from . import ieeg 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 from . import eyetracking diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 7096d146264..7d76c1610e8 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -8,10 +8,12 @@ morlet, tfr_morlet, AverageTFR, + AverageTFRArray, tfr_multitaper, read_tfrs, write_tfrs, EpochsTFR, + EpochsTFRArray, tfr_array_morlet, fwhm, ) diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index 89d6b7a4d02..eb21a1f9bfc 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -120,6 +120,7 @@ _pl, _clean_names, pformat, + _empty_hash, _file_like, _explain_exception, _get_argvalues, @@ -188,6 +189,7 @@ _hashable_ndarray, _custom_lru_cache, _array_repr, + _date_to_julian, ) from .mixin import ( SizeMixin, diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index 522189f1326..4a8561f05b5 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -92,3 +92,4 @@ from ._figure import get_browser_backend, set_browser_backend, use_browser_backend from ._proj import plot_projs_joint from . import ui_events +from .evoked_field import EvokedField From f56351610301956d32d1503e8ff76d4e94dc1dd9 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 10:13:20 +0100 Subject: [PATCH 32/46] More --- mne/__init__.py | 8 ++++++++ mne/time_frequency/__init__.py | 2 ++ mne/utils/__init__.py | 1 + 3 files changed, 11 insertions(+) diff --git a/mne/__init__.py b/mne/__init__.py index f1f497f1f92..f68cab7ec8a 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -220,8 +220,16 @@ from .source_space import ( SourceSpaces, add_source_space_distances, + compute_distance_to_sensors, + find_source_space_hemi, + get_decimated_surfaces, + get_volume_labels_from_src, + label_src_vertno_sel, + morph_source_spaces, read_source_spaces, + setup_source_space, setup_volume_source_space, + write_source_spaces, ) from . import beamformer diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 7d76c1610e8..594d8053a87 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -9,6 +9,8 @@ tfr_morlet, AverageTFR, AverageTFRArray, + RawTFR, + RawTFRArray, tfr_multitaper, read_tfrs, write_tfrs, diff --git a/mne/utils/__init__.py b/mne/utils/__init__.py index eb21a1f9bfc..4c1242912cf 100644 --- a/mne/utils/__init__.py +++ b/mne/utils/__init__.py @@ -190,6 +190,7 @@ _custom_lru_cache, _array_repr, _date_to_julian, + _julian_to_date, ) from .mixin import ( SizeMixin, From aa5cd1722ad2b20cc9ffcb70a0c2c66d0f91895e Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 10:50:33 +0100 Subject: [PATCH 33/46] Export BaseTFR --- mne/time_frequency/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/time_frequency/__init__.py b/mne/time_frequency/__init__.py index 594d8053a87..cca2f757b7d 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -9,6 +9,7 @@ tfr_morlet, AverageTFR, AverageTFRArray, + BaseTFR, RawTFR, RawTFRArray, tfr_multitaper, From 4406c5bd75f0e41a0e532f73d31c05d376604356 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 11:31:47 +0100 Subject: [PATCH 34/46] Fix fft --- mne/filter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/filter.py b/mne/filter.py index 0643bc5b8fe..e3145b3efe8 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -1942,7 +1942,8 @@ def _resample_polyphase(x, *, up, down, pad, window, n_jobs): def _resample_fft(x_flat, *, ratio, final_len, pad, window, npad, n_jobs): - from scipy.signal import fft, get_window + from scipy import fft + from scipy.signal import get_window x_len = x_flat.shape[-1] pad = "reflect_limited" if pad == "auto" else pad From d800621edf075df44aced7b09e55d1f0f2e79e78 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 12:04:39 +0100 Subject: [PATCH 35/46] Remove public exports --- mne/__init__.py | 2 -- mne/source_space/__init__.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/mne/__init__.py b/mne/__init__.py index f68cab7ec8a..2338a61a17d 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -221,10 +221,8 @@ SourceSpaces, add_source_space_distances, compute_distance_to_sensors, - find_source_space_hemi, get_decimated_surfaces, get_volume_labels_from_src, - label_src_vertno_sel, morph_source_spaces, read_source_spaces, setup_source_space, diff --git a/mne/source_space/__init__.py b/mne/source_space/__init__.py index 8d7962fa2b7..48899d28641 100644 --- a/mne/source_space/__init__.py +++ b/mne/source_space/__init__.py @@ -8,10 +8,8 @@ SourceSpaces, add_source_space_distances, compute_distance_to_sensors, - find_source_space_hemi, get_decimated_surfaces, get_volume_labels_from_src, - label_src_vertno_sel, morph_source_spaces, read_source_spaces, setup_source_space, From ffad5459c6fa9cf5c10abe2691b742f1fb3aab8e Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 25 Feb 2025 12:30:41 +0100 Subject: [PATCH 36/46] Add __all__ --- mne/__init__.py | 203 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/mne/__init__.py b/mne/__init__.py index 2338a61a17d..1aadfac3f3b 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -257,3 +257,206 @@ # 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", + "dipole", + "epochs", + "event", + "io", + "filter", + "gui", + "inverse_sparse", + "minimum_norm", + "preprocessing", + "simulation", + "source_space", + "stats", + "surface", + "time_frequency", + "viz", + "export", +] From 1fb130ec49281c819dbdac5296d6868eb0a6775f Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Wed, 26 Feb 2025 07:30:48 +0100 Subject: [PATCH 37/46] Add missing submodules --- mne/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mne/__init__.py b/mne/__init__.py index 1aadfac3f3b..ca42be6b63f 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -237,15 +237,19 @@ 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 2c90634c856f58c120d5d52a639f00da8abdc1af Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Wed, 26 Feb 2025 16:16:25 +0100 Subject: [PATCH 38/46] Fix import nesting test --- mne/__init__.py | 4 ++++ mne/datasets/__init__.py | 25 ------------------------- mne/forward/__init__.py | 1 - mne/io/__init__.py | 29 ++--------------------------- mne/preprocessing/__init__.py | 5 ++--- mne/simulation/__init__.py | 1 - mne/viz/__init__.py | 4 ++-- mne/viz/_figure.py | 3 ++- 8 files changed, 12 insertions(+), 60 deletions(-) diff --git a/mne/__init__.py b/mne/__init__.py index ca42be6b63f..8210ed19136 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -447,15 +447,19 @@ "coreg", "cuda", "datasets", + "decoding", + "defaults", "dipole", "epochs", "event", + "forward", "io", "filter", "gui", "inverse_sparse", "minimum_norm", "preprocessing", + "report", "simulation", "source_space", "stats", diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py index e9861bdeddb..5db24a34650 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -7,31 +7,6 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -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 from ._fetch import fetch_dataset from .utils import ( _download_all_example_data, diff --git a/mne/forward/__init__.py b/mne/forward/__init__.py index 2ae65536a52..ecba5677832 100644 --- a/mne/forward/__init__.py +++ b/mne/forward/__init__.py @@ -49,4 +49,3 @@ _as_meg_type_inst, _map_meg_or_eeg_channels, ) -from . import _lead_dots # for testing purposes diff --git a/mne/io/__init__.py b/mne/io/__init__.py index dc4656e0294..de38a11e939 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -7,26 +7,7 @@ import sys as _sys from .base import BaseRaw, concatenate_raws, match_channel_orders - -from . import array -from . import base -from . import brainvision -from . import bti -from . import cnt -from . import ctf -from . import edf -from . import egi -from . import fiff -from . import fil -from . import kit -from . import nicolet -from . import nirx -from . import boxy -from . import persyst -from . import eeglab -from . import neuralynx -from . import nihon -from . import nsx +from ..utils import warn from .ant import read_raw_ant from .array import RawArray @@ -68,17 +49,13 @@ _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 -from . import constants -from . import pick - def __getattr__(name): """Try getting attribute from fiff submodule.""" - from ..utils import warn - if name in ("meas_info", "proj", "reference"): warn(f"mne.io.{name} {_dep_msg}", FutureWarning) return importlib.import_module(f"mne.io.{name}") @@ -100,8 +77,6 @@ def __getattr__(name): ) return Raw elif name == "Info": - from .._fiff.meas_info import Info - warn( "mne.io.Info is deprecated and will be removed in 1.6, " "use mne.Info instead", diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index 48aed8b8724..a42881f021d 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -30,7 +30,7 @@ from .realign import realign_raw # from .xdawn import Xdawn from ._csd import compute_current_source_density, compute_bridged_electrodes -from . import nirs + from .artifact_detection import ( annotate_movement, compute_average_dev_head_t, @@ -45,9 +45,8 @@ ) from ._annotate_nan import annotate_nan from .interpolate import equalize_bads, interpolate_bridged_electrodes -from . import ieeg + 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 -from . import eyetracking diff --git a/mne/simulation/__init__.py b/mne/simulation/__init__.py index cde16108f92..43cefc5c501 100644 --- a/mne/simulation/__init__.py +++ b/mne/simulation/__init__.py @@ -8,4 +8,3 @@ 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 -from . import metrics diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index 4a8561f05b5..24b2b8da4d2 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -87,9 +87,9 @@ close_all_3d_figures, get_brain_class, ) -from . import backends, _scraper + from ._brain import Brain from ._figure import get_browser_backend, set_browser_backend, use_browser_backend from ._proj import plot_projs_joint -from . import ui_events + from .evoked_field import EvokedField diff --git a/mne/viz/_figure.py b/mne/viz/_figure.py index 913211735cb..090c661f633 100644 --- a/mne/viz/_figure.py +++ b/mne/viz/_figure.py @@ -14,7 +14,6 @@ import numpy as np from .._fiff.pick import _DATA_CH_TYPES_SPLIT -from ..annotations import _sync_onset from ..defaults import _handle_default from ..filter import _iir_filter, _overlap_add_filter from ..fixes import _compare_version @@ -186,6 +185,8 @@ def _setup_annotation_colors(self): def _update_annotation_segments(self): """Update the array of annotation start/end times.""" + from ..annotations import _sync_onset + self.mne.annotation_segments = np.array([]) if len(self.mne.inst.annotations): annot_start = _sync_onset(self.mne.inst, self.mne.inst.annotations.onset) From 5e2f83cd5a7969c461d6dfaed64b7106d1960645 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Wed, 26 Feb 2025 16:29:15 +0100 Subject: [PATCH 39/46] Revert --- mne/datasets/__init__.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py index 5db24a34650..b5892b7a1d8 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -7,6 +7,32 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +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 + from ._fetch import fetch_dataset from .utils import ( _download_all_example_data, From d2c5d801b604132cb6044655a516f9f3a1496920 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 08:58:01 +0100 Subject: [PATCH 40/46] Adjust nesting test --- mne/tests/test_import_nesting.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mne/tests/test_import_nesting.py b/mne/tests/test_import_nesting.py index b90e387cb02..38f26484f59 100644 --- a/mne/tests/test_import_nesting.py +++ b/mne/tests/test_import_nesting.py @@ -112,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 ( From 7843ad55202a06c8b8a38b57ddb0d1db22a7e859 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 09:12:12 +0100 Subject: [PATCH 41/46] Add missing export --- mne/decoding/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/decoding/__init__.py b/mne/decoding/__init__.py index f59afeeafdb..479b4b5b36a 100644 --- a/mne/decoding/__init__.py +++ b/mne/decoding/__init__.py @@ -16,6 +16,7 @@ TemporalFilter, Scaler, FilterEstimator, + TransformerMixin, ) from .base import BaseEstimator, LinearModel, get_coef, cross_val_multiscore from .csp import CSP, SPoC From ae518da42f20175e70a9f25e22e469e3deb20b3a Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 10:38:38 +0100 Subject: [PATCH 42/46] csr_matrix -> csr_array --- mne/morph_map.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/morph_map.py b/mne/morph_map.py index 9d8b5b0f15e..4c36b66355d 100644 --- a/mne/morph_map.py +++ b/mne/morph_map.py @@ -204,7 +204,7 @@ 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_matrix + from scipy.sparse import csr_array from scipy.sparse import eye as speye # add speedy short-circuit for self-maps @@ -242,7 +242,7 @@ def _make_morph_map_hemi(subject_from, subject_to, subjects_dir, reg_from, reg_t weights = np.array(weights) row_ind = np.repeat(np.arange(len(to_rr)), 3) - this_map = csr_matrix( + this_map = csr_array( (weights.ravel(), (row_ind, nn_idx.ravel())), shape=(len(to_rr), len(from_rr)) ) return this_map From 50bc4e069fa9f4eda6abc52b5f0054a0562b527d Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 13:30:14 +0100 Subject: [PATCH 43/46] Fix csr_array --- mne/morph_map.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/morph_map.py b/mne/morph_map.py index 4c36b66355d..854dbe1caf1 100644 --- a/mne/morph_map.py +++ b/mne/morph_map.py @@ -21,6 +21,7 @@ write_int, write_string, ) +from .fixes import _eye_array from .surface import ( _compute_nearest, _find_nearest_tri_pts, @@ -205,13 +206,12 @@ 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 - from scipy.sparse import eye as speye # 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 n_pts = len(read_surface(fname, verbose=False)[0]) - return speye(n_pts, format="csr") + return _eye_array(n_pts, format="csr") # load surfaces and normalize points to be on unit sphere fname = subjects_dir / subject_from / "surf" / reg_from From 27db5fbab2177f4092c88f170f7c586f0ce050f9 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 14:08:46 +0100 Subject: [PATCH 44/46] Add more missing exports --- mne/io/__init__.py | 1 + mne/preprocessing/__init__.py | 6 +++++- mne/time_frequency/__init__.py | 22 +++++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/mne/io/__init__.py b/mne/io/__init__.py index de38a11e939..43d1f4b2f6d 100644 --- a/mne/io/__init__.py +++ b/mne/io/__init__.py @@ -43,6 +43,7 @@ # (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, diff --git a/mne/preprocessing/__init__.py b/mne/preprocessing/__init__.py index a42881f021d..f61b7fdc895 100644 --- a/mne/preprocessing/__init__.py +++ b/mne/preprocessing/__init__.py @@ -28,7 +28,6 @@ maxwell_filter_prepare_emptyroom, ) from .realign import realign_raw -# from .xdawn import Xdawn from ._csd import compute_current_source_density, compute_bridged_electrodes from .artifact_detection import ( @@ -50,3 +49,8 @@ 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/time_frequency/__init__.py b/mne/time_frequency/__init__.py index cca2f757b7d..8c3496e23d6 100644 --- a/mne/time_frequency/__init__.py +++ b/mne/time_frequency/__init__.py @@ -5,20 +5,21 @@ """Time frequency analysis tools.""" from .tfr import ( - morlet, - tfr_morlet, AverageTFR, AverageTFRArray, BaseTFR, + EpochsTFR, + EpochsTFRArray, RawTFR, RawTFRArray, + combine_tfr, + fwhm, + morlet, + tfr_array_morlet, + tfr_morlet, tfr_multitaper, read_tfrs, write_tfrs, - EpochsTFR, - EpochsTFRArray, - tfr_array_morlet, - fwhm, ) from .psd import psd_array_welch from .csd import ( @@ -35,6 +36,13 @@ ) from .ar import fit_iir_model_raw from .multitaper import dpss_windows, psd_array_multitaper, tfr_array_multitaper -from .spectrum import EpochsSpectrum, Spectrum, read_spectrum +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 From a21a1a1029675184bc9cf2c5ec30ee1569bb00d0 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 14:25:49 +0100 Subject: [PATCH 45/46] More missing exports --- mne/channels/__init__.py | 8 ++++++++ mne/datasets/__init__.py | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py index d71ae72cf58..df1c0ed4e5f 100644 --- a/mne/channels/__init__.py +++ b/mne/channels/__init__.py @@ -31,6 +31,7 @@ read_custom_montage, read_dig_hpts, read_dig_localite, + transform_to_head, compute_native_head_t, ) from .channels import ( @@ -53,6 +54,8 @@ # Data Structures "DigMontage", "Layout", + "_SELECTIONS", + "_EEG_SELECTIONS", # Factory Methods "make_dig_montage", "make_eeg_layout", @@ -82,6 +85,11 @@ "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/datasets/__init__.py b/mne/datasets/__init__.py index b5892b7a1d8..f75378c3b31 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -36,8 +36,9 @@ from ._fetch import fetch_dataset from .utils import ( _download_all_example_data, - fetch_hcp_mmp_parcellation, + default_path, fetch_aparc_sub_parcellation, + fetch_hcp_mmp_parcellation, has_dataset, ) from ._fsaverage.base import fetch_fsaverage @@ -48,6 +49,7 @@ "_download_all_example_data", "_fake", "brainstorm", + "default_path", "eegbci", "fetch_aparc_sub_parcellation", "fetch_fsaverage", @@ -77,4 +79,5 @@ "refmeg_noise", "fnirs_motor", "eyelink", + "ucl_opm_auditory", ] From 6fed24ec95c011b30fac88995488d1f89474604c Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Thu, 27 Feb 2025 15:54:52 +0100 Subject: [PATCH 46/46] Use minimum_phase from fixes --- mne/filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/filter.py b/mne/filter.py index 3f6e778b179..07ccdbf3529 100644 --- a/mne/filter.py +++ b/mne/filter.py @@ -20,6 +20,7 @@ _setup_cuda_fft_resample, _smart_pad, ) +from .fixes import minimum_phase from .parallel import parallel_func from .utils import ( _check_option, @@ -480,7 +481,6 @@ def _construct_fir_filter( else: assert fir_design == "firwin" fir_design = partial(_firwin_design, sfreq=sfreq) - from scipy.signal import minimum_phase # issue a warning if attenuation is less than this min_att_db = 12 if phase == "minimum-half" else 20