def test_make_flash_bem(): """Test computing bem from flash images.""" tmp = _TempDir() bemdir = op.join(subjects_dir, 'sample', 'bem') flash_path = op.join(subjects_dir, 'sample', 'mri', 'flash') for surf in ('inner_skull', 'outer_skull', 'outer_skin'): copy(op.join(bemdir, surf + '.surf'), tmp) copy(op.join(bemdir, surf + '.tri'), tmp) copy(op.join(bemdir, 'inner_skull_tmp.tri'), tmp) copy(op.join(bemdir, 'outer_skin_from_testing.surf'), tmp) # This function deletes the tri files at the end. try: make_flash_bem('sample', overwrite=True, subjects_dir=subjects_dir, flash_path=flash_path) for surf in ('inner_skull', 'outer_skull', 'outer_skin'): coords, faces = read_surface(op.join(bemdir, surf + '.surf')) surf = 'outer_skin_from_testing' if surf == 'outer_skin' else surf coords_c, faces_c = read_surface(op.join(tmp, surf + '.surf')) assert_equal(0, faces.min()) assert_equal(coords.shape[0], faces.max() + 1) assert_allclose(coords, coords_c) assert_allclose(faces, faces_c) finally: for surf in ('inner_skull', 'outer_skull', 'outer_skin'): remove(op.join(bemdir, surf + '.surf')) # delete symlinks copy(op.join(tmp, surf + '.tri'), bemdir) # return deleted tri copy(op.join(tmp, surf + '.surf'), bemdir) # return moved surf copy(op.join(tmp, 'inner_skull_tmp.tri'), bemdir) copy(op.join(tmp, 'outer_skin_from_testing.surf'), bemdir) plt.close('all')
def test_make_flash_bem(tmpdir): """Test computing bem from flash images.""" tmp = str(tmpdir) bemdir = op.join(subjects_dir, 'sample', 'bem') flash_path = op.join(subjects_dir, 'sample', 'mri', 'flash') for surf in ('inner_skull', 'outer_skull', 'outer_skin'): copy(op.join(bemdir, surf + '.surf'), tmp) copy(op.join(bemdir, surf + '.tri'), tmp) copy(op.join(bemdir, 'inner_skull_tmp.tri'), tmp) copy(op.join(bemdir, 'outer_skin_from_testing.surf'), tmp) # This function deletes the tri files at the end. try: make_flash_bem('sample', overwrite=True, subjects_dir=subjects_dir, flash_path=flash_path) for surf in ('inner_skull', 'outer_skull', 'outer_skin'): coords, faces = read_surface(op.join(bemdir, surf + '.surf')) surf = 'outer_skin_from_testing' if surf == 'outer_skin' else surf coords_c, faces_c = read_surface(op.join(tmp, surf + '.surf')) assert_equal(0, faces.min()) assert_equal(coords.shape[0], faces.max() + 1) assert_allclose(coords, coords_c) assert_allclose(faces, faces_c) finally: for surf in ('inner_skull', 'outer_skull', 'outer_skin'): remove(op.join(bemdir, surf + '.surf')) # delete symlinks copy(op.join(tmp, surf + '.tri'), bemdir) # return deleted tri copy(op.join(tmp, surf + '.surf'), bemdir) # return moved surf copy(op.join(tmp, 'inner_skull_tmp.tri'), bemdir) copy(op.join(tmp, 'outer_skin_from_testing.surf'), bemdir) plt.close('all')
def test_bem_model_topology(tmpdir): """Test BEM model topological checks.""" # bad topology (not enough neighboring tris) makedirs(tmpdir.join('foo', 'bem')) for fname in ('inner_skull', 'outer_skull', 'outer_skin'): fname += '.surf' copy(op.join(subjects_dir, 'sample', 'bem', fname), str(tmpdir.join('foo', 'bem', fname))) outer_fname = tmpdir.join('foo', 'bem', 'outer_skull.surf') rr, tris = read_surface(outer_fname) tris = tris[:-1] write_surface(outer_fname, rr, tris[:-1], overwrite=True) with pytest.raises(RuntimeError, match='Surface outer skull is not compl'): make_bem_model('foo', None, subjects_dir=tmpdir) # Now get past this error to reach gh-6127 (not enough neighbor tris) rr_bad = np.concatenate([rr, np.mean(rr, axis=0, keepdims=True)], axis=0) write_surface(outer_fname, rr_bad, tris, overwrite=True) with pytest.raises(RuntimeError, match='Surface outer skull.*triangles'): make_bem_model('foo', None, subjects_dir=tmpdir)
def add_volume_info(subject, surface, subjects_dir, volume='T1'): """Add volume info from MGZ volume """ import os.path as op from mne.bem import _extract_volume_info from mne.surface import (read_surface, write_surface) subject_dir = op.join(subjects_dir, subject) mri_dir = op.join(subject_dir, 'mri') T1_mgz = op.join(mri_dir, volume + '.mgz') new_info = _extract_volume_info(T1_mgz) print(new_info.keys()) rr, tris, volume_info = read_surface(surface, read_metadata=True) # volume_info.update(new_info) # replace volume info, 'head' stays print(volume_info.keys()) import numpy as np if 'head' not in volume_info.keys(): volume_info['head'] = np.array([2, 0, 20], dtype=np.int32) write_surface(surface, rr, tris, volume_info=volume_info)
def test_bem_model_topology(tmpdir): """Test BEM model topological checks.""" # bad topology (not enough neighboring tris) tempdir = str(tmpdir) makedirs(op.join(tempdir, 'foo', 'bem')) for fname in ('inner_skull', 'outer_skull', 'outer_skin'): fname += '.surf' copy(op.join(subjects_dir, 'sample', 'bem', fname), op.join(tempdir, 'foo', 'bem', fname)) outer_fname = op.join(tempdir, 'foo', 'bem', 'outer_skull.surf') rr, tris = read_surface(outer_fname) tris = tris[:-1] write_surface(outer_fname, rr, tris[:-1]) with pytest.raises(RuntimeError, match='Surface outer skull is not compl'): make_bem_model('foo', None, subjects_dir=tempdir) # Now get past this error to reach gh-6127 (not enough neighbor tris) rr_bad = np.concatenate([rr, np.mean(rr, axis=0, keepdims=True)], axis=0) write_surface(outer_fname, rr_bad, tris) with pytest.raises(RuntimeError, match='Surface outer skull.*triangles'): make_bem_model('foo', None, subjects_dir=tempdir)
def dip_depth(dip, fname_trans, subject, subjects_dir): trans = read_trans(fname_trans) trans = _get_trans(trans)[0] subjects_dir = get_subjects_dir(subjects_dir=subjects_dir) fname = os.path.join(subjects_dir, subject, 'bem', 'inner_skull.surf') points, faces = read_surface(fname) points = apply_trans(trans['trans'], points * 1e-3) pos = dip.pos ori = dip.ori from sklearn.neighbors import NearestNeighbors nn = NearestNeighbors() nn.fit(points) depth, idx = nn.kneighbors(pos, 1, return_distance=True) idx = np.ravel(idx) direction = pos - points[idx] direction /= np.sqrt(np.sum(direction**2, axis=1))[:, None] ori /= np.sqrt(np.sum(ori**2, axis=1))[:, None] radiality = np.abs(np.sum(ori * direction, axis=1)) return np.ravel(depth), radiality
def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False, subjects_dir=None, annot_fname=None, colormap='hsv', hemi='both'): """Create a FreeSurfer annotation from a list of labels FIX: always write both hemispheres Parameters ---------- labels : list with instances of mne.Label The labels to create a parcellation from. subject : str | None The subject for which to write the parcellation for. parc : str | None The parcellation name to use. overwrite : bool Overwrite files if they already exist. subjects_dir : string, or None Path to SUBJECTS_DIR if it is not set in the environment. annot_fname : str | None Filename of the .annot file. If not None, only this file is written and 'parc' and 'subject' are ignored. colormap : str Colormap to use to generate label colors for labels that do not have a color specified. hemi : 'both' | 'lh' | 'rh' The hemisphere(s) for which to write *.annot files (only applies if annot_fname is not specified; default is 'both'). verbose : bool, str, int, or None If not None, override default verbose level (see mne.verbose). Notes ----- Vertices that are not covered by any of the labels are assigned to a label named "unknown". """ subjects_dir = get_subjects_dir(subjects_dir) # get the .annot filenames and hemispheres annot_fname, hemis = _get_annot_fname(annot_fname, subject, hemi, parc, subjects_dir) if not overwrite: for fname in annot_fname: if op.exists(fname): raise ValueError('File %s exists. Use "overwrite=True" to ' 'overwrite it' % fname) # prepare container for data to save: to_save = [] # keep track of issues found in the labels duplicate_colors = [] invalid_colors = [] overlap = [] no_color = (-1, -1, -1, -1) no_color_rgb = (-1, -1, -1) for hemi, fname in zip(hemis, annot_fname): hemi_labels = [label for label in labels if label.hemi == hemi] n_hemi_labels = len(hemi_labels) if n_hemi_labels == 0: ctab = np.empty((0, 4), dtype=np.int32) ctab_rgb = ctab[:, :3] else: hemi_labels.sort(key=lambda label: label.name) # convert colors to 0-255 RGBA tuples hemi_colors = [ no_color if label.color is None else tuple( int(round(255 * i)) for i in label.color) for label in hemi_labels ] ctab = np.array(hemi_colors, dtype=np.int32) ctab_rgb = ctab[:, :3] # make color dict (for annot ID, only R, G and B count) labels_by_color = defaultdict(list) for label, color in zip(hemi_labels, ctab_rgb): labels_by_color[tuple(color)].append(label.name) # check label colors for color, names in labels_by_color.items(): if color == no_color_rgb: continue if color == (0, 0, 0): # we cannot have an all-zero color, otherw. e.g. tksurfer # refuses to read the parcellation msg = ('At least one label contains a color with, "r=0, ' 'g=0, b=0" value. Some FreeSurfer tools may fail ' 'to read the parcellation') logger.warning(msg) if any(i > 255 for i in color): msg = ("%s: %s (%s)" % (color, ', '.join(names), hemi)) invalid_colors.append(msg) if len(names) > 1: msg = "%s: %s (%s)" % (color, ', '.join(names), hemi) duplicate_colors.append(msg) # replace None values (labels with unspecified color) if labels_by_color[no_color_rgb]: default_colors = _n_colors(n_hemi_labels, bytes_=True, cmap=colormap) safe_color_i = 0 # keep track of colors known to be in hemi_colors for i in xrange(n_hemi_labels): if ctab[i, 0] == -1: color = default_colors[i] # make sure to add no duplicate color while np.any(np.all(color[:3] == ctab_rgb, 1)): color = default_colors[safe_color_i] safe_color_i += 1 # assign the color ctab[i] = color # find number of vertices in surface if subject is not None and subjects_dir is not None: fpath = op.join(subjects_dir, subject, 'surf', '%s.white' % hemi) points, _ = read_surface(fpath) n_vertices = len(points) else: if len(hemi_labels) > 0: max_vert = max(np.max(label.vertices) for label in hemi_labels) n_vertices = max_vert + 1 else: n_vertices = 1 msg = (' Number of vertices in the surface could not be ' 'verified because the surface file could not be found; ' 'specify subject and subjects_dir parameters.') logger.warning(msg) # Create annot and color table array to write annot = np.empty(n_vertices, dtype=np.intp) annot[:] = -1 # create the annotation ids from the colors annot_id_coding = np.array((1, 2**8, 2**16)) annot_ids = list(np.sum(ctab_rgb * annot_id_coding, axis=1)) for label, annot_id in zip(hemi_labels, annot_ids): # make sure the label is not overwriting another label if np.any(annot[label.vertices] != -1): other_ids = set(annot[label.vertices]) other_ids.discard(-1) other_indices = (annot_ids.index(i) for i in other_ids) other_names = (hemi_labels[i].name for i in other_indices) other_repr = ', '.join(other_names) msg = "%s: %s overlaps %s" % (hemi, label.name, other_repr) overlap.append(msg) annot[label.vertices] = annot_id hemi_names = [label.name for label in hemi_labels] # Assign unlabeled vertices to an "unknown" label unlabeled = (annot == -1) if np.any(unlabeled): msg = ("Assigning %i unlabeled vertices to " "'unknown-%s'" % (unlabeled.sum(), hemi)) logger.info(msg) # find an unused color (try shades of gray first) for i in range(1, 257): if not np.any(np.all((i, i, i) == ctab_rgb, 1)): break if i < 256: color = (i, i, i, 0) else: err = ("Need one free shade of gray for 'unknown' label. " "Please modify your label colors, or assign the " "unlabeled vertices to another label.") raise ValueError(err) # find the id annot_id = np.sum(annot_id_coding * color[:3]) # update data to write annot[unlabeled] = annot_id ctab = np.vstack((ctab, color)) hemi_names.append("unknown") # convert to FreeSurfer alpha values ctab[:, 3] = 255 - ctab[:, 3] # remove hemi ending in names hemi_names = [ name[:-3] if name.endswith(hemi) else name for name in hemi_names ] to_save.append((fname, annot, ctab, hemi_names)) issues = [] if duplicate_colors: msg = ("Some labels have the same color values (all labels in one " "hemisphere must have a unique color):") duplicate_colors.insert(0, msg) issues.append('\n'.join(duplicate_colors)) if invalid_colors: msg = ("Some labels have invalid color values (all colors should be " "RGBA tuples with values between 0 and 1)") invalid_colors.insert(0, msg) issues.append('\n'.join(invalid_colors)) if overlap: msg = ("Some labels occupy vertices that are also occupied by one or " "more other labels. Each vertex can only be occupied by a " "single label in *.annot files.") overlap.insert(0, msg) issues.append('\n'.join(overlap)) if issues: raise ValueError('\n\n'.join(issues)) # write it for fname, annot, ctab, hemi_names in to_save: logger.info(' writing %d labels to %s' % (len(hemi_names), fname)) _write_annot(fname, annot, ctab, hemi_names) logger.info('[done]')
def write_labels_to_annot(labels, subject=None, parc=None, overwrite=False, subjects_dir=None, annot_fname=None, colormap='hsv', hemi='both'): """Create a FreeSurfer annotation from a list of labels FIX: always write both hemispheres Parameters ---------- labels : list with instances of mne.Label The labels to create a parcellation from. subject : str | None The subject for which to write the parcellation for. parc : str | None The parcellation name to use. overwrite : bool Overwrite files if they already exist. subjects_dir : string, or None Path to SUBJECTS_DIR if it is not set in the environment. annot_fname : str | None Filename of the .annot file. If not None, only this file is written and 'parc' and 'subject' are ignored. colormap : str Colormap to use to generate label colors for labels that do not have a color specified. hemi : 'both' | 'lh' | 'rh' The hemisphere(s) for which to write *.annot files (only applies if annot_fname is not specified; default is 'both'). verbose : bool, str, int, or None If not None, override default verbose level (see mne.verbose). Notes ----- Vertices that are not covered by any of the labels are assigned to a label named "unknown". """ subjects_dir = get_subjects_dir(subjects_dir) # get the .annot filenames and hemispheres annot_fname, hemis = _get_annot_fname(annot_fname, subject, hemi, parc, subjects_dir) if not overwrite: for fname in annot_fname: if op.exists(fname): raise ValueError('File %s exists. Use "overwrite=True" to ' 'overwrite it' % fname) # prepare container for data to save: to_save = [] # keep track of issues found in the labels duplicate_colors = [] invalid_colors = [] overlap = [] no_color = (-1, -1, -1, -1) no_color_rgb = (-1, -1, -1) for hemi, fname in zip(hemis, annot_fname): hemi_labels = [label for label in labels if label.hemi == hemi] n_hemi_labels = len(hemi_labels) if n_hemi_labels == 0: ctab = np.empty((0, 4), dtype=np.int32) ctab_rgb = ctab[:, :3] else: hemi_labels.sort(key=lambda label: label.name) # convert colors to 0-255 RGBA tuples hemi_colors = [no_color if label.color is None else tuple(int(round(255 * i)) for i in label.color) for label in hemi_labels] ctab = np.array(hemi_colors, dtype=np.int32) ctab_rgb = ctab[:, :3] # make color dict (for annot ID, only R, G and B count) labels_by_color = defaultdict(list) for label, color in zip(hemi_labels, ctab_rgb): labels_by_color[tuple(color)].append(label.name) # check label colors for color, names in labels_by_color.items(): if color == no_color_rgb: continue if color == (0, 0, 0): # we cannot have an all-zero color, otherw. e.g. tksurfer # refuses to read the parcellation msg = ('At least one label contains a color with, "r=0, ' 'g=0, b=0" value. Some FreeSurfer tools may fail ' 'to read the parcellation') logger.warning(msg) if any(i > 255 for i in color): msg = ("%s: %s (%s)" % (color, ', '.join(names), hemi)) invalid_colors.append(msg) if len(names) > 1: msg = "%s: %s (%s)" % (color, ', '.join(names), hemi) duplicate_colors.append(msg) # replace None values (labels with unspecified color) if labels_by_color[no_color_rgb]: default_colors = _n_colors(n_hemi_labels, bytes_=True, cmap=colormap) safe_color_i = 0 # keep track of colors known to be in hemi_colors for i in xrange(n_hemi_labels): if ctab[i, 0] == -1: color = default_colors[i] # make sure to add no duplicate color while np.any(np.all(color[:3] == ctab_rgb, 1)): color = default_colors[safe_color_i] safe_color_i += 1 # assign the color ctab[i] = color # find number of vertices in surface if subject is not None and subjects_dir is not None: fpath = os.path.join(subjects_dir, subject, 'surf', '%s.white' % hemi) points, _ = read_surface(fpath) n_vertices = len(points) else: if len(hemi_labels) > 0: max_vert = max(np.max(label.vertices) for label in hemi_labels) n_vertices = max_vert + 1 else: n_vertices = 1 msg = (' Number of vertices in the surface could not be ' 'verified because the surface file could not be found; ' 'specify subject and subjects_dir parameters.') logger.warning(msg) # Create annot and color table array to write annot = np.empty(n_vertices, dtype=np.int) annot[:] = -1 # create the annotation ids from the colors annot_id_coding = np.array((1, 2 ** 8, 2 ** 16)) annot_ids = list(np.sum(ctab_rgb * annot_id_coding, axis=1)) for label, annot_id in zip(hemi_labels, annot_ids): # make sure the label is not overwriting another label if np.any(annot[label.vertices] != -1): other_ids = set(annot[label.vertices]) other_ids.discard(-1) other_indices = (annot_ids.index(i) for i in other_ids) other_names = (hemi_labels[i].name for i in other_indices) other_repr = ', '.join(other_names) msg = "%s: %s overlaps %s" % (hemi, label.name, other_repr) overlap.append(msg) annot[label.vertices] = annot_id hemi_names = [label.name for label in hemi_labels] # Assign unlabeled vertices to an "unknown" label unlabeled = (annot == -1) if np.any(unlabeled): msg = ("Assigning %i unlabeled vertices to " "'unknown-%s'" % (unlabeled.sum(), hemi)) logger.info(msg) # find an unused color (try shades of gray first) for i in range(1, 257): if not np.any(np.all((i, i, i) == ctab_rgb, 1)): break if i < 256: color = (i, i, i, 0) else: err = ("Need one free shade of gray for 'unknown' label. " "Please modify your label colors, or assign the " "unlabeled vertices to another label.") raise ValueError(err) # find the id annot_id = np.sum(annot_id_coding * color[:3]) # update data to write annot[unlabeled] = annot_id ctab = np.vstack((ctab, color)) hemi_names.append("unknown") # convert to FreeSurfer alpha values ctab[:, 3] = 255 - ctab[:, 3] # remove hemi ending in names hemi_names = [name[:-3] if name.endswith(hemi) else name for name in hemi_names] to_save.append((fname, annot, ctab, hemi_names)) issues = [] if duplicate_colors: msg = ("Some labels have the same color values (all labels in one " "hemisphere must have a unique color):") duplicate_colors.insert(0, msg) issues.append(os.linesep.join(duplicate_colors)) if invalid_colors: msg = ("Some labels have invalid color values (all colors should be " "RGBA tuples with values between 0 and 1)") invalid_colors.insert(0, msg) issues.append(os.linesep.join(invalid_colors)) if overlap: msg = ("Some labels occupy vertices that are also occupied by one or " "more other labels. Each vertex can only be occupied by a " "single label in *.annot files.") overlap.insert(0, msg) issues.append(os.linesep.join(overlap)) if issues: raise ValueError('\n\n'.join(issues)) # write it for fname, annot, ctab, hemi_names in to_save: logger.info(' writing %d labels to %s' % (len(hemi_names), fname)) _write_annot(fname, annot, ctab, hemi_names) logger.info('[done]')