Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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 doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:`1444`)

🧐 API and behavior changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
9 changes: 5 additions & 4 deletions mne_bids/copyfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,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 @@ -410,7 +410,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 @@ -492,9 +493,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 @@ -1042,6 +1051,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}"
5 changes: 5 additions & 0 deletions mne_bids/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -2828,6 +2828,8 @@ def write_meg_crosstalk(fname, bids_path, verbose=None):
shutil.copyfile(src=fname, dst=str(out_path))


# Do you think we should expose the verbose parameter here?
# question for hoechenberger
def _get_daysback(
*, bids_paths: list[BIDSPath], rng: np.random.Generator, show_progress_thresh: int
) -> int:
Expand Down Expand Up @@ -2917,6 +2919,9 @@ def _check_finecal_path(bids_path: BIDSPath) -> bool:
return is_finecal_path


# This function is tricky because there is many verbose styles to consider:
# I think global verbose maybe not good here.
# what do you think hoechenberger?
@verbose
def anonymize_dataset(
bids_root_in,
Expand Down
Loading