针对pulse-transit的工具
This commit is contained in:
652
dist/client/mne/_fiff/reference.py
vendored
Normal file
652
dist/client/mne/_fiff/reference.py
vendored
Normal file
@@ -0,0 +1,652 @@
|
||||
# Authors: The MNE-Python contributors.
|
||||
# License: BSD-3-Clause
|
||||
# Copyright the MNE-Python contributors.
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..defaults import DEFAULTS
|
||||
from ..utils import (
|
||||
_check_option,
|
||||
_check_preload,
|
||||
_on_missing,
|
||||
_validate_type,
|
||||
fill_doc,
|
||||
logger,
|
||||
pinv,
|
||||
verbose,
|
||||
warn,
|
||||
)
|
||||
from .constants import FIFF
|
||||
from .meas_info import _check_ch_keys
|
||||
from .pick import _ELECTRODE_CH_TYPES, pick_channels, pick_channels_forward, pick_types
|
||||
from .proj import _has_eeg_average_ref_proj, make_eeg_average_ref_proj, setup_proj
|
||||
|
||||
|
||||
def _check_before_reference(inst, ref_from, ref_to, ch_type):
|
||||
"""Prepare instance for referencing."""
|
||||
# Check to see that data is preloaded
|
||||
_check_preload(inst, "Applying a reference")
|
||||
|
||||
ch_type = _get_ch_type(inst, ch_type)
|
||||
ch_dict = {**{type_: True for type_ in ch_type}, "meg": False, "ref_meg": False}
|
||||
eeg_idx = pick_types(inst.info, **ch_dict)
|
||||
|
||||
if ref_to is None:
|
||||
ref_to = [inst.ch_names[i] for i in eeg_idx]
|
||||
extra = "EEG channels found"
|
||||
else:
|
||||
extra = "channels supplied"
|
||||
if len(ref_to) == 0:
|
||||
raise ValueError(f"No {extra} to apply the reference to")
|
||||
|
||||
# After referencing, existing SSPs might not be valid anymore.
|
||||
projs_to_remove = []
|
||||
for i, proj in enumerate(inst.info["projs"]):
|
||||
# Remove any average reference projections
|
||||
if (
|
||||
proj["desc"] == "Average EEG reference"
|
||||
or proj["kind"] == FIFF.FIFFV_PROJ_ITEM_EEG_AVREF
|
||||
):
|
||||
logger.info("Removing existing average EEG reference projection.")
|
||||
# Don't remove the projection right away, but do this at the end of
|
||||
# this loop.
|
||||
projs_to_remove.append(i)
|
||||
|
||||
# Inactive SSPs may block re-referencing
|
||||
elif (
|
||||
not proj["active"]
|
||||
and len(
|
||||
[ch for ch in (ref_from + ref_to) if ch in proj["data"]["col_names"]]
|
||||
)
|
||||
> 0
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Inactive signal space projection (SSP) operators are "
|
||||
"present that operate on sensors involved in the desired "
|
||||
"referencing scheme. These projectors need to be applied "
|
||||
"using the apply_proj() method function before the desired "
|
||||
"reference can be set."
|
||||
)
|
||||
|
||||
for i in projs_to_remove:
|
||||
del inst.info["projs"][i]
|
||||
|
||||
# Need to call setup_proj after changing the projs:
|
||||
inst._projector, _ = setup_proj(inst.info, add_eeg_ref=False, activate=False)
|
||||
|
||||
# If the reference touches EEG/ECoG/sEEG/DBS electrodes, note in the
|
||||
# info that a non-CAR has been applied.
|
||||
ref_to_channels = pick_channels(inst.ch_names, ref_to, ordered=True)
|
||||
if len(np.intersect1d(ref_to_channels, eeg_idx)) > 0:
|
||||
with inst.info._unlock():
|
||||
inst.info["custom_ref_applied"] = FIFF.FIFFV_MNE_CUSTOM_REF_ON
|
||||
|
||||
return ref_to
|
||||
|
||||
|
||||
def _apply_reference(inst, ref_from, ref_to=None, forward=None, ch_type="auto"):
|
||||
"""Apply a custom EEG referencing scheme."""
|
||||
ref_to = _check_before_reference(inst, ref_from, ref_to, ch_type)
|
||||
|
||||
# Compute reference
|
||||
if len(ref_from) > 0:
|
||||
# this is guaranteed below, but we should avoid the crazy pick_channels
|
||||
# behavior that [] gives all. Also use ordered=True just to make sure
|
||||
# that all supplied channels actually exist.
|
||||
assert len(ref_to) > 0
|
||||
ref_names = ref_from
|
||||
ref_from = pick_channels(inst.ch_names, ref_from, ordered=True)
|
||||
ref_to = pick_channels(inst.ch_names, ref_to, ordered=True)
|
||||
|
||||
data = inst._data
|
||||
ref_data = data[..., ref_from, :].mean(-2, keepdims=True)
|
||||
data[..., ref_to, :] -= ref_data
|
||||
ref_data = ref_data[..., 0, :]
|
||||
|
||||
# REST
|
||||
if forward is not None:
|
||||
# use ch_sel and the given forward
|
||||
forward = pick_channels_forward(forward, ref_names, ordered=True)
|
||||
# 1-3. Compute a forward (G) and avg-ref'ed data (done above)
|
||||
G = forward["sol"]["data"]
|
||||
assert G.shape[0] == len(ref_names)
|
||||
# 4. Compute the forward (G) and average-reference it (Ga):
|
||||
Ga = G - np.mean(G, axis=0, keepdims=True)
|
||||
# 5. Compute the Ga_inv by SVD
|
||||
Ga_inv = pinv(Ga, rtol=1e-6)
|
||||
# 6. Compute Ra = (G @ Ga_inv) in eq (8) from G and Ga_inv
|
||||
Ra = G @ Ga_inv
|
||||
# 7-8. Compute Vp = Ra @ Va; then Vpa=average(Vp)
|
||||
Vpa = np.mean(Ra @ data[..., ref_from, :], axis=-2, keepdims=True)
|
||||
data[..., ref_to, :] += Vpa
|
||||
else:
|
||||
ref_data = None
|
||||
|
||||
return inst, ref_data
|
||||
|
||||
|
||||
@fill_doc
|
||||
def add_reference_channels(inst, ref_channels, copy=True):
|
||||
"""Add reference channels to data that consists of all zeros.
|
||||
|
||||
Adds reference channels to data that were not included during recording.
|
||||
This is useful when you need to re-reference your data to different
|
||||
channels. These added channels will consist of all zeros.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Instance of Raw or Epochs with EEG channels and reference channel(s).
|
||||
%(ref_channels)s
|
||||
copy : bool
|
||||
Specifies whether the data will be copied (True) or modified in-place
|
||||
(False). Defaults to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Data with added EEG reference channels.
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. warning::
|
||||
When :ref:`re-referencing <tut-set-eeg-ref>`,
|
||||
make sure to apply the montage using :meth:`mne.io.Raw.set_montage`
|
||||
only after calling this function. Applying a montage will only set
|
||||
locations of channels that exist at the time it is applied.
|
||||
"""
|
||||
from ..epochs import BaseEpochs
|
||||
from ..evoked import Evoked
|
||||
from ..io import BaseRaw
|
||||
|
||||
# Check to see that data is preloaded
|
||||
_check_preload(inst, "add_reference_channels")
|
||||
_validate_type(ref_channels, (list, tuple, str), "ref_channels")
|
||||
if isinstance(ref_channels, str):
|
||||
ref_channels = [ref_channels]
|
||||
for ch in ref_channels:
|
||||
if ch in inst.info["ch_names"]:
|
||||
raise ValueError(f"Channel {ch} already specified in inst.")
|
||||
|
||||
# Once CAR is applied (active), don't allow adding channels
|
||||
if _has_eeg_average_ref_proj(inst.info, check_active=True):
|
||||
raise RuntimeError("Average reference already applied to data.")
|
||||
|
||||
if copy:
|
||||
inst = inst.copy()
|
||||
|
||||
if isinstance(inst, (BaseRaw, Evoked)):
|
||||
data = inst._data
|
||||
refs = np.zeros((len(ref_channels), data.shape[1]))
|
||||
data = np.vstack((data, refs))
|
||||
inst._data = data
|
||||
elif isinstance(inst, BaseEpochs):
|
||||
data = inst._data
|
||||
x, y, z = data.shape
|
||||
refs = np.zeros((x * len(ref_channels), z))
|
||||
data = np.vstack((data.reshape((x * y, z), order="F"), refs))
|
||||
data = data.reshape(x, y + len(ref_channels), z, order="F")
|
||||
inst._data = data
|
||||
else:
|
||||
raise TypeError(
|
||||
f"inst should be Raw, Epochs, or Evoked instead of {type(inst)}."
|
||||
)
|
||||
nchan = len(inst.info["ch_names"])
|
||||
|
||||
# only do this if we actually have digitisation points
|
||||
if inst.info.get("dig", None) is not None:
|
||||
# "zeroth" EEG electrode dig points is reference
|
||||
ref_dig_loc = [
|
||||
dl
|
||||
for dl in inst.info["dig"]
|
||||
if (dl["kind"] == FIFF.FIFFV_POINT_EEG and dl["ident"] == 0)
|
||||
]
|
||||
if len(ref_channels) > 1 or len(ref_dig_loc) != len(ref_channels):
|
||||
ref_dig_array = np.full(12, np.nan)
|
||||
warn("The locations of multiple reference channels are ignored.")
|
||||
else: # n_ref_channels == 1 and a single ref digitization exists
|
||||
ref_dig_array = np.concatenate(
|
||||
(ref_dig_loc[0]["r"], ref_dig_loc[0]["r"], np.zeros(6))
|
||||
)
|
||||
# Replace the (possibly new) Ref location for each channel
|
||||
for idx in pick_types(inst.info, meg=False, eeg=True, exclude=[]):
|
||||
inst.info["chs"][idx]["loc"][3:6] = ref_dig_loc[0]["r"]
|
||||
else:
|
||||
# Ideally we'd fall back on getting the location from a montage, but
|
||||
# locations for non-present channels aren't stored, so location is
|
||||
# unknown. Users can call set_montage() again if needed.
|
||||
ref_dig_array = np.full(12, np.nan)
|
||||
logger.info(
|
||||
"Location for this channel is unknown; consider calling "
|
||||
"set_montage() after adding new reference channels if needed. "
|
||||
"Applying a montage will only set locations of channels that "
|
||||
"exist at the time it is applied."
|
||||
)
|
||||
|
||||
for ch in ref_channels:
|
||||
chan_info = {
|
||||
"ch_name": ch,
|
||||
"coil_type": FIFF.FIFFV_COIL_EEG,
|
||||
"kind": FIFF.FIFFV_EEG_CH,
|
||||
"logno": nchan + 1,
|
||||
"scanno": nchan + 1,
|
||||
"cal": 1,
|
||||
"range": 1.0,
|
||||
"unit_mul": FIFF.FIFF_UNITM_NONE,
|
||||
"unit": FIFF.FIFF_UNIT_V,
|
||||
"coord_frame": FIFF.FIFFV_COORD_HEAD,
|
||||
"loc": ref_dig_array,
|
||||
}
|
||||
inst.info["chs"].append(chan_info)
|
||||
inst.info._update_redundant()
|
||||
range_ = np.arange(1, len(ref_channels) + 1)
|
||||
if isinstance(inst, BaseRaw):
|
||||
inst._cals = np.hstack((inst._cals, [1] * len(ref_channels)))
|
||||
for pi, picks in enumerate(inst._read_picks):
|
||||
inst._read_picks[pi] = np.concatenate([picks, np.max(picks) + range_])
|
||||
elif isinstance(inst, BaseEpochs):
|
||||
picks = inst.picks
|
||||
inst.picks = np.concatenate([picks, np.max(picks) + range_])
|
||||
inst.info._check_consistency()
|
||||
set_eeg_reference(inst, ref_channels=ref_channels, copy=False, verbose=False)
|
||||
return inst
|
||||
|
||||
|
||||
_ref_dict = {
|
||||
FIFF.FIFFV_MNE_CUSTOM_REF_ON: "on",
|
||||
FIFF.FIFFV_MNE_CUSTOM_REF_OFF: "off",
|
||||
FIFF.FIFFV_MNE_CUSTOM_REF_CSD: "CSD",
|
||||
}
|
||||
|
||||
|
||||
def _check_can_reref(inst):
|
||||
from ..epochs import BaseEpochs
|
||||
from ..evoked import Evoked
|
||||
from ..io import BaseRaw
|
||||
|
||||
_validate_type(inst, (BaseRaw, BaseEpochs, Evoked), "Instance")
|
||||
current_custom = inst.info["custom_ref_applied"]
|
||||
if current_custom not in (
|
||||
FIFF.FIFFV_MNE_CUSTOM_REF_ON,
|
||||
FIFF.FIFFV_MNE_CUSTOM_REF_OFF,
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Cannot set new reference on data with custom reference type "
|
||||
f"{_ref_dict[current_custom]!r}"
|
||||
)
|
||||
|
||||
|
||||
@verbose
|
||||
def set_eeg_reference(
|
||||
inst,
|
||||
ref_channels="average",
|
||||
copy=True,
|
||||
projection=False,
|
||||
ch_type="auto",
|
||||
forward=None,
|
||||
*,
|
||||
joint=False,
|
||||
verbose=None,
|
||||
):
|
||||
"""Specify which reference to use for EEG data.
|
||||
|
||||
Use this function to explicitly specify the desired reference for EEG.
|
||||
This can be either an existing electrode or a new virtual channel.
|
||||
This function will re-reference the data according to the desired
|
||||
reference.
|
||||
|
||||
Note that it is also possible to re-reference the signal using a
|
||||
Laplacian (LAP) "reference-free" transformation using the
|
||||
:func:`.compute_current_source_density` function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Instance of Raw or Epochs with EEG channels and reference channel(s).
|
||||
%(ref_channels_set_eeg_reference)s
|
||||
copy : bool
|
||||
Specifies whether the data will be copied (True) or modified in-place
|
||||
(False). Defaults to True.
|
||||
%(projection_set_eeg_reference)s
|
||||
%(ch_type_set_eeg_reference)s
|
||||
%(forward_set_eeg_reference)s
|
||||
%(joint_set_eeg_reference)s
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Data with EEG channels re-referenced. If ``ref_channels='average'`` and
|
||||
``projection=True`` a projection will be added instead of directly
|
||||
re-referencing the data.
|
||||
ref_data : array
|
||||
Array of reference data subtracted from EEG channels. This will be
|
||||
``None`` if ``projection=True`` or ``ref_channels='REST'``.
|
||||
%(set_eeg_reference_see_also_notes)s
|
||||
"""
|
||||
from ..forward import Forward
|
||||
|
||||
_check_can_reref(inst)
|
||||
|
||||
ch_type = _get_ch_type(inst, ch_type)
|
||||
|
||||
if projection: # average reference projector
|
||||
if ref_channels != "average":
|
||||
raise ValueError(
|
||||
'Setting projection=True is only supported for ref_channels="average", '
|
||||
f"got {ref_channels!r}."
|
||||
)
|
||||
# We need verbose='error' here in case we add projs sequentially
|
||||
if _has_eeg_average_ref_proj(inst.info, ch_type=ch_type, verbose="error"):
|
||||
warn(
|
||||
"An average reference projection was already added. The data "
|
||||
"has been left untouched."
|
||||
)
|
||||
else:
|
||||
# Creating an average reference may fail. In this case, make
|
||||
# sure that the custom_ref_applied flag is left untouched.
|
||||
custom_ref_applied = inst.info["custom_ref_applied"]
|
||||
|
||||
try:
|
||||
with inst.info._unlock():
|
||||
inst.info["custom_ref_applied"] = FIFF.FIFFV_MNE_CUSTOM_REF_OFF
|
||||
if joint:
|
||||
inst.add_proj(
|
||||
make_eeg_average_ref_proj(
|
||||
inst.info, ch_type=ch_type, activate=False
|
||||
)
|
||||
)
|
||||
else:
|
||||
for this_ch_type in ch_type:
|
||||
inst.add_proj(
|
||||
make_eeg_average_ref_proj(
|
||||
inst.info, ch_type=this_ch_type, activate=False
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
with inst.info._unlock():
|
||||
inst.info["custom_ref_applied"] = custom_ref_applied
|
||||
raise
|
||||
# If the data has been preloaded, projections will no
|
||||
# longer be automatically applied.
|
||||
if inst.preload:
|
||||
logger.info(
|
||||
"Average reference projection was added, "
|
||||
"but has not been applied yet. Use the "
|
||||
"apply_proj method to apply it."
|
||||
)
|
||||
return inst, None
|
||||
del projection # not used anymore
|
||||
|
||||
inst = inst.copy() if copy else inst
|
||||
ch_dict = {**{type_: True for type_ in ch_type}, "meg": False, "ref_meg": False}
|
||||
ch_sel = [inst.ch_names[i] for i in pick_types(inst.info, **ch_dict)]
|
||||
|
||||
if ref_channels == "REST":
|
||||
_validate_type(forward, Forward, 'forward when ref_channels="REST"')
|
||||
else:
|
||||
forward = None # signal to _apply_reference not to do REST
|
||||
|
||||
if ref_channels in ("average", "REST"):
|
||||
logger.info(f"Applying {ref_channels} reference.")
|
||||
ref_channels = ch_sel
|
||||
|
||||
if ref_channels == []:
|
||||
logger.info("EEG data marked as already having the desired reference.")
|
||||
else:
|
||||
logger.info(
|
||||
"Applying a custom "
|
||||
f"{tuple(DEFAULTS['titles'][type_] for type_ in ch_type)} "
|
||||
"reference."
|
||||
)
|
||||
|
||||
return _apply_reference(inst, ref_channels, ch_sel, forward, ch_type=ch_type)
|
||||
|
||||
|
||||
def _get_ch_type(inst, ch_type):
|
||||
_validate_type(ch_type, (str, list, tuple), "ch_type")
|
||||
valid_ch_types = ("auto",) + _ELECTRODE_CH_TYPES
|
||||
if isinstance(ch_type, str):
|
||||
_check_option("ch_type", ch_type, valid_ch_types)
|
||||
if ch_type != "auto":
|
||||
ch_type = [ch_type]
|
||||
elif isinstance(ch_type, (list, tuple)):
|
||||
for type_ in ch_type:
|
||||
_validate_type(type_, str, "ch_type")
|
||||
_check_option("ch_type", type_, valid_ch_types[1:])
|
||||
ch_type = list(ch_type)
|
||||
|
||||
# if ch_type is 'auto', search through list to find first reasonable
|
||||
# reference-able channel type.
|
||||
if ch_type == "auto":
|
||||
for type_ in _ELECTRODE_CH_TYPES:
|
||||
if type_ in inst:
|
||||
ch_type = [type_]
|
||||
logger.info(
|
||||
f"{DEFAULTS['titles'][type_]} channel type selected for "
|
||||
"re-referencing"
|
||||
)
|
||||
break
|
||||
# if auto comes up empty, or the user specifies a bad ch_type.
|
||||
else:
|
||||
raise ValueError("No EEG, ECoG, sEEG or DBS channels found to rereference.")
|
||||
return ch_type
|
||||
|
||||
|
||||
@verbose
|
||||
def set_bipolar_reference(
|
||||
inst,
|
||||
anode,
|
||||
cathode,
|
||||
ch_name=None,
|
||||
ch_info=None,
|
||||
drop_refs=True,
|
||||
copy=True,
|
||||
on_bad="warn",
|
||||
verbose=None,
|
||||
):
|
||||
"""Re-reference selected channels using a bipolar referencing scheme.
|
||||
|
||||
A bipolar reference takes the difference between two channels (the anode
|
||||
minus the cathode) and adds it as a new virtual channel. The original
|
||||
channels will be dropped by default.
|
||||
|
||||
Multiple anodes and cathodes can be specified, in which case multiple
|
||||
virtual channels will be created. The 1st cathode will be subtracted
|
||||
from the 1st anode, the 2nd cathode from the 2nd anode, etc.
|
||||
|
||||
By default, the virtual channels will be annotated with channel-info and
|
||||
-location of the anodes and coil types will be set to EEG_BIPOLAR.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Data containing the unreferenced channels.
|
||||
anode : str | list of str
|
||||
The name(s) of the channel(s) to use as anode in the bipolar reference.
|
||||
cathode : str | list of str
|
||||
The name(s) of the channel(s) to use as cathode in the bipolar
|
||||
reference.
|
||||
ch_name : str | list of str | None
|
||||
The channel name(s) for the virtual channel(s) containing the resulting
|
||||
signal. By default, bipolar channels are named after the anode and
|
||||
cathode, but it is recommended to supply a more meaningful name.
|
||||
ch_info : dict | list of dict | None
|
||||
This parameter can be used to supply a dictionary (or a dictionary for
|
||||
each bipolar channel) containing channel information to merge in,
|
||||
overwriting the default values. Defaults to None.
|
||||
drop_refs : bool
|
||||
Whether to drop the anode/cathode channels from the instance.
|
||||
copy : bool
|
||||
Whether to operate on a copy of the data (True) or modify it in-place
|
||||
(False). Defaults to True.
|
||||
on_bad : str
|
||||
If a bipolar channel is created from a bad anode or a bad cathode, mne
|
||||
warns if on_bad="warns", raises ValueError if on_bad="raise", and does
|
||||
nothing if on_bad="ignore". For "warn" and "ignore", the new bipolar
|
||||
channel will be marked as bad. Defaults to on_bad="warns".
|
||||
%(verbose)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
inst : instance of Raw | Epochs | Evoked
|
||||
Data with the specified channels re-referenced.
|
||||
|
||||
See Also
|
||||
--------
|
||||
set_eeg_reference : Convenience function for creating an EEG reference.
|
||||
|
||||
Notes
|
||||
-----
|
||||
1. If the anodes contain any EEG channels, this function removes
|
||||
any pre-existing average reference projections.
|
||||
|
||||
2. During source localization, the EEG signal should have an average
|
||||
reference.
|
||||
|
||||
3. The data must be preloaded.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
"""
|
||||
from ..epochs import BaseEpochs, EpochsArray
|
||||
from ..evoked import EvokedArray
|
||||
from ..io import BaseRaw, RawArray
|
||||
from .meas_info import create_info
|
||||
|
||||
_check_can_reref(inst)
|
||||
if not isinstance(anode, list):
|
||||
anode = [anode]
|
||||
|
||||
if not isinstance(cathode, list):
|
||||
cathode = [cathode]
|
||||
|
||||
if len(anode) != len(cathode):
|
||||
raise ValueError(
|
||||
f"Number of anodes (got {len(anode)}) must equal the number "
|
||||
f"of cathodes (got {len(cathode)})."
|
||||
)
|
||||
|
||||
if ch_name is None:
|
||||
ch_name = [f"{a}-{c}" for (a, c) in zip(anode, cathode)]
|
||||
elif not isinstance(ch_name, list):
|
||||
ch_name = [ch_name]
|
||||
if len(ch_name) != len(anode):
|
||||
raise ValueError(
|
||||
"Number of channel names must equal the number of "
|
||||
f"anodes/cathodes (got {len(ch_name)})."
|
||||
)
|
||||
|
||||
# Check for duplicate channel names (it is allowed to give the name of the
|
||||
# anode or cathode channel, as they will be replaced).
|
||||
for ch, a, c in zip(ch_name, anode, cathode):
|
||||
if ch not in [a, c] and ch in inst.ch_names:
|
||||
raise ValueError(
|
||||
f'There is already a channel named "{ch}", please '
|
||||
"specify a different name for the bipolar "
|
||||
"channel using the ch_name parameter."
|
||||
)
|
||||
|
||||
if ch_info is None:
|
||||
ch_info = [{} for _ in anode]
|
||||
elif not isinstance(ch_info, list):
|
||||
ch_info = [ch_info]
|
||||
if len(ch_info) != len(anode):
|
||||
raise ValueError(
|
||||
"Number of channel info dictionaries must equal the "
|
||||
"number of anodes/cathodes."
|
||||
)
|
||||
|
||||
if copy:
|
||||
inst = inst.copy()
|
||||
|
||||
anode = _check_before_reference(
|
||||
inst, ref_from=cathode, ref_to=anode, ch_type="auto"
|
||||
)
|
||||
|
||||
# Create bipolar reference channels by multiplying the data
|
||||
# (channels x time) with a matrix (n_virtual_channels x channels)
|
||||
# and add them to the instance.
|
||||
multiplier = np.zeros((len(anode), len(inst.ch_names)))
|
||||
for idx, (a, c) in enumerate(zip(anode, cathode)):
|
||||
multiplier[idx, inst.ch_names.index(a)] = 1
|
||||
multiplier[idx, inst.ch_names.index(c)] = -1
|
||||
|
||||
ref_info = create_info(
|
||||
ch_names=ch_name,
|
||||
sfreq=inst.info["sfreq"],
|
||||
ch_types=inst.get_channel_types(picks=anode),
|
||||
)
|
||||
|
||||
# Update "chs" in Reference-Info.
|
||||
for ch_idx, (an, info) in enumerate(zip(anode, ch_info)):
|
||||
_check_ch_keys(info, ch_idx, name="ch_info", check_min=False)
|
||||
an_idx = inst.ch_names.index(an)
|
||||
# Copy everything from anode (except ch_name).
|
||||
an_chs = {k: v for k, v in inst.info["chs"][an_idx].items() if k != "ch_name"}
|
||||
ref_info["chs"][ch_idx].update(an_chs)
|
||||
# Set coil-type to bipolar.
|
||||
ref_info["chs"][ch_idx]["coil_type"] = FIFF.FIFFV_COIL_EEG_BIPOLAR
|
||||
# Update with info from ch_info-parameter.
|
||||
ref_info["chs"][ch_idx].update(info)
|
||||
|
||||
# Set other info-keys from original instance.
|
||||
pick_info = {
|
||||
k: v
|
||||
for k, v in inst.info.items()
|
||||
if k not in ["chs", "ch_names", "bads", "nchan", "sfreq"]
|
||||
}
|
||||
|
||||
with ref_info._unlock():
|
||||
ref_info.update(pick_info)
|
||||
|
||||
# Rereferencing of data.
|
||||
ref_data = multiplier @ inst._data
|
||||
|
||||
if isinstance(inst, BaseRaw):
|
||||
ref_inst = RawArray(ref_data, ref_info, first_samp=inst.first_samp, copy=None)
|
||||
elif isinstance(inst, BaseEpochs):
|
||||
ref_inst = EpochsArray(
|
||||
ref_data,
|
||||
ref_info,
|
||||
events=inst.events,
|
||||
tmin=inst.tmin,
|
||||
event_id=inst.event_id,
|
||||
metadata=inst.metadata,
|
||||
)
|
||||
else:
|
||||
ref_inst = EvokedArray(
|
||||
ref_data,
|
||||
ref_info,
|
||||
tmin=inst.tmin,
|
||||
comment=inst.comment,
|
||||
nave=inst.nave,
|
||||
kind="average",
|
||||
)
|
||||
|
||||
# Add referenced instance to original instance.
|
||||
inst.add_channels([ref_inst], force_update_info=True)
|
||||
|
||||
# Handle bad channels.
|
||||
bad_bipolar_chs = []
|
||||
for ch_idx, (a, c) in enumerate(zip(anode, cathode)):
|
||||
if a in inst.info["bads"] or c in inst.info["bads"]:
|
||||
bad_bipolar_chs.append(ch_name[ch_idx])
|
||||
|
||||
# Add warnings if bad channels are present.
|
||||
if bad_bipolar_chs:
|
||||
msg = f"Bipolar channels are based on bad channels: {bad_bipolar_chs}."
|
||||
_on_missing(on_bad, msg)
|
||||
inst.info["bads"] += bad_bipolar_chs
|
||||
|
||||
added_channels = ", ".join([name for name in ch_name])
|
||||
logger.info(f"Added the following bipolar channels:\n{added_channels}")
|
||||
|
||||
for attr_name in ["picks", "_projector"]:
|
||||
setattr(inst, attr_name, None)
|
||||
|
||||
# Drop remaining channels.
|
||||
if drop_refs:
|
||||
drop_channels = list((set(anode) | set(cathode)) & set(inst.ch_names))
|
||||
inst.drop_channels(drop_channels)
|
||||
|
||||
return inst
|
||||
Reference in New Issue
Block a user