Skip to content

Commit

Permalink
MRG: Write events.json with "sample", "value", and "trial_type" entri…
Browse files Browse the repository at this point in the history
…es (#1132)

Write events.json with "sample", "value", and "trial_type" entries

Fixes #1130

Co-authored-by: Stefan Appelhoff <stefan.appelhoff@mailbox.org>
  • Loading branch information
hoechenberger and sappelhoff authored Apr 25, 2023
1 parent 82867d4 commit f599e76
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 16 deletions.
1 change: 1 addition & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Detailed list of changes
🧐 API and behavior changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^

- When writing events, we now also create an ``*_events.json`` file in addition to ``*_events.tsv``. This ensures compatibility with the upcoming release of BIDS 1.9, by `Richard Höchenberger`_ (:gh:`1132`)
- We silenced warnings about missing ``events.tsv`` files when reading empty-room or resting-state data, by `Richard Höchenberger`_ (:gh:`1133`)

🛠 Requirements
Expand Down
5 changes: 4 additions & 1 deletion examples/convert_mne_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
session='01',
task=task,
run='1',
datatype='meg',
root=output_path
)
write_raw_bids(
Expand All @@ -110,7 +111,9 @@
# sidecar files that describe our data is correct.

# Get the sidecar ``.json`` file
sidecar_json_bids_path = bids_path.copy().update(extension='.json')
sidecar_json_bids_path = bids_path.copy().update(
suffix='meg', extension='.json'
)
sidecar_json_content = sidecar_json_bids_path.fpath.read_text(
encoding='utf-8-sig'
)
Expand Down
6 changes: 3 additions & 3 deletions mne_bids/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,9 +911,9 @@ def find_empty_room(self, use_sidecar_only=False, *, verbose=None):
'to set the root of the BIDS folder to read.')

# needed to deal with inheritance principle
sidecar_fname = \
self.copy().update(datatype=None).find_matching_sidecar(
extension='.json')
sidecar_fname = self.copy().update(
datatype=None, suffix='meg'
).find_matching_sidecar(extension='.json')
with open(sidecar_fname, 'r', encoding='utf-8') as f:
sidecar_json = json.load(f)

Expand Down
9 changes: 8 additions & 1 deletion mne_bids/tests/test_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -2647,6 +2647,7 @@ def test_annotations_and_events(_bids_validate, tmp_path, write_events):
suffix='events',
extension='.tsv',
)
events_json_fname = events_tsv_fname.copy().update(extension='.json')

events = mne.read_events(events_fname)
events = events[events[:, 2] != 0] # drop unknown "0" events
Expand Down Expand Up @@ -2699,6 +2700,12 @@ def test_annotations_and_events(_bids_validate, tmp_path, write_events):

if write_events:
n_events_expected = len(events) + len(raw.annotations)
events_json = json.loads(
events_json_fname.fpath.read_text(encoding='utf-8')
)
assert 'value' in events_json
assert 'sample' in events_json
assert 'trial_type' in events_json
else:
n_events_expected = len(raw.annotations)

Expand Down Expand Up @@ -3752,7 +3759,7 @@ def test_anonymize_dataset(_bids_validate, tmpdir):
# Ensure that additional JSON sidecar fields are transferred if they are
# "safe", and are omitted if they are not whitelisted
bids_path.datatype = 'meg'
meg_json_path = bids_path.copy().update(extension='.json')
meg_json_path = bids_path.copy().update(suffix='meg', extension='.json')
meg_json = json.loads(meg_json_path.fpath.read_text(encoding='utf-8'))
assert 'Instructions' not in meg_json # ensure following test makes sense
meg_json['Instructions'] = 'Foo'
Expand Down
63 changes: 52 additions & 11 deletions mne_bids/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,43 @@ def _events_tsv(events, durations, raw, fname, trial_type, overwrite=False):
_write_tsv(fname, data, overwrite)


def _events_json(fname, overwrite=False):
"""Create participants.json for non-default columns in accompanying TSV.
Parameters
----------
fname : str | mne_bids.BIDSPath
Output filename.
overwrite : bool
Whether to overwrite the output file if it exists.
"""
new_data = {
'sample': {
'Description': 'The event onset time in number of sampling points.'
},
'value': {
'Description': (
'The event code (also known as trigger code or event ID) '
'associated with the event.'
)
},
'trial_type': {
'Description': 'The type, category, or name of the event.'
},
}

# make sure to append any JSON fields added by the user
fname = Path(fname)
if fname.exists():
orig_data = json.loads(
fname.read_text(encoding='utf-8'),
object_pairs_hook=OrderedDict
)
new_data = {**orig_data, **new_data}

_write_json(fname, new_data, overwrite)


def _readme(datatype, fname, overwrite=False):
"""Create a README file and save it.
Expand Down Expand Up @@ -454,15 +491,15 @@ def _participants_json(fname, overwrite=False):
Parameters
----------
fname : str | mne_bids.BIDSPath
Filename to save the scans.tsv to.
Output filename.
overwrite : bool
Defaults to False.
Whether to overwrite the existing data in the file.
If there is already data for the given `fname` and overwrite is False,
an error will be raised.
"""
data = {
new_data = {
'participant_id': {
'Description': 'Unique participant identifier'
},
Expand Down Expand Up @@ -500,15 +537,13 @@ def _participants_json(fname, overwrite=False):
# if `overwrite` is True
fname = Path(fname)
if fname.exists():
orig_cols = json.loads(
orig_data = json.loads(
fname.read_text(encoding='utf-8'),
object_pairs_hook=OrderedDict
)
for key, val in orig_cols.items():
if key not in data:
data[key] = val
new_data = {**orig_data, **new_data}

_write_json(fname, data, overwrite)
_write_json(fname, new_data, overwrite)


def _scans_tsv(raw, raw_fname, fname, keep_source, overwrite=False):
Expand Down Expand Up @@ -1763,7 +1798,10 @@ def write_raw_bids(

sidecar_path = bids_path.copy().update(suffix=bids_path.datatype,
extension='.json')
events_path = bids_path.copy().update(suffix='events', extension='.tsv')
events_tsv_path = bids_path.copy().update(
suffix='events', extension='.tsv'
)
events_json_path = events_tsv_path.copy().update(extension='.json')
channels_path = bids_path.copy().update(
suffix='channels', extension='.tsv')

Expand Down Expand Up @@ -1838,9 +1876,12 @@ def write_raw_bids(
events_array, event_dur, event_desc_id_map = _read_events(
events, event_id, raw, bids_path=bids_path)
if events_array.size != 0:
_events_tsv(events=events_array, durations=event_dur, raw=raw,
fname=events_path.fpath, trial_type=event_desc_id_map,
overwrite=overwrite)
_events_tsv(
events=events_array, durations=event_dur, raw=raw,
fname=events_tsv_path.fpath, trial_type=event_desc_id_map,
overwrite=overwrite
)
_events_json(fname=events_json_path.fpath, overwrite=overwrite)
# Kepp events_array around for BrainVision writing below.
del event_desc_id_map, events, event_id, event_dur

Expand Down

0 comments on commit f599e76

Please sign in to comment.