# Authors: The MNE-Python contributors. # License: BSD-3-Clause # Copyright the MNE-Python contributors. import os.path as op import numpy as np from ..._fiff._digitization import _artemis123_read_pos from ...transforms import rotation3d_align_z_axis from ...utils import logger def _load_mne_locs(fname=None): """Load MNE locs structure from file (if exists) or recreate it.""" if not fname: # find input file resource_dir = op.join(op.dirname(op.abspath(__file__)), "resources") fname = op.join(resource_dir, "Artemis123_mneLoc.csv") if not op.exists(fname): raise OSError(f'MNE locs file "{fname}" does not exist') logger.info(f"Loading mne loc file {fname}") locs = dict() with open(fname) as fid: for line in fid: vals = line.strip().split(",") locs[vals[0]] = np.array(vals[1::], np.float64) return locs def _generate_mne_locs_file(output_fname): """Generate mne coil locs and save to supplied file.""" logger.info("Converting Tristan coil file to mne loc file...") resource_dir = op.join(op.dirname(op.abspath(__file__)), "resources") chan_fname = op.join(resource_dir, "Artemis123_ChannelMap.csv") chans = _load_tristan_coil_locs(chan_fname) # compute a dict of loc structs locs = {n: _compute_mne_loc(cinfo) for n, cinfo in chans.items()} # write it out to output_fname with open(output_fname, "w") as fid: for n in sorted(locs.keys()): fid.write(f"{n},") fid.write(",".join(locs[n].astype(str))) fid.write("\n") def _load_tristan_coil_locs(coil_loc_path): """Load the Coil locations from Tristan CAD drawings.""" channel_info = dict() with open(coil_loc_path) as fid: # skip 2 Header lines fid.readline() fid.readline() for line in fid: line = line.strip() vals = line.split(",") channel_info[vals[0]] = dict() if vals[6]: channel_info[vals[0]]["inner_coil"] = np.array(vals[2:5], np.float64) channel_info[vals[0]]["outer_coil"] = np.array(vals[5:8], np.float64) else: # nothing supplied channel_info[vals[0]]["inner_coil"] = np.zeros(3) channel_info[vals[0]]["outer_coil"] = np.zeros(3) return channel_info def _compute_mne_loc(coil_loc): """Convert a set of coils to an mne Struct. Note input coil locations are in inches. """ loc = np.zeros(12) if (np.linalg.norm(coil_loc["inner_coil"]) == 0) and ( np.linalg.norm(coil_loc["outer_coil"]) == 0 ): return loc # channel location is inner coil location converted to meters From inches loc[0:3] = coil_loc["inner_coil"] / 39.370078 # figure out rotation z_axis = coil_loc["outer_coil"] - coil_loc["inner_coil"] R = rotation3d_align_z_axis(z_axis) loc[3:13] = R.T.reshape(9) return loc def _read_pos(fname): """Read the .pos file and return positions as dig points.""" nas, lpa, rpa, hpi, extra = None, None, None, None, None with open(fname) as fid: for line in fid: line = line.strip() if len(line) > 0: parts = line.split() # The lines can have 4 or 5 parts. First part is for the id, # which can be an int or a string. The last three are for xyz # coordinates. The extra part is for additional info # (e.g. 'Pz', 'Cz') which is ignored. if len(parts) not in [4, 5]: continue if parts[0].lower() == "nasion": nas = np.array([float(p) for p in parts[-3:]]) / 100.0 elif parts[0].lower() == "left": lpa = np.array([float(p) for p in parts[-3:]]) / 100.0 elif parts[0].lower() == "right": rpa = np.array([float(p) for p in parts[-3:]]) / 100.0 elif "hpi" in parts[0].lower(): if hpi is None: hpi = list() hpi.append(np.array([float(p) for p in parts[-3:]]) / 100.0) else: if extra is None: extra = list() extra.append(np.array([float(p) for p in parts[-3:]]) / 100.0) return _artemis123_read_pos(nas, lpa, rpa, hpi, extra)