Skip to content

Commit

Permalink
Merge branch 'main' into pre-commit-ci-update-config
Browse files Browse the repository at this point in the history
  • Loading branch information
sappelhoff authored Jan 20, 2025
2 parents ad822d0 + d7d08dd commit b11653e
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ authors:
family-names: O'Reilly
affiliation: 'Computer Science and Engineering, University of South Carolina'
orcid: 'https://orcid.org/0000-0002-3149-4934'
- given-names: Berk
family-names: Gerçek
affiliation: 'University of Geneva, Department of Fundamental Neuroscience'
orcid: 'https://orcid.org/0000-0003-1063-6769'
- given-names: Alexandre
family-names: Gramfort
affiliation: 'Université Paris-Saclay, Inria, CEA, Palaiseau, France'
Expand Down
1 change: 1 addition & 0 deletions doc/authors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
.. _Anand Saini: https://github.com/anandsaini024
.. _Ariel Rokem: https://github.com/arokem
.. _Austin Hurst: https://github.com/a-hurst
.. _Berk Gerçek: https://github.com/berkgercek
.. _Bruno Hebling Vieira: https://github.com/bhvieira
.. _Chris Holdgraf: https://bids.berkeley.edu/people/chris-holdgraf
.. _Clemens Brunner: https://github.com/cbrnr
Expand Down
2 changes: 2 additions & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Version 0.17 (unreleased)
The following authors contributed for the first time. Thank you so much! 🤩

* `Christian O'Reilly`_
* `Berk Gerçek`_

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

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

- :func:`mne_bids.write_raw_bids()` can now handle mne `Raw` objects with `eyegaze` and `pupil` channels, by `Christian O'Reilly`_ (:gh:`1344`)
- :func:`mne_bids.get_entity_vals()` has a new parameter ``ignore_suffixes`` to easily ignore sidecar files, by `Daniel McCloy`_ (:gh:`1362`)
- Empty-room matching now preferentially finds recordings in the subject directory tagged as `task-noise` before looking in the `sub-emptyroom` directories. This adds support for a part of the BIDS specification for ER recordings, by `Berk Gerçek`_ (:gh:`1364`)


🧐 API and behavior changes
Expand Down
64 changes: 56 additions & 8 deletions mne_bids/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,62 @@ def _find_empty_room_candidates(bids_path):
datatype = "meg" # We're only concerned about MEG data here
bids_fname = bids_path.update(suffix=datatype).fpath
_, ext = _parse_ext(bids_fname)
# Create a path for the empty-room directory to be used for matching.
emptyroom_dir = BIDSPath(root=bids_root, subject="emptyroom").directory

if not emptyroom_dir.exists():
# Find matching "task-noise" files in the same directory as the recording.
noisetask_path = bids_path.update(
split=None, run=None, task="noise", datatype=datatype, suffix=datatype
)

allowed_extensions = list(reader.keys())
# `.pdf` is just a "virtual" extension for BTi data (which is stored inside
# a dedicated directory that doesn't have an extension)
del allowed_extensions[allowed_extensions.index(".pdf")]

# Get possible noise task files in the same directory as the recording.
noisetask_tmp = [
candidate
for candidate in noisetask_path.match()
if candidate.extension in allowed_extensions
]
# For some reason a single file can produce multiple hits in the match function.
# Remove dups
noisetask_fns = []
for i in range(len(noisetask_tmp)):
fn = noisetask_tmp.pop()
if len(noisetask_tmp) == 0 or not any(
fn.fpath == f.fpath for f in noisetask_fns
):
noisetask_fns.append(fn)

# If we have more than one noise task file, we need to disambiguate.
# It might be that it's a
# split recording.
if len(noisetask_fns) > 1 and any(path.split is not None for path in noisetask_fns):
noisetask_path.update(split="01")
noisetask_fns = [
candidate
for candidate in noisetask_path.match()
if candidate.extension in allowed_extensions
]

# If it wasn't a split recording, warn the user that something is wonky,
# then resort to looking for sub-emptyroom recordings and date-matching.
if len(noisetask_fns) > 1:
msg = (
"Found more than one matching noise task file."
" Falling back to looking for sub-emptyroom recordings and date-matching."
)
warn(msg)
noisetask_fns = []
elif len(noisetask_fns) == 1:
return noisetask_fns[0]

if not emptyroom_dir.exists() and not noisetask_fns:
return list()

# Find the empty-room recording sessions.
# Check the 'emptyroom' subject for empty-room recording sessions.
emptyroom_session_dirs = [
x
for x in emptyroom_dir.iterdir()
Expand All @@ -71,12 +121,6 @@ def _find_empty_room_candidates(bids_path):
emptyroom_session_dirs = [emptyroom_dir]

# Now try to discover all recordings inside the session directories.

allowed_extensions = list(reader.keys())
# `.pdf` is just a "virtual" extension for BTi data (which is stored inside
# a dedicated directory that doesn't have an extension)
del allowed_extensions[allowed_extensions.index(".pdf")]

candidate_er_fnames = []
for session_dir in emptyroom_session_dirs:
dir_contents = glob.glob(
Expand Down Expand Up @@ -105,6 +149,10 @@ def _find_matched_empty_room(bids_path):
from mne_bids import read_raw_bids # avoid circular import.

candidates = _find_empty_room_candidates(bids_path)
# If a single candidate is returned, then there's a same-session noise
# task recording that takes priority.
if not isinstance(candidates, list):
return candidates

# Walk through recordings, trying to extract the recording date:
# First, from the filename; and if that fails, from `info['meas_date']`.
Expand Down
42 changes: 42 additions & 0 deletions mne_bids/tests/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -1137,9 +1137,13 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):
task="audiovisual",
run="01",
root=bids_root,
datatype="meg",
suffix="meg",
)
write_raw_bids(raw, bids_path, overwrite=True, verbose=False)
noroot = bids_path.copy().update(root=None)
with pytest.raises(ValueError, match="The root of the"):
noroot.find_empty_room()

# No empty-room data present.
er_basename = bids_path.find_empty_room()
Expand All @@ -1164,6 +1168,44 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):
recovered_er_bids_path = bids_path.find_empty_room()
assert er_bids_path == recovered_er_bids_path

# Test that when there is a noise task file in the subject directory it will take
# precedence over the emptyroom directory file
er_noise_task_path = bids_path.copy().update(
run=None,
task="noise",
)

write_raw_bids(er_raw, er_noise_task_path, overwrite=True, verbose=False)
recovered_er_bids_path = bids_path.find_empty_room()
assert er_noise_task_path == recovered_er_bids_path
er_noise_task_path.fpath.unlink()

# When a split empty room file is present, the first split should be returned as
# the matching empty room file
split_er_bids_path = er_noise_task_path.copy().update(split="01", extension=".fif")
split_er_bids_path.fpath.touch()
split_er_bids_path2 = split_er_bids_path.copy().update(
split="02", extension=".fif"
) # not used
split_er_bids_path2.fpath.touch()
recovered_er_bids_path = bids_path.find_empty_room()
assert split_er_bids_path == recovered_er_bids_path
split_er_bids_path.fpath.unlink()
split_er_bids_path2.fpath.unlink()
write_raw_bids(er_raw, er_noise_task_path, overwrite=True, verbose=False)

# Check that when there are multiple matches that cannot be resolved via assigning
# split=01 that the sub-emptyroom is the fallback
dup_noise_task_path = er_noise_task_path.copy()
dup_noise_task_path.update(run="100", split=None)
write_raw_bids(er_raw, dup_noise_task_path, overwrite=True, verbose=False)
write_raw_bids(er_raw, er_bids_path, overwrite=True, verbose=False)
with pytest.warns(RuntimeWarning):
recovered_er_bids_path = bids_path.find_empty_room()
assert er_bids_path == recovered_er_bids_path
er_noise_task_path.fpath.unlink()
dup_noise_task_path.fpath.unlink()

# assert that we get best emptyroom if there are multiple available
sh.rmtree(op.join(bids_root, "sub-emptyroom"))
dates = ["20021204", "20021201", "20021001"]
Expand Down

0 comments on commit b11653e

Please sign in to comment.