Skip to content

Commit

Permalink
Deprecate write_raw_bids()'s events_data parameter in favor of "event…
Browse files Browse the repository at this point in the history
…s" (#1054)

* Deprecate write_raw_bids()'s events_data parameter in favor of "events"

For more consistency with MNE-Python.

This is WIP.

WDYT?

* Fix a few tests

* Fix examples

* Update changelog

* Additional changelog entry

* Fix CLI

* Better backward-compat

* Fix CLI

* Add test

* Fix test

* Raise ValueError instead of RuntimeError

* Schedule removal for MNE-BIDS 0.14
  • Loading branch information
hoechenberger committed Aug 21, 2022
1 parent 7607a0e commit 18999ca
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 104 deletions.
6 changes: 6 additions & 0 deletions doc/whats_new.rst
Expand Up @@ -54,6 +54,10 @@ Detailed list of changes
🧐 API and behavior changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^

- :func:`~mne_bids.write_raw_bids` now expects all but the first four parameters to be passed as keyword arguments, by `Richard Höchenberger`_ (:gh:`1054`)

- The ``events_data`` parameter of :func:`~mne_bids.write_raw_bids` has been deprecated in favor of a new parameter named ``events``. This ensures more consistency between the MNE-BIDS and MNE-Python APIs. You may continue using the ``events_data`` parameter for now, but a ``FutureWarning`` will be raised. ``events_data`` will be removed in MNE-BIDS 0.14, by `Richard Höchenberger`_ (:gh:`1054`)

- In many places, we used to infer the ``datatype`` of a :class:`~mne_bids.BIDSPath` from the ``suffix``, if not explicitly provided. However, this has lead to trouble in certain edge cases. In an effort to reduce the amount of implicit behavior in MNE-BIDS, we now require users to explicitly specify a ``datatype`` whenever the invoked functions or methods expect one, by `Richard Höchenberger`_ (:gh:`1030`)

- :func:`mne_bids.make_dataset_description` now accepts keyword arguments only, and can now also write the following metadata: ``HEDVersion``, ``EthicsApprovals``, ``GeneratedBy``, and ``SourceDatasets``, by `Stefan Appelhoff`_ (:gh:`406`)
Expand All @@ -62,6 +66,8 @@ Detailed list of changes

- :func:`mne_bids.print_dir_tree` now raises a :py:class:`FileNotFoundError` instead of a :py:class:`ValueError` if the directory does not exist, by `Richard Höchenberger`_ (:gh:`1013`)

- Passing only one of ``events`` and ``event_id`` to :func:`~mne_bids.write_raw_bids` now raises a ``ValueError`` instead of a ``RuntimeError``, by `Richard Höchenberger`_ (:gh:`1054`)

🛠 Requirements
^^^^^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion examples/anonymize_dataset.py
Expand Up @@ -75,7 +75,7 @@

# Write experimental MEG data, fine-calibration and crosstalk files
write_raw_bids(
raw=raw, bids_path=bids_path, events_data=events_path, event_id=event_id,
raw=raw, bids_path=bids_path, events=events_path, event_id=event_id,
empty_room=bids_path_er, verbose=False
)
write_meg_calibration(cal_path, bids_path=bids_path, verbose=False)
Expand Down
3 changes: 1 addition & 2 deletions examples/convert_empty_room.py
Expand Up @@ -59,8 +59,7 @@
shutil.rmtree(bids_root)

# %%
# Specify the raw_file and events_data and run the BIDS conversion, and write
# the BIDS data.
# Specify the raw file and write the BIDS data.

raw = mne.io.read_raw_fif(raw_fname)
raw.info['line_freq'] = 60 # specify power line frequency as required by BIDS
Expand Down
6 changes: 3 additions & 3 deletions examples/convert_mne_sample.py
Expand Up @@ -44,7 +44,7 @@
# Now we can read the MNE sample data. We define an `event_id` based on our
# knowledge of the data, to give meaning to events in the data.
#
# With `raw_fname` and `events_data`, we determine where to get the sample data
# With `raw_fname` and `events`, we determine where to get the sample data
# from. `output_path` determines where we will write the BIDS conversion to.

data_path = sample.data_path()
Expand All @@ -53,7 +53,7 @@

raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
er_fname = op.join(data_path, 'MEG', 'sample', 'ernoise_raw.fif') # empty room
events_data = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-eve.fif')
events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-eve.fif')
output_path = op.join(data_path, '..', 'MNE-sample-data-bids')

# %%
Expand Down Expand Up @@ -99,7 +99,7 @@
write_raw_bids(
raw=raw,
bids_path=bids_path,
events_data=events_data,
events=events_fname,
event_id=event_id,
empty_room=raw_er,
overwrite=True
Expand Down
4 changes: 2 additions & 2 deletions examples/convert_mri_and_trans.py
Expand Up @@ -61,7 +61,7 @@
event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3,
'Visual/Right': 4, 'Smiley': 5, 'Button': 32}
raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif')
events_data = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-eve.fif')
events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-eve.fif')
output_path = op.abspath(op.join(data_path, '..', 'MNE-sample-data-bids'))
fs_subjects_dir = op.join(data_path, 'subjects') # FreeSurfer subjects dir

Expand All @@ -87,7 +87,7 @@
run = '01'
bids_path = BIDSPath(subject=sub, session=ses, task=task,
run=run, root=output_path)
write_raw_bids(raw, bids_path, events_data=events_data,
write_raw_bids(raw, bids_path, events=events_fname,
event_id=event_id, overwrite=True)

# %%
Expand Down
2 changes: 1 addition & 1 deletion examples/mark_bad_channels.py
Expand Up @@ -57,7 +57,7 @@

raw = mne.io.read_raw_fif(raw_fname, verbose=False)
raw.info['line_freq'] = 60 # Specify power line frequency as required by BIDS.
write_raw_bids(raw, bids_path=bids_path, events_data=events_fname,
write_raw_bids(raw, bids_path=bids_path, events=events_fname,
event_id=event_id, overwrite=True, verbose=False)

# %%
Expand Down
7 changes: 5 additions & 2 deletions mne_bids/commands/mne_bids_raw_to_bids.py
Expand Up @@ -37,6 +37,8 @@ def run():
parser.add_option('--acq', dest='acq',
help='acquisition parameter for this dataset')
parser.add_option('--events_data', dest='events_data',
help='Deprecated. Pass --events instead.')
parser.add_option('--events', dest='events',
help='events file (events.tsv)')
parser.add_option('--event_id', dest='event_id',
help='event id dict', metavar='eid')
Expand Down Expand Up @@ -80,9 +82,10 @@ def run():
if opt.line_freq is not None:
line_freq = None if opt.line_freq == "None" else opt.line_freq
raw.info['line_freq'] = line_freq

write_raw_bids(raw, bids_path, event_id=opt.event_id,
events_data=opt.events_data, overwrite=opt.overwrite,
verbose=True)
events=opt.events, overwrite=opt.overwrite,
events_data=opt.events_data, verbose=True)


if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions mne_bids/commands/tests/test_cli.py
Expand Up @@ -257,8 +257,8 @@ def test_count_events(tmp_path):
'visual/right': 4, 'face': 5, 'button': 32}

bids_path = BIDSPath(subject='01', root=output_path, task='foo')
write_raw_bids(raw, bids_path, events, event_id, overwrite=True,
verbose=False)
write_raw_bids(raw, bids_path, events=events, event_id=event_id,
overwrite=True, verbose=False)

with ArgvSetter(('--bids_root', output_path)):
mne_bids_count_events.run()
Expand Down
2 changes: 1 addition & 1 deletion mne_bids/inspect.py
Expand Up @@ -239,7 +239,7 @@ def _save_annotations(*, annotations, bids_path):
raw = read_raw_bids(bids_path=bids_path, extra_params=extra_params,
verbose='warning')
raw.set_annotations(annotations)
events, durs, descrs = _read_events(events_data=None, event_id=None,
events, durs, descrs = _read_events(events=None, event_id=None,
bids_path=bids_path, raw=raw)

# Write sidecar – or remove it if no events are left.
Expand Down
26 changes: 13 additions & 13 deletions mne_bids/read.py
Expand Up @@ -74,12 +74,12 @@ def _read_raw(raw_path, electrode=None, hsp=None, hpi=None,
return raw


def _read_events(events_data, event_id, raw, bids_path=None):
def _read_events(events, event_id, raw, bids_path=None):
"""Retrieve events (for use in *_events.tsv) from FIFF/array & Annotations.
Parameters
----------
events_data : path-like | np.ndarray | None
events : path-like | np.ndarray | None
If a string, a path to an events file. If an array, an MNE events array
(shape n_events, 3). If None, events will be generated from
``raw.annotations``.
Expand Down Expand Up @@ -107,19 +107,19 @@ def _read_events(events_data, event_id, raw, bids_path=None):
the values to the event IDs.
"""
# get events from events_data
if isinstance(events_data, np.ndarray):
if events_data.ndim != 2:
# retrieve events
if isinstance(events, np.ndarray):
if events.ndim != 2:
raise ValueError('Events must have two dimensions, '
f'found {events_data.ndim}')
if events_data.shape[1] != 3:
f'found {events.ndim}')
if events.shape[1] != 3:
raise ValueError('Events must have second dimension of length 3, '
f'found {events_data.shape[1]}')
events = events_data
elif events_data is None:
f'found {events.shape[1]}')
events = events
elif events is None:
events = np.empty(shape=(0, 3), dtype=int)
else:
events = read_events(events_data).astype(int)
events = read_events(events).astype(int)

if events.size > 0:
# Only keep events for which we have an ID <> description mapping.
Expand All @@ -129,7 +129,7 @@ def _read_events(events_data, event_id, raw, bids_path=None):
f'No description was specified for the following event(s): '
f'{", ".join([str(x) for x in sorted(ids_without_desc)])}. '
f'Please add them to the event_id dictionary, or drop them '
f'from the events_data array.'
f'from the events array.'
)
del ids_without_desc
mask = [e in list(event_id.values()) for e in events[:, 2]]
Expand Down Expand Up @@ -175,7 +175,7 @@ def _read_events(events_data, event_id, raw, bids_path=None):
)
):
warn('No events found or provided. Please add annotations to the raw '
'data, or provide the events_data and event_id parameters. For '
'data, or provide the events and event_id parameters. For '
'resting state data, BIDS recommends naming the task using '
'labels beginning with "rest".')

Expand Down
Expand Up @@ -46,7 +46,7 @@
# %%

write_raw_bids(
raw, bids_path, events_data=events, event_id=event_id, overwrite=True
raw, bids_path, events=events, event_id=event_id, overwrite=True
)

# %%
Expand Down
2 changes: 1 addition & 1 deletion mne_bids/tests/test_inspect.py
Expand Up @@ -47,7 +47,7 @@ def setup_bids_test_dir(bids_root):
events = events[events[:, 2] != 0]

bids_path = _bids_path.copy().update(root=bids_root)
write_raw_bids(raw, bids_path=bids_path, events_data=events,
write_raw_bids(raw, bids_path=bids_path, events=events,
event_id=event_id, overwrite=True)
write_meg_calibration(cal_fname, bids_path=bids_path)
write_meg_crosstalk(crosstalk_fname, bids_path=bids_path)
Expand Down
2 changes: 1 addition & 1 deletion mne_bids/tests/test_path.py
Expand Up @@ -64,7 +64,7 @@ def return_bids_test_dir(tmp_path_factory):
# Write multiple runs for test_purposes
for run_idx in [run, '02']:
name = bids_path.copy().update(run=run_idx)
write_raw_bids(raw, name, events_data=events,
write_raw_bids(raw, name, events=events,
event_id=event_id, overwrite=True)

write_meg_calibration(cal_fname, bids_path=bids_path)
Expand Down
4 changes: 2 additions & 2 deletions mne_bids/tests/test_read.py
Expand Up @@ -229,7 +229,7 @@ def test_get_head_mri_trans(tmp_path):
bids_path = _bids_path.copy().update(
root=tmp_path, datatype='meg', suffix='meg'
)
write_raw_bids(raw, bids_path, events_data=events, event_id=event_id,
write_raw_bids(raw, bids_path, events=events, event_id=event_id,
overwrite=False)

# We cannot recover trans if no MRI has yet been written
Expand Down Expand Up @@ -293,7 +293,7 @@ def test_get_head_mri_trans(tmp_path):
# test we are permissive for different casings of landmark names in the
# sidecar, and also accept "nasion" instead of just "NAS"
raw = _read_raw_fif(raw_fname)
write_raw_bids(raw, bids_path, events_data=events, event_id=event_id,
write_raw_bids(raw, bids_path, events=events, event_id=event_id,
overwrite=True) # overwrite with new acq
t1w_bids_path = write_anat(
t1w_mgh, bids_path=t1w_bids_path, landmarks=landmarks, overwrite=True
Expand Down
4 changes: 2 additions & 2 deletions mne_bids/tests/test_stats.py
Expand Up @@ -36,8 +36,8 @@ def _make_dataset(root, subjects, tasks=(None,), runs=(None,),
bids_path = BIDSPath(
subject=subject, session=session, run=run, task=task, root=root,
)
write_raw_bids(raw, bids_path, events, event_id, overwrite=True,
verbose=False)
write_raw_bids(raw, bids_path, events=events, event_id=event_id,
overwrite=True, verbose=False)

return root, events, event_id

Expand Down
2 changes: 1 addition & 1 deletion mne_bids/tests/test_update.py
Expand Up @@ -58,7 +58,7 @@ def _get_bids_test_dir(tmp_path_factory):
# Write multiple runs for test_purposes
for run_idx in [run, '02']:
name = bids_path.copy().update(run=run_idx)
write_raw_bids(raw, name, events_data=events,
write_raw_bids(raw, name, events=events,
event_id=event_id, overwrite=True)

write_meg_calibration(cal_fname, bids_path=bids_path)
Expand Down

0 comments on commit 18999ca

Please sign in to comment.