# Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. from copy import deepcopy import numpy as np from ..utils import _pl, logger, verbose from .constants import FIFF from .matrix import _read_named_matrix, write_named_matrix from .tag import read_tag from .tree import dir_tree_find from .write import end_block, start_block, write_int def _add_kind(one): """Convert CTF kind to MNE kind.""" if one["ctfkind"] == int("47314252", 16): one["kind"] = 1 elif one["ctfkind"] == int("47324252", 16): one["kind"] = 2 elif one["ctfkind"] == int("47334252", 16): one["kind"] = 3 else: one["kind"] = int(one["ctfkind"]) def _calibrate_comp( comp, chs, row_names, col_names, mult_keys=("range", "cal"), flip=False ): """Get row and column cals.""" ch_names = [c["ch_name"] for c in chs] row_cals = np.zeros(len(row_names)) col_cals = np.zeros(len(col_names)) for names, cals, inv in zip( (row_names, col_names), (row_cals, col_cals), (False, True) ): for ii in range(len(cals)): p = ch_names.count(names[ii]) if p != 1: raise RuntimeError( f"Channel {names[ii]} does not appear exactly once " f"in data, found {p:d} instance{_pl(p)}" ) idx = ch_names.index(names[ii]) val = chs[idx][mult_keys[0]] * chs[idx][mult_keys[1]] val = float(1.0 / val) if inv else float(val) val = 1.0 / val if flip else val cals[ii] = val comp["rowcals"] = row_cals comp["colcals"] = col_cals comp["data"]["data"] = row_cals[:, None] * comp["data"]["data"] * col_cals[None, :] @verbose def read_ctf_comp(fid, node, chs, verbose=None): """Read the CTF software compensation data from the given node. Parameters ---------- fid : file The file descriptor. node : dict The node in the FIF tree. chs : list The list of channels from info['chs'] to match with compensators that are read. %(verbose)s Returns ------- compdata : list The compensation data """ return _read_ctf_comp(fid, node, chs, None) def _read_ctf_comp(fid, node, chs, ch_names_mapping): """Read the CTF software compensation data from the given node. Parameters ---------- fid : file The file descriptor. node : dict The node in the FIF tree. chs : list The list of channels from info['chs'] to match with compensators that are read. ch_names_mapping : dict | None The channel renaming to use. %(verbose)s Returns ------- compdata : list The compensation data """ from .meas_info import _rename_comps ch_names_mapping = dict() if ch_names_mapping is None else ch_names_mapping compdata = [] comps = dir_tree_find(node, FIFF.FIFFB_MNE_CTF_COMP_DATA) for node in comps: # Read the data we need mat = _read_named_matrix(fid, node, FIFF.FIFF_MNE_CTF_COMP_DATA) for p in range(node["nent"]): kind = node["directory"][p].kind pos = node["directory"][p].pos if kind == FIFF.FIFF_MNE_CTF_COMP_KIND: tag = read_tag(fid, pos) break else: raise Exception("Compensation type not found") # Get the compensation kind and map it to a simple number one = dict(ctfkind=tag.data.item()) del tag _add_kind(one) for p in range(node["nent"]): kind = node["directory"][p].kind pos = node["directory"][p].pos if kind == FIFF.FIFF_MNE_CTF_COMP_CALIBRATED: tag = read_tag(fid, pos) calibrated = tag.data break else: calibrated = False one["save_calibrated"] = bool(calibrated) one["data"] = mat _rename_comps([one], ch_names_mapping) if not calibrated: # Calibrate... _calibrate_comp(one, chs, mat["row_names"], mat["col_names"]) else: one["rowcals"] = np.ones(mat["data"].shape[0], dtype=np.float64) one["colcals"] = np.ones(mat["data"].shape[1], dtype=np.float64) compdata.append(one) if len(compdata) > 0: logger.info(f" Read {len(compdata)} compensation matrices") return compdata ############################################################################### # Writing def write_ctf_comp(fid, comps): """Write the CTF compensation data into a fif file. Parameters ---------- fid : file The open FIF file descriptor comps : list The compensation data to write """ if len(comps) <= 0: return # This is very simple in fact start_block(fid, FIFF.FIFFB_MNE_CTF_COMP) for comp in comps: start_block(fid, FIFF.FIFFB_MNE_CTF_COMP_DATA) # Write the compensation kind write_int(fid, FIFF.FIFF_MNE_CTF_COMP_KIND, comp["ctfkind"]) if comp.get("save_calibrated", False): write_int(fid, FIFF.FIFF_MNE_CTF_COMP_CALIBRATED, comp["save_calibrated"]) if not comp.get("save_calibrated", True): # Undo calibration comp = deepcopy(comp) data = ( (1.0 / comp["rowcals"][:, None]) * comp["data"]["data"] * (1.0 / comp["colcals"][None, :]) ) comp["data"]["data"] = data write_named_matrix(fid, FIFF.FIFF_MNE_CTF_COMP_DATA, comp["data"]) end_block(fid, FIFF.FIFFB_MNE_CTF_COMP_DATA) end_block(fid, FIFF.FIFFB_MNE_CTF_COMP)