Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Alex Rockhill <[email protected]> <[email protected]>
Alex Rockhill <[email protected]> <[email protected]>
Alexandre Gramfort <[email protected]>
Alexandre Gramfort <[email protected]> <[email protected]>
Bruno Aristimunha <[email protected]>
Ariel Rokem <[email protected]>
Chris Holdgraf <[email protected]>
Chris Holdgraf <[email protected]> <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions doc/authors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
.. _Arne Gottwald: https://github.com/waldie11
.. _Austin Hurst: https://github.com/a-hurst
.. _Berk Gerçek: https://github.com/berkgercek
.. _Bruno Aristimunha: https://github.com/bruAristimunha
.. _Bruno Hebling Vieira: https://github.com/bhvieira
.. _Chris Holdgraf: https://bids.berkeley.edu/people/chris-holdgraf
.. _Christian O'Reilly: https://github.com/christian-oreilly
Expand Down
9 changes: 5 additions & 4 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The following authors contributed for the first time. Thank you so much! 🤩

* `Julius Welzel`_
* `Alex Lopez Marquez`_

* `Bruno Aristimunha`_

The following authors had contributed before. Thank you for sticking around! 🤘

Expand All @@ -36,6 +36,7 @@ Detailed list of changes

- :func:`mne_bids.write_raw_bids()` has a new parameter `electrodes_tsv_task` which allows adding the `task` entity to the `electrodes.tsv` filepath, by `Alex Lopez Marquez`_ (:gh:`1424`)
- Extended the configuration to recognise `motion` as a valid BIDS datatype by `Julius Welzel`_ (:gh:`1430`)
- Better control of verbosity in several functions, by `Bruno Aristimunha`_ (:gh:`1449`)

🧐 API and behavior changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -51,13 +52,13 @@ Detailed list of changes
^^^^^^^^^^^^

- Fixed a bug that modified the name and help message of some of the available commands, by `Alex Lopez Marquez`_ (:gh:`1441`)
- Updated MEG/iEEG writers to satisfy the stricter checks in the latest BIDS validator releases: BTi/4D run folders now retain their ``.pdf`` suffix (falling back to the legacy naming when an older validator is detected), KIT marker files encode the run via the ``acq`` entity instead of ``run``, datasets lacking iEEG montages receive placeholder ``electrodes.tsv``/``coordsystem.json`` files, and the ``AssociatedEmptyRoom`` entry stores dataset-relative paths by `Bruno Aritimunha`_ (:gh:`1449`)
- Updated MEG/iEEG writers to satisfy the stricter checks in the latest BIDS validator releases: BTi/4D run folders now retain their ``.pdf`` suffix (falling back to the legacy naming when an older validator is detected), KIT marker files encode the run via the ``acq`` entity instead of ``run``, datasets lacking iEEG montages receive placeholder ``electrodes.tsv``/``coordsystem.json`` files, and the ``AssociatedEmptyRoom`` entry stores dataset-relative paths by `Bruno Aristimunha`_ (:gh:`1449`)

⚕️ Code health
^^^^^^^^^^^^^^

- Made :func:`mne_bids.copyfiles.copyfile_brainvision` output more meaningful error messages when encountering problematic files, by `Stefan Appelhoff`_ (:gh:`1444`)
- Raised the minimum ``edfio`` requirement to ``0.4.10``, eeglabio to ``0.1.0`` by `Bruno Aritimunha`_ (:gh:`1449`)
- Relaxed EDF padding warnings in the test suite to accommodate upstream changes by `Bruno Aritimunha`_ (:gh:`1449`)
- Raised the minimum ``edfio`` requirement to ``0.4.10``, eeglabio to ``0.1.0`` by `Bruno Aristimunha`_ (:gh:`1449`)
- Relaxed EDF padding warnings in the test suite to accommodate upstream changes by `Bruno Aristimunha`_ (:gh:`1449`)

:doc:`Find out what was new in previous releases <whats_new_previous_releases>`
9 changes: 5 additions & 4 deletions mne_bids/copyfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def copyfile_brainvision(vhdr_src, vhdr_dest, anonymize=None, verbose=None):
fout.write(line)

if anonymize is not None:
raw = read_raw_brainvision(vhdr_src, preload=False, verbose=0)
raw = read_raw_brainvision(vhdr_src, preload=False, verbose=verbose)
daysback, keep_his, _ = _check_anonymize(anonymize, raw, ".vhdr")
raw.info = anonymize_info(raw.info, daysback=daysback, keep_his=keep_his)
_anonymize_brainvision(fname_dest + ".vhdr", date=raw.info["meas_date"])
Expand All @@ -423,7 +423,8 @@ def copyfile_brainvision(vhdr_src, vhdr_dest, anonymize=None, verbose=None):
logger.info("Anonymized all dates in VHDR and VMRK.")


def copyfile_edf(src, dest, anonymize=None):
@verbose
def copyfile_edf(src, dest, anonymize=None, verbose=None):
"""Copy an EDF, EDF+, or BDF file to a new location, optionally anonymize.

.. warning:: EDF/EDF+/BDF files contain two fields for recording dates:
Expand Down Expand Up @@ -505,9 +506,9 @@ def copyfile_edf(src, dest, anonymize=None):
# Anonymize EDF/BDF data, if requested
if anonymize is not None:
if ext_src in [".bdf", ".BDF"]:
raw = read_raw_bdf(dest, preload=False, verbose=0)
raw = read_raw_bdf(dest, preload=False, verbose=verbose)
elif ext_src in [".edf", ".EDF"]:
raw = read_raw_edf(dest, preload=False, verbose=0)
raw = read_raw_edf(dest, preload=False, verbose=verbose)
else:
raise ValueError(f"Unsupported file type ({ext_src})")

Expand Down
1 change: 1 addition & 0 deletions mne_bids/dig.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def _write_electrodes_tsv(raw, fname, datatype, overwrite=False):
_write_tsv(fname, data, overwrite=True)


@verbose
def _write_optodes_tsv(raw, fname, overwrite=False, verbose=True):
"""Create a optodes.tsv file and save it.

Expand Down
6 changes: 5 additions & 1 deletion mne_bids/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ def inspect_dataset(
_global_vars = dict(raw_fig=None, dialog_fig=None, mne_close_key=None)


def _inspect_raw(*, bids_path, l_freq, h_freq, find_flat, show_annotations):
# do you think we should expose the verbose of this functions?
@verbose
def _inspect_raw(
*, bids_path, l_freq, h_freq, find_flat, show_annotations, verbose=None
):
"""Raw data inspection."""
# Delay the import
import matplotlib
Expand Down
16 changes: 13 additions & 3 deletions mne_bids/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@
from mne_bids.utils import _get_ch_type_mapping, _import_nibabel, verbose, warn


@verbose
def _read_raw(
raw_path,
electrode=None,
hsp=None,
hpi=None,
allow_maxshield=False,
config_path=None,
verbose=None,
**kwargs,
):
"""Read a raw file into MNE, making inferences based on extension."""
Expand All @@ -50,7 +52,13 @@ def _read_raw(
# KIT systems
if ext in [".con", ".sqd"]:
raw = io.read_raw_kit(
raw_path, elp=electrode, hsp=hsp, mrk=hpi, preload=False, **kwargs
raw_path,
elp=electrode,
hsp=hsp,
mrk=hpi,
preload=False,
verbose=verbose,
**kwargs,
)

# BTi systems
Expand All @@ -60,15 +68,16 @@ def _read_raw(
config_fname=config_path,
head_shape_fname=hsp,
preload=False,
verbose=verbose,
**kwargs,
)

elif ext == ".fif":
raw = reader[ext](raw_path, allow_maxshield, **kwargs)
raw = reader[ext](raw_path, allow_maxshield, verbose=verbose, **kwargs)

elif ext in [".ds", ".vhdr", ".set", ".edf", ".bdf", ".EDF", ".snirf", ".cdt"]:
raw_path = Path(raw_path)
raw = reader[ext](raw_path, **kwargs)
raw = reader[ext](raw_path, verbose=verbose, **kwargs)

# MEF and NWB are allowed, but not yet implemented
elif ext in [".mef", ".nwb"]:
Expand Down Expand Up @@ -1048,6 +1057,7 @@ def read_raw_bids(
hsp=None,
hpi=None,
config_path=config_path,
verbose=verbose,
**extra_params,
)

Expand Down
19 changes: 19 additions & 0 deletions mne_bids/tests/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import contextlib
import json
import logging
import os
import os.path as op
import re
Expand Down Expand Up @@ -1368,6 +1369,24 @@ def test_ignore_exclude_param(tmp_path):
assert ch_name in raw.ch_names


@testing.requires_testing_data
def test_read_raw_bids_respects_verbose(tmp_path, caplog):
"""Ensure ``verbose=False`` suppresses info-level logging."""
bids_path = _bids_path.copy().update(root=tmp_path, datatype="meg")
raw = _read_raw_fif(raw_fname, verbose=False)
write_raw_bids(raw, bids_path=bids_path, overwrite=True, verbose=False)

caplog.set_level("INFO", logger="mne")
read_raw_bids(bids_path=bids_path, verbose=False)

info_logs = [
record
for record in caplog.records
if record.levelno <= logging.INFO and record.name.startswith("mne")
]
assert not info_logs


@pytest.mark.filterwarnings(warning_str["channel_unit_changed"])
@testing.requires_testing_data
def test_channels_tsv_raw_mismatch(tmp_path):
Expand Down
56 changes: 56 additions & 0 deletions mne_bids/tests/test_verbose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Testing utilities for the MNE BIDS verbosity."""

# Authors: The MNE-BIDS developers
# SPDX-License-Identifier: BSD-3-Clause

import importlib
import inspect
import pkgutil

import mne_bids


def _iter_modules():
# include top-level package module
yield importlib.import_module("mne_bids")
for module_info in pkgutil.walk_packages(
mne_bids.__path__, mne_bids.__name__ + "."
):
yield importlib.import_module(module_info.name)


def _has_verbose_param(obj):
try:
signature = inspect.signature(obj)
except (TypeError, ValueError):
return False
return "verbose" in signature.parameters


def _is_verbose_decorated(obj):
source = getattr(obj, "__source__", "")
return "_use_log_level_" in source


def _iter_functions_and_methods(module):
for name, obj in inspect.getmembers(module, inspect.isfunction):
if obj.__module__ != module.__name__:
continue
yield f"{module.__name__}.{name}", obj
for name, cls in inspect.getmembers(module, inspect.isclass):
if cls.__module__ != module.__name__:
continue
for method_name, method in inspect.getmembers(cls, inspect.isfunction):
if method.__module__ != module.__name__:
continue
yield f"{module.__name__}.{cls.__name__}.{method_name}", method


def test_functions_with_verbose_are_decorated():
"""Test that all functions with a verbose parameter are decorated."""
missing = []
for module in _iter_modules():
for qualname, obj in _iter_functions_and_methods(module):
if _has_verbose_param(obj) and not _is_verbose_decorated(obj):
missing.append(qualname)
assert not missing, f"Missing @verbose decorator: {missing}"