169 lines
5.5 KiB
Python
169 lines
5.5 KiB
Python
# Authors: The MNE-Python contributors.
|
|
# License: BSD-3-Clause
|
|
# Copyright the MNE-Python contributors.
|
|
|
|
import numpy as np
|
|
|
|
from ..utils import fill_doc
|
|
from .constants import FIFF
|
|
|
|
|
|
def get_current_comp(info):
|
|
"""Get the current compensation in effect in the data."""
|
|
comp = None
|
|
first_comp = -1
|
|
for k, chan in enumerate(info["chs"]):
|
|
if chan["kind"] == FIFF.FIFFV_MEG_CH:
|
|
comp = int(chan["coil_type"]) >> 16
|
|
if first_comp < 0:
|
|
first_comp = comp
|
|
elif comp != first_comp:
|
|
raise ValueError("Compensation is not set equally on all MEG channels")
|
|
return comp
|
|
|
|
|
|
def set_current_comp(info, comp):
|
|
"""Set the current compensation in effect in the data."""
|
|
comp_now = get_current_comp(info)
|
|
for k, chan in enumerate(info["chs"]):
|
|
if chan["kind"] == FIFF.FIFFV_MEG_CH:
|
|
rem = chan["coil_type"] - (comp_now << 16)
|
|
chan["coil_type"] = int(rem + (comp << 16))
|
|
|
|
|
|
def _make_compensator(info, grade):
|
|
"""Auxiliary function for make_compensator."""
|
|
for k in range(len(info["comps"])):
|
|
if info["comps"][k]["kind"] == grade:
|
|
this_data = info["comps"][k]["data"]
|
|
|
|
# Create the preselector
|
|
presel = np.zeros((this_data["ncol"], info["nchan"]))
|
|
for col, col_name in enumerate(this_data["col_names"]):
|
|
ind = [k for k, ch in enumerate(info["ch_names"]) if ch == col_name]
|
|
if len(ind) == 0:
|
|
raise ValueError(f"Channel {col_name} is not available in data")
|
|
elif len(ind) > 1:
|
|
raise ValueError(f"Ambiguous channel {col_name}")
|
|
presel[col, ind[0]] = 1.0
|
|
|
|
# Create the postselector (zero entries for channels not found)
|
|
postsel = np.zeros((info["nchan"], this_data["nrow"]))
|
|
for c, ch_name in enumerate(info["ch_names"]):
|
|
ind = [
|
|
k for k, ch in enumerate(this_data["row_names"]) if ch == ch_name
|
|
]
|
|
if len(ind) > 1:
|
|
raise ValueError(f"Ambiguous channel {ch_name}")
|
|
elif len(ind) == 1:
|
|
postsel[c, ind[0]] = 1.0
|
|
# else, don't use it at all (postsel[c, ?] = 0.0) by allocation
|
|
this_comp = np.dot(postsel, np.dot(this_data["data"], presel))
|
|
return this_comp
|
|
|
|
raise ValueError(f"Desired compensation matrix (grade = {grade:d}) not found")
|
|
|
|
|
|
@fill_doc
|
|
def make_compensator(info, from_, to, exclude_comp_chs=False):
|
|
"""Return compensation matrix eg. for CTF system.
|
|
|
|
Create a compensation matrix to bring the data from one compensation
|
|
state to another.
|
|
|
|
Parameters
|
|
----------
|
|
%(info_not_none)s
|
|
from_ : int
|
|
Compensation in the input data.
|
|
to : int
|
|
Desired compensation in the output.
|
|
exclude_comp_chs : bool
|
|
Exclude compensation channels from the output.
|
|
|
|
Returns
|
|
-------
|
|
comp : array | None.
|
|
The compensation matrix. Might be None if no compensation
|
|
is needed (from == to).
|
|
"""
|
|
if from_ == to:
|
|
return None
|
|
|
|
# s_orig = s_from + C1*s_from = (I + C1)*s_from
|
|
# s_to = s_orig - C2*s_orig = (I - C2)*s_orig
|
|
# s_to = (I - C2)*(I + C1)*s_from = (I + C1 - C2 - C2*C1)*s_from
|
|
if from_ != 0:
|
|
C1 = _make_compensator(info, from_)
|
|
comp_from_0 = np.linalg.inv(np.eye(info["nchan"]) - C1)
|
|
if to != 0:
|
|
C2 = _make_compensator(info, to)
|
|
comp_0_to = np.eye(info["nchan"]) - C2
|
|
if from_ != 0:
|
|
if to != 0:
|
|
# This is mathematically equivalent, but has higher numerical
|
|
# error than using the inverse to always go to zero and back
|
|
# comp = np.eye(info['nchan']) + C1 - C2 - np.dot(C2, C1)
|
|
comp = np.dot(comp_0_to, comp_from_0)
|
|
else:
|
|
comp = comp_from_0
|
|
else:
|
|
# from == 0, to != 0 guaranteed here
|
|
comp = comp_0_to
|
|
|
|
if exclude_comp_chs:
|
|
pick = [
|
|
k for k, c in enumerate(info["chs"]) if c["kind"] != FIFF.FIFFV_REF_MEG_CH
|
|
]
|
|
|
|
if len(pick) == 0:
|
|
raise ValueError(
|
|
"Nothing remains after excluding the compensation channels"
|
|
)
|
|
|
|
comp = comp[pick, :]
|
|
|
|
return comp
|
|
|
|
|
|
# @verbose
|
|
# def compensate_to(data, to, verbose=None):
|
|
# """
|
|
# %
|
|
# % [newdata] = mne_compensate_to(data,to)
|
|
# %
|
|
# % Apply compensation to the data as desired
|
|
# %
|
|
# """
|
|
#
|
|
# newdata = data.copy()
|
|
# now = get_current_comp(newdata['info'])
|
|
#
|
|
# # Are we there already?
|
|
# if now == to:
|
|
# logger.info('Data are already compensated as desired')
|
|
#
|
|
# # Make the compensator and apply it to all data sets
|
|
# comp = make_compensator(newdata['info'], now, to)
|
|
# for k in range(len(newdata['evoked'])):
|
|
# newdata['evoked'][k]['epochs'] = np.dot(comp,
|
|
# newdata['evoked'][k]['epochs'])
|
|
#
|
|
# # Update the compensation info in the channel descriptors
|
|
# newdata['info']['chs'] = set_current_comp(newdata['info']['chs'], to)
|
|
# return newdata
|
|
|
|
|
|
# def set_current_comp(chs, value):
|
|
# """Set the current compensation value in the channel info structures
|
|
# """
|
|
# new_chs = chs
|
|
#
|
|
# lower_half = int('FFFF', 16) # hex2dec('FFFF')
|
|
# for k in range(len(chs)):
|
|
# if chs[k]['kind'] == FIFF.FIFFV_MEG_CH:
|
|
# coil_type = float(chs[k]['coil_type']) & lower_half
|
|
# new_chs[k]['coil_type'] = int(coil_type | (value << 16))
|
|
#
|
|
# return new_chs
|