def _handle_events_reading(events_fname, raw,combine_trialType_value=True): """Read associated events.tsv and populate raw. Handle onset, duration, and description of each event. """ logger.info('Reading events from {}.'.format(events_fname)) events_dict = _from_tsv(events_fname) # Get the descriptions of the events if 'trial_type' in events_dict: if combine_trialType_value and ('value' in events_dict): descriptions = np.asarray([a+':'+b for a,b in zip(events_dict["trial_type"],events_dict["value"])], dtype=str) # Drop events unrelated to a trial type events_dict = _drop(events_dict, 'n/a', 'trial_type') descriptions = np.asarray(events_dict['trial_type'], dtype=str) # If we don't have a proper description of the events, perhaps we have # at least an event value? elif 'value' in events_dict: # Drop events unrelated to value events_dict = _drop(events_dict, 'n/a', 'value') descriptions = np.asarray(events_dict['value'], dtype=str) # Worst case, we go with 'n/a' for all events else: descriptions = 'n/a' # Deal with "n/a" strings before converting to float ons = [np.nan if on == 'n/a' else on for on in events_dict['onset']] dus = [0 if du == 'n/a' else du for du in events_dict['duration']] onsets = np.asarray(ons, dtype=float) durations = np.asarray(dus, dtype=float) # Keep only events where onset is known good_events_idx = ~np.isnan(onsets) onsets = onsets[good_events_idx] durations = durations[good_events_idx] descriptions = descriptions[good_events_idx] del good_events_idx # Add Events to raw as annotations annot_from_events = mne.Annotations(onset=onsets, duration=durations, description=descriptions, orig_time=None) raw.set_annotations(annot_from_events) return raw
def _handle_events_reading(events_fname, raw): """Read associated events.tsv and populate raw. Handle onset, duration, and description of each event. """ logger.info('Reading events from {}.'.format(events_fname)) events_dict = _from_tsv(events_fname) # Get the descriptions of the events if 'trial_type' in events_dict: # Drop events unrelated to a trial type events_dict = _drop(events_dict, 'n/a', 'trial_type') descriptions = np.asarray(events_dict['trial_type'], dtype=str) # If we don't have a proper description of the events, perhaps we have # at least an event value? elif 'value' in events_dict: # Drop events unrelated to value events_dict = _drop(events_dict, 'n/a', 'value') descriptions = np.asarray(events_dict['value'], dtype=str) # Worst case, we go with 'n/a' for all events else: descriptions = 'n/a' # Deal with "n/a" strings before converting to float ons = [np.nan if on == 'n/a' else on for on in events_dict['onset']] dus = [np.nan if du == 'n/a' else du for du in events_dict['duration']] # Add Events to raw as annotations onsets = np.asarray(ons, dtype=float) durations = np.asarray(dus, dtype=float) annot_from_events = mne.Annotations(onset=onsets, duration=durations, description=descriptions, orig_time=None) raw.set_annotations(annot_from_events) return raw
def test_drop_different_types(): """Test that _drop() can handle different dtypes without warning. This is to check if we're successfully avoiding a FutureWarning emitted by NumPy, see https://github.com/mne-tools/mne-bids/pull/372 (pytest must be configured to fail on warnings for this to work!) """ column = 'age' data = odict([(column, [20, 30, 40, 'n/a'])]) # string values_to_drop = (20, ) # int result = _drop(data, values=values_to_drop, column=column) for value in values_to_drop: assert value not in result
def test_tsv_handler(tmp_path): """Test the TSV handling.""" # create some dummy data d = odict(a=[1, 2, 3, 4], b=['five', 'six', 'seven', 'eight']) assert _contains_row(d, {'a': 1, 'b': 'five'}) d2 = odict(a=[5], b=['nine']) d = _combine_rows(d, d2) assert 5 in d['a'] d2 = odict(a=[5]) d = _combine_rows(d, d2) assert 'n/a' in d['b'] d2 = odict(a=[5], b=['ten']) d = _combine_rows(d, d2, drop_column='a') # make sure that the repeated data was dropped assert 'nine' not in d['b'] print(_tsv_to_str(d)) d_path = tmp_path / 'output.tsv' # write the data to an output tsv file _to_tsv(d, d_path) # now read it back d = _from_tsv(d_path) # test reading the file in with the incorrect number of datatypes raises # an Error with pytest.raises(ValueError): d = _from_tsv(d_path, dtypes=[str]) # we can also pass just a single data type and it will be applied to all # columns d = _from_tsv(d_path, str) # remove any rows with 2 or 5 in them d = _drop(d, [2, 5], 'a') assert 2 not in d['a'] # test combining data with differing numbers of columns d = odict(a=[1, 2], b=['three', 'four']) d2 = odict(a=[4], b=['five'], c=[3.1415]) # raise error if a new column is tried to be added with pytest.raises(KeyError): d = _combine_rows(d, d2) d2 = odict(a=[5]) d = _combine_rows(d, d2) assert d['b'] == ['three', 'four', 'n/a'] assert _contains_row(d, {'a': 5}) # test reading a single column _to_tsv(odict(a=[1, 2, 3, 4]), d_path) d = _from_tsv(d_path) assert d['a'] == ['1', '2', '3', '4']
def _handle_events_reading(events_fname, raw): """Read associated events.tsv and populate raw. Handle onset, duration, and description of each event. """ logger.info('Reading events from {}.'.format(events_fname)) events_dict = _from_tsv(events_fname) # Get the descriptions of the events if 'trial_type' in events_dict: trial_type_col_name = 'trial_type' elif 'stim_type' in events_dict: # Backward-compat with old datasets. trial_type_col_name = 'stim_type' warn(f'The events file, {events_fname}, contains a "stim_type" ' f'column. This column should be renamed to "trial_type" for ' f'BIDS compatibility.') else: trial_type_col_name = None if trial_type_col_name is not None: # Drop events unrelated to a trial type events_dict = _drop(events_dict, 'n/a', trial_type_col_name) if 'value' in events_dict: # Check whether the `trial_type` <> `value` mapping is unique. trial_types = events_dict[trial_type_col_name] values = np.asarray(events_dict['value'], dtype=str) for trial_type in np.unique(trial_types): idx = np.where(trial_type == np.atleast_1d(trial_types))[0] matching_values = values[idx] if len(np.unique(matching_values)) > 1: # Event type descriptors are ambiguous; create hierarchical # event descriptors. logger.info( f'The event "{trial_type}" refers to multiple event ' f'values. Creating hierarchical event names.') for ii in idx: new_name = f'{trial_type}/{values[ii]}' logger.info(f' Renaming event: {trial_type} -> ' f'{new_name}') trial_types[ii] = new_name descriptions = np.asarray(trial_types, dtype=str) else: descriptions = np.asarray(events_dict[trial_type_col_name], dtype=str) elif 'value' in events_dict: # If we don't have a proper description of the events, perhaps we have # at least an event value? # Drop events unrelated to value events_dict = _drop(events_dict, 'n/a', 'value') descriptions = np.asarray(events_dict['value'], dtype=str) # Worst case, we go with 'n/a' for all events else: descriptions = np.array(['n/a'] * len(events_dict['onset']), dtype=str) # Deal with "n/a" strings before converting to float ons = [np.nan if on == 'n/a' else on for on in events_dict['onset']] dus = [0 if du == 'n/a' else du for du in events_dict['duration']] onsets = np.asarray(ons, dtype=float) durations = np.asarray(dus, dtype=float) # Keep only events where onset is known good_events_idx = ~np.isnan(onsets) onsets = onsets[good_events_idx] durations = durations[good_events_idx] descriptions = descriptions[good_events_idx] del good_events_idx # Add Events to raw as annotations annot_from_events = mne.Annotations(onset=onsets, duration=durations, description=descriptions, orig_time=None) raw.set_annotations(annot_from_events) return raw
def _channels_tsv(raw, fname, overwrite=False, verbose=True): """Create a channels.tsv file and save it. Parameters ---------- raw : instance of Raw The data as MNE-Python Raw object. fname : str Filename to save the channels.tsv to. overwrite : bool Whether to overwrite the existing file. Defaults to False. verbose : bool Set verbose output to true or false. """ # Get channel type mappings between BIDS and MNE nomenclatures map_chs = _get_ch_type_mapping(fro='mne', to='bids') # Prepare the descriptions for each channel type map_desc = defaultdict(lambda: 'Other type of channel') map_desc.update(meggradaxial='Axial Gradiometer', megrefgradaxial='Axial Gradiometer Reference', meggradplanar='Planar Gradiometer', megmag='Magnetometer', megrefmag='Magnetometer Reference', stim='Trigger', eeg='ElectroEncephaloGram', ecog='Electrocorticography', seeg='StereoEEG', ecg='ElectroCardioGram', eog='ElectroOculoGram', emg='ElectroMyoGram', misc='Miscellaneous') get_specific = ('mag', 'ref_meg', 'grad') # get the manufacturer from the file in the Raw object manufacturer = None _, ext = _parse_ext(raw.filenames[0], verbose=verbose) manufacturer = MANUFACTURERS[ext] ignored_channels = IGNORED_CHANNELS.get(manufacturer, list()) status, ch_type, description = list(), list(), list() for idx, ch in enumerate(raw.info['ch_names']): status.append('bad' if ch in raw.info['bads'] else 'good') _channel_type = channel_type(raw.info, idx) if _channel_type in get_specific: _channel_type = coil_type(raw.info, idx, _channel_type) ch_type.append(map_chs[_channel_type]) description.append(map_desc[_channel_type]) low_cutoff, high_cutoff = (raw.info['highpass'], raw.info['lowpass']) if raw._orig_units: units = [raw._orig_units.get(ch, 'n/a') for ch in raw.ch_names] else: units = [_unit2human.get(ch_i['unit'], 'n/a') for ch_i in raw.info['chs']] units = [u if u not in ['NA'] else 'n/a' for u in units] n_channels = raw.info['nchan'] sfreq = raw.info['sfreq'] ch_data = OrderedDict([ ('name', raw.info['ch_names']), ('type', ch_type), ('units', units), ('low_cutoff', np.full((n_channels), low_cutoff)), ('high_cutoff', np.full((n_channels), high_cutoff)), ('description', description), ('sampling_frequency', np.full((n_channels), sfreq)), ('status', status)]) ch_data = _drop(ch_data, ignored_channels, 'name') _write_tsv(fname, ch_data, overwrite, verbose) return fname