diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index c2532dacb..3b1a084ef 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -176,7 +176,14 @@ jobs: run: | export BIDS_VALIDATOR_VERSION=`bids-validator --version` echo Using bids-validator $BIDS_VALIDATOR_VERSION - python -m pytest . --cov=mne_bids mne_bids/tests/ mne_bids/commands/tests/ --cov-report=xml --cov-config=setup.cfg --verbose --ignore mne-python + python -m pytest . \ + --doctest-modules \ + --cov=mne_bids mne_bids/tests/ mne_bids/commands/tests/ \ + --cov-report=xml \ + --cov-config=setup.cfg \ + --verbose \ + --ignore mne-python \ + --ignore examples shell: bash - name: Upload coverage stats to codecov if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' && matrix.bids-validator == 'main' }} diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 1b2f1e6a1..8ce6f95f6 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -26,14 +26,20 @@ Authors * `Alex Rockhill`_ * `Richard Höchenberger`_ * `Adam Li`_ +* `Eduard Ort`_ * `Richard Köhler`_ (new contributor) * `Jean-Rémi King`_ (new contributor) +* `Sin Kim`_ (new contributor) +* `Alexandre Gramfort`_ +* `Mainak Jas`_ +* `Stefan Appelhoff`_ Detailed list of changes ~~~~~~~~~~~~~~~~~~~~~~~~ Enhancements ^^^^^^^^^^^^ + - The fields "DigitizedLandmarks" and "DigitizedHeadPoints" in the json sidecar of Neuromag data are now set to True/False depending on whether any landmarks (NAS, RPA, LPA) or extra points are found in raw.info['dig'], by `Eduard Ort`_ (:gh:`772`) - Updated the "Read BIDS datasets" example to use data from `OpenNeuro `_, by `Alex Rockhill`_ (:gh:`753`) - :func:`mne_bids.get_head_mri_trans` is now more lenient when looking for the fiducial points (LPA, RPA, and nasion) in the MRI JSON sidecar file, and accepts a larger variety of landmark names (upper- and lowercase letters; ``'nasion'`` instead of only ``'NAS'``), by `Richard Höchenberger`_ (:gh:`769`) @@ -56,8 +62,9 @@ API and behavior changes - The ``raw_to_bids`` command has lost its ``--allow_maxshield`` parameter. If writing a FIFF file, we will now always assume that writing data before applying a Maxwell filter is fine, by `Richard Höchenberger`_ (:gh:`787`) - :meth:`mne_bids.BIDSPath.find_empty_room` now first looks for an ``AssociatedEmptyRoom`` field in the MEG JSON sidecar file to retrieve the empty-room recording; only if this information is missing, it will proceed to try and find the best-matching empty-room recording based on measurement date (i.e., fall back to the previous behavior), by `Richard Höchenberger`_ (:gh:`795`) - If :func:`mne_bids.read_raw_bids` encounters raw data with the ``STI 014`` stimulus channel and this channel is not explicitly listed in ``*_channels.tsv``, it is now automatically removed upon reading, by `Richard Höchenberger`_ (:gh:`823`) -- :func:`mne_bids.get_anat_landmarks` was added to clarify and simplify the process of generating landmarks that now need to be passed to :func:`mne_bids.write_anat`; this depreciates the arguments ``raw``, ``trans`` and ``t1w`` of :func:`mne_bids.write_anat`, by `Alex Rockhill`_ and `Alexandre Gramfort`_ (:gh:`827`) -- :func:`write_raw_bids` now accepts preloaded raws as input with some caveats if the new parameter ``allow_preload`` is explicitly set to ``True``. This enables some preliminary support for uncommon file formats, generated data, processed derivatives etc., by `Sin Kim`_ (:gh:`819`) +- :func:`mne_bids.get_anat_landmarks` was added to clarify and simplify the process of generating landmarks that now need to be passed to :func:`mne_bids.write_anat`; this deprecates the arguments ``raw``, ``trans`` and ``t1w`` of :func:`mne_bids.write_anat`, by `Alex Rockhill`_ and `Alexandre Gramfort`_ (:gh:`827`) +- :func:`write_raw_bids` now accepts preloaded raws as input with some caveats if the new parameter ``allow_preload`` is explicitly set to ``True``. This enables some preliminary support for items such as uncommon file formats, generated data, and processed derivatives, by `Sin Kim`_ (:gh:`819`) +- MNE-BIDS now writes all TSV data files with a newline character at the end of the file, complying with UNIX/POSIX standards, by `Stefan Appelhoff`_ (:gh:`831`) Requirements ^^^^^^^^^^^^ diff --git a/examples/bidspath.py b/examples/bidspath.py index dfd48d05f..86ad98d6b 100644 --- a/examples/bidspath.py +++ b/examples/bidspath.py @@ -14,6 +14,9 @@ # %% # Obviously, to start exploring BIDSPath, we first need to import it. +from pathlib import Path + +import mne_bids from mne_bids import BIDSPath # %% @@ -27,13 +30,15 @@ # consider where to store your data upon BIDS conversion. Again, the intended # target folder will be the BIDS root of your data. # -# Let's just pick an arbitrary BIDS root, for the purpose of this -# demonstration. +# For the purpose of this demonstration, let's pick the ``tiny_bids`` example +# dataset that ships with the MNE-BIDS test suite. -bids_root = './my_bids_root' +# We are using a pathlib.Path object for convenience, but you could just use +# a string to specify ``bids_root`` here. +bids_root = Path(mne_bids.__file__).parent / 'tests' / 'data' / 'tiny_bids' # %% -# This refers to a folder named `my_bids_root` in the current working +# This refers to a folder named ``my_bids_root`` in the current working # directory. Finally, let is create a ``BIDSPath``, and tell it about our # BIDS root. We can then also query the ``BIDSPath`` for its root. @@ -45,7 +50,7 @@ # identifiers**. We can either create a new ``BIDSPath``, or update our # existing one. The value can be retrieved via the ``.subject`` attribute. -subject = '123' +subject = '01' # Option 1: Create an entirely new BIDSPath. bids_path_new = BIDSPath(subject=subject, root=bids_root) @@ -66,7 +71,7 @@ # information on our experimental session, and try to retrieve it again via # ``.session``. -session = 'test' +session = 'eeg' bids_path.update(session=session) print(bids_path.session) @@ -78,7 +83,7 @@ # using `mne_bids.write_raw_bids`. For the sake of this example, however, we # are going to specify the data type explicitly. -datatype = 'meg' +datatype = 'eeg' bids_path.update(datatype=datatype) print(bids_path.datatype) @@ -110,7 +115,7 @@ # %% # The two entities you can see here are the ``subject`` entity (``sub``) and # the ``session`` entity (``ses``). Each entity name also has a value; for -# ``sub``, this is ``123``, and for ``ses``, it is ``test`` in our example. +# ``sub``, this is ``01``, and for ``ses``, it is ``eeg`` in our example. # Entity names (or "keys") and values are separated via hyphens. # BIDS knows a much larger number of entities, and MNE-BIDS allows you to make # use of them. To get a list of all supported entities, use: @@ -129,11 +134,25 @@ # %% # As you can see, the ``basename`` has been updated. In fact, the entire -# **path** has been updated, and the ``ses-test`` folder has been dropped from +# **path** has been updated, and the ``ses-eeg`` folder has been dropped from # the path: print(bids_path.fpath) +# %% +# Oups! The cell above produced a ``RuntimeWarning`` that our data file could +# not be found. That's because we changed the ``run`` and ``session`` entities +# above, and the ``tiny_bids`` dataset does not contain corresponding data. +# +# That shows us that ``BIDSPath`` is doing a lot of guess-work and checking +# in the background, but note that this may change in the future. +# +# For now, let's revert to the last working iteration of our ``bids_path`` +# instance. + +bids_path.update(run=None, session='eeg') +print(bids_path.fpath) + # %% # Awesome! We're almost done! Two important things are still missing, though: # the so-called **suffix** and the filename **extension**. Sometimes these @@ -151,7 +170,7 @@ # ``.tsv``. # Let's put our new knowledge to use! -bids_path.update(suffix='meg', extension='fif') +bids_path.update(suffix='eeg', extension='.vhdr') print(bids_path.fpath) bids_path diff --git a/mne_bids/inspect.py b/mne_bids/inspect.py index e9533e1d1..8d8a3826a 100644 --- a/mne_bids/inspect.py +++ b/mne_bids/inspect.py @@ -1,3 +1,9 @@ +"""Inspect and annotate BIDS raw data.""" +# Authors: Richard Höchenberger +# Stefan Appelhoff +# +# License: BSD (3-clause) + from pathlib import Path import numpy as np @@ -78,8 +84,12 @@ def inspect_dataset(bids_path, find_flat=True, l_freq=None, h_freq=None, Disable flat channel & segment detection, and apply a filter with a passband of 1–30 Hz. - >>> inspect_dataset(bids_path=bids_path, find_flat=False, - l_freq=1, h_freq=30) + >>> from mne_bids import BIDSPath + >>> root = Path('./mne_bids/tests/data/tiny_bids').absolute() + >>> bids_path = BIDSPath(subject='01', task='rest', session='eeg', + ... suffix='eeg', extension='.vhdr', root=root) + >>> inspect_dataset(bids_path=bids_path, find_flat=False, # doctest: +SKIP + ... l_freq=1, h_freq=30) """ allowed_extensions = set(ALLOWED_DATATYPE_EXTENSIONS['meg'] + ALLOWED_DATATYPE_EXTENSIONS['eeg'] + diff --git a/mne_bids/path.py b/mne_bids/path.py index bceba12d0..e8ea9a872 100644 --- a/mne_bids/path.py +++ b/mne_bids/path.py @@ -1,5 +1,6 @@ """BIDS compatible path functionality.""" # Authors: Adam Li +# Stefan Appelhoff # # License: BSD (3-clause) import glob @@ -13,7 +14,7 @@ from pathlib import Path from datetime import datetime import json -from typing import Optional, Union +from typing import Optional import numpy as np from mne.utils import warn, logger, _validate_type @@ -224,37 +225,50 @@ class BIDSPath(object): Examples -------- + Generate a BIDSPath object and inspect it + >>> bids_path = BIDSPath(subject='test', session='two', task='mytask', - suffix='ieeg', extension='.edf') + ... suffix='ieeg', extension='.edf') >>> print(bids_path.basename) sub-test_ses-two_task-mytask_ieeg.edf >>> bids_path - BIDSPath(root: None, + BIDSPath( + root: None + datatype: ieeg basename: sub-test_ses-two_task-mytask_ieeg.edf) - >>> # copy and update multiple entities at once + + Copy and update multiple entities at once + >>> new_bids_path = bids_path.copy().update(subject='test2', - session='one') + ... session='one') >>> print(new_bids_path.basename) sub-test2_ses-one_task-mytask_ieeg.edf - >>> # printing the BIDSPath will show relative path when - >>> # root is not set + + Printing a BIDSPath will show a relative path when `root` is not set + >>> print(new_bids_path) sub-test2/ses-one/ieeg/sub-test2_ses-one_task-mytask_ieeg.edf - >>> new_bids_path.update(suffix='channels', extension='.tsv') - >>> # setting suffix without an identifiable datatype will - >>> # result in a wildcard at the datatype directory level + + Setting `suffix` without an identifiable datatype will make + BIDSPath try to guess the datatype + + >>> new_bids_path = new_bids_path.update(suffix='channels', + ... extension='.tsv') >>> print(new_bids_path) - sub-test2/ses-one/*/sub-test2_ses-one_task-mytask_channels.tsv - >>> # set a root for the BIDS dataset - >>> new_bids_path.update(root='/bids_dataset') - >>> print(new_bids_path.root) + sub-test2/ses-one/ieeg/sub-test2_ses-one_task-mytask_channels.tsv + + You can set a new root for the BIDS dataset. Let's see what the + different properties look like for our object: + + >>> new_bids_path = new_bids_path.update(root='/bids_dataset') + >>> print(new_bids_path.root.as_posix()) /bids_dataset >>> print(new_bids_path.basename) - sub-test2_ses-one_task-mytask_ieeg.edf + sub-test2_ses-one_task-mytask_channels.tsv >>> print(new_bids_path) - /bids_dataset/sub-test2/ses-one/ieeg/sub-test2_ses-one_task-mytask_ieeg.edf - >>> print(new_bids_path.directory) - /bids_dataset/sub-test2/ses-one/ieeg/ + /bids_dataset/sub-test2/ses-one/ieeg/sub-test2_ses-one_task-mytask_channels.tsv + >>> print(new_bids_path.directory.as_posix()) + /bids_dataset/sub-test2/ses-one/ieeg Notes ----- @@ -440,7 +454,7 @@ def suffix(self, value): self.update(suffix=value) @property - def root(self) -> Optional[Union[str, Path]]: + def root(self) -> Optional[Path]: """The root directory of the BIDS dataset.""" return self._root @@ -477,12 +491,14 @@ def extension(self, value): def __str__(self): """Return the string representation of the path.""" - return str(self.fpath) + return str(self.fpath.as_posix()) def __repr__(self): """Representation in the style of `pathlib.Path`.""" + root = self.root.as_posix() if self.root is not None else None + return f'{self.__class__.__name__}(\n' \ - f'root: {self.root}\n' \ + f'root: {root}\n' \ f'datatype: {self.datatype}\n' \ f'basename: {self.basename})' @@ -651,13 +667,13 @@ def update(self, *, check=None, **kwargs): :func:`mne_bids.BIDSPath`: >>> bids_path = BIDSPath(subject='test', session='two', - task='mytask', suffix='channels', - extension='.tsv') + ... task='mytask', suffix='channels', + ... extension='.tsv') >>> print(bids_path.basename) sub-test_ses-two_task-mytask_channels.tsv >>> # Then, one can update this `BIDSPath` object in place - >>> bids_path.update(acquisition='test', suffix='ieeg', - extension='.vhdr', task=None) + >>> bids_path = bids_path.update(acquisition='test', suffix='ieeg', + ... extension='.vhdr', task=None) >>> print(bids_path.basename) sub-test_ses-two_acq-test_ieeg.vhdr """ @@ -1147,16 +1163,16 @@ def get_entities_from_fname(fname, on_error='raise'): -------- >>> fname = 'sub-01_ses-exp_run-02_meg.fif' >>> get_entities_from_fname(fname) - {'subject': '01', - 'session': 'exp', - 'task': None, - 'acquisition': None, - 'run': '02', - 'processing': None, - 'space': None, - 'recording': None, - 'split': None, - 'suffix': 'meg'} + {'subject': '01', \ +'session': 'exp', \ +'task': None, \ +'acquisition': None, \ +'run': '02', \ +'processing': None, \ +'space': None, \ +'recording': None, \ +'split': None, \ +'suffix': 'meg'} """ if on_error not in ('warn', 'raise', 'ignore'): raise ValueError(f'Acceptable values for on_error are: warn, raise, ' @@ -1398,12 +1414,12 @@ def get_entity_vals(root, entity_key, *, ignore_subjects='emptyroom', Examples -------- - >>> root = os.path.expanduser('~/mne_data/eeg_matchingpennies') + >>> root = Path('./mne_bids/tests/data/tiny_bids').absolute() >>> entity_key = 'subject' >>> get_entity_vals(root, entity_key) - ['05', '06', '07', '08', '09', '10', '11'] + ['01'] >>> get_entity_vals(root, entity_key, with_key=True) - ['sub-05', 'sub-06', 'sub-07', 'sub-08', 'sub-09', 'sub-10', 'sub-11'] + ['sub-01'] Notes ----- diff --git a/mne_bids/sidecar_updates.py b/mne_bids/sidecar_updates.py index 551c32b84..169668a31 100644 --- a/mne_bids/sidecar_updates.py +++ b/mne_bids/sidecar_updates.py @@ -1,7 +1,9 @@ """Update BIDS directory structures and sidecar files meta data.""" # Authors: Adam Li # Austin Hurst +# Stefan Appelhoff # mne-bids developers +# # License: BSD (3-clause) import json @@ -71,17 +73,15 @@ def update_sidecar_json(bids_path, entries, verbose=True): Examples -------- - >>> # update sidecar json file - >>> bids_path = BIDSPath(root='./', subject='001', session='001', - task='test', run='01', suffix='ieeg', - extension='.json') - >>> entries = {'PowerLineFrequency': 50} - >>> update_sidecar_json(bids_path, entries) - >>> # update sidecar coordsystem json file - >>> bids_path = BIDSPath(root='./', subject='001', session='001', - suffix='coordsystem', extension='.json') - >>> entries = {'iEEGCoordinateSystem,': 'Other'} - >>> update_sidecar_json(bids_path, entries) + Update a sidecar JSON file + + >>> from pathlib import Path + >>> root = Path('./mne_bids/tests/data/tiny_bids').absolute() + >>> bids_path = BIDSPath(subject='01', task='rest', session='eeg', + ... suffix='eeg', extension='.json', root=root) + >>> entries = {'PowerLineFrequency': 60} + >>> update_sidecar_json(bids_path, entries, verbose=False) + """ # get all matching json files bids_path = bids_path.copy() @@ -135,8 +135,7 @@ def _update_sidecar(sidecar_fname, key, val): with open(sidecar_fname, 'r', encoding='utf-8-sig') as fin: sidecar_json = json.load(fin) sidecar_json[key] = val - with open(sidecar_fname, 'w', encoding='utf-8') as fout: - json.dump(sidecar_json, fout) + _write_json(sidecar_fname, sidecar_json, overwrite=True, verbose=False) def update_anat_landmarks(bids_path, landmarks): @@ -246,7 +245,6 @@ def update_anat_landmarks(bids_path, landmarks): bids_path_json = bids_path.copy().update(extension='.json') if not bids_path_json.fpath.exists(): # Must exist before we can update it - with bids_path_json.fpath.open('w', encoding='utf-8') as f: - json.dump(dict(), f) + _write_json(bids_path_json.fpath, dict()) update_sidecar_json(bids_path=bids_path_json, entries=mri_json) diff --git a/mne_bids/tests/data/tiny_bids/README b/mne_bids/tests/data/tiny_bids/README new file mode 100644 index 000000000..ce5b9ab17 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/README @@ -0,0 +1,6 @@ +References +---------- +Appelhoff, S., Sanderson, M., Brooks, T., Vliet, M., Quentin, R., Holdgraf, C., Chaumon, M., Mikulan, E., Tavabi, K., Höchenberger, R., Welke, D., Brunner, C., Rockhill, A., Larson, E., Gramfort, A. and Jas, M. (2019). MNE-BIDS: Organizing electrophysiological data into the BIDS format and facilitating their analysis. Journal of Open Source Software 4: (1896). https://doi.org/10.21105/joss.01896 + +Pernet, C. R., Appelhoff, S., Gorgolewski, K. J., Flandin, G., Phillips, C., Delorme, A., Oostenveld, R. (2019). EEG-BIDS, an extension to the brain imaging data structure for electroencephalography. Scientific Data, 6, 103. https://doi.org/10.1038/s41597-019-0104-8 + diff --git a/mne_bids/tests/data/tiny_bids/code/make_tiny_bids_dataset.py b/mne_bids/tests/data/tiny_bids/code/make_tiny_bids_dataset.py new file mode 100644 index 000000000..9b27b6620 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/code/make_tiny_bids_dataset.py @@ -0,0 +1,62 @@ +"""Code used to generate the tiny_bids dataset.""" +# %% +import json +import os +import os.path as op + +import mne +import numpy as np + +import mne_bids +from mne_bids import BIDSPath, write_raw_bids + +data_path = mne.datasets.testing.data_path() +vhdr_fname = op.join(data_path, "montage", "bv_dig_test.vhdr") +captrak_path = op.join(data_path, "montage", "captrak_coords.bvct") + +mne_bids_root = os.sep.join(mne_bids.__file__.split("/")[:-2]) +tiny_bids = op.join(mne_bids_root, "mne_bids", "tests", "data", "tiny_bids") +os.makedirs(tiny_bids, exist_ok=True) + +bids_path = BIDSPath(subject="01", task="rest", session="eeg", root=tiny_bids) + +# %% +raw = mne.io.read_raw_brainvision(vhdr_fname) +montage = mne.channels.read_dig_captrak(captrak_path) + +raw.set_channel_types(dict(ECG="ecg", HEOG="eog", VEOG="eog")) +raw.set_montage(montage) +raw.info["line_freq"] = 50 + +raw.info["subject_info"] = { + "id": 1, + "last_name": "Musterperson", + "first_name": "Maxi", + "middle_name": "Luka", + "birthday": (1970, 10, 20), + "sex": 2, + "hand": 3, +} + +raw.set_annotations(None) +events = np.array([[0, 0, 1], [1000, 0, 2]]) +event_id = {"start_experiment": 1, "show_stimulus": 2} + +# %% + +write_raw_bids( + raw, bids_path, events_data=events, event_id=event_id, overwrite=True +) + +# %% + +dataset_description_json = op.join(tiny_bids, "dataset_description.json") +with open(dataset_description_json, "r") as fin: + ds_json = json.load(fin) + +ds_json["Name"] = "tiny_bids" +ds_json["Authors"] = ["MNE-BIDS Developers", "And Friends"] + +with open(dataset_description_json, "w") as fout: + json.dump(ds_json, fout, indent=4) + fout.write("\n") diff --git a/mne_bids/tests/data/tiny_bids/dataset_description.json b/mne_bids/tests/data/tiny_bids/dataset_description.json new file mode 100644 index 000000000..71532911e --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/dataset_description.json @@ -0,0 +1,9 @@ +{ + "Name": "tiny_bids", + "BIDSVersion": "1.6.0", + "DatasetType": "raw", + "Authors": [ + "MNE-BIDS Developers", + "And Friends" + ] +} diff --git a/mne_bids/tests/data/tiny_bids/participants.json b/mne_bids/tests/data/tiny_bids/participants.json new file mode 100644 index 000000000..de061dfc2 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/participants.json @@ -0,0 +1,24 @@ +{ + "participant_id": { + "Description": "Unique participant identifier" + }, + "age": { + "Description": "Age of the participant at time of testing", + "Units": "years" + }, + "sex": { + "Description": "Biological sex of the participant", + "Levels": { + "F": "female", + "M": "male" + } + }, + "hand": { + "Description": "Handedness of the participant", + "Levels": { + "R": "right", + "L": "left", + "A": "ambidextrous" + } + } +} diff --git a/mne_bids/tests/data/tiny_bids/participants.tsv b/mne_bids/tests/data/tiny_bids/participants.tsv new file mode 100644 index 000000000..75086fe10 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/participants.tsv @@ -0,0 +1,2 @@ +participant_id age sex hand +sub-01 29 F A diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_coordsystem.json b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_coordsystem.json new file mode 100644 index 000000000..b902d6fc7 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_coordsystem.json @@ -0,0 +1,24 @@ +{ + "EEGCoordinateSystem": "CapTrak", + "EEGCoordinateUnits": "m", + "EEGCoordinateSystemDescription": "The X-axis goes from the left preauricular point (LPA) through the right preauricular point (RPA). The Y-axis goes orthogonally to the X-axis through the nasion (NAS). The Z-axis goes orthogonally to the XY-plane through the vertex of the head. This corresponds to a \"RAS\" orientation with the origin of the coordinate system approximately between the ears. See Appendix VIII in the BIDS specification.", + "AnatomicalLandmarkCoordinates": { + "NAS": [ + -3.788238398497924e-18, + 0.11309931478694205, + -3.0814879110195774e-33 + ], + "LPA": [ + -0.09189697162389295, + 3.078070254157709e-18, + 0.0 + ], + "RPA": [ + 0.09240077493980713, + -3.094945043100789e-18, + -6.162975822039155e-33 + ] + }, + "AnatomicalLandmarkCoordinateSystem": "CapTrak", + "AnatomicalLandmarkCoordinateUnits": "m" +} diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_electrodes.tsv b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_electrodes.tsv new file mode 100644 index 000000000..6422b897e --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_electrodes.tsv @@ -0,0 +1,68 @@ +name x y z impedance +Fp1 -0.03741270038437644 0.11102138145885607 0.04987088609474466 5.0 +Fp2 0.02485529277372901 0.11470061734309513 0.051792653682490544 2.0 +F7 -0.08409763256980056 0.0651383327975854 0.04885693263757247 4.0 +F3 -0.06033321416227938 0.07429454777471324 0.1003425150886266 1.0 +Fz -0.004358766283031327 0.08568143210491122 0.12205334790141567 5.0 +F4 0.054248614498413714 0.08039407987521177 0.09908756593625902 0.0 +F8 0.07985344457502894 0.06590997733368428 0.047504910890930334 0.0 +FC5 -0.08856262271830136 0.03656538257252212 0.08223925927400842 0.0 +FC1 -0.04102029132824396 0.04689188172302073 0.13593463220555657 5.0 +FC2 0.03154947116645066 0.052194369449069106 0.13882908999751697 4.0 +FC6 0.08262912998454677 0.041103054281419346 0.0874926634879591 0.0 +T7 -0.09731968723468239 -0.0021423686071579805 0.050226741225281264 7.0 +C3 -0.07560861330060216 0.00379803246039862 0.1234494784937915 7.0 +Cz -0.006180417265753086 0.012315925271535114 0.15916621398101838 7.0 +C4 0.06771780154881832 0.012607270994125496 0.12635776802248064 5.0 +T8 0.09380794264378244 0.003856915183230266 0.05572229320000353 3.0 +TP9 -0.08788277893731682 -0.044663525156301793 0.018366495084764893 2.0 +CP5 -0.08573650192101132 -0.03761412179890887 0.08666886062797521 2.0 +CP1 -0.04005140947629612 -0.03233107162343741 0.15014596318924497 1.0 +CP2 0.031717794003817336 -0.0296088007148207 0.15221703399068814 0.0 +CP6 0.08561136739245928 -0.02720456615478355 0.0902401015260155 0.0 +TP10 0.08961349613268568 -0.039552949075016205 0.019334677566709556 6.0 +P7 -0.07806326604115213 -0.07056190192615615 0.054534433604549515 2.0 +P3 -0.05988675497167928 -0.07014191680061976 0.11264449281813038 3.0 +Pz 0.0006598293180978853 -0.06804813714721136 0.1401607191300863 3.0 +P4 0.05712134266236288 -0.06349053843971156 0.1161043631186527 3.0 +P8 0.07981691958558523 -0.06052077913488906 0.06169955870397669 2.0 +PO9 -0.05689512980123602 -0.10004211993311017 0.024158443841012435 0.0 +O1 -0.02968467135335781 -0.11245008371099957 0.0698530819823185 0.0 +Oz 0.0007596706595483696 -0.11571632630102933 0.07284087113946844 0.0 +O2 0.03439028693906701 -0.10937515964204104 0.07138023552971996 1.0 +PO10 0.061903361072981396 -0.09474806509500813 0.03333978523037232 1.0 +AF7 -0.06827717522853415 0.08936527746345276 0.04754798622540827 0.0 +AF3 -0.04262101269367339 0.10268157306800194 0.07886835797387867 0.0 +AF4 0.03225018478101126 0.10529515184701174 0.08026648552783476 0.0 +AF8 0.05956439040238648 0.09485953374400294 0.047945480521261415 0.0 +F5 -0.07472092061394837 0.0711718724278273 0.07658228616101855 1.0 +F1 -0.034810704300574445 0.07683856227323796 0.11751875341931364 0.0 +F2 0.025336640790593185 0.0821915554814207 0.11870219411577368 1.0 +F6 0.07170582263944832 0.07278864400463472 0.07387636924196503 2.0 +FT9 -0.09501686722799514 0.0266282891146297 0.011193532499484526 4.0 +FT7 -0.09514340411749506 0.033122249460160715 0.046783546930167653 4.0 +FC3 -0.07076636427255573 0.04222010515235073 0.11253273493424133 5.0 +FC4 0.06459420065117943 0.04794650194990631 0.11446098877470175 0.0 +FT8 0.09119200553670907 0.03543463031800678 0.0495990840479741 0.0 +FT10 0.0924565546557565 0.025823362649082977 0.014698781944416511 0.0 +C5 -0.09271418986954857 -0.0008204160816824106 0.08669305289548136 0.0 +C1 -0.041067570836697036 0.007252977347652117 0.14981945629789892 2.0 +C2 0.031096077417169788 0.009822809030655128 0.15263556643976955 1.0 +C6 0.08823411500163923 0.006781675055354489 0.09062978525129138 0.0 +TP7 -0.09130133540418942 -0.03998047720710454 0.05072152381600354 0.0 +CP3 -0.07051068609538193 -0.03805150971360316 0.12118907085589545 0.0 +CPz -0.002804681696167738 -0.032506400711086986 0.15765098791790225 0.0 +CP4 0.06857184159847507 -0.02861094380649944 0.12476974691488656 0.0 +TP8 0.09015904987363602 -0.03087949475535255 0.05580695159940414 0.0 +P5 -0.07198353757029069 -0.07069605341309865 0.08424402565103543 1.0 +P1 -0.03160865255395789 -0.06888410750316204 0.13343172651379165 0.0 +P2 0.029678055865445845 -0.06524160677056923 0.13633979302106938 0.0 +P6 0.07231997488461793 -0.061210721352081214 0.08939618967378504 0.0 +PO7 -0.05917554476252128 -0.09600963297712298 0.05901355471464929 0.0 +PO3 -0.03295276056603997 -0.09836112961335748 0.09818535662931108 0.0 +POz 0.00141250247182117 -0.09659898488597438 0.1092860795662283 2.0 +PO4 0.03732555069518968 -0.09330847523481997 0.09871963971555255 0.0 +PO8 0.061422359682971855 -0.08824937021900595 0.06730156974139692 0.0 +ECG n/a n/a n/a n/a +HEOG n/a n/a n/a n/a +VEOG n/a n/a n/a n/a diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_channels.tsv b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_channels.tsv new file mode 100644 index 000000000..8c63a81de --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_channels.tsv @@ -0,0 +1,68 @@ +name type units low_cutoff high_cutoff description sampling_frequency status status_description +Fp1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +Fp2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +Fz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC5 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC6 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +T7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +Cz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +T8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +TP9 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP5 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP6 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +TP10 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +Pz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO9 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +O1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +Oz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +O2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO10 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +AF7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +AF3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +AF4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +AF8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F5 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +F6 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FT9 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FT7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FC4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FT8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +FT10 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C5 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +C6 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +TP7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CPz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +CP4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +TP8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P5 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P1 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P2 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +P6 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO7 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO3 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +POz EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO4 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +PO8 EEG µV 0.015915494309189534 1000.0 ElectroEncephaloGram 5000.0 good n/a +ECG ECG µV 0.015915494309189534 1000.0 ElectroCardioGram 5000.0 good n/a +HEOG EOG µV 0.015915494309189534 1000.0 ElectroOculoGram 5000.0 good n/a +VEOG EOG µV 0.015915494309189534 1000.0 ElectroOculoGram 5000.0 good n/a diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.eeg b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.eeg new file mode 100644 index 000000000..08c6c539e Binary files /dev/null and b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.eeg differ diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.json b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.json new file mode 100644 index 000000000..bbfde9540 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.json @@ -0,0 +1,18 @@ +{ + "TaskName": "rest", + "Manufacturer": "BrainProducts", + "PowerLineFrequency": 60, + "SamplingFrequency": 5000.0, + "SoftwareFilters": "n/a", + "RecordingDuration": 0.9998, + "RecordingType": "continuous", + "EEGReference": "n/a", + "EEGGround": "n/a", + "EEGPlacementScheme": "based on the extended 10/20 system", + "EEGChannelCount": 64, + "EOGChannelCount": 2, + "ECGChannelCount": 1, + "EMGChannelCount": 0, + "MiscChannelCount": 0, + "TriggerChannelCount": 0 +} diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vhdr b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vhdr new file mode 100644 index 000000000..c20d07297 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vhdr @@ -0,0 +1,251 @@ +Brain Vision Data Exchange Header File Version 1.0 +; Data created by the Vision Recorder + +[Common Infos] +Codepage=UTF-8 +DataFile=sub-01_ses-eeg_task-rest_eeg.eeg +MarkerFile=sub-01_ses-eeg_task-rest_eeg.vmrk +DataFormat=BINARY +; Data orientation: MULTIPLEXED=ch1,pt1, ch2,pt1 ... +DataOrientation=MULTIPLEXED +NumberOfChannels=67 +; Sampling interval in microseconds +SamplingInterval=200 + +[Binary Infos] +BinaryFormat=INT_16 + +[Channel Infos] +; Each entry: Ch=,, +; ,, Future extensions.. +; Fields are delimited by commas, some fields might be omitted (empty). +; Commas in channel names are coded as "\1". +Ch1=Fp1,,0.1,µV +Ch2=Fp2,,0.1,µV +Ch3=F7,,0.1,µV +Ch4=F3,,0.1,µV +Ch5=Fz,,0.1,µV +Ch6=F4,,0.1,µV +Ch7=F8,,0.1,µV +Ch8=FC5,,0.1,µV +Ch9=FC1,,0.1,µV +Ch10=FC2,,0.1,µV +Ch11=FC6,,0.1,µV +Ch12=T7,,0.1,µV +Ch13=C3,,0.1,µV +Ch14=Cz,,0.1,µV +Ch15=C4,,0.1,µV +Ch16=T8,,0.1,µV +Ch17=TP9,,0.1,µV +Ch18=CP5,,0.1,µV +Ch19=CP1,,0.1,µV +Ch20=CP2,,0.1,µV +Ch21=CP6,,0.1,µV +Ch22=TP10,,0.1,µV +Ch23=P7,,0.1,µV +Ch24=P3,,0.1,µV +Ch25=Pz,,0.1,µV +Ch26=P4,,0.1,µV +Ch27=P8,,0.1,µV +Ch28=PO9,,0.1,µV +Ch29=O1,,0.1,µV +Ch30=Oz,,0.1,µV +Ch31=O2,,0.1,µV +Ch32=PO10,,0.1,µV +Ch33=AF7,,0.1,µV +Ch34=AF3,,0.1,µV +Ch35=AF4,,0.1,µV +Ch36=AF8,,0.1,µV +Ch37=F5,,0.1,µV +Ch38=F1,,0.1,µV +Ch39=F2,,0.1,µV +Ch40=F6,,0.1,µV +Ch41=FT9,,0.1,µV +Ch42=FT7,,0.1,µV +Ch43=FC3,,0.1,µV +Ch44=FC4,,0.1,µV +Ch45=FT8,,0.1,µV +Ch46=FT10,,0.1,µV +Ch47=C5,,0.1,µV +Ch48=C1,,0.1,µV +Ch49=C2,,0.1,µV +Ch50=C6,,0.1,µV +Ch51=TP7,,0.1,µV +Ch52=CP3,,0.1,µV +Ch53=CPz,,0.1,µV +Ch54=CP4,,0.1,µV +Ch55=TP8,,0.1,µV +Ch56=P5,,0.1,µV +Ch57=P1,,0.1,µV +Ch58=P2,,0.1,µV +Ch59=P6,,0.1,µV +Ch60=PO7,,0.1,µV +Ch61=PO3,,0.1,µV +Ch62=POz,,0.1,µV +Ch63=PO4,,0.1,µV +Ch64=PO8,,0.1,µV +Ch65=ECG,,0.1,µV +Ch66=HEOG,,0.1,µV +Ch67=VEOG,,0.1,µV + +[Comment] + +BrainVision Recorder Professional - V. 1.21.0303 + + +A m p l i f i e r S e t u p +============================ +Number of channels: 67 +Sampling Rate [Hz]: 1000 +Sampling Interval [µS]: 1000 + +Channels +-------- +# Name Phys. Chn. Resolution / Unit Low Cutoff [s] High Cutoff [Hz] Notch [Hz] Series Res. [kOhm] Gradient Offset +1 Fp1 1 0.1 µV 10 1000 Off 0 +2 Fp2 2 0.1 µV 10 1000 Off 0 +3 F7 3 0.1 µV 10 1000 Off 0 +4 F3 4 0.1 µV 10 1000 Off 0 +5 Fz 5 0.1 µV 10 1000 Off 0 +6 F4 6 0.1 µV 10 1000 Off 0 +7 F8 7 0.1 µV 10 1000 Off 0 +8 FC5 8 0.1 µV 10 1000 Off 0 +9 FC1 9 0.1 µV 10 1000 Off 0 +10 FC2 10 0.1 µV 10 1000 Off 0 +11 FC6 11 0.1 µV 10 1000 Off 0 +12 T7 12 0.1 µV 10 1000 Off 0 +13 C3 13 0.1 µV 10 1000 Off 0 +14 Cz 14 0.1 µV 10 1000 Off 0 +15 C4 15 0.1 µV 10 1000 Off 0 +16 T8 16 0.1 µV 10 1000 Off 0 +17 TP9 17 0.1 µV 10 1000 Off 0 +18 CP5 18 0.1 µV 10 1000 Off 0 +19 CP1 19 0.1 µV 10 1000 Off 0 +20 CP2 20 0.1 µV 10 1000 Off 0 +21 CP6 21 0.1 µV 10 1000 Off 0 +22 TP10 22 0.1 µV 10 1000 Off 0 +23 P7 23 0.1 µV 10 1000 Off 0 +24 P3 24 0.1 µV 10 1000 Off 0 +25 Pz 25 0.1 µV 10 1000 Off 0 +26 P4 26 0.1 µV 10 1000 Off 0 +27 P8 27 0.1 µV 10 1000 Off 0 +28 PO9 28 0.1 µV 10 1000 Off 0 +29 O1 29 0.1 µV 10 1000 Off 0 +30 Oz 30 0.1 µV 10 1000 Off 0 +31 O2 31 0.1 µV 10 1000 Off 0 +32 PO10 32 0.1 µV 10 1000 Off 0 +33 AF7 33 0.1 µV 10 1000 Off 0 +34 AF3 34 0.1 µV 10 1000 Off 0 +35 AF4 35 0.1 µV 10 1000 Off 0 +36 AF8 36 0.1 µV 10 1000 Off 0 +37 F5 37 0.1 µV 10 1000 Off 0 +38 F1 38 0.1 µV 10 1000 Off 0 +39 F2 39 0.1 µV 10 1000 Off 0 +40 F6 40 0.1 µV 10 1000 Off 0 +41 FT9 41 0.1 µV 10 1000 Off 0 +42 FT7 42 0.1 µV 10 1000 Off 0 +43 FC3 43 0.1 µV 10 1000 Off 0 +44 FC4 44 0.1 µV 10 1000 Off 0 +45 FT8 45 0.1 µV 10 1000 Off 0 +46 FT10 46 0.1 µV 10 1000 Off 0 +47 C5 47 0.1 µV 10 1000 Off 0 +48 C1 48 0.1 µV 10 1000 Off 0 +49 C2 49 0.1 µV 10 1000 Off 0 +50 C6 50 0.1 µV 10 1000 Off 0 +51 TP7 51 0.1 µV 10 1000 Off 0 +52 CP3 52 0.1 µV 10 1000 Off 0 +53 CPz 53 0.1 µV 10 1000 Off 0 +54 CP4 54 0.1 µV 10 1000 Off 0 +55 TP8 55 0.1 µV 10 1000 Off 0 +56 P5 56 0.1 µV 10 1000 Off 0 +57 P1 57 0.1 µV 10 1000 Off 0 +58 P2 58 0.1 µV 10 1000 Off 0 +59 P6 59 0.1 µV 10 1000 Off 0 +60 PO7 60 0.1 µV 10 1000 Off 0 +61 PO3 61 0.1 µV 10 1000 Off 0 +62 POz 62 0.1 µV 10 1000 Off 0 +63 PO4 63 0.1 µV 10 1000 Off 0 +64 PO8 64 0.1 µV 10 1000 Off 0 +65 ECG 65 0.1 µV 10 1000 Off 0 +66 HEOG 66 0.1 µV 10 1000 Off 0 +67 VEOG 67 0.1 µV 10 1000 Off 0 + +S o f t w a r e F i l t e r s +============================== +Disabled + + +Impedances Imported from actiCAP Control Software: +Impedance [kOhm] at 11:55:00 : +Fp1: 5 +Fp2: 2 +F7: 4 +F3: 1 +Fz: 5 +F4: 0 +F8: 0 +FC5: 0 +FC1: 5 +FC2: 4 +FC6: 0 +T7: 7 +C3: 7 +Cz: 7 +C4: 5 +T8: 3 +TP9: 2 +CP5: 2 +CP1: 1 +CP2: 0 +CP6: 0 +TP10: 6 +P7: 2 +P3: 3 +Pz: 3 +P4: 3 +P8: 2 +PO9: 0 +O1: 0 +Oz: 0 +O2: 1 +PO10: 1 +AF7: 0 +AF3: 0 +AF4: 0 +AF8: 0 +F5: 1 +F1: 0 +F2: 1 +F6: 2 +FT9: 4 +FT7: 4 +FC3: 5 +FC4: 0 +FT8: 0 +FT10: 0 +C5: 0 +C1: 2 +C2: 1 +C6: 0 +TP7: 0 +CP3: 0 +CPz: 0 +CP4: 0 +TP8: 0 +P5: 1 +P1: 0 +P2: 0 +P6: 0 +PO7: 0 +PO3: 0 +POz: 2 +PO4: 0 +PO8: 0 +ECG+: 23 +ECG-: 36 +HEOG+: 3 +HEOG-: 9 +VEOG+: 2 +VEOG-: 5 +Ref: 9 +Gnd: 1 diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vmrk b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vmrk new file mode 100644 index 000000000..37d201ed8 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_eeg.vmrk @@ -0,0 +1,13 @@ +Brain Vision Data Exchange Marker File, Version 1.0 + +[Common Infos] +Codepage=UTF-8 +DataFile=sub-01_ses-eeg_task-rest_eeg.eeg + +[Marker Infos] +; Each entry: Mk=,,, +; , +; Fields are delimited by commas, some fields might be omitted (empty). +; Commas in type or description text are coded as "\1". +Mk1=New Segment,,1,1,0,20000101120000000000 +Mk2=Comment,ControlBox is not connected via USB,1,1,0 diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_events.tsv b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_events.tsv new file mode 100644 index 000000000..77fd1cd00 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/eeg/sub-01_ses-eeg_task-rest_events.tsv @@ -0,0 +1,3 @@ +onset duration trial_type value sample +0.0 0.0 start_experiment 1 0 +0.2 0.0 show_stimulus 2 1000 diff --git a/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/sub-01_ses-eeg_scans.tsv b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/sub-01_ses-eeg_scans.tsv new file mode 100644 index 000000000..d53fe2017 --- /dev/null +++ b/mne_bids/tests/data/tiny_bids/sub-01/ses-eeg/sub-01_ses-eeg_scans.tsv @@ -0,0 +1,2 @@ +filename acq_time +eeg/sub-01_ses-eeg_task-rest_eeg.vhdr 2000-01-01T12:00:00.000000Z diff --git a/mne_bids/tests/test_path.py b/mne_bids/tests/test_path.py index 2954d9bba..17440cf42 100644 --- a/mne_bids/tests/test_path.py +++ b/mne_bids/tests/test_path.py @@ -601,10 +601,11 @@ def test_make_filenames(): prefix_data = dict(subject='one', session='two', task='three', acquisition='four', run=1, processing='six', recording='seven', suffix='ieeg', extension='.json') - expected_str = 'sub-one_ses-two_task-three_acq-four_run-01_proc-six_rec-seven_ieeg.json' # noqa + expected_str = ('sub-one_ses-two_task-three_acq-four_run-01_proc-six_' + 'rec-seven_ieeg.json') assert BIDSPath(**prefix_data).basename == expected_str - assert BIDSPath(**prefix_data) == op.join('sub-one', 'ses-two', - 'ieeg', expected_str) + assert BIDSPath(**prefix_data) == ( + Path('sub-one') / 'ses-two' / 'ieeg' / expected_str).as_posix() # subsets of keys works assert (BIDSPath(subject='one', task='three', run=4).basename == diff --git a/mne_bids/tests/test_read.py b/mne_bids/tests/test_read.py index 1b489878e..99135093b 100644 --- a/mne_bids/tests/test_read.py +++ b/mne_bids/tests/test_read.py @@ -277,8 +277,7 @@ def test_get_head_mri_trans(tmpdir): coords['Nasion'] = coords['NAS'] del coords['LPA'], coords['RPA'], coords['NAS'] - with t1w_json_fpath.open('w', encoding='utf-8') as f: - json.dump(t1w_json, f) + _write_json(t1w_json_fpath, t1w_json, overwrite=True) estimated_trans = get_head_mri_trans( bids_path=bids_path, @@ -551,8 +550,7 @@ def test_handle_chpi_reading(tmpdir): # cHPI frequency mismatch meg_json_data_freq_mismatch = meg_json_data.copy() meg_json_data_freq_mismatch['HeadCoilFrequency'][0] = 123 - with open(meg_json_path, 'w', encoding='utf-8') as f: - json.dump(meg_json_data_freq_mismatch, f) + _write_json(meg_json_path, meg_json_data_freq_mismatch, overwrite=True) with pytest.raises(ValueError, match='cHPI coil frequencies'): raw_read = read_raw_bids(bids_path) @@ -560,8 +558,7 @@ def test_handle_chpi_reading(tmpdir): # cHPI "off" according to sidecar, but present in the data meg_json_data_chpi_mismatch = meg_json_data.copy() meg_json_data_chpi_mismatch['ContinuousHeadLocalization'] = False - with open(meg_json_path, 'w', encoding='utf-8') as f: - json.dump(meg_json_data_chpi_mismatch, f) + _write_json(meg_json_path, meg_json_data_chpi_mismatch, overwrite=True) raw_read = read_raw_bids(bids_path) assert raw_read.info['hpi_subsystem'] is None diff --git a/mne_bids/tsv_handler.py b/mne_bids/tsv_handler.py index f43ae8008..8f05ee6fc 100644 --- a/mne_bids/tsv_handler.py +++ b/mne_bids/tsv_handler.py @@ -168,6 +168,7 @@ def _to_tsv(data, fname): with open(fname, 'w', encoding='utf-8-sig') as f: f.write(output) + f.write('\n') def _tsv_to_str(data, rows=5): diff --git a/mne_bids/write.py b/mne_bids/write.py index 558ec3f5a..2ebcb4008 100644 --- a/mne_bids/write.py +++ b/mne_bids/write.py @@ -1900,30 +1900,40 @@ def mark_bad_channels(ch_names, descriptions=None, *, bids_path, -------- Mark a single channel as bad. - >>> mark_bad_channels('MEG 0112', bids_path=bids_path) - - Mark multiple channels as bad. - - >>> bads = ['MEG 0112', 'MEG 0131'] - >>> mark_bad_channels(bads, bids_path=bids_path) - - Mark channels as bad, and add a description as to why. - - >>> ch_names = ['MEG 0112', 'MEG 0131'] - >>> descriptions = ['Only produced noise', 'Continuously flat'] - >>> mark_bad_channels(bads, descriptions, bbids_path=bids_path) + >>> root = Path('./mne_bids/tests/data/tiny_bids').absolute() + >>> bids_path = BIDSPath(subject='01', task='rest', session='eeg', + ... datatype='eeg', root=root) + >>> mark_bad_channels('C4', bids_path=bids_path, verbose=False) + Processing channel C4: + status: bad + description: n/a + + Mark multiple channels as bad, and add a description as to why. + + >>> bads = ['C3', 'PO10'] + >>> descriptions = ['very noisy', 'continuously flat'] + >>> mark_bad_channels(bads, descriptions, bids_path=bids_path, + ... verbose=False) + Processing channel C3: + status: bad + description: very noisy + Processing channel PO10: + status: bad + description: continuously flat Mark two channels as bad, and mark all others as good by setting ``overwrite=True``. - >>> bads = ['MEG 0112', 'MEG 0131'] - >>> mark_bad_channels(bads, bids_path=bids_path, overwrite=True) + >>> bads = ['C3', 'C4'] + >>> mark_bad_channels(bads, bids_path=bids_path, # doctest: +SKIP + ... overwrite=True, verbose=False) Mark all channels as good by passing an empty list of bad channels, and setting ``overwrite=True``. - >>> mark_bad_channels([], bids_path=bids_path, overwrite=True) - + >>> mark_bad_channels([], bids_path=bids_path, overwrite=True, + ... verbose=False) + Resetting status and description for all channels. """ if not ch_names and not overwrite: raise ValueError('You did not pass a channel name, but set ' @@ -2012,10 +2022,13 @@ def write_meg_calibration(calibration, bids_path, verbose=None): Examples -------- - >>> calibration = mne.preprocessing.read_fine_calibration('sss_cal.dat') - >>> bids_path = BIDSPath(subject='01', session='test', root='/data') - >>> write_meg_calibration(calibration, bids_path) - """ + >>> data_path = mne.datasets.testing.data_path(download=False) + >>> calibration_fname = op.join(data_path, 'SSS', 'sss_cal_3053.dat') + >>> bids_path = BIDSPath(subject='01', session='test', + ... root=op.join(data_path, 'mne_bids')) + >>> write_meg_calibration(calibration_fname, bids_path) # doctest: +ELLIPSIS + Writing fine-calibration file to ...sub-01_ses-test_acq-calibration_meg.dat... + """ # noqa: E501 if bids_path.root is None or bids_path.subject is None: raise ValueError('bids_path must have root and subject set.') if bids_path.datatype not in (None, 'meg'): @@ -2066,9 +2079,12 @@ def write_meg_crosstalk(fname, bids_path, verbose=None): Examples -------- - >>> crosstalk_fname = 'ct_sparse.fif' - >>> bids_path = BIDSPath(subject='01', session='test', root='/data') - >>> write_megcrosstalk(crosstalk_fname, bids_path) + >>> data_path = mne.datasets.testing.data_path(download=False) + >>> crosstalk_fname = op.join(data_path, 'SSS', 'ct_sparse.fif') + >>> bids_path = BIDSPath(subject='01', session='test', + ... root=op.join(data_path, 'mne_bids')) + >>> write_meg_crosstalk(crosstalk_fname, bids_path) # doctest: +ELLIPSIS + Writing crosstalk file to ...sub-01_ses-test_acq-crosstalk_meg.fif """ if bids_path.root is None or bids_path.subject is None: raise ValueError('bids_path must have root and subject set.')