|
8 | 8 | # Many of the computations in this code were derived from Matti Hämäläinen's |
9 | 9 | # C code. |
10 | 10 |
|
| 11 | +from collections import OrderedDict |
11 | 12 | from copy import deepcopy |
12 | 13 | from functools import partial, lru_cache |
13 | | -from collections import OrderedDict |
14 | 14 | from glob import glob |
| 15 | +import json |
15 | 16 | from os import path as op |
| 17 | +from pathlib import Path |
16 | 18 | import time |
17 | 19 | import warnings |
18 | 20 |
|
|
33 | 35 | _get_trans, |
34 | 36 | apply_trans, |
35 | 37 | Transform, |
| 38 | + _fit_matched_points, |
| 39 | + _MatchedDisplacementFieldInterpolator, |
| 40 | + _angle_between_quats, |
36 | 41 | ) |
37 | 42 | from .utils import ( |
38 | 43 | logger, |
|
52 | 57 | _import_nibabel, |
53 | 58 | ) |
54 | 59 |
|
| 60 | +_helmet_path = Path(__file__).parent / "data" / "helmets" |
| 61 | + |
55 | 62 |
|
56 | 63 | ############################################################################### |
57 | 64 | # AUTOMATED SURFACE FINDING |
@@ -205,10 +212,11 @@ def get_meg_helmet_surf(info, trans=None, *, verbose=None): |
205 | 212 | system, have_helmet = _get_meg_system(info) |
206 | 213 | if have_helmet: |
207 | 214 | logger.info("Getting helmet for system %s" % system) |
208 | | - fname = op.join(op.split(__file__)[0], "data", "helmets", system + ".fif.gz") |
| 215 | + fname = _helmet_path / f"{system}.fif.gz" |
209 | 216 | surf = read_bem_surfaces( |
210 | 217 | fname, False, FIFF.FIFFV_MNE_SURF_MEG_HELMET, verbose=False |
211 | 218 | ) |
| 219 | + surf = _scale_helmet_to_sensors(system, surf, info) |
212 | 220 | else: |
213 | 221 | rr = np.array( |
214 | 222 | [ |
@@ -248,6 +256,40 @@ def get_meg_helmet_surf(info, trans=None, *, verbose=None): |
248 | 256 | return surf |
249 | 257 |
|
250 | 258 |
|
| 259 | +def _scale_helmet_to_sensors(system, surf, info): |
| 260 | + fname = _helmet_path / f"{system}_ch_pos.txt" |
| 261 | + if not fname.is_file(): |
| 262 | + return surf |
| 263 | + with open(fname) as fid: |
| 264 | + ch_pos_from = json.load(fid) |
| 265 | + # find correspondence |
| 266 | + fro, to = list(), list() |
| 267 | + for key, f_ in ch_pos_from.items(): |
| 268 | + t_ = [ch["loc"][:3] for ch in info["chs"] if ch["ch_name"].startswith(key)] |
| 269 | + if not len(t_): |
| 270 | + continue |
| 271 | + fro.append(f_) |
| 272 | + to.append(np.mean(t_, axis=0)) |
| 273 | + if len(fro) < 4: |
| 274 | + logger.info("Using CAD helmet because fewer than 4 sensors found") |
| 275 | + return surf |
| 276 | + fro = np.array(fro, float) |
| 277 | + to = np.array(to, float) |
| 278 | + interp = _MatchedDisplacementFieldInterpolator(fro, to) |
| 279 | + new_rr = interp(surf["rr"]) |
| 280 | + quat, sc = _fit_matched_points(surf["rr"], new_rr) |
| 281 | + rot = np.rad2deg(_angle_between_quats(quat[:3])) |
| 282 | + tr = 1000 * np.linalg.norm(quat[3:]) |
| 283 | + logger.info(f" Deforming to match info using {len(fro)} matched points:") |
| 284 | + logger.info(f" 1. Affine: {rot:0.1f}°, {tr:0.1f} mm, {sc:0.2f}× scale") |
| 285 | + deltas = interp._last_deltas * 1000 |
| 286 | + mu, mx = np.mean(deltas), np.max(deltas) |
| 287 | + logger.info(f" 2. Nonlinear displacement: " f"mean={mu:0.1f}, max={mx:0.1f} mm") |
| 288 | + surf["rr"] = new_rr |
| 289 | + complete_surface_info(surf, copy=False, verbose=False) |
| 290 | + return surf |
| 291 | + |
| 292 | + |
251 | 293 | def _reorder_ccw(rrs, tris): |
252 | 294 | """Reorder tris of a convex hull to be wound counter-clockwise.""" |
253 | 295 | # This ensures that rendering with front-/back-face culling works properly |
|
0 commit comments