Skip to content

Commit 46da987

Browse files
teonbrookssappelhoffagramfort
authored
[WIP] Improve check of scans.tsv for BrainVision files (#1034)
* Update read.py * cleanup * added test and fix the logic in the file handling * cleanup * Update doc/whats_new.rst Co-authored-by: Stefan Appelhoff <[email protected]> * fix the filepath for windows * Update read.py * fix? * restart ci * fix * fix Co-authored-by: Stefan Appelhoff <[email protected]> Co-authored-by: Alexandre Gramfort <[email protected]>
1 parent 16a7b0c commit 46da987

File tree

5 files changed

+61
-7
lines changed

5 files changed

+61
-7
lines changed

doc/whats_new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Detailed list of changes
9292

9393
- Instead of deleting files and raising cryptic errors, an intentional error message is now sent when calling :func:`~mne_bids.write_raw_bids` with the source file identical to the destination file, unless ``format`` is specified, by `Adam Li`_ and `Stefan Appelhoff`_ (:gh:`889`)
9494

95+
- Internal helper function to :func:`~mne_bids.read_raw_bids` would reject BrainVision data if ``_scans.tsv`` listed a ``.eeg`` file instead of ``.vhdr``, by `Teon Brooks`_ (:gh:`1034`)
96+
9597
:doc:`Find out what was new in previous releases <whats_new_previous_releases>`
9698

9799
.. include:: authors.rst

mne_bids/read.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,23 @@ def _handle_scans_reading(scans_fname, raw, bids_path):
233233
# get the row corresponding to the file
234234
# use string concatenation instead of os.path
235235
# to work nicely with windows
236-
data_fname = bids_path.datatype + '/' + fname
236+
data_fname = Path(bids_path.datatype) / fname
237237
fnames = scans_tsv['filename']
238+
fnames = [Path(fname) for fname in fnames]
238239
if 'acq_time' in scans_tsv:
239240
acq_times = scans_tsv['acq_time']
240241
else:
241242
acq_times = ['n/a'] * len(fnames)
242243

244+
# There are three possible extensions for BrainVision
245+
# First gather all the possible extensions
246+
acq_suffixes = set(fname.suffix for fname in fnames)
247+
# Add the filename extension for the bids folder
248+
acq_suffixes.add(Path(data_fname).suffix)
249+
250+
if all(suffix in ('.vhdr', '.eeg', '.vmrk') for suffix in acq_suffixes):
251+
ext = fnames[0].suffix
252+
data_fname = Path(data_fname).with_suffix(ext)
243253
row_ind = fnames.index(data_fname)
244254

245255
# check whether all split files have the same acq_time
@@ -250,7 +260,9 @@ def _handle_scans_reading(scans_fname, raw, bids_path):
250260
bids_path.basename[:split_idx] +
251261
r'split-\d+_' + bids_path.datatype +
252262
bids_path.fpath.suffix)
253-
split_fnames = list(filter(pattern.match, fnames))
263+
split_fnames = list(filter(
264+
lambda x: pattern.match(x.as_posix()), fnames
265+
))
254266
split_acq_times = []
255267
for split_f in split_fnames:
256268
split_acq_times.append(acq_times[fnames.index(split_f)])

mne_bids/tests/data/tiny_bids/code/make_tiny_bids_dataset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import os.path as op
6+
from pathlib import Path
67

78
import mne
89
import numpy as np
@@ -14,7 +15,7 @@
1415
vhdr_fname = op.join(data_path, "montage", "bv_dig_test.vhdr")
1516
captrak_path = op.join(data_path, "montage", "captrak_coords.bvct")
1617

17-
mne_bids_root = os.sep.join(mne_bids.__file__.split("/")[:-2])
18+
mne_bids_root = Path(mne_bids.__file__).parent.parent
1819
tiny_bids = op.join(mne_bids_root, "mne_bids", "tests", "data", "tiny_bids")
1920
os.makedirs(tiny_bids, exist_ok=True)
2021

mne_bids/tests/test_read.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import json
66
import os
77
import os.path as op
8-
import pathlib
8+
from pathlib import Path
99
from datetime import datetime, timezone
10+
from typing import OrderedDict
1011

1112
import pytest
1213
import shutil as sh
@@ -23,7 +24,7 @@
2324
from mne_bids.config import (MNE_STR_TO_FRAME, BIDS_SHARED_COORDINATE_FRAMES,
2425
BIDS_TO_MNE_FRAMES)
2526
from mne_bids.read import (read_raw_bids, _read_raw, get_head_mri_trans,
26-
_handle_events_reading)
27+
_handle_events_reading, _handle_scans_reading)
2728
from mne_bids.tsv_handler import _to_tsv, _from_tsv
2829
from mne_bids.utils import (_write_json)
2930
from mne_bids.sidecar_updates import _update_sidecar
@@ -56,6 +57,10 @@
5657
# Data with cHPI info
5758
raw_fname_chpi = op.join(data_path, 'SSS', 'test_move_anon_raw.fif')
5859

60+
# Tiny BIDS testing dataset
61+
mne_bids_root = Path(mne_bids.__file__).parent.parent
62+
tiny_bids = op.join(mne_bids_root, "mne_bids", "tests", "data", "tiny_bids")
63+
5964
warning_str = dict(
6065
channel_unit_changed='ignore:The unit for chann*.:RuntimeWarning:mne',
6166
meas_date_set_to_none="ignore:.*'meas_date' set to None:RuntimeWarning:"
@@ -567,6 +572,38 @@ def test_handle_scans_reading(tmp_path):
567572
assert new_acq_time != raw_01.info['meas_date']
568573

569574

575+
def test_handle_scans_reading_brainvision(tmp_path):
576+
"""Test stability of BrainVision's different file extensions"""
577+
test_scan_eeg = OrderedDict(
578+
[('filename', [Path('eeg/sub-01_ses-eeg_task-rest_eeg.eeg')]),
579+
('acq_time', ['2000-01-01T12:00:00.000000Z'])]
580+
)
581+
test_scan_vmrk = OrderedDict(
582+
[('filename', [Path('eeg/sub-01_ses-eeg_task-rest_eeg.vmrk')]),
583+
('acq_time', ['2000-01-01T12:00:00.000000Z'])]
584+
)
585+
test_scan_edf = OrderedDict(
586+
[('filename', [Path('eeg/sub-01_ses-eeg_task-rest_eeg.edf')]),
587+
('acq_time', ['2000-01-01T12:00:00.000000Z'])]
588+
)
589+
os.mkdir(tmp_path / 'eeg')
590+
for test_scan in [test_scan_eeg, test_scan_vmrk, test_scan_edf]:
591+
_to_tsv(test_scan, tmp_path / test_scan['filename'][0])
592+
593+
bids_path = BIDSPath(subject='01', session='eeg', task='rest',
594+
datatype='eeg', root=tiny_bids)
595+
with pytest.warns(RuntimeWarning, match='Not setting positions'):
596+
raw = read_raw_bids(bids_path)
597+
598+
for test_scan in [test_scan_eeg, test_scan_vmrk]:
599+
_handle_scans_reading(tmp_path / test_scan['filename'][0],
600+
raw, bids_path)
601+
602+
with pytest.raises(ValueError, match="is not in list"):
603+
_handle_scans_reading(tmp_path / test_scan_edf['filename'][0],
604+
raw, bids_path)
605+
606+
570607
@pytest.mark.filterwarnings(warning_str['channel_unit_changed'])
571608
def test_handle_info_reading(tmp_path):
572609
"""Test reading information from a BIDS sidecar JSON file."""
@@ -587,7 +624,7 @@ def test_handle_info_reading(tmp_path):
587624
bids_fname.update(datatype=suffix)
588625
sidecar_fname = _find_matching_sidecar(bids_fname, suffix=suffix,
589626
extension='.json')
590-
sidecar_fname = pathlib.Path(sidecar_fname)
627+
sidecar_fname = Path(sidecar_fname)
591628

592629
# assert that we get the same line frequency set
593630
raw = read_raw_bids(bids_path=bids_path)
@@ -1071,7 +1108,7 @@ def test_write_read_fif_split_file(tmp_path, monkeypatch):
10711108
n_times = int(2.5e6 / n_channels) # enough to produce a 10MB split
10721109
data = np.empty((n_channels, n_times), dtype=np.float32)
10731110
raw = mne.io.RawArray(data, raw.info)
1074-
big_fif_fname = pathlib.Path(tmp_dir) / 'test_raw.fif'
1111+
big_fif_fname = Path(tmp_dir) / 'test_raw.fif'
10751112

10761113
split_size = '10MB'
10771114
raw.save(big_fif_fname, split_size=split_size)

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ filterwarnings =
8787
ignore:MEG ref channel RMSP did not.*:RuntimeWarning
8888
# Python 3.10+ and NumPy 1.22 (and maybe also newer NumPy versions?)
8989
ignore:.*distutils\.sysconfig module is deprecated.*:DeprecationWarning
90+
# numba with NumPy dev
91+
ignore:`np.MachAr` is deprecated.*:DeprecationWarning
9092

9193
[pydocstyle]
9294
convention = pep257

0 commit comments

Comments
 (0)