|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +Functions for working with CIVET data (ugh) |
| 4 | +""" |
| 5 | + |
| 6 | +import nibabel as nib |
| 7 | +import numpy as np |
| 8 | +from scipy.spatial import cKDTree |
| 9 | + |
| 10 | +from .datasets import fetch_civet, fetch_fsaverage |
| 11 | + |
| 12 | +_MNI305to152 = np.array([[0.9975, -0.0073, 0.0176, -0.0429], |
| 13 | + [0.0146, 1.0009, -0.0024, 1.5496], |
| 14 | + [-0.0130, -0.0093, 0.9971, 1.1840]]) |
| 15 | + |
| 16 | + |
| 17 | +def read_civet(fname): |
| 18 | + """ |
| 19 | + Reads a CIVET-style .obj geometry file |
| 20 | +
|
| 21 | + Parameters |
| 22 | + ---------- |
| 23 | + fname : str or os.PathLike |
| 24 | + Filepath to .obj file |
| 25 | +
|
| 26 | + Returns |
| 27 | + ------- |
| 28 | + vertices : (N, 3) |
| 29 | + triangles : (T, 3) |
| 30 | + """ |
| 31 | + |
| 32 | + k, polygons = 0, [] |
| 33 | + with open(fname, 'r') as src: |
| 34 | + n_vert = int(src.readline().split()[6]) |
| 35 | + vertices = np.zeros((n_vert, 3)) |
| 36 | + for i, line in enumerate(src): |
| 37 | + if i < n_vert: |
| 38 | + vertices[i] = [float(i) for i in line.split()] |
| 39 | + elif i >= (2 * n_vert) + 5: |
| 40 | + if not line.strip(): |
| 41 | + k = 1 |
| 42 | + elif k == 1: |
| 43 | + polygons.extend([int(i) for i in line.split()]) |
| 44 | + |
| 45 | + triangles = np.reshape(np.asarray(polygons), (-1, 3)) |
| 46 | + |
| 47 | + return vertices, triangles |
| 48 | + |
| 49 | + |
| 50 | +def _get_civet_to_fs_mapping(obj, fs): |
| 51 | + """ |
| 52 | + Returns a mapping between `obj` and `fs` geometry files |
| 53 | +
|
| 54 | + Parameters |
| 55 | + ---------- |
| 56 | + obj : str or os.PathLike |
| 57 | + Path to CIVET geometry file |
| 58 | + fs : str or os.PathLike |
| 59 | + Path to FreeSurfer geometry file |
| 60 | +
|
| 61 | + Returns |
| 62 | + ------- |
| 63 | + idx : (N,) np.ndarray |
| 64 | + Mapping from CIVET to FreeSurfer space |
| 65 | + """ |
| 66 | + |
| 67 | + vert_cv, _ = read_civet(obj) |
| 68 | + vert_fs, _ = nib.freesurfer.read_geometry(fs) |
| 69 | + |
| 70 | + vert_fs = np.c_[vert_fs, np.ones(len(vert_fs))] @ _MNI305to152.T |
| 71 | + _, idx = cKDTree(vert_cv).query(vert_fs, k=1, distance_upper_bound=10) |
| 72 | + |
| 73 | + return idx |
| 74 | + |
| 75 | + |
| 76 | +def civet_to_freesurfer(brainmap, surface='mid', version='v1', |
| 77 | + freesurfer='fsaverage6', mapping=None, data_dir=None): |
| 78 | + """ |
| 79 | + Projects `brainmap` in CIVET space to `freesurfer` fsaverage space |
| 80 | +
|
| 81 | + Uses a nearest-neighbor projection based on the geometry of the vertices |
| 82 | +
|
| 83 | + Parameters |
| 84 | + ---------- |
| 85 | + brainmap : array_like |
| 86 | + CIVET brainmap to be converted to freesurfer space |
| 87 | + surface : {'white', 'mid'}, optional |
| 88 | + Which CIVET surface to use for geometry of `brainmap`. Default: 'mid' |
| 89 | + version : {'v1', 'v2'}, optional |
| 90 | + Which CIVET version to use for geometry of `brainmap`. Default: 'v1' |
| 91 | + freesurfer : str, optional |
| 92 | + Which version of FreeSurfer space to project data to. Must be one of |
| 93 | + {'fsaverage', 'fsaverage3', 'fsaverage4', 'fsaverage5', 'fsaverage6'}. |
| 94 | + Default: 'fsaverage6' |
| 95 | + mapping : array_like, optional |
| 96 | + If mapping has been pre-computed for `surface` --> `version` and is |
| 97 | + provided, this will be used instead of recomputing. Default: None |
| 98 | + data_dir : str, optional |
| 99 | + Path to use as data directory. If not specified, will check for |
| 100 | + environmental variable 'NNT_DATA'; if that is not set, will use |
| 101 | + `~/nnt-data` instead. Default: None |
| 102 | +
|
| 103 | + Returns |
| 104 | + ------- |
| 105 | + data : np.ndarray |
| 106 | + Provided `brainmap` mapped to FreeSurfer |
| 107 | + """ |
| 108 | + |
| 109 | + densities = (81924, 327684) |
| 110 | + n_vert = len(brainmap) |
| 111 | + if n_vert not in densities: |
| 112 | + raise ValueError('Unable to interpret `brainmap` space; provided ' |
| 113 | + 'array must have length in {}. Received: {}' |
| 114 | + .format(densities, n_vert)) |
| 115 | + |
| 116 | + n_vert = n_vert // 2 |
| 117 | + icbm = fetch_civet(density='41k' if n_vert == 40962 else '164k', |
| 118 | + version=version, data_dir=data_dir, verbose=0)[surface] |
| 119 | + fsavg = fetch_fsaverage(version=freesurfer, data_dir=data_dir, verbose=0) |
| 120 | + fsavg = fsavg['pial' if surface == 'mid' else 'white'] |
| 121 | + |
| 122 | + data = [] |
| 123 | + for n, hemi in enumerate(('lh', 'rh')): |
| 124 | + sl = slice(n_vert * n, n_vert * (n + 1)) |
| 125 | + if mapping is None: |
| 126 | + idx = _get_civet_to_fs_mapping(getattr(icbm, hemi), |
| 127 | + getattr(fsavg, hemi)) |
| 128 | + else: |
| 129 | + idx = mapping[sl] |
| 130 | + |
| 131 | + data.append(brainmap[sl][idx]) |
| 132 | + |
| 133 | + return np.hstack(data) |
0 commit comments