Skip to content

Instantly share code, notes, and snippets.

@endolith
Last active February 3, 2021 07:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save endolith/2879736 to your computer and use it in GitHub Desktop.
Save endolith/2879736 to your computer and use it in GitHub Desktop.
Bipolar colormap translated from Matlab into Python
#!/usr/bin/env python
"""
Copyright 2012 endolith at gmail com
Copyright 2009 Ged Ridgway at gmail com
Translation and modification of
http://www.mathworks.com/matlabcentral/fileexchange/26026-bipolar-colormap
Based on Manja Lehmann's hand-crafted colormap for cortical visualisation
"""
from __future__ import division
import scipy
from matplotlib import cm
def bipolar(lutsize=256, n=0.333, interp=[]):
"""
Bipolar hot/cold colormap, with neutral central color.
This colormap is meant for visualizing diverging data; positive
and negative deviations from a central value. It is similar to a
blackbody colormap for positive values, but with a complementary
"cold" colormap for negative values.
Parameters
----------
lutsize : int
The number of elements in the colormap lookup table. (Default is 256.)
n : float
The gray value for the neutral middle of the colormap. (Default is
1/3.)
The colormap goes from cyan-blue-neutral-red-yellow if neutral
is < 0.5, and from blue-cyan-neutral-yellow-red if neutral > 0.5.
For shaded 3D surfaces, an `n` near 0.5 is better, because it
minimizes luminance changes that would otherwise obscure shading cues
for determining 3D structure.
For 2D heat maps, an `n` near the 0 or 1 extremes is better, for
maximizing luminance change and showing details of the data.
interp : str or int, optional
Specifies the type of interpolation.
('linear', 'nearest', 'zero', 'slinear', 'quadratic, 'cubic')
or as an integer specifying the order of the spline interpolator
to use. Default is 'linear'. See `scipy.interpolate.interp1d`.
Returns
-------
out : matplotlib.colors.LinearSegmentedColormap
The resulting colormap object
Notes
-----
If neutral is exactly 0.5, then a map which yields a linear increase in
intensity when converted to grayscale is produced. This colormap should
also be reasonably good
for colorblind viewers, as it avoids green and is predominantly based on
the purple-yellow pairing which is easily discriminated by the two common
types of colorblindness. [2]_
Examples
--------
>>> from mpl_toolkits.mplot3d import Axes3D
>>> from matplotlib import cm
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> fig = plt.figure()
>>> ax = fig.gca(projection='3d')
>>> x = y = np.arange(-4, 4, 0.15)
>>> x, y = np.meshgrid(x, y)
>>> z = (1- x/2 + x**5 + y**3)*exp(-x**2-y**2)
>>> surf = ax.plot_surface(x, y, z, rstride=1, cstride=1, linewidth=0.1,
>>> vmax=abs(z).max(), vmin=-abs(z).max())
>>> fig.colorbar(surf)
>>> plt.show()
>>> set_cmap(bipolar(201))
>>> waitforbuttonpress()
>>> set_cmap(bipolar(201, 0.1)) # dark gray as neutral
>>> waitforbuttonpress()
>>> set_cmap(bipolar(201, 0.9)) # light gray as neutral
>>> waitforbuttonpress()
>>> set_cmap(bipolar(201, 0.5)) # grayscale-friendly colormap
References
----------
.. [1] Lehmann Manja, Crutch SJ, Ridgway GR et al. "Cortical thickness
and voxel-based morphometry in posterior cortical atrophy and typical
Alzheimer's disease", Neurobiology of Aging, 2009,
doi:10.1016/j.neurobiolaging.2009.08.017
.. [2] Brewer, Cynthia A., "Guidelines for Selecting Colors for
Diverging Schemes on Maps", The Cartographic Journal, Volume 33,
Number 2, December 1996, pp. 79-86(8)
http://www.ingentaconnect.com/content/maney/caj/1996/00000033/00000002/art00002
"""
if n < 0.5:
if not interp:
interp = 'linear' # seems to work well with dark neutral colors cyan-blue-dark-red-yellow
_data = (
(0, 1, 1), # cyan
(0, 0, 1), # blue
(n, n, n), # dark neutral
(1, 0, 0), # red
(1, 1, 0), # yellow
)
elif n >= 0.5:
if not interp:
interp = 'cubic' # seems to work better with bright neutral colors blue-cyan-light-yellow-red
# produces bright yellow or cyan rings otherwise
_data = (
(0, 0, 1), # blue
(0, 1, 1), # cyan
(n, n, n), # light neutral
(1, 1, 0), # yellow
(1, 0, 0), # red
)
else:
raise ValueError('n must be 0.0 < n < 1.0')
xi = linspace(0, 1, size(_data, 0))
cm_interp = scipy.interpolate.interp1d(xi, _data, axis=0, kind=interp)
xnew = linspace(0, 1, lutsize)
ynew = cm_interp(xnew)
# No form of interpolation works without this, but that means the interpolations are not working right.
ynew = clip(ynew, 0, 1)
return cm.colors.LinearSegmentedColormap.from_list('bipolar', ynew, lutsize)
if __name__ == "__main__":
from pylab import *
def func3(x,y):
return (1- x/2 + x**5 + y**3)*exp(-x**2-y**2)
# make these smaller to increase the resolution
dx, dy = 0.05, 0.05
x = arange(-3.0, 3.0001, dx)
y = arange(-3.0, 3.0001, dy)
X,Y = meshgrid(x, y)
Z = func3(X, Y)
pcolor(X, Y, Z, cmap=bipolar(n=1./3, interp='linear'), vmax=abs(Z).max(), vmin=-abs(Z).max())
colorbar()
axis([-3,3,-3,3])
show()
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 28 14:28:50 2013
Translation of
http://www.mathworks.com/matlabcentral/fileexchange/11037-lab-color-scale
Based on https://code.google.com/p/python-colormath/
"""
from __future__ import division
from numpy import pi, linspace, cos, sin, vstack
from colormath.color_objects import LabColor
from matplotlib import cm
def color_scale(n=256, theta=0, r=50, angle_offset=pi/2, dir='cw'):
if dir == 'cw':
angle_offset = -angle_offset
else:
angle_offset = angle_offset
theta = pi * theta / 180
theta_vec = linspace(theta, theta + angle_offset, n)
a = r * cos(theta_vec)
b = r * sin(theta_vec)
L = linspace(0, 100, n)
rgbs = []
Lab = vstack([L, a, b])
for L, a, b in Lab.T:
rgb = LabColor(L, a, b).convert_to('rgb')
r = rgb.rgb_r/255.0
g = rgb.rgb_g/255.0
b = rgb.rgb_b/255.0
rgbs.append((r, g, b))
return cm.colors.LinearSegmentedColormap.from_list('color_scale', rgbs, n)
if __name__ == "__main__":
from pylab import *
dx, dy = 0.01, 0.01
x = arange(-2.0, 2.0001, dx)
y = arange(-2.0, 2.0001, dy)
X,Y = meshgrid(x, y)
Z = X * np.exp(-X**2 - Y**2) # Matplotlib's 2-bump example
figure()
imshow(Z, vmax=abs(Z).max(), vmin=-abs(Z).max(),
cmap=color_scale(theta = 0, r = 50))
colorbar()
show()
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 28 21:13:00 2013
"""
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from color_scale import color_scale
from bipolar import bipolar
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(1)
ax = fig.gca(projection='3d')
#colormap = color_scale(theta = 0, angle_offset=2*pi, r = 50)
#colormap = bipolar(n=0)
colormap = get_cmap('hsv')
lutsize = 256
# Plain lines
# colors = zeros((lutsize, 3))
# for i in xrange(0, lutsize):
# colors[i] = colormap.__call__(i)[:3]
#
# r = colors[:,0]
# g = colors[:,1]
# b = colors[:,2]
#
# ax.plot(r, g, b, label=colormap.name)
# Colored lines
for i in xrange(0, lutsize-1):
r1, g1, b1 = colormap.__call__(i )[:3]
r2, g2, b2 = colormap.__call__(i+1)[:3]
ax.plot([r1, r2], [g1, g2], [b1, b2], lw=3, color = (r1, g1, b1))
ax.legend()
ax.set_xlim3d(0, 1)
ax.set_ylim3d(0, 1)
ax.set_zlim3d(0, 1)
ax.set_xlabel("red")
ax.set_ylabel("blue")
ax.set_zlabel("green")
plt.show()
color_scale.m (Steve Eddins):
Copyright (c) 2009, The MathWorks, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution
* Neither the name of the The MathWorks, Inc. nor the names
of its contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
bipolar.m:
Copyright (c) 2009, Ged Ridgway
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
@endolith
Copy link
Author

Why did I put the color_scale here though? That's a totally different thing...

@endolith
Copy link
Author

I cleaned up the git history and moved it here: https://github.com/endolith/bipolar-colormap

I'm planning to keep the "bipolar" map matching the existing ones, with the RGB cube angles and "halos", and rename the bezier curve version to "hotcold".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment