Skip to content

Commit 1ac999d

Browse files
authored
MRG: Add units kwarg (#238)
* ENH: Add units kwarg * FIX: Tweak for CircleCI * FIX: Wrong check * FIX: One more * FIX: Another * FIX: Try running another example
1 parent e8a0879 commit 1ac999d

File tree

10 files changed

+101
-109
lines changed

10 files changed

+101
-109
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
if [[ $(cat build.txt) == "html_dev" ]] || [[ $(cat build.txt) == "html_stable" ]]; then
6262
SUBJECTS_DIR=~/mne_data/MNE-sample-data/subjects python -c "import mne; mne.datasets._download_all_example_data()";
6363
fi;
64-
- run: cd doc && sphinx-build -D plot_gallery=1 -D sphinx_gallery_conf.filename_pattern=^\(\(?\!plot_fmri_activation_volume\|plot_morphometry\|plot_label\|plot_probabilistic_label\|plot_resting_correlations\|plot_transparent_brain\|rotate_animation\|save_movie\|save_views\).\)*\$ -b html -d _build/doctrees . _build/html
64+
- run: cd doc && sphinx-build -D plot_gallery=1 -D sphinx_gallery_conf.filename_pattern=^\(\(?\!plot_fmri_activation_volume\|plot_morphometry\|plot_label\.py\|plot_probabilistic_label\|plot_resting_correlations\|plot_transparent_brain\|rotate_animation\|save_movie\|save_views\).\)*\$ -b html -d _build/doctrees . _build/html
6565

6666
- store_artifacts:
6767
path: doc/_build/html/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ pysurfer.egg-info
2727
examples/example_data/coord-lh.label
2828
.ipynb_checkpoints/
2929
.cache/
30+
.pytest_cache/

examples/plot_label_foci.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"""
1717
Bring up the visualization.
1818
"""
19-
brain = Brain(subject_id, "lh", "inflated", cortex=("gray", -2, 7, True))
19+
brain = Brain(subject_id, "lh", "inflated", cortex=("gray", -2, 7, True),
20+
units='m')
2021

2122
"""
2223
First we'll identify a stereotaxic focus in the MNI coordinate system. This
@@ -62,4 +63,4 @@
6263
"""
6364
Set the camera position to show the extent of the labels.
6465
"""
65-
brain.show_view(dict(elevation=40, distance=430))
66+
brain.show_view(dict(elevation=40, distance=0.430))

examples/plot_meg_inverse_solution.py

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,72 +13,59 @@
1313

1414
print(__doc__)
1515

16-
"""
17-
define subject, surface and hemisphere(s) to plot
18-
"""
16+
###############################################################################
17+
# Set up some useful variables and make the plot.
18+
19+
# define subject, surface and hemisphere(s) to plot:
20+
1921
subject_id, surf = 'fsaverage', 'inflated'
2022
hemi = 'lh'
2123

22-
"""
23-
create Brain object for visualization
24-
"""
24+
# create Brain object for visualization
2525
brain = Brain(subject_id, hemi, surf, size=(400, 400), background='w',
26-
interaction='terrain', cortex='bone')
26+
interaction='terrain', cortex='bone', units='m')
2727

28-
"""
29-
label for time annotation in milliseconds
30-
"""
28+
# label for time annotation in milliseconds
3129

3230

3331
def time_label(t):
3432
return 'time=%0.2f ms' % (t * 1e3)
3533

3634

37-
"""
38-
read MNE dSPM inverse solution
39-
"""
35+
# Read MNE dSPM inverse solution and plot
36+
4037
for hemi in ['lh']: # , 'rh']:
4138
stc_fname = os.path.join('example_data', 'meg_source_estimate-' +
4239
hemi + '.stc')
4340
stc = read_stc(stc_fname)
4441

45-
"""
46-
data and vertices for which the data is defined
47-
"""
42+
# data and vertices for which the data is defined
4843
data = stc['data']
4944
vertices = stc['vertices']
5045

51-
"""
52-
time points (in seconds)
53-
"""
46+
# time points (in seconds)
5447
time = np.linspace(stc['tmin'], stc['tmin'] + data.shape[1] * stc['tstep'],
5548
data.shape[1], endpoint=False)
5649

57-
"""
58-
colormap to use
59-
"""
50+
# colormap to use
6051
colormap = 'hot'
6152

62-
"""
63-
add data and set the initial time displayed to 100 ms
64-
"""
53+
# add data and set the initial time displayed to 100 ms
6554
brain.add_data(data, colormap=colormap, vertices=vertices,
6655
smoothing_steps=5, time=time, time_label=time_label,
6756
hemi=hemi, initial_time=0.1, verbose=False)
6857

69-
"""
70-
scale colormap
71-
"""
58+
# scale colormap
7259
brain.scale_data_colormap(fmin=13, fmid=18, fmax=22, transparent=True,
7360
verbose=False)
7461

75-
"""
76-
To change the time displayed to 80 ms uncomment this line
77-
"""
62+
###############################################################################
63+
# To change the time displayed to 80 ms uncomment this line:
64+
7865
# brain.set_time(0.08)
7966

80-
"""
81-
uncomment these lines to use the interactive TimeViewer GUI
82-
"""
67+
###############################################################################
68+
# uncomment these lines to use the interactive TimeViewer GUI
69+
8370
# from surfer import TimeViewer
8471
# viewer = TimeViewer(brain)

examples/plot_vector_meg_inverse_solution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
subject_id, surf = 'fsaverage', 'white'
2020
hemi = 'lh'
2121
brain = Brain(subject_id, hemi, surf, size=(800, 800), interaction='terrain',
22-
cortex='0.5', alpha=0.5, show_toolbar=True)
22+
cortex='0.5', alpha=0.5, show_toolbar=True, units='m')
2323

2424
# Read the MNE dSPM inverse solution
2525

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[tool:pytest]
2-
addopts = --showlocals --durations=10 --doctest-modules -rs --cov --cov-report= --doctest-ignore-import-errors
2+
addopts = --showlocals --durations=10 --doctest-modules -rs --cov-report= --doctest-ignore-import-errors

surfer/tests/test_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ def test_surface():
5151
nn = _slow_compute_normals(surface.coords, surface.faces[:10000])
5252
nn_fast = utils._compute_normals(surface.coords, surface.faces[:10000])
5353
assert_array_almost_equal(nn, nn_fast)
54+
assert 50 < np.linalg.norm(surface.coords, axis=-1).mean() < 100 # mm
55+
surface = utils.Surface('fsaverage', 'lh', 'inflated',
56+
subjects_dir=subj_dir, units='m')
57+
surface.load_geometry()
58+
assert 0.05 < np.linalg.norm(surface.coords, axis=-1).mean() < 0.1 # m
5459

5560

5661
def test_huge_cross():

surfer/tests/test_viz.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from tempfile import mkdtemp, mktemp
77
import warnings
88

9+
import pytest
910
from nose.tools import assert_equal, assert_in, assert_not_in
1011
from nose.plugins.skip import SkipTest
1112
from mayavi import mlab
@@ -240,6 +241,9 @@ def test_label():
240241
brain.add_label("V2", color="#FF6347", alpha=.6)
241242
brain.add_label("entorhinal", color=(.2, 1, .5), alpha=.6)
242243
brain.set_surf('white')
244+
brain.show_view(dict(elevation=40, distance=430), distance=430)
245+
with pytest.raises(ValueError, match='!='):
246+
brain.show_view(dict(elevation=40, distance=430), distance=431)
243247

244248
# remove labels
245249
brain.remove_labels('V1')

surfer/utils.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,18 @@ class Surface(object):
7171
Which hemisphere to load
7272
surf : string
7373
Name of the surface to load (eg. inflated, orig ...)
74-
data_path : string
75-
Path where to look for data
76-
x: 1d array
77-
x coordinates of vertices
78-
y: 1d array
79-
y coordinates of vertices
80-
z: 1d array
81-
z coordinates of vertices
82-
coords : 2d array of shape [n_vertices, 3]
83-
The vertices coordinates
84-
faces : 2d array
85-
The faces ie. the triangles
86-
nn : 2d array
87-
Normalized surface normals for vertices.
8874
subjects_dir : str | None
8975
If not None, this directory will be used as the subjects directory
9076
instead of the value set using the SUBJECTS_DIR environment variable.
77+
offset : float | None
78+
If float, align inside edge of each hemisphere to center + offset.
79+
If None, do not change coordinates (default).
80+
units : str
81+
Can be 'm' or 'mm' (default).
9182
"""
9283

9384
def __init__(self, subject_id, hemi, surf, subjects_dir=None,
94-
offset=None):
85+
offset=None, units='mm'):
9586
"""Surface
9687
9788
Parameters
@@ -116,6 +107,7 @@ def __init__(self, subject_id, hemi, surf, subjects_dir=None,
116107
self.coords = None
117108
self.faces = None
118109
self.nn = None
110+
self.units = _check_units(units)
119111

120112
subjects_dir = _get_subjects_dir(subjects_dir)
121113
self.data_path = op.join(subjects_dir, subject_id)
@@ -124,6 +116,8 @@ def load_geometry(self):
124116
surf_path = op.join(self.data_path, "surf",
125117
"%s.%s" % (self.hemi, self.surf))
126118
coords, faces = nib.freesurfer.read_geometry(surf_path)
119+
if self.units == 'm':
120+
coords /= 1000.
127121
if self.offset is not None:
128122
if self.hemi == 'lh':
129123
coords[:, 0] -= (np.max(coords[:, 0]) + self.offset)
@@ -140,11 +134,6 @@ def load_geometry(self):
140134
self.faces[:] = faces
141135
self.nn[:] = nn
142136

143-
def save_geometry(self):
144-
surf_path = op.join(self.data_path, "surf",
145-
"%s.%s" % (self.hemi, self.surf))
146-
nib.freesurfer.write_geometry(surf_path, self.coords, self.faces)
147-
148137
@property
149138
def x(self):
150139
return self.coords[:, 0]
@@ -392,6 +381,12 @@ def dec(*args, **kwargs):
392381
###############################################################################
393382
# USEFUL FUNCTIONS
394383

384+
def _check_units(units):
385+
if units not in ('m', 'mm'):
386+
raise ValueError('Units must be "m" or "mm", got %r' % (units,))
387+
return units
388+
389+
395390
def find_closest_vertices(surface_coords, point_coords):
396391
"""Return the vertices on a surface mesh closest to some given coordinates.
397392
@@ -414,25 +409,29 @@ def find_closest_vertices(surface_coords, point_coords):
414409
return np.argmin(cdist(surface_coords, point_coords), axis=0)
415410

416411

417-
def tal_to_mni(coords):
412+
def tal_to_mni(coords, units='mm'):
418413
"""Convert Talairach coords to MNI using the Lancaster transform.
419414
420415
Parameters
421416
----------
422417
coords : n x 3 numpy array
423418
Array of Talairach coordinates
419+
units : str
420+
Can be 'm' or 'mm' (default).
424421
425422
Returns
426423
-------
427424
mni_coords : n x 3 numpy array
428-
Array of coordinates converted to MNI space
429-
425+
Array of coordinates converted to MNI space.
430426
"""
431427
coords = np.atleast_2d(coords)
432428
xfm = np.array([[1.06860, -0.00396, 0.00826, 1.07816],
433429
[0.00640, 1.05741, 0.08566, 1.16824],
434430
[-0.01281, -0.08863, 1.10792, -4.17805],
435431
[0.00000, 0.00000, 0.00000, 1.00000]])
432+
units = _check_units(units)
433+
if units == 'm':
434+
xfm[:3, 3] /= 1000.
436435
mni_coords = np.dot(np.c_[coords, np.ones(coords.shape[0])], xfm.T)[:, :3]
437436
return mni_coords
438437

@@ -597,7 +596,8 @@ def smoothing_matrix(vertices, adj_mat, smoothing_steps=20, verbose=None):
597596

598597
@verbose
599598
def coord_to_label(subject_id, coord, label, hemi='lh', n_steps=30,
600-
map_surface='white', coord_as_vert=False, verbose=None):
599+
map_surface='white', coord_as_vert=False, units='mm',
600+
verbose=None):
601601
"""Create label from MNI coordinate
602602
603603
Parameters
@@ -616,18 +616,24 @@ def coord_to_label(subject_id, coord, label, hemi='lh', n_steps=30,
616616
The surface name used to find the closest point
617617
coord_as_vert : bool
618618
whether the coords parameter should be interpreted as vertex ids
619+
units : str
620+
Can be 'm' or 'mm' (default).
619621
verbose : bool, str, int, or None
620622
If not None, override default verbose level (see surfer.verbose).
621623
"""
622-
geo = Surface(subject_id, hemi, map_surface)
624+
geo = Surface(subject_id, hemi, map_surface, units=units)
623625
geo.load_geometry()
624626

627+
coords = geo.coords
628+
# work in mm from here on
629+
if geo.units == 'm':
630+
coords = coords * 1000
625631
if coord_as_vert:
626-
coord = geo.coords[coord]
632+
coord = coords[coord]
627633

628-
n_vertices = len(geo.coords)
634+
n_vertices = len(coords)
629635
adj_mat = mesh_edges(geo.faces)
630-
foci_vtxs = find_closest_vertices(geo.coords, [coord])
636+
foci_vtxs = find_closest_vertices(coords, [coord])
631637
data = np.zeros(n_vertices)
632638
data[foci_vtxs] = 1.
633639
smooth_mat = smoothing_matrix(np.arange(n_vertices), adj_mat, 1)
@@ -641,7 +647,7 @@ def coord_to_label(subject_id, coord, label, hemi='lh', n_steps=30,
641647
f.write('#label at %s from subject %s\n' % (coord, subject_id))
642648
f.write('%d\n' % len(idx))
643649
for i in idx:
644-
x, y, z = geo.coords[i]
650+
x, y, z = coords[i]
645651
f.write('%d %f %f %f 0.000000\n' % (i, x, y, z))
646652

647653

@@ -696,7 +702,7 @@ def has_fsaverage(subjects_dir=None, raise_error=True, return_why=False):
696702

697703
def has_imageio():
698704
try:
699-
import imageio # NOQA
705+
import imageio # noqa, analysis:ignore
700706
except ImportError:
701707
return False
702708
else:

0 commit comments

Comments
 (0)