Skip to content

Commit 746193d

Browse files
committed
WIP: Add flatmap [skip travis]
1 parent a78e20d commit 746193d

File tree

8 files changed

+109
-25
lines changed

8 files changed

+109
-25
lines changed

mne/source_estimate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ def plot(self, subject=None, surface='inflated', hemi='lh',
615615
colormap='auto', time_label='auto', smoothing_steps=10,
616616
transparent=True, alpha=1.0, time_viewer='auto',
617617
subjects_dir=None,
618-
figure=None, views='lat', colorbar=True, clim='auto',
618+
figure=None, views='auto', colorbar=True, clim='auto',
619619
cortex="classic", size=800, background="black",
620620
foreground=None, initial_time=None, time_unit='s',
621621
backend='auto', spacing='oct6', title=None, show_traces='auto',

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)
@@ -1582,7 +1583,7 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
15821583
colormap='auto', time_label='auto',
15831584
smoothing_steps=10, transparent=True, alpha=1.0,
15841585
time_viewer='auto', subjects_dir=None, figure=None,
1585-
views='lat', colorbar=True, clim='auto',
1586+
views='auto', colorbar=True, clim='auto',
15861587
cortex="classic", size=800, background="black",
15871588
foreground=None, initial_time=None,
15881589
time_unit='s', backend='auto', spacing='oct6',

mne/viz/_brain/_brain.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,15 @@ class _Brain(object):
154154
+---------------------------+--------------+-----------------------+
155155
| view_layout | | ✓ |
156156
+---------------------------+--------------+-----------------------+
157+
| flatmaps | | ✓ |
158+
+---------------------------+--------------+-----------------------+
157159
158160
"""
159161

160162
def __init__(self, subject_id, hemi, surf, title=None,
161163
cortex="classic", alpha=1.0, size=800, background="black",
162164
foreground=None, figure=None, subjects_dir=None,
163-
views=['lateral'], offset=True, show_toolbar=False,
165+
views='auto', offset=True, show_toolbar=False,
164166
offscreen=False, interaction='trackball', units='mm',
165167
view_layout='vertical', show=True):
166168
from ..backends.renderer import backend, _get_renderer, _get_3d_backend
@@ -196,6 +198,7 @@ def __init__(self, subject_id, hemi, surf, title=None,
196198

197199
if isinstance(views, str):
198200
views = [views]
201+
views = _check_views_hemi_flat(surf, views, hemi)
199202
col_dict = dict(lh=1, rh=1, both=1, split=2)
200203
shape = (len(views), col_dict[hemi])
201204
if self._view_layout == 'horizontal':
@@ -1271,7 +1274,7 @@ def set_data_smoothing(self, n_steps):
12711274
maps = sparse.eye(len(self.geo[hemi].coords), format='csr')
12721275
with use_log_level(False):
12731276
smooth_mat = _hemi_morph(
1274-
self.geo[hemi].faces,
1277+
self.geo[hemi].orig_faces,
12751278
np.arange(len(self.geo[hemi].coords)),
12761279
vertices, morph_n_steps, maps, warn=False)
12771280
self._data[hemi]['smooth_mat'] = smooth_mat
@@ -1645,16 +1648,18 @@ def _to_borders(self, label, hemi, borders, restrict_idx=None):
16451648
raise ValueError('borders must be a bool or positive integer')
16461649
if borders:
16471650
n_vertices = label.size
1648-
edges = mesh_edges(self.geo[hemi].faces)
1651+
edges = mesh_edges(self.geo[hemi].orig_faces)
16491652
edges = edges.tocoo()
16501653
border_edges = label[edges.row] != label[edges.col]
16511654
show = np.zeros(n_vertices, dtype=np.int64)
16521655
keep_idx = np.unique(edges.row[border_edges])
16531656
if isinstance(borders, int):
16541657
for _ in range(borders):
1655-
keep_idx = np.in1d(self.geo[hemi].faces.ravel(), keep_idx)
1656-
keep_idx.shape = self.geo[hemi].faces.shape
1657-
keep_idx = self.geo[hemi].faces[np.any(keep_idx, axis=1)]
1658+
keep_idx = np.in1d(
1659+
self.geo[hemi].orig_faces.ravel(), keep_idx)
1660+
keep_idx.shape = self.geo[hemi].orig_faces.shape
1661+
keep_idx = self.geo[hemi].orig_faces[
1662+
np.any(keep_idx, axis=1)]
16581663
keep_idx = np.unique(keep_idx)
16591664
if restrict_idx is not None:
16601665
keep_idx = keep_idx[np.in1d(keep_idx, restrict_idx)]
@@ -1732,3 +1737,14 @@ def _update_limits(fmin, fmid, fmax, center, array):
17321737
% (fmid, fmax))
17331738

17341739
return fmin, fmid, fmax
1740+
1741+
1742+
def _check_views_hemi_flat(surf, views, hemi):
1743+
if surf == 'flat':
1744+
if len(views) != 1 or views[0] != 'auto':
1745+
raise ValueError(
1746+
f'views must be "auto" for flat maps, got {views}')
1747+
views = ['flat']
1748+
elif len(views) == 1 and views[0] == 'auto':
1749+
views = ['lat']
1750+
return views

mne/viz/_brain/_timeviewer.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,9 @@ def configure_sliders(self):
660660
for hemi in hemis_ref:
661661
for ri, ci, view in self.brain._iter_views(hemi):
662662
self.plotter.subplot(ri, ci)
663+
if view == 'flat':
664+
self.orientation_call = None
665+
continue
663666
self.orientation_call = ShowView(
664667
plotter=self.plotter,
665668
brain=self.brain,
@@ -1297,9 +1300,10 @@ def clean(self):
12971300
self.reps = None
12981301
self._time_slider = None
12991302
self._playback_speed_slider = None
1300-
self.orientation_call.plotter = None
1301-
self.orientation_call.brain = None
1302-
self.orientation_call = None
1303+
if self.orientation_call is not None:
1304+
self.orientation_call.plotter = None
1305+
self.orientation_call.brain = None
1306+
self.orientation_call = None
13031307
self.smoothing_call.plotter = None
13041308
self.smoothing_call = None
13051309
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
@@ -36,9 +36,11 @@
3636
lh_views_dict = _lh_views_dict.copy()
3737
for k, v in _lh_views_dict.items():
3838
lh_views_dict[k[:3]] = v
39+
lh_views_dict['flat'] = dict(azimuth=250, elevation=0)
3940

4041
rh_views_dict = _rh_views_dict.copy()
4142
for k, v in _rh_views_dict.items():
4243
rh_views_dict[k[:3]] = v
44+
rh_views_dict['flat'] = dict(azimuth=-70, elevation=0)
4345
views_dicts = dict(lh=lh_views_dict, vol=lh_views_dict, both=lh_views_dict,
4446
rh=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)