Skip to content

Commit d295bfb

Browse files
committed
Merge pull request #82 from nipy/better_colormaps
Better colormaps
2 parents ea3b9b0 + ec7d602 commit d295bfb

File tree

6 files changed

+117
-32
lines changed

6 files changed

+117
-32
lines changed

CHANGES

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
PySurfer Changes
22
================
33

4+
Version 0.5
5+
-----------
6+
7+
- The visual display of the surfaces was improved by using surface normals
8+
- Made colormap specification in Brain.add_data and Brain.add_contour_overlay
9+
more flexible, with better defaults
10+
411
Version 0.4
5-
-------------
12+
-----------
613

714
Enhancements
815
~~~~~~~~~~~~

examples/plot_freesurfer_normalization.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
curv_bin = (curv.get_data() > 0).squeeze()
2929

3030
# Add the data as a contour overlay, but turn off the colorbar
31-
brain.add_contour_overlay(curv_bin, 0, 1.15, 2, 3, hemi=hemi)
31+
brain.add_contour_overlay(curv_bin, min=0, max=1.5, n_contours=2,
32+
line_width=3, hemi=hemi)
3233
brain.contour_list[-1]["colorbar"].visible = False
3334

3435
brain.show_view("dorsal")

examples/plot_resting_correlations.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,8 @@
5050
We'll also plot the map with some transparency so that we can see through to the
5151
underlying anatomy.
5252
"""
53-
brain.add_data(surf_data_lh, -.7, .7, colormap="RdBu", alpha=.75, hemi='lh')
54-
brain.add_data(surf_data_rh, -.7, .7, colormap="RdBu", alpha=.75, hemi='rh')
55-
56-
"""
57-
We'll next flip the colormap so negative correlations are blue and positive
58-
correlations are red, which is better aligned with the conventions in fMRI
59-
visualization.
60-
"""
61-
for hemi in ["lh", "rh"]:
62-
for cbar in brain.data_dict[hemi]["colorbars"]:
63-
cbar.reverse_lut = True
53+
brain.add_data(surf_data_lh, -.7, .7, colormap="coolwarm", alpha=.75, hemi='lh')
54+
brain.add_data(surf_data_rh, -.7, .7, colormap="coolwarm", alpha=.75, hemi='rh')
6455

6556
"""
6657
This overlay represents resting-state correlations with a

surfer/tests/test_utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from os.path import join as pjoin
22

33
import numpy as np
4+
import nose.tools as nt
45
from numpy.testing import assert_array_almost_equal, assert_array_equal
56

67
from surfer import utils
@@ -64,3 +65,29 @@ def test_huge_cross():
6465
z = np.cross(x, y)
6566
zz = utils._fast_cross_3d(x, y)
6667
assert_array_equal(z, zz)
68+
69+
70+
def test_create_color_lut():
71+
"""Test various ways of making a colormap."""
72+
# Test valid lut
73+
cmap_in = (np.random.rand(256, 4) * 255).astype(np.int)
74+
cmap_out = utils.create_color_lut(cmap_in)
75+
assert_array_equal(cmap_in, cmap_out)
76+
77+
# Test mostly valid lut
78+
cmap_in = cmap_in[:, :3]
79+
cmap_out = utils.create_color_lut(cmap_in)
80+
assert_array_equal(cmap_in, cmap_out[:, :3])
81+
assert_array_equal(cmap_out[:, 3], np.ones(256, np.int) * 255)
82+
83+
# Test matplotlib lut
84+
cmap_out = utils.create_color_lut("BuGn_r")
85+
nt.assert_equal(cmap_out.shape, (256, 4))
86+
87+
# Test list of colors lut
88+
cmap_out = utils.create_color_lut(["purple", "pink", "white"])
89+
nt.assert_equal(cmap_out.shape, (256, 4))
90+
91+
# Test that we can ask for a specific number of colors
92+
cmap_out = utils.create_color_lut("Reds", 12)
93+
nt.assert_equal(cmap_out.shape, (12, 4))

surfer/utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import nibabel as nib
1111
from scipy import sparse
1212
from scipy.spatial.distance import cdist
13+
import matplotlib as mpl
14+
from matplotlib import cm
1315

1416
from .config import config
1517

@@ -413,6 +415,60 @@ def mesh_edges(faces):
413415
return edges
414416

415417

418+
def create_color_lut(cmap, n_colors=256):
419+
"""Return a colormap suitable for setting as a Mayavi LUT.
420+
421+
Parameters
422+
----------
423+
cmap : string, list of colors, n x 3 or n x 4 array
424+
Input colormap definition. This can be the name of a matplotlib
425+
colormap, a list of valid matplotlib colors, or a suitable
426+
mayavi LUT (possibly missing the alpha channel).
427+
n_colors : int, optional
428+
Number of colors in the resulting LUT. This is ignored if cmap
429+
is a 2d array.
430+
Returns
431+
-------
432+
lut : n_colors x 4 integer array
433+
Color LUT suitable for passing to mayavi
434+
"""
435+
if isinstance(cmap, np.ndarray):
436+
if np.ndim(cmap) == 2:
437+
if cmap.shape[1] == 4:
438+
# This looks likes a LUT that's ready to go
439+
lut = cmap.astype(np.int)
440+
elif cmap.shape[1] == 3:
441+
# This looks like a LUT, but it's missing the alpha channel
442+
alpha = np.ones(len(cmap), np.int) * 255
443+
lut = np.c_[cmap, alpha]
444+
445+
return lut
446+
447+
# Otherwise, we're going to try and use matplotlib to create it
448+
449+
if cmap in dir(cm):
450+
# This is probably a matplotlib colormap, so build from that
451+
# The matplotlib colormaps are a superset of the mayavi colormaps
452+
# except for one or two cases (i.e. blue-red, which is a crappy
453+
# rainbow colormap and shouldn't be used for anything, although in
454+
# its defense it's better than "Jet")
455+
cmap = getattr(cm, cmap)
456+
457+
elif np.iterable(cmap):
458+
# This looks like a list of colors? Let's try that.
459+
colors = list(map(mpl.colors.colorConverter.to_rgb, cmap))
460+
cmap = mpl.colors.LinearSegmentedColormap.from_list("_", colors)
461+
462+
else:
463+
# If we get here, it's a bad input
464+
raise ValueError("Input %s was not valid for making a lut" % cmap)
465+
466+
# Convert from a matplotlib colormap to a lut array
467+
lut = (cmap(np.linspace(0, 1, n_colors)) * 255).astype(np.int)
468+
469+
return lut
470+
471+
416472
@verbose
417473
def smoothing_matrix(vertices, adj_mat, smoothing_steps=20, verbose=None):
418474
"""Create a smoothing matrix which can be used to interpolate data defined

surfer/viz.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from . import utils, io
1717
from .config import config
18-
from .utils import Surface, verbose, _get_subjects_dir
18+
from .utils import Surface, verbose, create_color_lut, _get_subjects_dir
1919

2020

2121
import logging
@@ -757,7 +757,7 @@ def add_overlay(self, source, min=None, max=None, sign="abs", name=None,
757757
self._toggle_render(True, views)
758758

759759
def add_data(self, array, min=None, max=None, thresh=None,
760-
colormap="blue-red", alpha=1,
760+
colormap="RdBu_r", alpha=1,
761761
vertices=None, smoothing_steps=20, time=None,
762762
time_label="time index=%d", colorbar=True,
763763
hemi=None):
@@ -788,10 +788,10 @@ def add_data(self, array, min=None, max=None, thresh=None,
788788
max value in colormap (uses real max if None)
789789
thresh : None or float
790790
if not None, values below thresh will not be visible
791-
colormap : str | array [256x4]
792-
name of Mayavi colormap to use, or a custom look up table (a 256x4
793-
array, with the columns representing RGBA (red, green, blue, alpha)
794-
coded with integers going from 0 to 255).
791+
colormap : string, list of colors, or array
792+
name of matplotlib colormap to use, a list of matplotlib colors,
793+
or a custom look up table (an n x 4 array coded with RBGA values
794+
between 0 and 255).
795795
alpha : float in [0, 1]
796796
alpha level to control opacity
797797
vertices : numpy array
@@ -841,16 +841,9 @@ def add_data(self, array, min=None, max=None, thresh=None,
841841
# Copy and byteswap to deal with Mayavi bug
842842
mlab_plot = _prepare_data(array_plot)
843843

844-
# process colormap argument
845-
if isinstance(colormap, basestring):
846-
lut = None
847-
else:
848-
lut = np.asarray(colormap)
849-
if lut.shape != (256, 4):
850-
err = ("colormap argument must be mayavi colormap (string) or"
851-
" look up table (array of shape (256, 4))")
852-
raise ValueError(err)
853-
colormap = "blue-red"
844+
# Process colormap argument into a lut
845+
lut = create_color_lut(colormap)
846+
colormap = "Greys"
854847

855848
data = dict(array=array, smoothing_steps=smoothing_steps,
856849
fmin=min, fmid=(min + max) / 2, fmax=max,
@@ -1256,7 +1249,8 @@ def add_foci(self, coords, coords_as_verts=False, map_surface=None,
12561249
self._toggle_render(True, views)
12571250

12581251
def add_contour_overlay(self, source, min=None, max=None,
1259-
n_contours=7, line_width=1.5, hemi=None):
1252+
n_contours=7, line_width=1.5, colormap="YlOrRd_r",
1253+
hemi=None):
12601254
"""Add a topographic contour overlay of the positive data.
12611255
12621256
Note: This visualization will look best when using the "low_contrast"
@@ -1274,6 +1268,10 @@ def add_contour_overlay(self, source, min=None, max=None,
12741268
number of contours to use in the display
12751269
line_width : float
12761270
width of contour lines
1271+
colormap : string, list of colors, or array
1272+
name of matplotlib colormap to use, a list of matplotlib colors,
1273+
or a custom look up table (an n x 4 array coded with RBGA values
1274+
between 0 and 255).
12771275
hemi : str | None
12781276
If None, it is assumed to belong to the hemipshere being
12791277
shown. If two hemispheres are being shown, an error will
@@ -1294,14 +1292,17 @@ def add_contour_overlay(self, source, min=None, max=None,
12941292
c['surface'].remove()
12951293
c['colorbar'].visible = False
12961294

1295+
# Process colormap argument into a lut
1296+
lut = create_color_lut(colormap)
1297+
12971298
views = self._toggle_render(False)
12981299
cl = []
12991300
for brain in self._brain_list:
13001301
if brain['hemi'] == hemi:
13011302
cl.append(brain['brain'].add_contour_overlay(scalar_data,
13021303
min, max,
13031304
n_contours,
1304-
line_width))
1305+
line_width, lut))
13051306
self.contour_list = cl
13061307
self._toggle_render(True, views)
13071308

@@ -2258,7 +2259,7 @@ def add_foci(self, foci_coords, scale_factor, color, alpha, name):
22582259
return points
22592260

22602261
def add_contour_overlay(self, scalar_data, min=None, max=None,
2261-
n_contours=7, line_width=1.5):
2262+
n_contours=7, line_width=1.5, lut=None):
22622263
"""Add a topographic contour overlay of the positive data"""
22632264
# Set up the pipeline
22642265
mesh = mlab.pipeline.triangular_mesh_source(self._geo.x, self._geo.y,
@@ -2271,6 +2272,8 @@ def add_contour_overlay(self, scalar_data, min=None, max=None,
22712272
thresh = mlab.pipeline.threshold(mesh, low=min)
22722273
surf = mlab.pipeline.contour_surface(thresh, contours=n_contours,
22732274
line_width=line_width)
2275+
if lut is not None:
2276+
surf.module_manager.scalar_lut_manager.lut.table = lut
22742277

22752278
# Set the colorbar and range correctly
22762279
bar = mlab.scalarbar(surf,

0 commit comments

Comments
 (0)