Source code for utils.channels

"""Module for handling channels."""

from collections.abc import Iterable
from typing import TYPE_CHECKING
import numpy as np

if TYPE_CHECKING:
    import pandas as pd

_LFP_TYPES = ["seeg", "dbs", "lfp"]  # must be lower-case


[docs] def set_channels( ch_names: list[str], ch_types: list[str], reference: list | str = "default", bads: list[str] | None = None, new_names: str | list[str] = "default", ecog_only: bool = False, used_types: Iterable[str] | None = ("ecog", "dbs", "seeg"), target_keywords: Iterable[str] | None = ("mov", "squared", "label"), ) -> "pd.DataFrame": """Return dataframe with channel-specific settings in channels format. Return an channels dataframe with the columns: "name", "rereference", "used", "target", "type", "status", "new_name"]. "name" is set to ch_names, "rereference" can be specified individually. "used" is set to 1 for all channel types specified in `used_types`, else to 0. "target" is set to 1 for all channels containing any of the `target_keywords`, else to 0. Possible channel types: https://github.com/mne-tools/mne-python/blob/6ae3b22033c745cce5cd5de9b92da54c13c36484/doc/_includes/channel_types.rst Arguments --------- ch_names : list list of channel names. ch_types : list list of channel types. Should optimally be of the types: "ECOG", "DBS" or "SEEG". reference : str | list of str | None, default: 'default' re-referencing scheme. Default is "default". This sets ECOG channel references to "average" and creates a bipolar referencing scheme for LFP/DBS/SEEG channels, where each channel is referenced to the adjacent lower channel, split by left and right hemisphere. For this, the channel names must contain the substring "_L_" and/or "_R_" (lower or upper case). CAVE: Adjacent channels will be determined using the sort() function. bads : str | list of str, default: None channels that should be marked as bad and not be used for average re-referencing etc. new_names : list of str | None, default: 'default' new channel names that should be used when writing out the features and results. Useful when applying re-referencing. Set to 'None' if no renaming should be performed. 'default' will infer channel renaming from re-referencing information. If a list is given, it should be in the same order as 'ch_names'. ecog_only : boolean, default: False if True, set only 'ecog' channel type to used used_types : iterable of str | None, default : ("ecog", "dbs", "seeg") data channel types to be used. Set to `None` to use no channel types. target_keywords : iterable of str | None, default : ("ecog", "dbs", "seeg") keywords for target channels Returns ------- df: DataFrame in channels format """ import pandas as pd if not (len(ch_names) == len(ch_types)): raise ValueError( "Number of `ch_names` and `ch_types` must match." f"Got: {len(ch_names)} `ch_names` and {len(ch_types)} `ch_types`." ) df = pd.DataFrame( data=None, columns=[ "name", "rereference", "used", "target", "type", "status", "new_name", ], ) df["name"] = ch_names if used_types: if isinstance(used_types, str): used_types = [ used_types ] # Even if the user passes only ("ecog"), the if statement bellow will work used_list = [] for ch_type in ch_types: if any(use_type.lower() == ch_type.lower() for use_type in used_types): used_list.append(1) else: used_list.append(0) df["used"] = used_list else: df["used"] = 0 if target_keywords: if isinstance(target_keywords, str): target_keywords = [target_keywords] targets = [] for ch_name in ch_names: if any(kw.lower() in ch_name.lower() for kw in target_keywords): targets.append(1) else: targets.append(0) df["target"] = targets else: df["target"] = 0 # note: BIDS types are in caps, mne.io.RawArray types lower case # so that 'type' will be in lower case here df["type"] = ch_types if ecog_only: df.loc[(df["type"] == "seeg") | (df["type"] == "dbs"), "used"] = 0 if isinstance(reference, str): if reference.lower() == "default": df = _get_default_references(df=df, ch_names=ch_names, ch_types=ch_types) else: raise ValueError( "`reference` must be either `default`, `None` or " "an iterable of new reference channel names. " f"Got: {reference}." ) elif isinstance(reference, list): if len(ch_names) != len(reference): raise ValueError( "Number of `ch_names` and `reference` must match." f"Got: {len(ch_names)} `ch_names` and {len(reference)}" " `references`." ) df["rereference"] = reference elif not reference: df.loc[:, "rereference"] = "None" else: raise ValueError( "`reference` must be either `default`, None or " "an iterable of new reference channel names. " f"Got: {reference}." ) if bads: if isinstance(bads, str): bads = [bads] df["status"] = ["bad" if ch in bads else "good" for ch in ch_names] df.loc[df["status"] == "bad", "used"] = ( 0 # setting bad channels to not being used ) else: df["status"] = "good" if not new_names: df["new_name"] = ch_names elif isinstance(new_names, str): if new_names.lower() != "default": raise ValueError( "`new_names` must be either `default`, None or " "an iterable of new channel names. Got: " f"{new_names}." ) new_names = [] for name, ref in zip(df["name"], df["rereference"]): if ref == "None": new_names.append(name) elif isinstance(ref, float): if np.isnan(ref): new_names.append(name) elif ref == "average": new_names.append(name + "_avgref") else: new_names.append(name + "_" + ref) df["new_name"] = new_names elif hasattr(new_names, "__iter__"): if len(new_names) != len(ch_names): raise ValueError( "Number of `ch_names` and `new_names` must match." f" Got: {len(ch_names)} `ch_names` and {len(new_names)}" " `new_names`." ) else: df["new_name"] = ch_names else: raise ValueError( "`new_names` must be either `default`, None or" f" an iterable of new channel names. Got: {new_names}." ) return df
def _get_default_references( df: "pd.DataFrame", ch_names: list[str], ch_types: list[str] ) -> "pd.DataFrame": """Add references with default settings (ECOG CAR, LFP bipolar).""" ecog_chs = [] lfp_chs = [] other_chs = [] for ch_name, ch_type in zip(ch_names, ch_types): if "ecog" in ch_type.lower() or "ecog" in ch_name.lower(): ecog_chs.append(ch_name) elif any( lfp_type in ch_type.lower() or lfp_type in ch_name.lower() for lfp_type in _LFP_TYPES ): lfp_chs.append(ch_name) else: other_chs.append(ch_name) lfp_l = [ lfp_ch for lfp_ch in lfp_chs if ("_l_" in lfp_ch.lower()) or ("_left_" in lfp_ch.lower()) ] lfp_l.sort() lfp_r = [ lfp_ch for lfp_ch in lfp_chs if ("_r_" in lfp_ch.lower()) or ("_right_" in lfp_ch.lower()) ] lfp_r.sort() lfp_l_refs = [lfp_l[i - 1] if i > 0 else lfp_l[-1] for i, _ in enumerate(lfp_l)] lfp_r_refs = [lfp_r[i - 1] if i > 0 else lfp_r[-1] for i, _ in enumerate(lfp_r)] ref_idx = list(df.columns).index("rereference") if len(ecog_chs) > 1: for ecog_ch in ecog_chs: df.iloc[df[df["name"] == ecog_ch].index[0], ref_idx] = "average" if ( len(lfp_l) > 1 ): # if there is only a single channel, the channel would be subtracted from itself for i, lfp in enumerate(lfp_l): df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_l_refs[i] if len(lfp_r) > 1: for i, lfp in enumerate(lfp_r): df.iloc[df[df["name"] == lfp].index[0], ref_idx] = lfp_r_refs[i] for other_ch in other_chs: df.iloc[df[df["name"] == other_ch].index[0], ref_idx] = "None" df = df.replace(np.nan, "None") return df
[docs] def get_default_channels_from_data( data: np.ndarray, car_rereferencing: bool = True, ): """Return default channels dataframe with ecog datatype, no bad channels, no targets, common average rereferencing Parameters ---------- data : np.ndarray Data array in shape (n_channels, n_time) car_rereferencing : bool, optional use common average rereferencing, by default True Returns ------- pd.DataFrame nm_channel dataframe containing columns: - name - rereference - used - target - type - status - new_name """ import pandas as pd ch_name = [f"ch{idx}" for idx in range(data.shape[0])] status = ["good" for _ in range(data.shape[0])] type_nm = ["ecog" for _ in range(data.shape[0])] if car_rereferencing: rereference = ["average" for _ in range(data.shape[0])] new_name = [f"{ch}_avgref" for ch in ch_name] else: rereference = ["None" for _ in range(data.shape[0])] new_name = ch_name new_name = [f"{ch}_avgref" for ch in ch_name] target = np.array([0 for _ in range(data.shape[0])]) used = np.array([1 for _ in range(data.shape[0])]) df = pd.DataFrame() df["name"] = ch_name df["rereference"] = rereference df["used"] = used df["target"] = target df["type"] = type_nm df["status"] = status df["new_name"] = new_name return df