# Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. import numpy as np from ..fixes import _csc_array_cast from ..utils import _check_fname, warn from .constants import FIFF from .open import fiff_open, read_tag from .tag import _float_item, _int_item, find_tag from .tree import dir_tree_find from .write import ( _safe_name_list, end_block, start_block, write_float, write_float_matrix, write_float_sparse, write_id, write_int, write_int_matrix, write_name_list_sanitized, write_string, ) _proc_keys = [ "parent_file_id", "block_id", "parent_block_id", "date", "experimenter", "creator", ] _proc_ids = [ FIFF.FIFF_PARENT_FILE_ID, FIFF.FIFF_BLOCK_ID, FIFF.FIFF_PARENT_BLOCK_ID, FIFF.FIFF_MEAS_DATE, FIFF.FIFF_EXPERIMENTER, FIFF.FIFF_CREATOR, ] _proc_writers = [write_id, write_id, write_id, write_int, write_string, write_string] _proc_casters = [dict, dict, dict, np.array, str, str] def _read_proc_history(fid, tree): """Read processing history from fiff file. This function reads the SSS info, the CTC correction and the calibaraions from the SSS processing logs inside af a raw file (C.f. Maxfilter v2.2 manual (October 2010), page 21):: 104 = { 900 = proc. history 104 = { 901 = proc. record 103 = block ID 204 = date 212 = scientist 113 = creator program 104 = { 502 = SSS info 264 = SSS task 263 = SSS coord frame 265 = SSS origin 266 = SSS ins.order 267 = SSS outs.order 268 = SSS nr chnls 269 = SSS components 278 = SSS nfree 243 = HPI g limit 0.98 244 = HPI dist limit 0.005 105 = } 502 = SSS info 104 = { 504 = MaxST info 264 = SSS task 272 = SSST subspace correlation 279 = SSST buffer length 105 = } 104 = { 501 = CTC correction 103 = block ID 204 = date 113 = creator program 800 = CTC matrix 3417 = proj item chs 105 = } 501 = CTC correction 104 = { 503 = SSS finecalib. 270 = SSS cal chnls 271 = SSS cal coeff 105 = } 503 = SSS finecalib. 105 = } 901 = proc. record 105 = } 900 = proc. history """ proc_history = dir_tree_find(tree, FIFF.FIFFB_PROCESSING_HISTORY) out = list() if len(proc_history) > 0: proc_history = proc_history[0] proc_records = dir_tree_find(proc_history, FIFF.FIFFB_PROCESSING_RECORD) for proc_record in proc_records: record = dict() for i_ent in range(proc_record["nent"]): kind = proc_record["directory"][i_ent].kind pos = proc_record["directory"][i_ent].pos for key, id_, cast in zip(_proc_keys, _proc_ids, _proc_casters): if kind == id_: tag = read_tag(fid, pos) record[key] = cast(tag.data) break else: warn(f"Unknown processing history item {kind}") record["max_info"] = _read_maxfilter_record(fid, proc_record) iass = dir_tree_find(proc_record, FIFF.FIFFB_IAS) if len(iass) > 0: # XXX should eventually populate this ss = [dict() for _ in range(len(iass))] record["ias"] = ss if len(record["max_info"]) > 0: out.append(record) return out def _write_proc_history(fid, info): """Write processing history to file.""" if len(info["proc_history"]) > 0: start_block(fid, FIFF.FIFFB_PROCESSING_HISTORY) for record in info["proc_history"]: start_block(fid, FIFF.FIFFB_PROCESSING_RECORD) for key, id_, writer in zip(_proc_keys, _proc_ids, _proc_writers): if key in record: writer(fid, id_, record[key]) _write_maxfilter_record(fid, record["max_info"]) if "ias" in record: for _ in record["ias"]: start_block(fid, FIFF.FIFFB_IAS) # XXX should eventually populate this end_block(fid, FIFF.FIFFB_IAS) end_block(fid, FIFF.FIFFB_PROCESSING_RECORD) end_block(fid, FIFF.FIFFB_PROCESSING_HISTORY) _sss_info_keys = ( "job", "frame", "origin", "in_order", "out_order", "nchan", "components", "nfree", "hpi_g_limit", "hpi_dist_limit", ) _sss_info_ids = ( FIFF.FIFF_SSS_JOB, FIFF.FIFF_SSS_FRAME, FIFF.FIFF_SSS_ORIGIN, FIFF.FIFF_SSS_ORD_IN, FIFF.FIFF_SSS_ORD_OUT, FIFF.FIFF_SSS_NMAG, FIFF.FIFF_SSS_COMPONENTS, FIFF.FIFF_SSS_NFREE, FIFF.FIFF_HPI_FIT_GOOD_LIMIT, FIFF.FIFF_HPI_FIT_DIST_LIMIT, ) _sss_info_writers = ( write_int, write_int, write_float, write_int, write_int, write_int, write_int, write_int, write_float, write_float, ) _sss_info_casters = ( _int_item, _int_item, np.array, _int_item, _int_item, _int_item, np.array, _int_item, _float_item, _float_item, ) _max_st_keys = ("job", "subspcorr", "buflen") _max_st_ids = (FIFF.FIFF_SSS_JOB, FIFF.FIFF_SSS_ST_CORR, FIFF.FIFF_SSS_ST_LENGTH) _max_st_writers = (write_int, write_float, write_float) _max_st_casters = (_int_item, _float_item, _float_item) _sss_ctc_keys = ("block_id", "date", "creator", "decoupler") _sss_ctc_ids = ( FIFF.FIFF_BLOCK_ID, FIFF.FIFF_MEAS_DATE, FIFF.FIFF_CREATOR, FIFF.FIFF_DECOUPLER_MATRIX, ) _sss_ctc_writers = (write_id, write_int, write_string, write_float_sparse) _sss_ctc_casters = (dict, np.array, str, _csc_array_cast) _sss_cal_keys = ("cal_chans", "cal_corrs") _sss_cal_ids = (FIFF.FIFF_SSS_CAL_CHANS, FIFF.FIFF_SSS_CAL_CORRS) _sss_cal_writers = (write_int_matrix, write_float_matrix) _sss_cal_casters = (np.array, np.array) def _read_ctc(fname): """Read cross-talk correction matrix.""" fname = _check_fname(fname, overwrite="read", must_exist=True) f, tree, _ = fiff_open(fname) with f as fid: sss_ctc = _read_maxfilter_record(fid, tree)["sss_ctc"] bad_str = f"Invalid cross-talk FIF: {fname}" if len(sss_ctc) == 0: raise ValueError(bad_str) node = dir_tree_find(tree, FIFF.FIFFB_DATA_CORRECTION)[0] comment = find_tag(fid, node, FIFF.FIFF_COMMENT).data if comment != "cross-talk compensation matrix": raise ValueError(bad_str) sss_ctc["creator"] = find_tag(fid, node, FIFF.FIFF_CREATOR).data sss_ctc["date"] = find_tag(fid, node, FIFF.FIFF_MEAS_DATE).data return sss_ctc def _read_maxfilter_record(fid, tree): """Read maxfilter processing record from file.""" sss_info_block = dir_tree_find(tree, FIFF.FIFFB_SSS_INFO) # 502 sss_info = dict() if len(sss_info_block) > 0: sss_info_block = sss_info_block[0] for i_ent in range(sss_info_block["nent"]): kind = sss_info_block["directory"][i_ent].kind pos = sss_info_block["directory"][i_ent].pos for key, id_, cast in zip(_sss_info_keys, _sss_info_ids, _sss_info_casters): if kind == id_: tag = read_tag(fid, pos) sss_info[key] = cast(tag.data) break max_st_block = dir_tree_find(tree, FIFF.FIFFB_SSS_ST_INFO) # 504 max_st = dict() if len(max_st_block) > 0: max_st_block = max_st_block[0] for i_ent in range(max_st_block["nent"]): kind = max_st_block["directory"][i_ent].kind pos = max_st_block["directory"][i_ent].pos for key, id_, cast in zip(_max_st_keys, _max_st_ids, _max_st_casters): if kind == id_: tag = read_tag(fid, pos) max_st[key] = cast(tag.data) break sss_ctc_block = dir_tree_find(tree, FIFF.FIFFB_CHANNEL_DECOUPLER) # 501 sss_ctc = dict() if len(sss_ctc_block) > 0: sss_ctc_block = sss_ctc_block[0] for i_ent in range(sss_ctc_block["nent"]): kind = sss_ctc_block["directory"][i_ent].kind pos = sss_ctc_block["directory"][i_ent].pos for key, id_, cast in zip(_sss_ctc_keys, _sss_ctc_ids, _sss_ctc_casters): if kind == id_: tag = read_tag(fid, pos) sss_ctc[key] = cast(tag.data) break else: if kind == FIFF.FIFF_PROJ_ITEM_CH_NAME_LIST: tag = read_tag(fid, pos) chs = _safe_name_list(tag.data, "read", "proj_items_chs") # This list can null chars in the last entry, e.g.: # [..., 'MEG2642', 'MEG2643', 'MEG2641\x00 ... \x00'] chs[-1] = chs[-1].split("\x00")[0] sss_ctc["proj_items_chs"] = chs sss_cal_block = dir_tree_find(tree, FIFF.FIFFB_SSS_CAL) # 503 sss_cal = dict() if len(sss_cal_block) > 0: sss_cal_block = sss_cal_block[0] for i_ent in range(sss_cal_block["nent"]): kind = sss_cal_block["directory"][i_ent].kind pos = sss_cal_block["directory"][i_ent].pos for key, id_, cast in zip(_sss_cal_keys, _sss_cal_ids, _sss_cal_casters): if kind == id_: tag = read_tag(fid, pos) sss_cal[key] = cast(tag.data) break max_info = dict(sss_info=sss_info, sss_ctc=sss_ctc, sss_cal=sss_cal, max_st=max_st) return max_info def _write_maxfilter_record(fid, record): """Write maxfilter processing record to file.""" sss_info = record["sss_info"] if len(sss_info) > 0: start_block(fid, FIFF.FIFFB_SSS_INFO) for key, id_, writer in zip(_sss_info_keys, _sss_info_ids, _sss_info_writers): if key in sss_info: writer(fid, id_, sss_info[key]) end_block(fid, FIFF.FIFFB_SSS_INFO) max_st = record["max_st"] if len(max_st) > 0: start_block(fid, FIFF.FIFFB_SSS_ST_INFO) for key, id_, writer in zip(_max_st_keys, _max_st_ids, _max_st_writers): if key in max_st: writer(fid, id_, max_st[key]) end_block(fid, FIFF.FIFFB_SSS_ST_INFO) sss_ctc = record["sss_ctc"] if len(sss_ctc) > 0: # dict has entries start_block(fid, FIFF.FIFFB_CHANNEL_DECOUPLER) for key, id_, writer in zip(_sss_ctc_keys, _sss_ctc_ids, _sss_ctc_writers): if key in sss_ctc: writer(fid, id_, sss_ctc[key]) if "proj_items_chs" in sss_ctc: write_name_list_sanitized( fid, FIFF.FIFF_PROJ_ITEM_CH_NAME_LIST, sss_ctc["proj_items_chs"], "proj_items_chs", ) end_block(fid, FIFF.FIFFB_CHANNEL_DECOUPLER) sss_cal = record["sss_cal"] if len(sss_cal) > 0: start_block(fid, FIFF.FIFFB_SSS_CAL) for key, id_, writer in zip(_sss_cal_keys, _sss_cal_ids, _sss_cal_writers): if key in sss_cal: writer(fid, id_, sss_cal[key]) end_block(fid, FIFF.FIFFB_SSS_CAL)