Skip to content

Commit d7d08dd

Browse files
berkgercekpre-commit-ci[bot]sappelhoffdrammock
authored
[MRG] Update empty-room matching to look for and prioritize same-session recordings with task "noise" (closes #1354) (#1364)
* Changed empty-room matching logic to prioritize task-noise files * Simple test for noise task * Tests for the split noise file case and fallback to sub-emptyroom * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix line length and clean up after test * Work around the weird multiple-match behavior of path.match() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Better handling of "run" set in the base path for ER search * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove an extraneous print statement * Update mne_bids/tests/test_path.py Co-authored-by: Stefan Appelhoff <[email protected]> * Updated changelog, added self to author list per new contrib guide * Update CITATION.cff to remove name from original pub * Update doc/authors.rst to fix doc build Co-authored-by: Daniel McCloy <[email protected]> * Added test logic for new code * Line length * Cover an unrelated error to bring up % * correct author order --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Stefan Appelhoff <[email protected]> Co-authored-by: Daniel McCloy <[email protected]>
1 parent 18bd973 commit d7d08dd

File tree

5 files changed

+105
-8
lines changed

5 files changed

+105
-8
lines changed

CITATION.cff

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ authors:
202202
family-names: O'Reilly
203203
affiliation: 'Computer Science and Engineering, University of South Carolina'
204204
orcid: 'https://orcid.org/0000-0002-3149-4934'
205+
- given-names: Berk
206+
family-names: Gerçek
207+
affiliation: 'University of Geneva, Department of Fundamental Neuroscience'
208+
orcid: 'https://orcid.org/0000-0003-1063-6769'
205209
- given-names: Alexandre
206210
family-names: Gramfort
207211
affiliation: 'Université Paris-Saclay, Inria, CEA, Palaiseau, France'

doc/authors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
.. _Anand Saini: https://github.com/anandsaini024
77
.. _Ariel Rokem: https://github.com/arokem
88
.. _Austin Hurst: https://github.com/a-hurst
9+
.. _Berk Gerçek: https://github.com/berkgercek
910
.. _Bruno Hebling Vieira: https://github.com/bhvieira
1011
.. _Chris Holdgraf: https://bids.berkeley.edu/people/chris-holdgraf
1112
.. _Clemens Brunner: https://github.com/cbrnr

doc/whats_new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Version 0.17 (unreleased)
1818
The following authors contributed for the first time. Thank you so much! 🤩
1919

2020
* `Christian O'Reilly`_
21+
* `Berk Gerçek`_
2122

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

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

3435
- :func:`mne_bids.write_raw_bids()` can now handle mne `Raw` objects with `eyegaze` and `pupil` channels, by `Christian O'Reilly`_ (:gh:`1344`)
3536
- :func:`mne_bids.get_entity_vals()` has a new parameter ``ignore_suffixes`` to easily ignore sidecar files, by `Daniel McCloy`_ (:gh:`1362`)
37+
- 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`)
3638

3739

3840
🧐 API and behavior changes

mne_bids/path.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,62 @@ def _find_empty_room_candidates(bids_path):
5656
datatype = "meg" # We're only concerned about MEG data here
5757
bids_fname = bids_path.update(suffix=datatype).fpath
5858
_, ext = _parse_ext(bids_fname)
59+
# Create a path for the empty-room directory to be used for matching.
5960
emptyroom_dir = BIDSPath(root=bids_root, subject="emptyroom").directory
6061

61-
if not emptyroom_dir.exists():
62+
# Find matching "task-noise" files in the same directory as the recording.
63+
noisetask_path = bids_path.update(
64+
split=None, run=None, task="noise", datatype=datatype, suffix=datatype
65+
)
66+
67+
allowed_extensions = list(reader.keys())
68+
# `.pdf` is just a "virtual" extension for BTi data (which is stored inside
69+
# a dedicated directory that doesn't have an extension)
70+
del allowed_extensions[allowed_extensions.index(".pdf")]
71+
72+
# Get possible noise task files in the same directory as the recording.
73+
noisetask_tmp = [
74+
candidate
75+
for candidate in noisetask_path.match()
76+
if candidate.extension in allowed_extensions
77+
]
78+
# For some reason a single file can produce multiple hits in the match function.
79+
# Remove dups
80+
noisetask_fns = []
81+
for i in range(len(noisetask_tmp)):
82+
fn = noisetask_tmp.pop()
83+
if len(noisetask_tmp) == 0 or not any(
84+
fn.fpath == f.fpath for f in noisetask_fns
85+
):
86+
noisetask_fns.append(fn)
87+
88+
# If we have more than one noise task file, we need to disambiguate.
89+
# It might be that it's a
90+
# split recording.
91+
if len(noisetask_fns) > 1 and any(path.split is not None for path in noisetask_fns):
92+
noisetask_path.update(split="01")
93+
noisetask_fns = [
94+
candidate
95+
for candidate in noisetask_path.match()
96+
if candidate.extension in allowed_extensions
97+
]
98+
99+
# If it wasn't a split recording, warn the user that something is wonky,
100+
# then resort to looking for sub-emptyroom recordings and date-matching.
101+
if len(noisetask_fns) > 1:
102+
msg = (
103+
"Found more than one matching noise task file."
104+
" Falling back to looking for sub-emptyroom recordings and date-matching."
105+
)
106+
warn(msg)
107+
noisetask_fns = []
108+
elif len(noisetask_fns) == 1:
109+
return noisetask_fns[0]
110+
111+
if not emptyroom_dir.exists() and not noisetask_fns:
62112
return list()
63113

64-
# Find the empty-room recording sessions.
114+
# Check the 'emptyroom' subject for empty-room recording sessions.
65115
emptyroom_session_dirs = [
66116
x
67117
for x in emptyroom_dir.iterdir()
@@ -71,12 +121,6 @@ def _find_empty_room_candidates(bids_path):
71121
emptyroom_session_dirs = [emptyroom_dir]
72122

73123
# Now try to discover all recordings inside the session directories.
74-
75-
allowed_extensions = list(reader.keys())
76-
# `.pdf` is just a "virtual" extension for BTi data (which is stored inside
77-
# a dedicated directory that doesn't have an extension)
78-
del allowed_extensions[allowed_extensions.index(".pdf")]
79-
80124
candidate_er_fnames = []
81125
for session_dir in emptyroom_session_dirs:
82126
dir_contents = glob.glob(
@@ -105,6 +149,10 @@ def _find_matched_empty_room(bids_path):
105149
from mne_bids import read_raw_bids # avoid circular import.
106150

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

109157
# Walk through recordings, trying to extract the recording date:
110158
# First, from the filename; and if that fails, from `info['meas_date']`.

mne_bids/tests/test_path.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,9 +1146,13 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):
11461146
task="audiovisual",
11471147
run="01",
11481148
root=bids_root,
1149+
datatype="meg",
11491150
suffix="meg",
11501151
)
11511152
write_raw_bids(raw, bids_path, overwrite=True, verbose=False)
1153+
noroot = bids_path.copy().update(root=None)
1154+
with pytest.raises(ValueError, match="The root of the"):
1155+
noroot.find_empty_room()
11521156

11531157
# No empty-room data present.
11541158
er_basename = bids_path.find_empty_room()
@@ -1173,6 +1177,44 @@ def test_find_empty_room(return_bids_test_dir, tmp_path):
11731177
recovered_er_bids_path = bids_path.find_empty_room()
11741178
assert er_bids_path == recovered_er_bids_path
11751179

1180+
# Test that when there is a noise task file in the subject directory it will take
1181+
# precedence over the emptyroom directory file
1182+
er_noise_task_path = bids_path.copy().update(
1183+
run=None,
1184+
task="noise",
1185+
)
1186+
1187+
write_raw_bids(er_raw, er_noise_task_path, overwrite=True, verbose=False)
1188+
recovered_er_bids_path = bids_path.find_empty_room()
1189+
assert er_noise_task_path == recovered_er_bids_path
1190+
er_noise_task_path.fpath.unlink()
1191+
1192+
# When a split empty room file is present, the first split should be returned as
1193+
# the matching empty room file
1194+
split_er_bids_path = er_noise_task_path.copy().update(split="01", extension=".fif")
1195+
split_er_bids_path.fpath.touch()
1196+
split_er_bids_path2 = split_er_bids_path.copy().update(
1197+
split="02", extension=".fif"
1198+
) # not used
1199+
split_er_bids_path2.fpath.touch()
1200+
recovered_er_bids_path = bids_path.find_empty_room()
1201+
assert split_er_bids_path == recovered_er_bids_path
1202+
split_er_bids_path.fpath.unlink()
1203+
split_er_bids_path2.fpath.unlink()
1204+
write_raw_bids(er_raw, er_noise_task_path, overwrite=True, verbose=False)
1205+
1206+
# Check that when there are multiple matches that cannot be resolved via assigning
1207+
# split=01 that the sub-emptyroom is the fallback
1208+
dup_noise_task_path = er_noise_task_path.copy()
1209+
dup_noise_task_path.update(run="100", split=None)
1210+
write_raw_bids(er_raw, dup_noise_task_path, overwrite=True, verbose=False)
1211+
write_raw_bids(er_raw, er_bids_path, overwrite=True, verbose=False)
1212+
with pytest.warns(RuntimeWarning):
1213+
recovered_er_bids_path = bids_path.find_empty_room()
1214+
assert er_bids_path == recovered_er_bids_path
1215+
er_noise_task_path.fpath.unlink()
1216+
dup_noise_task_path.fpath.unlink()
1217+
11761218
# assert that we get best emptyroom if there are multiple available
11771219
sh.rmtree(op.join(bids_root, "sub-emptyroom"))
11781220
dates = ["20021204", "20021201", "20021001"]

0 commit comments

Comments
 (0)