Skip to content

Commit 18bd6ab

Browse files
committed
WIP: Add flatmap [skip travis]
1 parent f52272a commit 18bd6ab

File tree

8 files changed

+107
-25
lines changed

8 files changed

+107
-25
lines changed

mne/source_estimate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,7 @@ def plot(self, subject=None, surface='inflated', hemi='lh',
15801580
colormap='auto', time_label='auto', smoothing_steps=10,
15811581
transparent=True, alpha=1.0, time_viewer='auto',
15821582
subjects_dir=None,
1583-
figure=None, views='lat', colorbar=True, clim='auto',
1583+
figure=None, views='auto', colorbar=True, clim='auto',
15841584
cortex="classic", size=800, background="black",
15851585
foreground=None, initial_time=None, time_unit='s',
15861586
backend='auto', spacing='oct6', title=None,

mne/surface.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,53 @@ def _read_wavefront_obj(fname):
764764
return np.array(coords), np.array(faces)
765765

766766

767+
def _read_patch(fname):
768+
"""Load a FreeSurfer binary patch file.
769+
770+
Parameters
771+
----------
772+
fname : str
773+
The filename.
774+
775+
Returns
776+
-------
777+
rrs : ndarray, shape (n_vertices, 3)
778+
The points.
779+
tris : ndarray, shape (n_tris, 3)
780+
The patches. Not all vertices will be present.
781+
"""
782+
# This is adapted from PySurfer PR #269, Bruce Fischl's read_patch.m,
783+
# and PyCortex (BSD)
784+
patch = dict()
785+
with open(fname, 'r') as fid:
786+
ver = np.fromfile(fid, dtype='>i4', count=1)[0]
787+
if ver != -1:
788+
raise RuntimeError(f'incorrect version # {ver} (not -1) found')
789+
npts = np.fromfile(fid, dtype='>i4', count=1)[0]
790+
dtype = np.dtype(
791+
[('vertno', '>i4'), ('x', '>f'), ('y', '>f'), ('z', '>f')])
792+
recs = np.fromfile(fid, dtype=dtype, count=npts)
793+
# numpy to dict
794+
patch = {key: recs[key] for key in dtype.fields.keys()}
795+
patch['vertno'] -= 1
796+
797+
# read surrogate surface
798+
rrs, tris = read_surface(
799+
op.join(op.dirname(fname), op.basename(fname)[:3] + 'pial'))
800+
orig_tris = tris
801+
is_vert = patch['vertno'] > 0 # negative are edges, ignored for now
802+
verts = patch['vertno'][is_vert]
803+
804+
# eliminate invalid tris and zero out unused rrs
805+
mask = np.zeros((len(rrs),), dtype=bool)
806+
mask[verts] = True
807+
rrs[~mask] = 0.
808+
tris = tris[mask[tris].all(1)]
809+
for ii, key in enumerate(['x', 'y', 'z']):
810+
rrs[verts, ii] = patch[key][is_vert]
811+
return rrs, tris, orig_tris
812+
813+
767814
##############################################################################
768815
# SURFACE CREATION
769816

mne/viz/_3d.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,7 @@ def _plot_mpl_stc(stc, subject=None, surface='inflated', hemi='lh',
14261426
'par': {'elev': 30, 'azim': -60}}
14271427
time_viewer = False if time_viewer == 'auto' else time_viewer
14281428
kwargs = dict(lh=lh_kwargs, rh=rh_kwargs)
1429+
views = 'lat' if views == 'auto' else views
14291430
_check_option('views', views, sorted(lh_kwargs.keys()))
14301431
mapdata = _process_clim(clim, colormap, transparent, stc.data)
14311432
_separate_map(mapdata)
@@ -1554,7 +1555,7 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
15541555
colormap='auto', time_label='auto',
15551556
smoothing_steps=10, transparent=True, alpha=1.0,
15561557
time_viewer='auto', subjects_dir=None, figure=None,
1557-
views='lat', colorbar=True, clim='auto',
1558+
views='auto', colorbar=True, clim='auto',
15581559
cortex="classic", size=800, background="black",
15591560
foreground=None, initial_time=None,
15601561
time_unit='s', backend='auto', spacing='oct6',

mne/viz/_brain/_brain.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class _Brain(object):
154154
def __init__(self, subject_id, hemi, surf, title=None,
155155
cortex="classic", alpha=1.0, size=800, background="black",
156156
foreground=None, figure=None, subjects_dir=None,
157-
views=['lateral'], offset=True, show_toolbar=False,
157+
views='auto', offset=True, show_toolbar=False,
158158
offscreen=False, interaction=None, units='mm',
159159
show=True):
160160
from ..backends.renderer import backend, _get_renderer, _get_3d_backend
@@ -188,6 +188,7 @@ def __init__(self, subject_id, hemi, surf, title=None,
188188
self._fg_color = foreground
189189
if isinstance(views, str):
190190
views = [views]
191+
views = _check_views_hemi_flat(surf, views, hemi)
191192
n_row = len(views)
192193
col_dict = dict(lh=1, rh=1, both=1, split=2)
193194
n_col = col_dict[hemi]
@@ -1068,7 +1069,7 @@ def set_data_smoothing(self, n_steps):
10681069
maps = sparse.eye(len(self.geo[hemi].coords), format='csr')
10691070
with use_log_level(False):
10701071
smooth_mat = _hemi_morph(
1071-
self.geo[hemi].faces,
1072+
self.geo[hemi].orig_faces,
10721073
np.arange(len(self.geo[hemi].coords)),
10731074
vertices, morph_n_steps, maps, warn=False)
10741075
self._data[hemi]['smooth_mat'] = smooth_mat
@@ -1415,16 +1416,18 @@ def _to_borders(self, label, hemi, borders, restrict_idx=None):
14151416
raise ValueError('borders must be a bool or positive integer')
14161417
if borders:
14171418
n_vertices = label.size
1418-
edges = mesh_edges(self.geo[hemi].faces)
1419+
edges = mesh_edges(self.geo[hemi].orig_faces)
14191420
edges = edges.tocoo()
14201421
border_edges = label[edges.row] != label[edges.col]
14211422
show = np.zeros(n_vertices, dtype=np.int64)
14221423
keep_idx = np.unique(edges.row[border_edges])
14231424
if isinstance(borders, int):
14241425
for _ in range(borders):
1425-
keep_idx = np.in1d(self.geo[hemi].faces.ravel(), keep_idx)
1426-
keep_idx.shape = self.geo[hemi].faces.shape
1427-
keep_idx = self.geo[hemi].faces[np.any(keep_idx, axis=1)]
1426+
keep_idx = np.in1d(
1427+
self.geo[hemi].orig_faces.ravel(), keep_idx)
1428+
keep_idx.shape = self.geo[hemi].orig_faces.shape
1429+
keep_idx = self.geo[hemi].orig_faces[
1430+
np.any(keep_idx, axis=1)]
14281431
keep_idx = np.unique(keep_idx)
14291432
if restrict_idx is not None:
14301433
keep_idx = keep_idx[np.in1d(keep_idx, restrict_idx)]
@@ -1501,3 +1504,14 @@ def _update_limits(fmin, fmid, fmax, center, array):
15011504
% (fmid, fmax))
15021505

15031506
return fmin, fmid, fmax
1507+
1508+
1509+
def _check_views_hemi_flat(surf, views, hemi):
1510+
if surf == 'flat':
1511+
if len(views) != 1 or views[0] != 'auto':
1512+
raise ValueError(
1513+
f'views must be "auto" for flat maps, got {views}')
1514+
views = ['flat']
1515+
elif len(views) == 1 and views[0] == 'auto':
1516+
views = ['lat']
1517+
return views

mne/viz/_brain/_timeviewer.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,9 @@ def configure_sliders(self):
634634
ci = 0
635635
for ri, view in enumerate(self.brain._views):
636636
self.plotter.subplot(ri, ci)
637+
if view == 'flat':
638+
self.orientation_call = None
639+
continue
637640
self.orientation_call = ShowView(
638641
plotter=self.plotter,
639642
brain=self.brain,
@@ -1152,9 +1155,10 @@ def clean(self):
11521155
self.reps = None
11531156
self._time_slider = None
11541157
self._playback_speed_slider = None
1155-
self.orientation_call.plotter = None
1156-
self.orientation_call.brain = None
1157-
self.orientation_call = None
1158+
if self.orientation_call is not None:
1159+
self.orientation_call.plotter = None
1160+
self.orientation_call.brain = None
1161+
self.orientation_call = None
11581162
self.smoothing_call.plotter = None
11591163
self.smoothing_call = None
11601164
if self.time_call is not None:

mne/viz/_brain/surface.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
import numpy as np
1212
from ...utils import _check_option, get_subjects_dir
13-
from ...surface import complete_surface_info, read_surface, read_curvature
13+
from ...surface import (complete_surface_info, read_surface, read_curvature,
14+
_read_patch)
1415

1516

1617
class Surface(object):
@@ -108,9 +109,15 @@ def load_geometry(self):
108109
-------
109110
None
110111
"""
111-
surf_path = path.join(self.data_path, 'surf',
112-
'%s.%s' % (self.hemi, self.surf))
113-
coords, faces = read_surface(surf_path)
112+
if self.surf == 'flat': # special case
113+
coords, faces, orig_faces = _read_patch(
114+
path.join(self.data_path, 'surf',
115+
'%s.%s' % (self.hemi, 'cortex.patch.flat')))
116+
else:
117+
coords, faces = read_surface(
118+
path.join(self.data_path, 'surf',
119+
'%s.%s' % (self.hemi, self.surf)))
120+
orig_faces = faces
114121
if self.units == 'm':
115122
coords /= 1000.
116123
if self.offset is not None:
@@ -121,15 +128,10 @@ def load_geometry(self):
121128
surf = dict(rr=coords, tris=faces)
122129
complete_surface_info(surf, copy=False, verbose=False)
123130
nn = surf['nn']
124-
125-
if self.coords is None:
126-
self.coords = coords
127-
self.faces = faces
128-
self.nn = nn
129-
else:
130-
self.coords[:] = coords
131-
self.faces[:] = faces
132-
self.nn[:] = nn
131+
self.coords = coords
132+
self.faces = faces
133+
self.orig_faces = orig_faces
134+
self.nn = nn
133135

134136
def __len__(self):
135137
"""Return number of vertices."""

mne/viz/_brain/view.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
_lh_views_dict = dict()
3232
for k, v in lh_views_dict.items():
3333
_lh_views_dict[k[:3]] = v
34+
_lh_views_dict['flat'] = View(azim=250, elev=0)
3435
lh_views_dict.update(_lh_views_dict)
3536

3637
_rh_views_dict = dict()
3738
for k, v in rh_views_dict.items():
3839
_rh_views_dict[k[:3]] = v
40+
_rh_views_dict['flat'] = View(azim=-70, elev=0)
3941
rh_views_dict.update(_rh_views_dict)

tutorials/source-modeling/plot_visualize_stc.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,19 @@
5050
# and ``pysurfer`` installed on your machine.
5151
initial_time = 0.1
5252
brain = stc.plot(subjects_dir=subjects_dir, initial_time=initial_time,
53-
clim=dict(kind='value', pos_lims=[3, 6, 9]))
53+
clim=dict(kind='value', lims=[3, 6, 9]))
54+
55+
###############################################################################
56+
# You can also morph it to fsaverage and visualize it using a flat map:
57+
58+
# sphinx_gallery_thumbnail_number = 2
59+
60+
stc_fs = mne.compute_source_morph(stc, 'sample', 'fsaverage', subjects_dir,
61+
smooth=5, verbose='error').apply(stc)
62+
brain = stc_fs.plot(subjects_dir=subjects_dir, initial_time=initial_time,
63+
clim=dict(kind='value', lims=[3, 6, 9]),
64+
surface='flat', hemi='split', size=(1000, 500),
65+
smoothing_steps=3)
5466

5567
###############################################################################
5668
#

0 commit comments

Comments
 (0)