def _handle_channels_reading(channels_fname, raw): """Read associated channels.tsv and populate raw. Updates status (bad) and types of channels. """ logger.info('Reading channel info from {}.'.format(channels_fname)) channels_dict = _from_tsv(channels_fname) ch_names_tsv = channels_dict['name'] # Now we can do some work. # The "type" column is mandatory in BIDS. We can use it to set channel # types in the raw data using a mapping between channel types channel_type_dict = dict() # Get the best mapping we currently have from BIDS to MNE nomenclature bids_to_mne_ch_types = _get_ch_type_mapping(fro='bids', to='mne') ch_types_json = channels_dict['type'] for ch_name, ch_type in zip(ch_names_tsv, ch_types_json): # Try to map from BIDS nomenclature to MNE, leave channel type # untouched if we are uncertain updated_ch_type = bids_to_mne_ch_types.get(ch_type, None) if updated_ch_type is None: # XXX Try again with uppercase spelling – this should be removed # XXX once https://github.com/bids-standard/bids-validator/issues/1018 # noqa:E501 # XXX has been resolved. # XXX x-ref https://github.com/mne-tools/mne-bids/issues/481 updated_ch_type = bids_to_mne_ch_types.get(ch_type.upper(), None) if updated_ch_type is not None: msg = ('The BIDS dataset contains channel types in lowercase ' 'spelling. This violates the BIDS specification and ' 'will raise an error in the future.') warn(msg) if updated_ch_type is not None: channel_type_dict[ch_name] = updated_ch_type # Rename channels in loaded Raw to match those read from the BIDS sidecar for bids_ch_name, raw_ch_name in zip(ch_names_tsv, raw.ch_names.copy()): if bids_ch_name != raw_ch_name: raw.rename_channels({raw_ch_name: bids_ch_name}) # Set the channel types in the raw data according to channels.tsv raw.set_channel_types(channel_type_dict) # Set bad channels based on _channels.tsv sidecar if 'status' in channels_dict: bads = _get_bads_from_tsv_data(channels_dict) raw.info['bads'] = bads return raw
def _handle_channels_reading(channels_fname, bids_fname, raw): """Read associated channels.tsv and populate raw. Updates status (bad) and types of channels. """ logger.info('Reading channel info from {}.'.format(channels_fname)) channels_dict = _from_tsv(channels_fname) # First, make sure that ordering of names in channels.tsv matches the # ordering of names in the raw data. The "name" column is mandatory in BIDS ch_names_raw = list(raw.ch_names) ch_names_tsv = channels_dict['name'] if ch_names_raw != ch_names_tsv: msg = ('Channels do not correspond between raw data and the ' 'channels.tsv file. For MNE-BIDS, the channel names in the ' 'tsv MUST be equal and in the same order as the channels in ' 'the raw data.\n\n' '{} channels in tsv file: "{}"\n\n --> {}\n\n' '{} channels in raw file: "{}"\n\n --> {}\n\n'.format( len(ch_names_tsv), channels_fname, ch_names_tsv, len(ch_names_raw), bids_fname, ch_names_raw)) # XXX: this could be due to MNE inserting a 'STI 014' channel as the # last channel: In that case, we can work. --> Can be removed soon, # because MNE will stop the synthesis of stim channels in the near # future if not (ch_names_raw[-1] == 'STI 014' and ch_names_raw[:-1] == ch_names_tsv): raise RuntimeError(msg) # Now we can do some work. # The "type" column is mandatory in BIDS. We can use it to set channel # types in the raw data using a mapping between channel types channel_type_dict = dict() # Get the best mapping we currently have from BIDS to MNE nomenclature bids_to_mne_ch_types = _get_ch_type_mapping(fro='bids', to='mne') ch_types_json = channels_dict['type'] for ch_name, ch_type in zip(ch_names_tsv, ch_types_json): # Try to map from BIDS nomenclature to MNE, leave channel type # untouched if we are uncertain updated_ch_type = bids_to_mne_ch_types.get(ch_type, None) if updated_ch_type is None: # XXX Try again with uppercase spelling – this should be removed # XXX once https://github.com/bids-standard/bids-validator/issues/1018 # noqa:E501 # XXX has been resolved. # XXX x-ref https://github.com/mne-tools/mne-bids/issues/481 updated_ch_type = bids_to_mne_ch_types.get(ch_type.upper(), None) if updated_ch_type is not None: msg = ('The BIDS dataset contains channel types in lowercase ' 'spelling. This violates the BIDS specification and ' 'will raise an error in the future.') warn(msg) if updated_ch_type is not None: channel_type_dict[ch_name] = updated_ch_type # Set the channel types in the raw data according to channels.tsv raw.set_channel_types(channel_type_dict) # Check whether there is the optional "status" column from which to infer # good and bad channels if 'status' in channels_dict: # find bads from channels.tsv bads_from_tsv = _get_bads_from_tsv_data(channels_dict) if raw.info['bads'] and set(bads_from_tsv) != set(raw.info['bads']): warn(f'Encountered conflicting information on channel status ' f'between {op.basename(channels_fname)} and the associated ' f'raw data file.\n' f'Channels marked as bad in ' f'{op.basename(channels_fname)}: {bads_from_tsv}\n' f'Channels marked as bad in ' f'raw.info["bads"]: {raw.info["bads"]}\n' f'Setting list of bad channels to: {bads_from_tsv}') raw.info['bads'] = bads_from_tsv elif raw.info['bads']: # We do have info['bads'], but no `status` in channels.tsv logger.info(f'No "status" column found in ' f'{op.basename(channels_fname)}; using list of bad ' f'channels found in raw.info["bads"]: {raw.info["bads"]}') return raw
def test_get_ch_type_mapping(): """Test getting a correct channel mapping.""" with pytest.raises(ValueError, match='specified from "bogus" to "mne"'): _get_ch_type_mapping(fro='bogus', to='mne')
def _handle_channels_reading(channels_fname, bids_fname, raw): """Read associated channels.tsv and populate raw. Updates status (bad) and types of channels. """ logger.info('Reading channel info from {}.'.format(channels_fname)) channels_dict = _from_tsv(channels_fname) # First, make sure that ordering of names in channels.tsv matches the # ordering of names in the raw data. The "name" column is mandatory in BIDS ch_names_raw = list(raw.ch_names) ch_names_tsv = channels_dict['name'] if ch_names_raw != ch_names_tsv: msg = ('Channels do not correspond between raw data and the ' 'channels.tsv file. For MNE-BIDS, the channel names in the ' 'tsv MUST be equal and in the same order as the channels in ' 'the raw data.\n\n' '{} channels in tsv file: "{}"\n\n --> {}\n\n' '{} channels in raw file: "{}"\n\n --> {}\n\n'.format( len(ch_names_tsv), channels_fname, ch_names_tsv, len(ch_names_raw), bids_fname, ch_names_raw)) # XXX: this could be due to MNE inserting a 'STI 014' channel as the # last channel: In that case, we can work. --> Can be removed soon, # because MNE will stop the synthesis of stim channels in the near # future if not (ch_names_raw[-1] == 'STI 014' and ch_names_raw[:-1] == ch_names_tsv): raise RuntimeError(msg) # Now we can do some work. # The "type" column is mandatory in BIDS. We can use it to set channel # types in the raw data using a mapping between channel types channel_type_dict = dict() # Get the best mapping we currently have from BIDS to MNE nomenclature bids_to_mne_ch_types = _get_ch_type_mapping(fro='bids', to='mne') ch_types_json = channels_dict['type'] for ch_name, ch_type in zip(ch_names_tsv, ch_types_json): # Try to map from BIDS nomenclature to MNE, leave channel type # untouched if we are uncertain updated_ch_type = bids_to_mne_ch_types.get(ch_type, None) if updated_ch_type is not None: channel_type_dict[ch_name] = updated_ch_type # Set the channel types in the raw data according to channels.tsv raw.set_channel_types(channel_type_dict) # Check whether there is the optional "status" column from which to infer # good and bad channels if 'status' in channels_dict: # find bads from channels.tsv bad_bool = [ True if chn.lower() == 'bad' else False for chn in channels_dict['status'] ] bads = np.asarray(channels_dict['name'])[bad_bool] # merge with bads already present in raw data file (if there are any) unique_bads = set(raw.info['bads']).union(set(bads)) raw.info['bads'] = list(unique_bads) 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
def _handle_channels_reading(channels_fname, raw): """Read associated channels.tsv and populate raw. Updates status (bad) and types of channels. """ logger.info('Reading channel info from {}.'.format(channels_fname)) channels_dict = _from_tsv(channels_fname) ch_names_tsv = channels_dict['name'] # Now we can do some work. # The "type" column is mandatory in BIDS. We can use it to set channel # types in the raw data using a mapping between channel types channel_type_dict = dict() # Get the best mapping we currently have from BIDS to MNE nomenclature bids_to_mne_ch_types = _get_ch_type_mapping(fro='bids', to='mne') ch_types_json = channels_dict['type'] for ch_name, ch_type in zip(ch_names_tsv, ch_types_json): # Try to map from BIDS nomenclature to MNE, leave channel type # untouched if we are uncertain updated_ch_type = bids_to_mne_ch_types.get(ch_type, None) if updated_ch_type is None: # XXX Try again with uppercase spelling – this should be removed # XXX once https://github.com/bids-standard/bids-validator/issues/1018 # noqa:E501 # XXX has been resolved. # XXX x-ref https://github.com/mne-tools/mne-bids/issues/481 updated_ch_type = bids_to_mne_ch_types.get(ch_type.upper(), None) if updated_ch_type is not None: msg = ('The BIDS dataset contains channel types in lowercase ' 'spelling. This violates the BIDS specification and ' 'will raise an error in the future.') warn(msg) if updated_ch_type is not None: channel_type_dict[ch_name] = updated_ch_type # Special handling for (synthesized) stimulus channel synthesized_stim_ch_name = 'STI 014' if (synthesized_stim_ch_name in raw.ch_names and synthesized_stim_ch_name not in ch_names_tsv): logger.info( f'The stimulus channel "{synthesized_stim_ch_name}" is present in ' f'the raw data, but not included in channels.tsv. Removing the ' f'channel.') raw.drop_channels([synthesized_stim_ch_name]) # Rename channels in loaded Raw to match those read from the BIDS sidecar if len(ch_names_tsv) != len(raw.ch_names): warn(f'The number of channels in the channels.tsv sidecar file ' f'({len(ch_names_tsv)}) does not match the number of channels ' f'in the raw data file ({len(raw.ch_names)}). Will not try to ' f'set channel names.') else: for bids_ch_name, raw_ch_name in zip(ch_names_tsv, raw.ch_names.copy()): if bids_ch_name != raw_ch_name: raw.rename_channels({raw_ch_name: bids_ch_name}) # Set the channel types in the raw data according to channels.tsv ch_type_map_avail = { ch_name: ch_type for ch_name, ch_type in channel_type_dict.items() if ch_name in raw.ch_names } ch_diff = set(channel_type_dict.keys()) - set(ch_type_map_avail.keys()) if ch_diff: warn(f'Cannot set channel type for the following channels, as they ' f'are missing in the raw data: {", ".join(sorted(ch_diff))}') raw.set_channel_types(ch_type_map_avail) # Set bad channels based on _channels.tsv sidecar if 'status' in channels_dict: bads_tsv = _get_bads_from_tsv_data(channels_dict) bads_avail = [ ch_name for ch_name in bads_tsv if ch_name in raw.ch_names ] ch_diff = set(bads_tsv) - set(bads_avail) if ch_diff: warn(f'Cannot set "bad" status for the following channels, as ' f'they are missing in the raw data: ' f'{", ".join(sorted(ch_diff))}') raw.info['bads'] = bads_avail return raw