From 24dff77255706e5498d8cc80ebe35b757725c4e9 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 3 May 2023 14:11:24 -0400 Subject: [PATCH 1/2] MAINT: Improve README (#11673) Co-authored-by: Daniel McCloy --- README.rst | 21 ++++++++++----------- setup.py | 7 +++++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index ed37a718154..247b2d8cf11 100644 --- a/README.rst +++ b/README.rst @@ -39,6 +39,14 @@ Documentation `MNE documentation`_ for MNE-Python is available online. +Forum +^^^^^^ + +Our user forum is https://mne.discourse.group and is the best place to ask +questions about MNE-Python usage or about the contribution process. It also +includes job opportunities and other announcements. + + Installing MNE-Python ^^^^^^^^^^^^^^^^^^^^^ @@ -46,7 +54,7 @@ To install the latest stable version of MNE-Python, you can use pip_ in a termin .. code-block:: console - $ pip install -U mne + $ pip install --upgrade mne - MNE-Python 0.17 was the last release to support Python 2.7 - MNE-Python 0.18 requires Python 3.5 or higher @@ -65,7 +73,7 @@ To install the latest version of the code using pip_ open a terminal and type: .. code-block:: console - $ pip install -U https://github.com/mne-tools/mne-python/archive/main.zip + $ pip install --upgrade git+https://github.com/mne-tools/mne-python@main To get the latest code using `git `__, open a terminal and type: @@ -73,9 +81,6 @@ To get the latest code using `git `__, open a terminal and $ git clone https://github.com/mne-tools/mne-python.git -Alternatively, you can also download a -`zip file of the latest development version `__. - Dependencies ^^^^^^^^^^^^ @@ -126,12 +131,6 @@ Please see the documentation on the MNE-Python homepage: https://mne.tools/dev/install/contributing.html -Forum -^^^^^^ - -https://mne.discourse.group - - Licensing ^^^^^^^^^ diff --git a/setup.py b/setup.py index 7b1d3ad5f21..7649cdc0f37 100644 --- a/setup.py +++ b/setup.py @@ -85,9 +85,12 @@ def package_tree(pkgroot): ], keywords="neuroscience neuroimaging MEG EEG ECoG fNIRS brain", project_urls={ + "Homepage": "https://mne.tools/", + "Download": "https://pypi.org/project/scikit-learn/#files", + "Bug Tracker": "https://github.com/mne-tools/mne-python/issues/", "Documentation": "https://mne.tools/", - "Source": "https://github.com/mne-tools/mne-python/", - "Tracker": "https://github.com/mne-tools/mne-python/issues/", + "Forum": "https://mne.discourse.group/", + "Source Code": "https://github.com/mne-tools/mne-python/", }, platforms="any", python_requires=">=3.8", From da3823f906f871bda7b133778a99c800496e7963 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Wed, 3 May 2023 21:19:31 +0200 Subject: [PATCH 2/2] ENH: Change known_config_types to dict (#11166) Co-authored-by: Daniel McCloy Co-authored-by: Eric Larson --- doc/changes/latest.inc | 1 + doc/logging.rst | 1 + mne/parallel.py | 2 +- mne/utils/config.py | 224 ++++++++++++++++++++------------- mne/utils/tests/test_config.py | 10 +- 5 files changed, 151 insertions(+), 87 deletions(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index a0383e025f1..93028ee5668 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -34,6 +34,7 @@ Enhancements - Added ability to read stimulus durations from SNIRF files when using :func:`mne.io.read_raw_snirf` (:gh:`11397` by `Robert Luke`_) - Add :meth:`mne.Info.save` to save an :class:`mne.Info` object to a fif file (:gh:`11401` by `Alex Rockhill`_) - Improved error message when downloads are corrupted for :func:`mne.datasets.sample.data_path` and related functions (:gh:`11407` by `Eric Larson`_) +- Improved :func:`mne.get_config("") ` behavior to return a dict of key-value pairs (config key and its description) rather than just a tuple of config keys (:gh:`11166` by `Clemens Brunner`_, `Daniel McCloy`_, and `Eric Larson`_) - Add support for ``skip_by_annotation`` in :func:`mne.io.Raw.notch_filter` (:gh:`11388` by `Mainak Jas`_) - Add support for ``output='complex'`` to :func:`mne.time_frequency.psd_array_welch` and when using ``method='welch'`` with :meth:`mne.Epochs.compute_psd` (:gh:`11556` by `Eric Larson`_) - Slightly adjusted the window title for :func:`mne.Epochs.plot` (:gh:`11419` by `Richard Höchenberger`_ and `Daniel McCloy`_) diff --git a/doc/logging.rst b/doc/logging.rst index 1168b3e086f..453f9544994 100644 --- a/doc/logging.rst +++ b/doc/logging.rst @@ -14,6 +14,7 @@ Logging and Configuration set_log_file set_config set_cache_dir + set_memmap_min_size sys_info use_log_level verbose diff --git a/mne/parallel.py b/mne/parallel.py index ec1468a53ef..5e77fcae724 100644 --- a/mne/parallel.py +++ b/mne/parallel.py @@ -38,7 +38,7 @@ def parallel_func( triggers automated memory mapping. Can be an int in Bytes, or a human-readable string, e.g., '1M' for 1 megabyte. Use None to disable memmaping of large arrays. Use 'auto' to - use the value set using mne.set_memmap_min_size. + use the value set using :func:`mne.set_memmap_min_size`. pre_dispatch : int | str See :class:`joblib.Parallel`. total : int | None diff --git a/mne/utils/config.py b/mne/utils/config.py index 409c17ce9e5..1d3c6d9baf3 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -56,98 +56,152 @@ def set_memmap_min_size(memmap_min_size): mapping for parallel processing, e.g., '1M' for 1 megabyte. Use None to disable memmaping of large arrays. """ + _validate_type(memmap_min_size, (str, None), "memmap_min_size") if memmap_min_size is not None: - if not isinstance(memmap_min_size, str): - raise ValueError("'memmap_min_size' has to be a string.") if memmap_min_size[-1] not in ["K", "M", "G"]: raise ValueError( "The size has to be given in kilo-, mega-, or " - "gigabytes, e.g., 100K, 500M, 1G." + f"gigabytes, e.g., 100K, 500M, 1G, got {repr(memmap_min_size)}" ) set_config("MNE_MEMMAP_MIN_SIZE", memmap_min_size, set_env=False) # List the known configuration values -known_config_types = ( - "MNE_3D_OPTION_ANTIALIAS", - "MNE_3D_OPTION_DEPTH_PEELING", - "MNE_3D_OPTION_MULTI_SAMPLES", - "MNE_3D_OPTION_SMOOTH_SHADING", - "MNE_3D_OPTION_THEME", - "MNE_BROWSE_RAW_SIZE", - "MNE_BROWSER_BACKEND", - "MNE_BROWSER_OVERVIEW_MODE", - "MNE_BROWSER_PRECOMPUTE", - "MNE_BROWSER_THEME", - "MNE_BROWSER_USE_OPENGL", - "MNE_CACHE_DIR", - "MNE_COREG_ADVANCED_RENDERING", - "MNE_COREG_COPY_ANNOT", - "MNE_COREG_FULLSCREEN", - "MNE_COREG_GUESS_MRI_SUBJECT", - "MNE_COREG_HEAD_HIGH_RES", - "MNE_COREG_HEAD_OPACITY", - "MNE_COREG_HEAD_INSIDE", - "MNE_COREG_INTERACTION", - "MNE_COREG_MARK_INSIDE", - "MNE_COREG_PREPARE_BEM", - "MNE_COREG_ORIENT_TO_SURFACE", - "MNE_COREG_SCALE_LABELS", - "MNE_COREG_SCALE_BY_DISTANCE", - "MNE_COREG_SCENE_SCALE", - "MNE_COREG_WINDOW_HEIGHT", - "MNE_COREG_WINDOW_WIDTH", - "MNE_COREG_SUBJECTS_DIR", - "MNE_CUDA_DEVICE", - "MNE_CUDA_IGNORE_PRECISION", - "MNE_DATA", - "MNE_DATASETS_BRAINSTORM_PATH", - "MNE_DATASETS_EEGBCI_PATH", - "MNE_DATASETS_EPILEPSY_ECOG_PATH", - "MNE_DATASETS_EYELINK_PATH", - "MNE_DATASETS_HF_SEF_PATH", - "MNE_DATASETS_MEGSIM_PATH", - "MNE_DATASETS_MISC_PATH", - "MNE_DATASETS_MTRF_PATH", - "MNE_DATASETS_SAMPLE_PATH", - "MNE_DATASETS_SOMATO_PATH", - "MNE_DATASETS_MULTIMODAL_PATH", - "MNE_DATASETS_FNIRS_MOTOR_PATH", - "MNE_DATASETS_OPM_PATH", - "MNE_DATASETS_SPM_FACE_DATASETS_TESTS", - "MNE_DATASETS_SPM_FACE_PATH", - "MNE_DATASETS_TESTING_PATH", - "MNE_DATASETS_VISUAL_92_CATEGORIES_PATH", - "MNE_DATASETS_KILOWORD_PATH", - "MNE_DATASETS_FIELDTRIP_CMC_PATH", - "MNE_DATASETS_PHANTOM_4DBTI_PATH", - "MNE_DATASETS_LIMO_PATH", - "MNE_DATASETS_REFMEG_NOISE_PATH", - "MNE_DATASETS_SSVEP_PATH", - "MNE_DATASETS_ERP_CORE_PATH", - "MNE_DATASETS_EPILEPSY_ECOG_PATH", - "MNE_DATASETS_UCL_OPM_AUDITORY_PATH", - "MNE_FORCE_SERIAL", - "MNE_KIT2FIFF_STIM_CHANNELS", - "MNE_KIT2FIFF_STIM_CHANNEL_CODING", - "MNE_KIT2FIFF_STIM_CHANNEL_SLOPE", - "MNE_KIT2FIFF_STIM_CHANNEL_THRESHOLD", - "MNE_LOGGING_LEVEL", - "MNE_MEMMAP_MIN_SIZE", - "MNE_REPR_HTML", - "MNE_SKIP_FTP_TESTS", - "MNE_SKIP_NETWORK_TESTS", - "MNE_SKIP_TESTING_DATASET_TESTS", - "MNE_STIM_CHANNEL", - "MNE_TQDM", - "MNE_USE_CUDA", - "MNE_USE_NUMBA", - "SUBJECTS_DIR", -) +_known_config_types = { + "MNE_3D_OPTION_ANTIALIAS": ( + "bool, whether to use full-screen antialiasing in 3D plots" + ), + "MNE_3D_OPTION_DEPTH_PEELING": "bool, whether to use depth peeling in 3D plots", + "MNE_3D_OPTION_MULTI_SAMPLES": ( + "int, number of samples to use for full-screen antialiasing" + ), + "MNE_3D_OPTION_SMOOTH_SHADING": ("bool, whether to use smooth shading in 3D plots"), + "MNE_3D_OPTION_THEME": ("str, the color theme (light or dark) to use for 3D plots"), + "MNE_BROWSE_RAW_SIZE": ( + "tuple, width and height of the raw browser window (in inches)" + ), + "MNE_BROWSER_BACKEND": ( + "str, the backend to use for the MNE Browse Raw window (qt or matplotlib)" + ), + "MNE_BROWSER_OVERVIEW_MODE": ( + "str, the overview mode to use in the MNE Browse Raw window )" + "(see mne.viz.plot_raw for valid options)" + ), + "MNE_BROWSER_PRECOMPUTE": ( + "bool, whether to precompute raw data in the MNE Browse Raw window" + ), + "MNE_BROWSER_THEME": "str, the color theme (light or dark) to use for the browser", + "MNE_BROWSER_USE_OPENGL": ( + "bool, whether to use OpenGL for rendering in the MNE Browse Raw window" + ), + "MNE_CACHE_DIR": "str, path to the cache directory for parallel execution", + "MNE_COREG_ADVANCED_RENDERING": ( + "bool, whether to use advanced OpenGL rendering in mne coreg" + ), + "MNE_COREG_COPY_ANNOT": ( + "bool, whether to copy the annotation files during warping" + ), + "MNE_COREG_FULLSCREEN": "bool, whether to use full-screen mode in mne coreg", + "MNE_COREG_GUESS_MRI_SUBJECT": ( + "bool, whether to guess the MRI subject in mne coreg" + ), + "MNE_COREG_HEAD_HIGH_RES": ( + "bool, whether to use high-res head surface in mne coreg" + ), + "MNE_COREG_HEAD_OPACITY": ("bool, the head surface opacity to use in mne coreg"), + "MNE_COREG_HEAD_INSIDE": ( + "bool, whether to add an opaque inner scalp head surface to help " + "occlude points behind the head in mne coreg" + ), + "MNE_COREG_INTERACTION": ( + "str, interaction style in mne coreg (trackball or terrain)" + ), + "MNE_COREG_MARK_INSIDE": ( + "bool, whether to mark points inside the head surface in mne coreg" + ), + "MNE_COREG_PREPARE_BEM": ( + "bool, whether to prepare the BEM solution after warping in mne coreg" + ), + "MNE_COREG_ORIENT_TO_SURFACE": ( + "bool, whether to orient the digitization markers to the head surface " + "in mne coreg" + ), + "MNE_COREG_SCALE_LABELS": ( + "bool, whether to scale the MRI labels during warping in mne coreg" + ), + "MNE_COREG_SCALE_BY_DISTANCE": ( + "bool, whether to scale the digitization markers by their distance from " + "the scalp in mne coreg" + ), + "MNE_COREG_SCENE_SCALE": ( + "float, the scale factor of the 3D scene in mne coreg (default 0.16)" + ), + "MNE_COREG_WINDOW_HEIGHT": "int, window height for mne coreg", + "MNE_COREG_WINDOW_WIDTH": "int, window width for mne coreg", + "MNE_COREG_SUBJECTS_DIR": "str, path to the subjects directory for mne coreg", + "MNE_CUDA_DEVICE": "int, CUDA device to use for GPU processing", + "MNE_DATA": "str, default data directory", + "MNE_DATASETS_BRAINSTORM_PATH": "str, path for brainstorm data", + "MNE_DATASETS_EEGBCI_PATH": "str, path for EEGBCI data", + "MNE_DATASETS_EPILEPSY_ECOG_PATH": "str, path for epilepsy_ecog data", + "MNE_DATASETS_HF_SEF_PATH": "str, path for HF_SEF data", + "MNE_DATASETS_MEGSIM_PATH": "str, path for MEGSIM data", + "MNE_DATASETS_MISC_PATH": "str, path for misc data", + "MNE_DATASETS_MTRF_PATH": "str, path for MTRF data", + "MNE_DATASETS_SAMPLE_PATH": "str, path for sample data", + "MNE_DATASETS_SOMATO_PATH": "str, path for somato data", + "MNE_DATASETS_MULTIMODAL_PATH": "str, path for multimodal data", + "MNE_DATASETS_FNIRS_MOTOR_PATH": "str, path for fnirs_motor data", + "MNE_DATASETS_OPM_PATH": "str, path for OPM data", + "MNE_DATASETS_SPM_FACE_DATASETS_TESTS": "str, path for spm_face data", + "MNE_DATASETS_SPM_FACE_PATH": "str, path for spm_face data", + "MNE_DATASETS_TESTING_PATH": "str, path for testing data", + "MNE_DATASETS_VISUAL_92_CATEGORIES_PATH": "str, path for visual_92_categories data", + "MNE_DATASETS_KILOWORD_PATH": "str, path for kiloword data", + "MNE_DATASETS_FIELDTRIP_CMC_PATH": "str, path for fieldtrip_cmc data", + "MNE_DATASETS_PHANTOM_4DBTI_PATH": "str, path for phantom_4dbti data", + "MNE_DATASETS_LIMO_PATH": "str, path for limo data", + "MNE_DATASETS_REFMEG_NOISE_PATH": "str, path for refmeg_noise data", + "MNE_DATASETS_SSVEP_PATH": "str, path for ssvep data", + "MNE_DATASETS_ERP_CORE_PATH": "str, path for erp_core data", + "MNE_FORCE_SERIAL": "bool, force serial rather than parallel execution", + "MNE_LOGGING_LEVEL": ( + "str or int, controls the level of verbosity of any function " + "decorated with @verbose. See " + "https://mne.tools/stable/auto_tutorials/intro/50_configure_mne.html#logging" + ), + "MNE_MEMMAP_MIN_SIZE": ( + "str, threshold on the minimum size of arrays passed to the workers that " + "triggers automated memory mapping, e.g., 1M or 0.5G" + ), + "MNE_REPR_HTML": ( + "bool, represent some of our objects with rich HTML in a notebook " + "environment" + ), + "MNE_SKIP_NETWORK_TESTS": ( + "bool, used in a test decorator (@requires_good_network) to skip " + "tests that include large downloads" + ), + "MNE_SKIP_TESTING_DATASET_TESTS": ( + "bool, used in test decorators (@requires_spm_data, " + "@requires_bstraw_data) to skip tests that require specific datasets" + ), + "MNE_STIM_CHANNEL": "string, the default channel name for mne.find_events", + "MNE_TQDM": ( + 'str, either "tqdm", "tqdm.auto", or "off". Controls presence/absence ' + "of progress bars" + ), + "MNE_USE_CUDA": "bool, use GPU for filtering/resampling", + "MNE_USE_NUMBA": ( + "bool, use Numba just-in-time compiler for some of our intensive " + "computations" + ), + "SUBJECTS_DIR": "path-like, directory of freesurfer MRI files for each subject", +} # These allow for partial matches, e.g. 'MNE_STIM_CHANNEL_1' is okay key -known_config_wildcards = ( +_known_config_wildcards = ( "MNE_STIM_CHANNEL", "MNE_DATASETS_FNIRS", "MNE_NIRS", @@ -229,7 +283,9 @@ def get_config(key=None, default=None, raise_error=False, home_dir=None, use_env _validate_type(key, (str, type(None)), "key", "string or None") if key == "": - return known_config_types + # These are str->str (immutable) so we should just copy the dict + # itself, no need for deepcopy + return _known_config_types.copy() # first, check to see if key is in env if use_env and key is not None and key in os.environ: @@ -245,7 +301,7 @@ def get_config(key=None, default=None, raise_error=False, home_dir=None, use_env if key is None: # update config with environment variables if use_env: - env_keys = set(config).union(known_config_types).intersection(os.environ) + env_keys = set(config).union(_known_config_types).intersection(os.environ) config.update({key: os.environ[key] for key in env_keys}) return config elif raise_error is True and key not in config: @@ -301,8 +357,8 @@ def set_config(key, value, home_dir=None, set_env=True): if value is not None: value = str(value) - if key not in known_config_types and not any( - key.startswith(k) for k in known_config_wildcards + if key not in _known_config_types and not any( + key.startswith(k) for k in _known_config_wildcards ): warn('Setting non-standard config type: "%s"' % key) diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index a7d6c57eefb..9763b53cbd9 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -29,7 +29,12 @@ def test_config(tmp_path): assert get_config(key) == value del os.environ[key] # catch the warning about it being a non-standard config key - assert len(get_config("")) > 10 # tuple of valid keys + known_config_keys = get_config("") + assert len(known_config_keys) > 10 # dict of valid keys + for k, val in known_config_keys.items(): + assert isinstance(k, str) + assert isinstance(val, str), k + assert len(val) > 0, k with pytest.warns(RuntimeWarning, match="non-standard"): set_config(key, None, home_dir=tempdir, set_env=False) assert get_config(key, home_dir=tempdir) is None @@ -77,7 +82,8 @@ def test_config(tmp_path): pytest.raises(RuntimeError, set_config, key, "true", home_dir=tempdir) # degenerate conditions - pytest.raises(ValueError, set_memmap_min_size, 1) + with pytest.raises(TypeError, match="must be an instance"): + set_memmap_min_size(1) pytest.raises(ValueError, set_memmap_min_size, "foo") pytest.raises(TypeError, get_config, 1) pytest.raises(TypeError, set_config, 1)