Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Allow returning candidate BIDSPaths #1083

Merged
merged 5 commits into from
Oct 15, 2022
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 doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Detailed list of changes
^^^^^^^^^^^^^^^

- Speed up :func:`mne_bids.read_raw_bids` when lots of events are present by `Alexandre Gramfort`_ (:gh:`1079`)
- Add the option ``return_candidates`` to :meth:`mne_bids.BIDSPath.find_empty_room` by `Eric Larson`_ (:gh:`1083`)

🧐 API and behavior changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
29 changes: 22 additions & 7 deletions mne_bids/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _find_matched_empty_room(bids_path):
emptyroom_dir = BIDSPath(root=bids_root, subject='emptyroom').directory

if not emptyroom_dir.exists():
return None
return None, list()

# Find the empty-room recording sessions.
emptyroom_session_dirs = [x for x in emptyroom_dir.iterdir()
Expand Down Expand Up @@ -85,12 +85,14 @@ def _find_matched_empty_room(bids_path):
date_tie = False

failed_to_get_er_date_count = 0
candidates = list()
for er_fname in candidate_er_fnames:
# get entities from filenamme
er_bids_path = get_bids_path_from_fname(er_fname, check=False)
er_bids_path.subject = 'emptyroom' # er subject entity is different
er_bids_path.root = bids_root
er_bids_path.datatype = 'meg'
candidates.append(er_bids_path)
er_meas_date = None

# Try to extract date from filename.
Expand Down Expand Up @@ -137,7 +139,7 @@ def _find_matched_empty_room(bids_path):
'same recording date. Selecting the first match.')
warn(msg)

return best_er_bids_path
return best_er_bids_path, candidates


class BIDSPath(object):
Expand Down Expand Up @@ -893,7 +895,8 @@ def _check(self):
f'{ALLOWED_FILENAME_SUFFIX}.')

@verbose
def find_empty_room(self, use_sidecar_only=False, verbose=None):
def find_empty_room(self, use_sidecar_only=False, *,
return_candidates=False, verbose=None):
"""Find the corresponding empty-room file of an MEG recording.

This will only work if the ``.root`` attribute of the
Expand All @@ -906,13 +909,21 @@ def find_empty_room(self, use_sidecar_only=False, verbose=None):
sidecar JSON file or not. If ``False``, first look for the entry,
and if unsuccessful, try to find the best-matching empty-room
recording in the dataset based on the measurement date.
return_candidates : bool
If True (default False), return candidate filenames checked during
the search for the best-matching empty-room recording.
%(verbose)s

Returns
-------
BIDSPath | None
The path corresponding to the best-matching empty-room measurement.
Returns ``None`` if none was found.
%(verbose)s
list | None
The candidates checked during the search for the best-matching
empty-room recording. Only returned if ``return_candidates`` is
``True``. Will be None if a sidecar is used to find the empty-room
recording.
"""
if self.datatype not in ('meg', None):
raise ValueError('Empty-room data is only supported for MEG '
Expand All @@ -938,28 +949,32 @@ def find_empty_room(self, use_sidecar_only=False, verbose=None):
er_bids_path = get_bids_path_from_fname(emptytoom_path)
er_bids_path.root = self.root
er_bids_path.datatype = 'meg'
candidates = None
elif use_sidecar_only:
logger.info(
'The MEG sidecar file does not contain an '
'"AssociatedEmptyRoom" entry. Aborting search for an '
'empty-room recording, as you passed use_sidecar_only=True'
)
return None
return None if not return_candidates else (None, None)
else:
logger.info(
'The MEG sidecar file does not contain an '
'"AssociatedEmptyRoom" entry. Will try to find a matching '
'empty-room recording based on the measurement date …'
)
er_bids_path = _find_matched_empty_room(self)
er_bids_path, candidates = _find_matched_empty_room(self)

if er_bids_path is not None and not er_bids_path.fpath.exists():
raise FileNotFoundError(
f'Empty-room BIDS path resolved but not found:\n'
f'{er_bids_path}\n'
'Check your BIDS dataset for completeness.')

return er_bids_path
out = er_bids_path
if return_candidates:
out = (out, candidates)
return out

@property
def meg_calibration_fpath(self):
Expand Down
14 changes: 10 additions & 4 deletions mne_bids/tests/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,8 +960,11 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):

# Retrieve empty-room BIDSPath
assert bids_path.find_empty_room() == er_associated_bids_path
assert bids_path.find_empty_room(
use_sidecar_only=True) == er_associated_bids_path
for use_sidecar_only in [True, False]: # same result either way
path, candidates = bids_path.find_empty_room(
use_sidecar_only=use_sidecar_only, return_candidates=True)
assert path == er_associated_bids_path
assert candidates is None

# Should only work for MEG
with pytest.raises(ValueError, match='only supported for MEG'):
Expand All @@ -975,11 +978,14 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):
# Don't create `AssociatedEmptyRoom` entry in sidecar – we should now
# retrieve the empty-room recording closer in time
write_raw_bids(raw, bids_path=bids_path, empty_room=None, overwrite=True)
assert bids_path.find_empty_room() == er_matching_date_bids_path
path, candidates = bids_path.find_empty_room(return_candidates=True)
assert path == er_matching_date_bids_path
assert er_matching_date_bids_path in candidates

# If we enforce searching only via `AssociatedEmptyRoom`, we should get no
# result
assert bids_path.find_empty_room(use_sidecar_only=True) is None
assert bids_path.find_empty_room(
use_sidecar_only=True, return_candidates=True) == (None, None)


@pytest.mark.filterwarnings(warning_str['channel_unit_changed'])
Expand Down
2 changes: 1 addition & 1 deletion test_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ matplotlib>=3.1
pandas>=0.24.0
nibabel>=2.5
pybv>=0.7.3
git+https://github.com/larsoner/openneuro-py@iterate#egg=openneuro-py
openneuro-py>=2022.2
EDFlib-Python>=1.0.6
pymatreader>=0.0.29
pytest
Expand Down