1416 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1416 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Authors: The MNE-Python contributors.
 | 
						|
# License: BSD-3-Clause
 | 
						|
# Copyright the MNE-Python contributors.
 | 
						|
 | 
						|
import functools
 | 
						|
import os.path as op
 | 
						|
from io import BytesIO
 | 
						|
from itertools import count
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
from ..._fiff._digitization import _make_bti_dig_points
 | 
						|
from ..._fiff.constants import FIFF
 | 
						|
from ..._fiff.meas_info import _empty_info
 | 
						|
from ..._fiff.tag import _coil_trans_to_loc, _loc_to_coil_trans
 | 
						|
from ..._fiff.utils import _mult_cal_one, read_str
 | 
						|
from ...transforms import Transform, combine_transforms, invert_transform
 | 
						|
from ...utils import _stamp_to_dt, _validate_type, logger, path_like, verbose
 | 
						|
from ..base import BaseRaw
 | 
						|
from .constants import BTI
 | 
						|
from .read import (
 | 
						|
    read_char,
 | 
						|
    read_dev_header,
 | 
						|
    read_double,
 | 
						|
    read_double_matrix,
 | 
						|
    read_float,
 | 
						|
    read_float_matrix,
 | 
						|
    read_int16,
 | 
						|
    read_int16_matrix,
 | 
						|
    read_int32,
 | 
						|
    read_int64,
 | 
						|
    read_transform,
 | 
						|
    read_uint16,
 | 
						|
    read_uint32,
 | 
						|
)
 | 
						|
 | 
						|
BTI_WH2500_REF_MAG = ("MxA", "MyA", "MzA", "MxaA", "MyaA", "MzaA")
 | 
						|
BTI_WH2500_REF_GRAD = ("GxxA", "GyyA", "GyxA", "GzaA", "GzyA")
 | 
						|
 | 
						|
dtypes = zip(list(range(1, 5)), (">i2", ">i4", ">f4", ">f8"))
 | 
						|
DTYPES = {i: np.dtype(t) for i, t in dtypes}
 | 
						|
 | 
						|
 | 
						|
def _instantiate_default_info_chs():
 | 
						|
    """Populate entries in info['chs'] with default values."""
 | 
						|
    return dict(
 | 
						|
        loc=np.array([0, 0, 0, 1] * 3, dtype="f4"),
 | 
						|
        ch_name=None,
 | 
						|
        unit_mul=FIFF.FIFF_UNITM_NONE,
 | 
						|
        coord_frame=FIFF.FIFFV_COORD_UNKNOWN,
 | 
						|
        coil_type=FIFF.FIFFV_COIL_NONE,
 | 
						|
        range=1.0,
 | 
						|
        unit=FIFF.FIFF_UNIT_V,
 | 
						|
        cal=1.0,
 | 
						|
        scanno=None,
 | 
						|
        kind=FIFF.FIFFV_MISC_CH,
 | 
						|
        logno=None,
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
class _bytes_io_mock_context:
 | 
						|
    """Make a context for BytesIO."""
 | 
						|
 | 
						|
    def __init__(self, target):
 | 
						|
        self.target = target
 | 
						|
 | 
						|
    def __enter__(self):  # noqa: D105
 | 
						|
        return self.target
 | 
						|
 | 
						|
    def __exit__(self, exception_type, value, tb):  # noqa: D105
 | 
						|
        pass
 | 
						|
 | 
						|
 | 
						|
def _bti_open(fname, *args, **kwargs):
 | 
						|
    """Handle BytesIO."""
 | 
						|
    if isinstance(fname, path_like):
 | 
						|
        return open(fname, *args, **kwargs)
 | 
						|
    elif isinstance(fname, BytesIO):
 | 
						|
        return _bytes_io_mock_context(fname)
 | 
						|
    else:
 | 
						|
        raise RuntimeError("Cannot mock this.")
 | 
						|
 | 
						|
 | 
						|
def _get_bti_dev_t(adjust=0.0, translation=(0.0, 0.02, 0.11)):
 | 
						|
    """Get the general Magnes3600WH to Neuromag coordinate transform.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    adjust : float | None
 | 
						|
        Degrees to tilt x-axis for sensor frame misalignment.
 | 
						|
        If None, no adjustment will be applied.
 | 
						|
    translation : array-like
 | 
						|
        The translation to place the origin of coordinate system
 | 
						|
        to the center of the head.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    m_nm_t : ndarray
 | 
						|
        4 x 4 rotation, translation, scaling matrix.
 | 
						|
    """
 | 
						|
    flip_t = np.array([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
 | 
						|
    rad = np.deg2rad(adjust)
 | 
						|
    adjust_t = np.array(
 | 
						|
        [
 | 
						|
            [1.0, 0.0, 0.0],
 | 
						|
            [0.0, np.cos(rad), -np.sin(rad)],
 | 
						|
            [0.0, np.sin(rad), np.cos(rad)],
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    m_nm_t = np.eye(4)
 | 
						|
    m_nm_t[:3, :3] = np.dot(flip_t, adjust_t)
 | 
						|
    m_nm_t[:3, 3] = translation
 | 
						|
    return m_nm_t
 | 
						|
 | 
						|
 | 
						|
def _rename_channels(names, ecg_ch="E31", eog_ch=("E63", "E64")):
 | 
						|
    """Rename appropriately ordered list of channel names.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    names : list of str
 | 
						|
        Lists of 4-D channel names in ascending order
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    new : list
 | 
						|
        List of names, channel names in Neuromag style
 | 
						|
    """
 | 
						|
    new = list()
 | 
						|
    ref_mag, ref_grad, eog, eeg, ext = (count(1) for _ in range(5))
 | 
						|
    for i, name in enumerate(names, 1):
 | 
						|
        if name.startswith("A"):
 | 
						|
            name = "MEG %3.3d" % i
 | 
						|
        elif name == "RESPONSE":
 | 
						|
            name = "STI 013"
 | 
						|
        elif name == "TRIGGER":
 | 
						|
            name = "STI 014"
 | 
						|
        elif any(name == k for k in eog_ch):
 | 
						|
            name = "EOG %3.3d" % next(eog)
 | 
						|
        elif name == ecg_ch:
 | 
						|
            name = "ECG 001"
 | 
						|
        elif name.startswith("E"):
 | 
						|
            name = "EEG %3.3d" % next(eeg)
 | 
						|
        elif name == "UACurrent":
 | 
						|
            name = "UTL 001"
 | 
						|
        elif name.startswith("M"):
 | 
						|
            name = "RFM %3.3d" % next(ref_mag)
 | 
						|
        elif name.startswith("G"):
 | 
						|
            name = "RFG %3.3d" % next(ref_grad)
 | 
						|
        elif name.startswith("X"):
 | 
						|
            name = "EXT %3.3d" % next(ext)
 | 
						|
 | 
						|
        new += [name]
 | 
						|
 | 
						|
    return new
 | 
						|
 | 
						|
 | 
						|
# read the points
 | 
						|
def _read_head_shape(fname):
 | 
						|
    """Read the head shape."""
 | 
						|
    with _bti_open(fname, "rb") as fid:
 | 
						|
        fid.seek(BTI.FILE_HS_N_DIGPOINTS)
 | 
						|
        _n_dig_points = read_int32(fid)
 | 
						|
        idx_points = read_double_matrix(fid, BTI.DATA_N_IDX_POINTS, 3)
 | 
						|
        dig_points = read_double_matrix(fid, _n_dig_points, 3)
 | 
						|
 | 
						|
    # reorder to lpa, rpa, nasion so = is direct.
 | 
						|
    nasion, lpa, rpa = (idx_points[_, :] for _ in [2, 0, 1])
 | 
						|
    hpi = idx_points[3 : len(idx_points), :]
 | 
						|
 | 
						|
    return nasion, lpa, rpa, hpi, dig_points
 | 
						|
 | 
						|
 | 
						|
def _check_nan_dev_head_t(dev_ctf_t):
 | 
						|
    """Make sure we deal with nans."""
 | 
						|
    has_nan = np.isnan(dev_ctf_t["trans"])
 | 
						|
    if np.any(has_nan):
 | 
						|
        logger.info(
 | 
						|
            "Missing values BTI dev->head transform. Replacing with identity matrix."
 | 
						|
        )
 | 
						|
        dev_ctf_t["trans"] = np.identity(4)
 | 
						|
 | 
						|
 | 
						|
def _convert_coil_trans(coil_trans, dev_ctf_t, bti_dev_t):
 | 
						|
    """Convert the coil trans."""
 | 
						|
    t = combine_transforms(invert_transform(dev_ctf_t), bti_dev_t, "ctf_head", "meg")
 | 
						|
    t = np.dot(t["trans"], coil_trans)
 | 
						|
    return t
 | 
						|
 | 
						|
 | 
						|
def _correct_offset(fid):
 | 
						|
    """Align fid pointer."""
 | 
						|
    current = fid.tell()
 | 
						|
    if (current % BTI.FILE_CURPOS) != 0:
 | 
						|
        offset = current % BTI.FILE_CURPOS
 | 
						|
        fid.seek(BTI.FILE_CURPOS - (offset), 1)
 | 
						|
 | 
						|
 | 
						|
def _read_config(fname):
 | 
						|
    """Read BTi system config file.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    fname : str
 | 
						|
        The absolute path to the config file
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    cfg : dict
 | 
						|
        The config blocks found.
 | 
						|
    """
 | 
						|
    with _bti_open(fname, "rb") as fid:
 | 
						|
        cfg = dict()
 | 
						|
        cfg["hdr"] = {
 | 
						|
            "version": read_int16(fid),
 | 
						|
            "site_name": read_str(fid, 32),
 | 
						|
            "dap_hostname": read_str(fid, 16),
 | 
						|
            "sys_type": read_int16(fid),
 | 
						|
            "sys_options": read_int32(fid),
 | 
						|
            "supply_freq": read_int16(fid),
 | 
						|
            "total_chans": read_int16(fid),
 | 
						|
            "system_fixed_gain": read_float(fid),
 | 
						|
            "volts_per_bit": read_float(fid),
 | 
						|
            "total_sensors": read_int16(fid),
 | 
						|
            "total_user_blocks": read_int16(fid),
 | 
						|
            "next_der_chan_no": read_int16(fid),
 | 
						|
        }
 | 
						|
 | 
						|
        fid.seek(2, 1)
 | 
						|
 | 
						|
        cfg["checksum"] = read_uint32(fid)
 | 
						|
        cfg["reserved"] = read_char(fid, 32)
 | 
						|
        cfg["transforms"] = [
 | 
						|
            read_transform(fid) for t in range(cfg["hdr"]["total_sensors"])
 | 
						|
        ]
 | 
						|
 | 
						|
        cfg["user_blocks"] = dict()
 | 
						|
        for block in range(cfg["hdr"]["total_user_blocks"]):
 | 
						|
            ub = dict()
 | 
						|
 | 
						|
            ub["hdr"] = {
 | 
						|
                "nbytes": read_uint32(fid),
 | 
						|
                "kind": read_str(fid, 20),
 | 
						|
                "checksum": read_int32(fid),
 | 
						|
                "username": read_str(fid, 32),
 | 
						|
                "timestamp": read_uint32(fid),
 | 
						|
                "user_space_size": read_uint32(fid),
 | 
						|
                "reserved": read_char(fid, 32),
 | 
						|
            }
 | 
						|
 | 
						|
            _correct_offset(fid)
 | 
						|
            start_bytes = fid.tell()
 | 
						|
            kind = ub["hdr"].pop("kind")
 | 
						|
            if not kind:  # make sure reading goes right. Should never be empty
 | 
						|
                raise RuntimeError(
 | 
						|
                    "Could not read user block. Probably you "
 | 
						|
                    "acquired data using a BTi version "
 | 
						|
                    "currently not supported. Please contact "
 | 
						|
                    "the mne-python developers."
 | 
						|
                )
 | 
						|
            dta, cfg["user_blocks"][kind] = dict(), ub
 | 
						|
            if kind in [v for k, v in BTI.items() if k[:5] == "UB_B_"]:
 | 
						|
                if kind == BTI.UB_B_MAG_INFO:
 | 
						|
                    dta["version"] = read_int32(fid)
 | 
						|
                    fid.seek(20, 1)
 | 
						|
                    dta["headers"] = list()
 | 
						|
                    for hdr in range(6):
 | 
						|
                        d = {
 | 
						|
                            "name": read_str(fid, 16),
 | 
						|
                            "transform": read_transform(fid),
 | 
						|
                            "units_per_bit": read_float(fid),
 | 
						|
                        }
 | 
						|
                        dta["headers"] += [d]
 | 
						|
                        fid.seek(20, 1)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_COH_POINTS:
 | 
						|
                    dta["n_points"] = read_int32(fid)
 | 
						|
                    dta["status"] = read_int32(fid)
 | 
						|
                    dta["points"] = [
 | 
						|
                        {
 | 
						|
                            "pos": read_double_matrix(fid, 1, 3),
 | 
						|
                            "direction": read_double_matrix(fid, 1, 3),
 | 
						|
                            "error": read_double(fid),
 | 
						|
                        }
 | 
						|
                        for _ in range(16)
 | 
						|
                    ]
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_CCP_XFM_BLOCK:
 | 
						|
                    dta["method"] = read_int32(fid)
 | 
						|
                    # handle difference btw/ linux (0) and solaris (4)
 | 
						|
                    size = 0 if ub["hdr"]["user_space_size"] == 132 else 4
 | 
						|
                    fid.seek(size, 1)
 | 
						|
                    dta["transform"] = read_transform(fid)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_EEG_LOCS:
 | 
						|
                    dta["electrodes"] = []
 | 
						|
                    while True:
 | 
						|
                        d = {
 | 
						|
                            "label": read_str(fid, 16),
 | 
						|
                            "location": read_double_matrix(fid, 1, 3),
 | 
						|
                        }
 | 
						|
                        if not d["label"]:
 | 
						|
                            break
 | 
						|
                        dta["electrodes"] += [d]
 | 
						|
 | 
						|
                elif kind in [BTI.UB_B_WHC_CHAN_MAP_VER, BTI.UB_B_WHS_SUBSYS_VER]:
 | 
						|
                    dta["version"] = read_int16(fid)
 | 
						|
                    dta["struct_size"] = read_int16(fid)
 | 
						|
                    dta["entries"] = read_int16(fid)
 | 
						|
 | 
						|
                    fid.seek(8, 1)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_WHC_CHAN_MAP:
 | 
						|
                    num_channels = None
 | 
						|
                    for name, data in cfg["user_blocks"].items():
 | 
						|
                        if name == BTI.UB_B_WHC_CHAN_MAP_VER:
 | 
						|
                            num_channels = data["entries"]
 | 
						|
                            break
 | 
						|
 | 
						|
                    if num_channels is None:
 | 
						|
                        raise ValueError(
 | 
						|
                            f"Cannot find block {BTI.UB_B_WHC_CHAN_MAP_VER} to "
 | 
						|
                            "determine number of channels"
 | 
						|
                        )
 | 
						|
 | 
						|
                    dta["channels"] = list()
 | 
						|
                    for i in range(num_channels):
 | 
						|
                        d = {
 | 
						|
                            "subsys_type": read_int16(fid),
 | 
						|
                            "subsys_num": read_int16(fid),
 | 
						|
                            "card_num": read_int16(fid),
 | 
						|
                            "chan_num": read_int16(fid),
 | 
						|
                            "recdspnum": read_int16(fid),
 | 
						|
                        }
 | 
						|
                        dta["channels"] += [d]
 | 
						|
                        fid.seek(8, 1)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_WHS_SUBSYS:
 | 
						|
                    num_subsys = None
 | 
						|
                    for name, data in cfg["user_blocks"].items():
 | 
						|
                        if name == BTI.UB_B_WHS_SUBSYS_VER:
 | 
						|
                            num_subsys = data["entries"]
 | 
						|
                            break
 | 
						|
 | 
						|
                    if num_subsys is None:
 | 
						|
                        raise ValueError(
 | 
						|
                            f"Cannot find block {BTI.UB_B_WHS_SUBSYS_VER} to determine"
 | 
						|
                            " number of subsystems"
 | 
						|
                        )
 | 
						|
 | 
						|
                    dta["subsys"] = list()
 | 
						|
                    for _ in range(num_subsys):
 | 
						|
                        d = {
 | 
						|
                            "subsys_type": read_int16(fid),
 | 
						|
                            "subsys_num": read_int16(fid),
 | 
						|
                            "cards_per_sys": read_int16(fid),
 | 
						|
                            "channels_per_card": read_int16(fid),
 | 
						|
                            "card_version": read_int16(fid),
 | 
						|
                        }
 | 
						|
 | 
						|
                        fid.seek(2, 1)
 | 
						|
 | 
						|
                        d.update(
 | 
						|
                            {
 | 
						|
                                "offsetdacgain": read_float(fid),
 | 
						|
                                "squid_type": read_int32(fid),
 | 
						|
                                "timesliceoffset": read_int16(fid),
 | 
						|
                                "padding": read_int16(fid),
 | 
						|
                                "volts_per_bit": read_float(fid),
 | 
						|
                            }
 | 
						|
                        )
 | 
						|
 | 
						|
                        dta["subsys"] += [d]
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_CH_LABELS:
 | 
						|
                    dta["version"] = read_int32(fid)
 | 
						|
                    dta["entries"] = read_int32(fid)
 | 
						|
                    fid.seek(16, 1)
 | 
						|
 | 
						|
                    dta["labels"] = list()
 | 
						|
                    for label in range(dta["entries"]):
 | 
						|
                        dta["labels"] += [read_str(fid, 16)]
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_CALIBRATION:
 | 
						|
                    dta["sensor_no"] = read_int16(fid)
 | 
						|
                    fid.seek(2, 1)
 | 
						|
                    dta["timestamp"] = read_int32(fid)
 | 
						|
                    dta["logdir"] = read_str(fid, 256)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_SYS_CONFIG_TIME:
 | 
						|
                    # handle difference btw/ linux (256) and solaris (512)
 | 
						|
                    size = 256 if ub["hdr"]["user_space_size"] == 260 else 512
 | 
						|
                    dta["sysconfig_name"] = read_str(fid, size)
 | 
						|
                    dta["timestamp"] = read_int32(fid)
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_DELTA_ENABLED:
 | 
						|
                    dta["delta_enabled"] = read_int16(fid)
 | 
						|
 | 
						|
                elif kind in [BTI.UB_B_E_TABLE_USED, BTI.UB_B_E_TABLE]:
 | 
						|
                    dta["hdr"] = {
 | 
						|
                        "version": read_int32(fid),
 | 
						|
                        "entry_size": read_int32(fid),
 | 
						|
                        "n_entries": read_int32(fid),
 | 
						|
                        "filtername": read_str(fid, 16),
 | 
						|
                        "n_e_values": read_int32(fid),
 | 
						|
                        "reserved": read_str(fid, 28),
 | 
						|
                    }
 | 
						|
 | 
						|
                    if dta["hdr"]["version"] == 2:
 | 
						|
                        size = 16
 | 
						|
                        dta["ch_names"] = [
 | 
						|
                            read_str(fid, size) for ch in range(dta["hdr"]["n_entries"])
 | 
						|
                        ]
 | 
						|
                        dta["e_ch_names"] = [
 | 
						|
                            read_str(fid, size)
 | 
						|
                            for ch in range(dta["hdr"]["n_e_values"])
 | 
						|
                        ]
 | 
						|
 | 
						|
                        rows = dta["hdr"]["n_entries"]
 | 
						|
                        cols = dta["hdr"]["n_e_values"]
 | 
						|
                        dta["etable"] = read_float_matrix(fid, rows, cols)
 | 
						|
                    else:  # handle MAGNES2500 naming scheme
 | 
						|
                        dta["ch_names"] = ["WH2500"] * dta["hdr"]["n_e_values"]
 | 
						|
                        dta["hdr"]["n_e_values"] = 6
 | 
						|
                        dta["e_ch_names"] = BTI_WH2500_REF_MAG
 | 
						|
                        rows = dta["hdr"]["n_entries"]
 | 
						|
                        cols = dta["hdr"]["n_e_values"]
 | 
						|
                        dta["etable"] = read_float_matrix(fid, rows, cols)
 | 
						|
 | 
						|
                elif any(
 | 
						|
                    [kind == BTI.UB_B_WEIGHTS_USED, kind[:4] == BTI.UB_B_WEIGHT_TABLE]
 | 
						|
                ):
 | 
						|
                    dta["hdr"] = dict(
 | 
						|
                        version=read_int32(fid),
 | 
						|
                        n_bytes=read_uint32(fid),
 | 
						|
                        n_entries=read_uint32(fid),
 | 
						|
                        name=read_str(fid, 32),
 | 
						|
                    )
 | 
						|
                    if dta["hdr"]["version"] == 2:
 | 
						|
                        dta["hdr"].update(
 | 
						|
                            description=read_str(fid, 80),
 | 
						|
                            n_anlg=read_uint32(fid),
 | 
						|
                            n_dsp=read_uint32(fid),
 | 
						|
                            reserved=read_str(fid, 72),
 | 
						|
                        )
 | 
						|
                        dta["ch_names"] = [
 | 
						|
                            read_str(fid, 16) for ch in range(dta["hdr"]["n_entries"])
 | 
						|
                        ]
 | 
						|
                        dta["anlg_ch_names"] = [
 | 
						|
                            read_str(fid, 16) for ch in range(dta["hdr"]["n_anlg"])
 | 
						|
                        ]
 | 
						|
 | 
						|
                        dta["dsp_ch_names"] = [
 | 
						|
                            read_str(fid, 16) for ch in range(dta["hdr"]["n_dsp"])
 | 
						|
                        ]
 | 
						|
                        dta["dsp_wts"] = read_float_matrix(
 | 
						|
                            fid, dta["hdr"]["n_entries"], dta["hdr"]["n_dsp"]
 | 
						|
                        )
 | 
						|
                        dta["anlg_wts"] = read_int16_matrix(
 | 
						|
                            fid, dta["hdr"]["n_entries"], dta["hdr"]["n_anlg"]
 | 
						|
                        )
 | 
						|
                    else:  # handle MAGNES2500 naming scheme
 | 
						|
                        fid.seek(
 | 
						|
                            start_bytes
 | 
						|
                            + ub["hdr"]["user_space_size"]
 | 
						|
                            - dta["hdr"]["n_bytes"] * dta["hdr"]["n_entries"],
 | 
						|
                            0,
 | 
						|
                        )
 | 
						|
 | 
						|
                        dta["hdr"]["n_dsp"] = dta["hdr"]["n_bytes"] // 4 - 2
 | 
						|
                        assert dta["hdr"]["n_dsp"] == len(BTI_WH2500_REF_MAG) + len(
 | 
						|
                            BTI_WH2500_REF_GRAD
 | 
						|
                        )
 | 
						|
                        dta["ch_names"] = ["WH2500"] * dta["hdr"]["n_entries"]
 | 
						|
                        dta["hdr"]["n_anlg"] = 3
 | 
						|
                        # These orders could be wrong, so don't set them
 | 
						|
                        # for now
 | 
						|
                        # dta['anlg_ch_names'] = BTI_WH2500_REF_MAG[:3]
 | 
						|
                        # dta['dsp_ch_names'] = (BTI_WH2500_REF_GRAD +
 | 
						|
                        #                        BTI_WH2500_REF_MAG)
 | 
						|
                        dta["anlg_wts"] = np.zeros(
 | 
						|
                            (dta["hdr"]["n_entries"], dta["hdr"]["n_anlg"]), dtype="i2"
 | 
						|
                        )
 | 
						|
                        dta["dsp_wts"] = np.zeros(
 | 
						|
                            (dta["hdr"]["n_entries"], dta["hdr"]["n_dsp"]), dtype="f4"
 | 
						|
                        )
 | 
						|
                        for n in range(dta["hdr"]["n_entries"]):
 | 
						|
                            dta["anlg_wts"][n] = read_int16_matrix(
 | 
						|
                                fid, 1, dta["hdr"]["n_anlg"]
 | 
						|
                            )
 | 
						|
                            read_int16(fid)
 | 
						|
                            dta["dsp_wts"][n] = read_float_matrix(
 | 
						|
                                fid, 1, dta["hdr"]["n_dsp"]
 | 
						|
                            )
 | 
						|
 | 
						|
                elif kind == BTI.UB_B_TRIG_MASK:
 | 
						|
                    dta["version"] = read_int32(fid)
 | 
						|
                    dta["entries"] = read_int32(fid)
 | 
						|
                    fid.seek(16, 1)
 | 
						|
 | 
						|
                    dta["masks"] = []
 | 
						|
                    for entry in range(dta["entries"]):
 | 
						|
                        d = {
 | 
						|
                            "name": read_str(fid, 20),
 | 
						|
                            "nbits": read_uint16(fid),
 | 
						|
                            "shift": read_uint16(fid),
 | 
						|
                            "mask": read_uint32(fid),
 | 
						|
                        }
 | 
						|
                        dta["masks"] += [d]
 | 
						|
                        fid.seek(8, 1)
 | 
						|
 | 
						|
            else:
 | 
						|
                dta["unknown"] = {"hdr": read_char(fid, ub["hdr"]["user_space_size"])}
 | 
						|
 | 
						|
            n_read = fid.tell() - start_bytes
 | 
						|
            if n_read != ub["hdr"]["user_space_size"]:
 | 
						|
                raise RuntimeError(
 | 
						|
                    "Internal MNE reading error, read size %d "
 | 
						|
                    "!= %d expected size for kind %s"
 | 
						|
                    % (n_read, ub["hdr"]["user_space_size"], kind)
 | 
						|
                )
 | 
						|
            ub.update(dta)  # finally update the userblock data
 | 
						|
            _correct_offset(fid)  # after reading.
 | 
						|
 | 
						|
        cfg["chs"] = list()
 | 
						|
 | 
						|
        # prepare reading channels
 | 
						|
        for channel in range(cfg["hdr"]["total_chans"]):
 | 
						|
            ch = {
 | 
						|
                "name": read_str(fid, 16),
 | 
						|
                "chan_no": read_int16(fid),
 | 
						|
                "ch_type": read_uint16(fid),
 | 
						|
                "sensor_no": read_int16(fid),
 | 
						|
                "data": dict(),
 | 
						|
            }
 | 
						|
 | 
						|
            fid.seek(2, 1)
 | 
						|
            ch.update(
 | 
						|
                {
 | 
						|
                    "gain": read_float(fid),
 | 
						|
                    "units_per_bit": read_float(fid),
 | 
						|
                    "yaxis_label": read_str(fid, 16),
 | 
						|
                    "aar_val": read_double(fid),
 | 
						|
                    "checksum": read_int32(fid),
 | 
						|
                    "reserved": read_str(fid, 32),
 | 
						|
                }
 | 
						|
            )
 | 
						|
 | 
						|
            cfg["chs"] += [ch]
 | 
						|
            _correct_offset(fid)  # before and after
 | 
						|
            dta = dict()
 | 
						|
            if ch["ch_type"] in [BTI.CHTYPE_MEG, BTI.CHTYPE_REFERENCE]:
 | 
						|
                dev = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "inductance": read_float(fid),
 | 
						|
                    "padding": read_str(fid, 4),
 | 
						|
                    "transform": _correct_trans(read_transform(fid), False),
 | 
						|
                    "xform_flag": read_int16(fid),
 | 
						|
                    "total_loops": read_int16(fid),
 | 
						|
                }
 | 
						|
 | 
						|
                fid.seek(4, 1)
 | 
						|
                dev["reserved"] = read_str(fid, 32)
 | 
						|
                dta.update({"dev": dev, "loops": []})
 | 
						|
                for _ in range(dev["total_loops"]):
 | 
						|
                    d = {
 | 
						|
                        "position": read_double_matrix(fid, 1, 3),
 | 
						|
                        "orientation": read_double_matrix(fid, 1, 3),
 | 
						|
                        "radius": read_double(fid),
 | 
						|
                        "wire_radius": read_double(fid),
 | 
						|
                        "turns": read_int16(fid),
 | 
						|
                    }
 | 
						|
                    fid.seek(2, 1)
 | 
						|
                    d["checksum"] = read_int32(fid)
 | 
						|
                    d["reserved"] = read_str(fid, 32)
 | 
						|
                    dta["loops"] += [d]
 | 
						|
 | 
						|
            elif ch["ch_type"] == BTI.CHTYPE_EEG:
 | 
						|
                dta = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "impedance": read_float(fid),
 | 
						|
                    "padding": read_str(fid, 4),
 | 
						|
                    "transform": read_transform(fid),
 | 
						|
                    "reserved": read_char(fid, 32),
 | 
						|
                }
 | 
						|
 | 
						|
            elif ch["ch_type"] == BTI.CHTYPE_EXTERNAL:
 | 
						|
                dta = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "user_space_size": read_int32(fid),
 | 
						|
                    "reserved": read_str(fid, 32),
 | 
						|
                }
 | 
						|
 | 
						|
            elif ch["ch_type"] == BTI.CHTYPE_TRIGGER:
 | 
						|
                dta = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "user_space_size": read_int32(fid),
 | 
						|
                }
 | 
						|
                fid.seek(2, 1)
 | 
						|
                dta["reserved"] = read_str(fid, 32)
 | 
						|
 | 
						|
            elif ch["ch_type"] in [BTI.CHTYPE_UTILITY, BTI.CHTYPE_DERIVED]:
 | 
						|
                dta = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "user_space_size": read_int32(fid),
 | 
						|
                    "reserved": read_str(fid, 32),
 | 
						|
                }
 | 
						|
 | 
						|
            elif ch["ch_type"] == BTI.CHTYPE_SHORTED:
 | 
						|
                dta = {
 | 
						|
                    "device_info": read_dev_header(fid),
 | 
						|
                    "reserved": read_str(fid, 32),
 | 
						|
                }
 | 
						|
 | 
						|
            ch.update(dta)  # add data collected
 | 
						|
            _correct_offset(fid)  # after each reading
 | 
						|
 | 
						|
    return cfg
 | 
						|
 | 
						|
 | 
						|
def _read_epoch(fid):
 | 
						|
    """Read BTi PDF epoch."""
 | 
						|
    out = {
 | 
						|
        "pts_in_epoch": read_int32(fid),
 | 
						|
        "epoch_duration": read_float(fid),
 | 
						|
        "expected_iti": read_float(fid),
 | 
						|
        "actual_iti": read_float(fid),
 | 
						|
        "total_var_events": read_int32(fid),
 | 
						|
        "checksum": read_int32(fid),
 | 
						|
        "epoch_timestamp": read_int32(fid),
 | 
						|
    }
 | 
						|
 | 
						|
    fid.seek(28, 1)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_channel(fid):
 | 
						|
    """Read BTi PDF channel."""
 | 
						|
    out = {
 | 
						|
        "chan_label": read_str(fid, 16),
 | 
						|
        "chan_no": read_int16(fid),
 | 
						|
        "attributes": read_int16(fid),
 | 
						|
        "scale": read_float(fid),
 | 
						|
        "yaxis_label": read_str(fid, 16),
 | 
						|
        "valid_min_max": read_int16(fid),
 | 
						|
    }
 | 
						|
 | 
						|
    fid.seek(6, 1)
 | 
						|
    out.update(
 | 
						|
        {
 | 
						|
            "ymin": read_double(fid),
 | 
						|
            "ymax": read_double(fid),
 | 
						|
            "index": read_int32(fid),
 | 
						|
            "checksum": read_int32(fid),
 | 
						|
            "off_flag": read_str(fid, 4),
 | 
						|
            "offset": read_float(fid),
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    fid.seek(24, 1)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_event(fid):
 | 
						|
    """Read BTi PDF event."""
 | 
						|
    out = {
 | 
						|
        "event_name": read_str(fid, 16),
 | 
						|
        "start_lat": read_float(fid),
 | 
						|
        "end_lat": read_float(fid),
 | 
						|
        "step_size": read_float(fid),
 | 
						|
        "fixed_event": read_int16(fid),
 | 
						|
        "checksum": read_int32(fid),
 | 
						|
    }
 | 
						|
 | 
						|
    fid.seek(32, 1)
 | 
						|
    _correct_offset(fid)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_process(fid):
 | 
						|
    """Read BTi PDF process."""
 | 
						|
    out = {
 | 
						|
        "nbytes": read_int32(fid),
 | 
						|
        "process_type": read_str(fid, 20),
 | 
						|
        "checksum": read_int32(fid),
 | 
						|
        "user": read_str(fid, 32),
 | 
						|
        "timestamp": read_int32(fid),
 | 
						|
        "filename": read_str(fid, 256),
 | 
						|
        "total_steps": read_int32(fid),
 | 
						|
    }
 | 
						|
 | 
						|
    fid.seek(32, 1)
 | 
						|
    _correct_offset(fid)
 | 
						|
    out["processing_steps"] = list()
 | 
						|
    for step in range(out["total_steps"]):
 | 
						|
        this_step = {
 | 
						|
            "nbytes": read_int32(fid),
 | 
						|
            "process_type": read_str(fid, 20),
 | 
						|
            "checksum": read_int32(fid),
 | 
						|
        }
 | 
						|
        ptype = this_step["process_type"]
 | 
						|
        if ptype == BTI.PROC_DEFAULTS:
 | 
						|
            this_step["scale_option"] = read_int32(fid)
 | 
						|
 | 
						|
            fid.seek(4, 1)
 | 
						|
            this_step["scale"] = read_double(fid)
 | 
						|
            this_step["dtype"] = read_int32(fid)
 | 
						|
            this_step["selected"] = read_int16(fid)
 | 
						|
            this_step["color_display"] = read_int16(fid)
 | 
						|
 | 
						|
            fid.seek(32, 1)
 | 
						|
        elif ptype in BTI.PROC_FILTER:
 | 
						|
            this_step["freq"] = read_float(fid)
 | 
						|
            fid.seek(32, 1)
 | 
						|
        elif ptype in BTI.PROC_BPFILTER:
 | 
						|
            this_step["high_freq"] = read_float(fid)
 | 
						|
            this_step["low_freq"] = read_float(fid)
 | 
						|
        else:
 | 
						|
            jump = this_step["user_space_size"] = read_int32(fid)
 | 
						|
            fid.seek(32, 1)
 | 
						|
            fid.seek(jump, 1)
 | 
						|
 | 
						|
        out["processing_steps"] += [this_step]
 | 
						|
        _correct_offset(fid)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_assoc_file(fid):
 | 
						|
    """Read BTi PDF assocfile."""
 | 
						|
    out = {"file_id": read_int16(fid), "length": read_int16(fid)}
 | 
						|
 | 
						|
    fid.seek(32, 1)
 | 
						|
    out["checksum"] = read_int32(fid)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_pfid_ed(fid):
 | 
						|
    """Read PDF ed file."""
 | 
						|
    out = {"comment_size": read_int32(fid), "name": read_str(fid, 17)}
 | 
						|
 | 
						|
    fid.seek(9, 1)
 | 
						|
    out.update(
 | 
						|
        {
 | 
						|
            "pdf_number": read_int16(fid),
 | 
						|
            "total_events": read_int32(fid),
 | 
						|
            "timestamp": read_int32(fid),
 | 
						|
            "flags": read_int32(fid),
 | 
						|
            "de_process": read_int32(fid),
 | 
						|
            "checksum": read_int32(fid),
 | 
						|
            "ed_id": read_int32(fid),
 | 
						|
            "win_width": read_float(fid),
 | 
						|
            "win_offset": read_float(fid),
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    fid.seek(8, 1)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def _read_bti_header_pdf(pdf_fname):
 | 
						|
    """Read header from pdf file."""
 | 
						|
    with _bti_open(pdf_fname, "rb") as fid:
 | 
						|
        fid.seek(-8, 2)
 | 
						|
        start = fid.tell()
 | 
						|
        header_position = read_int64(fid)
 | 
						|
        check_value = header_position & BTI.FILE_MASK
 | 
						|
 | 
						|
        if (start + BTI.FILE_CURPOS - check_value) <= BTI.FILE_MASK:
 | 
						|
            header_position = check_value
 | 
						|
 | 
						|
        # Check header position for alignment issues
 | 
						|
        if (header_position % 8) != 0:
 | 
						|
            header_position += 8 - (header_position % 8)
 | 
						|
 | 
						|
        fid.seek(header_position, 0)
 | 
						|
 | 
						|
        # actual header starts here
 | 
						|
        info = {
 | 
						|
            "version": read_int16(fid),
 | 
						|
            "file_type": read_str(fid, 5),
 | 
						|
            "hdr_size": start - header_position,  # add for convenience
 | 
						|
            "start": start,
 | 
						|
        }
 | 
						|
 | 
						|
        fid.seek(1, 1)
 | 
						|
 | 
						|
        info.update(
 | 
						|
            {
 | 
						|
                "data_format": read_int16(fid),
 | 
						|
                "acq_mode": read_int16(fid),
 | 
						|
                "total_epochs": read_int32(fid),
 | 
						|
                "input_epochs": read_int32(fid),
 | 
						|
                "total_events": read_int32(fid),
 | 
						|
                "total_fixed_events": read_int32(fid),
 | 
						|
                "sample_period": read_float(fid),
 | 
						|
                "xaxis_label": read_str(fid, 16),
 | 
						|
                "total_processes": read_int32(fid),
 | 
						|
                "total_chans": read_int16(fid),
 | 
						|
            }
 | 
						|
        )
 | 
						|
 | 
						|
        fid.seek(2, 1)
 | 
						|
        info.update(
 | 
						|
            {
 | 
						|
                "checksum": read_int32(fid),
 | 
						|
                "total_ed_classes": read_int32(fid),
 | 
						|
                "total_associated_files": read_int16(fid),
 | 
						|
                "last_file_index": read_int16(fid),
 | 
						|
                "timestamp": read_int32(fid),
 | 
						|
            }
 | 
						|
        )
 | 
						|
 | 
						|
        fid.seek(20, 1)
 | 
						|
        _correct_offset(fid)
 | 
						|
 | 
						|
        # actual header ends here, so dar seems ok.
 | 
						|
 | 
						|
        info["epochs"] = [_read_epoch(fid) for _ in range(info["total_epochs"])]
 | 
						|
 | 
						|
        info["chs"] = [_read_channel(fid) for _ in range(info["total_chans"])]
 | 
						|
 | 
						|
        info["events"] = [_read_event(fid) for _ in range(info["total_events"])]
 | 
						|
 | 
						|
        info["processes"] = [_read_process(fid) for _ in range(info["total_processes"])]
 | 
						|
 | 
						|
        info["assocfiles"] = [
 | 
						|
            _read_assoc_file(fid) for _ in range(info["total_associated_files"])
 | 
						|
        ]
 | 
						|
 | 
						|
        info["edclasses"] = [
 | 
						|
            _read_pfid_ed(fid) for _ in range(info["total_ed_classes"])
 | 
						|
        ]
 | 
						|
 | 
						|
        info["extra_data"] = fid.read(start - fid.tell())
 | 
						|
        info["pdf"] = pdf_fname
 | 
						|
 | 
						|
    info["total_slices"] = sum(e["pts_in_epoch"] for e in info["epochs"])
 | 
						|
 | 
						|
    info["dtype"] = DTYPES[info["data_format"]]
 | 
						|
    bps = info["dtype"].itemsize * info["total_chans"]
 | 
						|
    info["bytes_per_slice"] = bps
 | 
						|
    return info
 | 
						|
 | 
						|
 | 
						|
def _read_bti_header(pdf_fname, config_fname, sort_by_ch_name=True):
 | 
						|
    """Read bti PDF header."""
 | 
						|
    info = _read_bti_header_pdf(pdf_fname) if pdf_fname is not None else dict()
 | 
						|
    cfg = _read_config(config_fname)
 | 
						|
    info["bti_transform"] = cfg["transforms"]
 | 
						|
 | 
						|
    # augment channel list by according info from config.
 | 
						|
    # get channels from config present in PDF
 | 
						|
    chans = info.get("chs", None)
 | 
						|
    if chans is not None:
 | 
						|
        chans_cfg = [
 | 
						|
            c for c in cfg["chs"] if c["chan_no"] in [c_["chan_no"] for c_ in chans]
 | 
						|
        ]
 | 
						|
 | 
						|
        # sort chans_cfg and chans
 | 
						|
        chans = sorted(chans, key=lambda k: k["chan_no"])
 | 
						|
        chans_cfg = sorted(chans_cfg, key=lambda k: k["chan_no"])
 | 
						|
 | 
						|
        # check all pdf channels are present in config
 | 
						|
        match = [c["chan_no"] for c in chans_cfg] == [c["chan_no"] for c in chans]
 | 
						|
 | 
						|
        if not match:
 | 
						|
            raise RuntimeError(
 | 
						|
                "Could not match raw data channels with"
 | 
						|
                " config channels. Some of the channels"
 | 
						|
                " found are not described in config."
 | 
						|
            )
 | 
						|
    else:
 | 
						|
        chans_cfg = cfg["chs"]
 | 
						|
        chans = [dict() for _ in chans_cfg]
 | 
						|
 | 
						|
    # transfer channel info from config to channel info
 | 
						|
    for ch, ch_cfg in zip(chans, chans_cfg):
 | 
						|
        ch["upb"] = ch_cfg["units_per_bit"]
 | 
						|
        ch["gain"] = ch_cfg["gain"]
 | 
						|
        ch["name"] = ch_cfg["name"]
 | 
						|
        if ch_cfg.get("dev", dict()).get("transform", None) is not None:
 | 
						|
            ch["loc"] = _coil_trans_to_loc(ch_cfg["dev"]["transform"])
 | 
						|
        else:
 | 
						|
            ch["loc"] = np.full(12, np.nan)
 | 
						|
        if pdf_fname is not None:
 | 
						|
            if info["data_format"] <= 2:  # see DTYPES, implies integer
 | 
						|
                ch["cal"] = ch["scale"] * ch["upb"] / float(ch["gain"])
 | 
						|
            else:  # float
 | 
						|
                ch["cal"] = ch["scale"] * ch["gain"]
 | 
						|
        else:  # if we are in this mode we don't read data, only channel info.
 | 
						|
            ch["cal"] = ch["scale"] = 1.0  # so we put a trivial default value
 | 
						|
 | 
						|
    if sort_by_ch_name:
 | 
						|
        by_index = [(i, d["index"]) for i, d in enumerate(chans)]
 | 
						|
        by_index.sort(key=lambda c: c[1])
 | 
						|
        by_index = [idx[0] for idx in by_index]
 | 
						|
        chs = [chans[pos] for pos in by_index]
 | 
						|
 | 
						|
        sort_by_name_idx = [(i, d["name"]) for i, d in enumerate(chs)]
 | 
						|
        a_chs = [c for c in sort_by_name_idx if c[1].startswith("A")]
 | 
						|
        other_chs = [c for c in sort_by_name_idx if not c[1].startswith("A")]
 | 
						|
        sort_by_name_idx = sorted(a_chs, key=lambda c: int(c[1][1:])) + sorted(
 | 
						|
            other_chs
 | 
						|
        )
 | 
						|
 | 
						|
        sort_by_name_idx = [idx[0] for idx in sort_by_name_idx]
 | 
						|
 | 
						|
        info["chs"] = [chans[pos] for pos in sort_by_name_idx]
 | 
						|
        info["order"] = sort_by_name_idx
 | 
						|
    else:
 | 
						|
        info["chs"] = chans
 | 
						|
        info["order"] = np.arange(len(chans))
 | 
						|
 | 
						|
    # finally add some important fields from the config
 | 
						|
    info["e_table"] = cfg["user_blocks"][BTI.UB_B_E_TABLE_USED]
 | 
						|
    info["weights"] = cfg["user_blocks"][BTI.UB_B_WEIGHTS_USED]
 | 
						|
 | 
						|
    return info
 | 
						|
 | 
						|
 | 
						|
def _correct_trans(t, check=True):
 | 
						|
    """Convert to a transformation matrix."""
 | 
						|
    t = np.array(t, np.float64)
 | 
						|
    t[:3, :3] *= t[3, :3][:, np.newaxis]  # apply scalings
 | 
						|
    t[3, :3] = 0.0  # remove them
 | 
						|
    if check:
 | 
						|
        assert t[3, 3] == 1.0
 | 
						|
    else:
 | 
						|
        t[3, 3] = 1.0
 | 
						|
    return t
 | 
						|
 | 
						|
 | 
						|
class RawBTi(BaseRaw):
 | 
						|
    """Raw object from 4D Neuroimaging MagnesWH3600 data.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    pdf_fname : path-like
 | 
						|
        Path to the processed data file (PDF).
 | 
						|
    config_fname : path-like
 | 
						|
        Path to system config file.
 | 
						|
    head_shape_fname : path-like | None
 | 
						|
        Path to the head shape file.
 | 
						|
    rotation_x : float
 | 
						|
        Degrees to tilt x-axis for sensor frame misalignment. Ignored
 | 
						|
        if convert is True.
 | 
						|
    translation : array-like, shape (3,)
 | 
						|
        The translation to place the origin of coordinate system
 | 
						|
        to the center of the head. Ignored if convert is True.
 | 
						|
    convert : bool
 | 
						|
        Convert to Neuromag coordinates or not.
 | 
						|
    rename_channels : bool
 | 
						|
        Whether to keep original 4D channel labels or not. Defaults to True.
 | 
						|
    sort_by_ch_name : bool
 | 
						|
        Reorder channels according to channel label. 4D channels don't have
 | 
						|
        monotonically increasing numbers in their labels. Defaults to True.
 | 
						|
    ecg_ch : str | None
 | 
						|
        The 4D name of the ECG channel. If None, the channel will be treated
 | 
						|
        as regular EEG channel.
 | 
						|
    eog_ch : tuple of str | None
 | 
						|
        The 4D names of the EOG channels. If None, the channels will be treated
 | 
						|
        as regular EEG channels.
 | 
						|
    %(preload)s
 | 
						|
 | 
						|
        .. versionadded:: 0.11
 | 
						|
 | 
						|
    %(verbose)s
 | 
						|
    """
 | 
						|
 | 
						|
    @verbose
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        pdf_fname,
 | 
						|
        config_fname="config",
 | 
						|
        head_shape_fname="hs_file",
 | 
						|
        rotation_x=0.0,
 | 
						|
        translation=(0.0, 0.02, 0.11),
 | 
						|
        convert=True,
 | 
						|
        rename_channels=True,
 | 
						|
        sort_by_ch_name=True,
 | 
						|
        ecg_ch="E31",
 | 
						|
        eog_ch=("E63", "E64"),
 | 
						|
        preload=False,
 | 
						|
        verbose=None,
 | 
						|
    ):
 | 
						|
        _validate_type(pdf_fname, ("path-like", BytesIO), "pdf_fname")
 | 
						|
        info, bti_info = _get_bti_info(
 | 
						|
            pdf_fname=pdf_fname,
 | 
						|
            config_fname=config_fname,
 | 
						|
            head_shape_fname=head_shape_fname,
 | 
						|
            rotation_x=rotation_x,
 | 
						|
            translation=translation,
 | 
						|
            convert=convert,
 | 
						|
            ecg_ch=ecg_ch,
 | 
						|
            rename_channels=rename_channels,
 | 
						|
            sort_by_ch_name=sort_by_ch_name,
 | 
						|
            eog_ch=eog_ch,
 | 
						|
        )
 | 
						|
        bti_info["bti_ch_labels"] = [c["chan_label"] for c in bti_info["chs"]]
 | 
						|
        # make Raw repr work if we have a BytesIO as input
 | 
						|
        filename = bti_info["pdf"]
 | 
						|
        if isinstance(filename, BytesIO):
 | 
						|
            filename = repr(filename)
 | 
						|
        super().__init__(
 | 
						|
            info,
 | 
						|
            preload,
 | 
						|
            filenames=[filename],
 | 
						|
            raw_extras=[bti_info],
 | 
						|
            last_samps=[bti_info["total_slices"] - 1],
 | 
						|
            verbose=verbose,
 | 
						|
        )
 | 
						|
 | 
						|
    def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
 | 
						|
        """Read a segment of data from a file."""
 | 
						|
        bti_info = self._raw_extras[fi]
 | 
						|
        fname_or_bytes = bti_info["pdf"]
 | 
						|
        dtype = bti_info["dtype"]
 | 
						|
        assert len(bti_info["chs"]) == self._raw_extras[fi]["orig_nchan"]
 | 
						|
        n_channels = len(bti_info["chs"])
 | 
						|
        n_bytes = np.dtype(dtype).itemsize
 | 
						|
        data_left = (stop - start) * n_channels
 | 
						|
        read_cals = np.empty((bti_info["total_chans"],))
 | 
						|
        for ch in bti_info["chs"]:
 | 
						|
            read_cals[ch["index"]] = ch["cal"]
 | 
						|
 | 
						|
        block_size = ((int(100e6) // n_bytes) // n_channels) * n_channels
 | 
						|
        block_size = min(data_left, block_size)
 | 
						|
        # extract data in chunks
 | 
						|
        with _bti_open(fname_or_bytes, "rb") as fid:
 | 
						|
            fid.seek(bti_info["bytes_per_slice"] * start, 0)
 | 
						|
            for sample_start in np.arange(0, data_left, block_size) // n_channels:
 | 
						|
                count = min(block_size, data_left - sample_start * n_channels)
 | 
						|
                if isinstance(fid, BytesIO):
 | 
						|
                    block = np.frombuffer(fid.getvalue(), dtype, count)
 | 
						|
                else:
 | 
						|
                    block = np.fromfile(fid, dtype, count)
 | 
						|
                sample_stop = sample_start + count // n_channels
 | 
						|
                shape = (sample_stop - sample_start, bti_info["total_chans"])
 | 
						|
                block.shape = shape
 | 
						|
                data_view = data[:, sample_start:sample_stop]
 | 
						|
                one = np.empty(block.shape[::-1])
 | 
						|
 | 
						|
                for ii, b_i_o in enumerate(bti_info["order"]):
 | 
						|
                    one[ii] = block[:, b_i_o] * read_cals[b_i_o]
 | 
						|
                _mult_cal_one(data_view, one, idx, cals, mult)
 | 
						|
 | 
						|
 | 
						|
@functools.lru_cache(1)
 | 
						|
def _1020_names():
 | 
						|
    from mne.channels import make_standard_montage
 | 
						|
 | 
						|
    return set(
 | 
						|
        ch_name.lower() for ch_name in make_standard_montage("standard_1005").ch_names
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def _eeg_like(ch_name):
 | 
						|
    # Some bti recordigs look like "F4-POz", so let's at least mark them
 | 
						|
    # as EEG
 | 
						|
    if ch_name.count("-") != 1:
 | 
						|
        return
 | 
						|
    ch, ref = ch_name.split("-")
 | 
						|
    eeg_names = _1020_names()
 | 
						|
    return ch.lower() in eeg_names and ref.lower() in eeg_names
 | 
						|
 | 
						|
 | 
						|
def _make_bti_digitization(
 | 
						|
    info, head_shape_fname, convert, use_hpi, bti_dev_t, dev_ctf_t
 | 
						|
):
 | 
						|
    with info._unlock():
 | 
						|
        if head_shape_fname:
 | 
						|
            logger.info(f"... Reading digitization points from {head_shape_fname}")
 | 
						|
 | 
						|
            nasion, lpa, rpa, hpi, dig_points = _read_head_shape(head_shape_fname)
 | 
						|
            info["dig"], dev_head_t, ctf_head_t = _make_bti_dig_points(
 | 
						|
                nasion,
 | 
						|
                lpa,
 | 
						|
                rpa,
 | 
						|
                hpi,
 | 
						|
                dig_points,
 | 
						|
                convert,
 | 
						|
                use_hpi,
 | 
						|
                bti_dev_t,
 | 
						|
                dev_ctf_t,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            logger.info("... no headshape file supplied, doing nothing.")
 | 
						|
            info["dig"] = None
 | 
						|
            dev_head_t = Transform("meg", "head", trans=None)
 | 
						|
            ctf_head_t = Transform("ctf_head", "head", trans=None)
 | 
						|
 | 
						|
    info.update(dev_head_t=dev_head_t, dev_ctf_t=dev_ctf_t, ctf_head_t=ctf_head_t)
 | 
						|
 | 
						|
    return info
 | 
						|
 | 
						|
 | 
						|
def _get_bti_info(
 | 
						|
    pdf_fname,
 | 
						|
    config_fname,
 | 
						|
    head_shape_fname,
 | 
						|
    rotation_x,
 | 
						|
    translation,
 | 
						|
    convert,
 | 
						|
    ecg_ch,
 | 
						|
    eog_ch,
 | 
						|
    rename_channels=True,
 | 
						|
    sort_by_ch_name=True,
 | 
						|
):
 | 
						|
    """Read BTI info.
 | 
						|
 | 
						|
    Note. This helper supports partial construction of infos when `pdf_fname`
 | 
						|
    is None. Some datasets, such as the HCP, are shipped as a large collection
 | 
						|
    of zipped files where it can be more efficient to only read the needed
 | 
						|
    information. In such a situation, some information can neither be accessed
 | 
						|
    directly nor guessed based on the `config`.
 | 
						|
 | 
						|
    These fields will thus be set to None:
 | 
						|
        - 'lowpass'
 | 
						|
        - 'highpass'
 | 
						|
        - 'sfreq'
 | 
						|
        - 'meas_date'
 | 
						|
 | 
						|
    """
 | 
						|
    if pdf_fname is None:
 | 
						|
        logger.info("No pdf_fname passed, trying to construct partial info from config")
 | 
						|
    if pdf_fname is not None and not isinstance(pdf_fname, BytesIO):
 | 
						|
        if not op.isabs(pdf_fname):
 | 
						|
            pdf_fname = op.abspath(pdf_fname)
 | 
						|
 | 
						|
    if not isinstance(config_fname, BytesIO):
 | 
						|
        if not op.isabs(config_fname):
 | 
						|
            config_tries = [
 | 
						|
                op.abspath(config_fname),
 | 
						|
                op.abspath(op.join(op.dirname(pdf_fname), config_fname)),
 | 
						|
            ]
 | 
						|
            for config_try in config_tries:
 | 
						|
                if op.isfile(config_try):
 | 
						|
                    config_fname = config_try
 | 
						|
                    break
 | 
						|
        if not op.isfile(config_fname):
 | 
						|
            raise ValueError(
 | 
						|
                f"Could not find the config file {config_fname}. Please check"
 | 
						|
                " whether you are in the right directory "
 | 
						|
                "or pass the full name"
 | 
						|
            )
 | 
						|
 | 
						|
    if head_shape_fname is not None and not isinstance(head_shape_fname, BytesIO):
 | 
						|
        orig_name = head_shape_fname
 | 
						|
        if not op.isfile(head_shape_fname):
 | 
						|
            head_shape_fname = op.join(op.dirname(pdf_fname), head_shape_fname)
 | 
						|
 | 
						|
        if not op.isfile(head_shape_fname):
 | 
						|
            raise ValueError(
 | 
						|
                f'Could not find the head_shape file "{orig_name}". '
 | 
						|
                "You should check whether you are in the "
 | 
						|
                "right directory, pass the full file name, "
 | 
						|
                "or pass head_shape_fname=None."
 | 
						|
            )
 | 
						|
 | 
						|
    logger.info(f"Reading 4D PDF file {pdf_fname}...")
 | 
						|
    bti_info = _read_bti_header(
 | 
						|
        pdf_fname, config_fname, sort_by_ch_name=sort_by_ch_name
 | 
						|
    )
 | 
						|
    extras = dict(
 | 
						|
        pdf_fname=pdf_fname,
 | 
						|
        head_shape_fname=head_shape_fname,
 | 
						|
        config_fname=config_fname,
 | 
						|
    )
 | 
						|
    for key, val in extras.items():
 | 
						|
        bti_info[key] = None if isinstance(val, BytesIO) else val
 | 
						|
 | 
						|
    dev_ctf_t = Transform(
 | 
						|
        "ctf_meg", "ctf_head", _correct_trans(bti_info["bti_transform"][0])
 | 
						|
    )
 | 
						|
 | 
						|
    _check_nan_dev_head_t(dev_ctf_t)
 | 
						|
    # for old backward compatibility and external processing
 | 
						|
    rotation_x = 0.0 if rotation_x is None else rotation_x
 | 
						|
    bti_dev_t = _get_bti_dev_t(rotation_x, translation) if convert else None
 | 
						|
    bti_dev_t = Transform("ctf_meg", "meg", bti_dev_t)
 | 
						|
 | 
						|
    use_hpi = False  # hard coded, but marked as later option.
 | 
						|
    logger.info("Creating Neuromag info structure ...")
 | 
						|
    if "sample_period" in bti_info.keys():
 | 
						|
        sfreq = 1.0 / bti_info["sample_period"]
 | 
						|
    else:
 | 
						|
        sfreq = None
 | 
						|
 | 
						|
    if pdf_fname is not None:
 | 
						|
        info = _empty_info(sfreq)
 | 
						|
        date = bti_info["processes"][0]["timestamp"]
 | 
						|
        info["meas_date"] = _stamp_to_dt((date, 0))
 | 
						|
    else:  # these cannot be guessed from config, see docstring
 | 
						|
        info = _empty_info(1.0)
 | 
						|
        info["sfreq"] = None
 | 
						|
        info["lowpass"] = None
 | 
						|
        info["highpass"] = None
 | 
						|
        info["meas_date"] = None
 | 
						|
        bti_info["processes"] = list()
 | 
						|
 | 
						|
    # browse processing info for filter specs.
 | 
						|
    hp, lp = info["highpass"], info["lowpass"]
 | 
						|
    for proc in bti_info["processes"]:
 | 
						|
        if "filt" in proc["process_type"]:
 | 
						|
            for step in proc["processing_steps"]:
 | 
						|
                if "high_freq" in step:
 | 
						|
                    hp, lp = step["high_freq"], step["low_freq"]
 | 
						|
                elif "hp" in step["process_type"]:
 | 
						|
                    hp = step["freq"]
 | 
						|
                elif "lp" in step["process_type"]:
 | 
						|
                    lp = step["freq"]
 | 
						|
 | 
						|
    info["highpass"] = hp
 | 
						|
    info["lowpass"] = lp
 | 
						|
    chs = []
 | 
						|
 | 
						|
    # Note that 'name' and 'chan_label' are not the same.
 | 
						|
    # We want the configured label if out IO parsed it
 | 
						|
    # except for the MEG channels for which we keep the config name
 | 
						|
    bti_ch_names = list()
 | 
						|
    for ch in bti_info["chs"]:
 | 
						|
        # we have always relied on 'A' as indicator of MEG data channels.
 | 
						|
        ch_name = ch["name"]
 | 
						|
        if not ch_name.startswith("A"):
 | 
						|
            ch_name = ch.get("chan_label", ch_name)
 | 
						|
        bti_ch_names.append(ch_name)
 | 
						|
 | 
						|
    neuromag_ch_names = _rename_channels(bti_ch_names, ecg_ch=ecg_ch, eog_ch=eog_ch)
 | 
						|
    ch_mapping = zip(bti_ch_names, neuromag_ch_names)
 | 
						|
 | 
						|
    logger.info("... Setting channel info structure.")
 | 
						|
    for idx, (chan_4d, chan_neuromag) in enumerate(ch_mapping):
 | 
						|
        chan_info = _instantiate_default_info_chs()
 | 
						|
        chan_info["ch_name"] = chan_neuromag if rename_channels else chan_4d
 | 
						|
        chan_info["logno"] = idx + BTI.FIFF_LOGNO
 | 
						|
        chan_info["scanno"] = idx + 1
 | 
						|
        chan_info["cal"] = float(bti_info["chs"][idx]["scale"])
 | 
						|
 | 
						|
        if any(chan_4d.startswith(k) for k in ("A", "M", "G")):
 | 
						|
            loc = bti_info["chs"][idx]["loc"]
 | 
						|
            if loc is not None:
 | 
						|
                if convert:
 | 
						|
                    if idx == 0:
 | 
						|
                        logger.info(
 | 
						|
                            "... putting coil transforms in Neuromag coordinates"
 | 
						|
                        )
 | 
						|
                    t = _loc_to_coil_trans(bti_info["chs"][idx]["loc"])
 | 
						|
                    t = _convert_coil_trans(t, dev_ctf_t, bti_dev_t)
 | 
						|
                    loc = _coil_trans_to_loc(t)
 | 
						|
            chan_info["loc"] = loc
 | 
						|
 | 
						|
        # BTI sensors are natively stored in 4D head coords we believe
 | 
						|
        meg_frame = FIFF.FIFFV_COORD_DEVICE if convert else FIFF.FIFFV_MNE_COORD_4D_HEAD
 | 
						|
        eeg_frame = FIFF.FIFFV_COORD_HEAD if convert else FIFF.FIFFV_MNE_COORD_4D_HEAD
 | 
						|
        if chan_4d.startswith("A"):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_MEG_CH
 | 
						|
            chan_info["coil_type"] = FIFF.FIFFV_COIL_MAGNES_MAG
 | 
						|
            chan_info["coord_frame"] = meg_frame
 | 
						|
            chan_info["unit"] = FIFF.FIFF_UNIT_T
 | 
						|
 | 
						|
        elif chan_4d.startswith("M"):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_REF_MEG_CH
 | 
						|
            chan_info["coil_type"] = FIFF.FIFFV_COIL_MAGNES_REF_MAG
 | 
						|
            chan_info["coord_frame"] = meg_frame
 | 
						|
            chan_info["unit"] = FIFF.FIFF_UNIT_T
 | 
						|
 | 
						|
        elif chan_4d.startswith("G"):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_REF_MEG_CH
 | 
						|
            chan_info["coord_frame"] = meg_frame
 | 
						|
            chan_info["unit"] = FIFF.FIFF_UNIT_T_M
 | 
						|
            if chan_4d in ("GxxA", "GyyA"):
 | 
						|
                chan_info["coil_type"] = FIFF.FIFFV_COIL_MAGNES_REF_GRAD
 | 
						|
            elif chan_4d in ("GyxA", "GzxA", "GzyA"):
 | 
						|
                chan_info["coil_type"] = FIFF.FIFFV_COIL_MAGNES_OFFDIAG_REF_GRAD
 | 
						|
 | 
						|
        elif chan_4d.startswith("EEG") or _eeg_like(chan_4d):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_EEG_CH
 | 
						|
            chan_info["coil_type"] = FIFF.FIFFV_COIL_EEG
 | 
						|
            chan_info["coord_frame"] = eeg_frame
 | 
						|
            chan_info["unit"] = FIFF.FIFF_UNIT_V
 | 
						|
            # TODO: We should use 'electrodes' to fill this in, and make sure
 | 
						|
            # we turn them into dig as well
 | 
						|
            chan_info["loc"][:3] = np.nan
 | 
						|
 | 
						|
        elif chan_4d == "RESPONSE":
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_STIM_CH
 | 
						|
        elif chan_4d == "TRIGGER":
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_STIM_CH
 | 
						|
        elif (
 | 
						|
            chan_4d.startswith("EOG")
 | 
						|
            or chan_4d[:4] in ("HEOG", "VEOG")
 | 
						|
            or chan_4d in eog_ch
 | 
						|
        ):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_EOG_CH
 | 
						|
        elif chan_4d.startswith("EMG"):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_EMG_CH
 | 
						|
        elif chan_4d == ecg_ch or chan_4d.startswith("ECG"):
 | 
						|
            chan_info["kind"] = FIFF.FIFFV_ECG_CH
 | 
						|
        # Our default is now misc, but if we ever change that,
 | 
						|
        # we'll need this:
 | 
						|
        # elif chan_4d.startswith('X') or chan_4d == 'UACurrent':
 | 
						|
        #     chan_info['kind'] = FIFF.FIFFV_MISC_CH
 | 
						|
 | 
						|
        chs.append(chan_info)
 | 
						|
 | 
						|
    info["chs"] = chs
 | 
						|
 | 
						|
    # ### Dig stuff
 | 
						|
    info = _make_bti_digitization(
 | 
						|
        info, head_shape_fname, convert, use_hpi, bti_dev_t, dev_ctf_t
 | 
						|
    )
 | 
						|
 | 
						|
    logger.info(
 | 
						|
        "Currently direct inclusion of 4D weight tables is not supported."
 | 
						|
        " For critical use cases please take into account the MNE command"
 | 
						|
        ' "mne_create_comp_data" to include weights as printed out by '
 | 
						|
        'the 4D "print_table" routine.'
 | 
						|
    )
 | 
						|
 | 
						|
    # check that the info is complete
 | 
						|
    info._unlocked = False
 | 
						|
    info._update_redundant()
 | 
						|
    info._check_consistency()
 | 
						|
    return info, bti_info
 | 
						|
 | 
						|
 | 
						|
@verbose
 | 
						|
def read_raw_bti(
 | 
						|
    pdf_fname,
 | 
						|
    config_fname="config",
 | 
						|
    head_shape_fname="hs_file",
 | 
						|
    rotation_x=0.0,
 | 
						|
    translation=(0.0, 0.02, 0.11),
 | 
						|
    convert=True,
 | 
						|
    rename_channels=True,
 | 
						|
    sort_by_ch_name=True,
 | 
						|
    ecg_ch="E31",
 | 
						|
    eog_ch=("E63", "E64"),
 | 
						|
    preload=False,
 | 
						|
    verbose=None,
 | 
						|
) -> RawBTi:
 | 
						|
    """Raw object from 4D Neuroimaging MagnesWH3600 data.
 | 
						|
 | 
						|
    .. note::
 | 
						|
        1. Currently direct inclusion of reference channel weights
 | 
						|
           is not supported. Please use ``mne_create_comp_data`` to include
 | 
						|
           the weights or use the low level functions from this module to
 | 
						|
           include them by yourself.
 | 
						|
        2. The informed guess for the 4D name is E31 for the ECG channel and
 | 
						|
           E63, E63 for the EOG channels. Please check and adjust if those
 | 
						|
           channels are present in your dataset but 'ECG 01' and 'EOG 01',
 | 
						|
           'EOG 02' don't appear in the channel names of the raw object.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    pdf_fname : path-like
 | 
						|
        Path to the processed data file (PDF).
 | 
						|
    config_fname : path-like
 | 
						|
        Path to system config file.
 | 
						|
    head_shape_fname : path-like | None
 | 
						|
        Path to the head shape file.
 | 
						|
    rotation_x : float
 | 
						|
        Degrees to tilt x-axis for sensor frame misalignment. Ignored
 | 
						|
        if convert is True.
 | 
						|
    translation : array-like, shape (3,)
 | 
						|
        The translation to place the origin of coordinate system
 | 
						|
        to the center of the head. Ignored if convert is True.
 | 
						|
    convert : bool
 | 
						|
        Convert to Neuromag coordinates or not.
 | 
						|
    rename_channels : bool
 | 
						|
        Whether to keep original 4D channel labels or not. Defaults to True.
 | 
						|
    sort_by_ch_name : bool
 | 
						|
        Reorder channels according to channel label. 4D channels don't have
 | 
						|
        monotonically increasing numbers in their labels. Defaults to True.
 | 
						|
    ecg_ch : str | None
 | 
						|
        The 4D name of the ECG channel. If None, the channel will be treated
 | 
						|
        as regular EEG channel.
 | 
						|
    eog_ch : tuple of str | None
 | 
						|
        The 4D names of the EOG channels. If None, the channels will be treated
 | 
						|
        as regular EEG channels.
 | 
						|
    %(preload)s
 | 
						|
 | 
						|
        .. versionadded:: 0.11
 | 
						|
    %(verbose)s
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    raw : instance of RawBTi
 | 
						|
        A Raw object containing BTI data.
 | 
						|
        See :class:`mne.io.Raw` for documentation of attributes and methods.
 | 
						|
 | 
						|
    See Also
 | 
						|
    --------
 | 
						|
    mne.io.Raw : Documentation of attributes and methods of RawBTi.
 | 
						|
    """
 | 
						|
    return RawBTi(
 | 
						|
        pdf_fname,
 | 
						|
        config_fname=config_fname,
 | 
						|
        head_shape_fname=head_shape_fname,
 | 
						|
        rotation_x=rotation_x,
 | 
						|
        translation=translation,
 | 
						|
        convert=convert,
 | 
						|
        rename_channels=rename_channels,
 | 
						|
        sort_by_ch_name=sort_by_ch_name,
 | 
						|
        ecg_ch=ecg_ch,
 | 
						|
        eog_ch=eog_ch,
 | 
						|
        preload=preload,
 | 
						|
        verbose=verbose,
 | 
						|
    )
 |