Using perceptually uniform colormaps in Mayavi volumetric visualization - matplotlib

AFAIK Mayavi does not come with any perceptually uniform colormaps. I tried naively to just pass it one of Matplotlib's colormaps but it failed:
from mayavi import mlab
import multiprocessing
import matplotlib.pyplot as plt
plasma = plt.get_cmap('plasma')
...
mlab.pipeline.volume(..., colormap=plasma)
TraitError: Cannot set the undefined 'colormap' attribute of a 'VolumeFactory' object.
Edit: I found a guide to convert Matplotlib colormaps to Mayavi colormaps. However, it unfortunately doesn't work since I am trying to use a volume using a perceptually uniform colormap.
from matplotlib.cm import get_cmap
import numpy as np
from mayavi import mlab
values = np.linspace(0., 1., 256)
lut_dict = {}
lut_dict['plasma'] = get_cmap('plasma')(values.copy())
x, y, z = np.ogrid[-10:10:20j, -10:10:20j, -10:10:20j]
s = np.sin(x*y*z)/(x*y*z)
mlab.pipeline.volume(mlab.pipeline.scalar_field(s), vmin=0, vmax=0.8, colormap=lut_dict['plasma']) # still getting the same error
mlab.axes()
mlab.show()
...

Instead of setting it as the colormap argument, if you set it as the ColorTransferFunction of the volume, it works as expected.
import numpy as np
from mayavi import mlab
from tvtk.util import ctf
from matplotlib.pyplot import cm
values = np.linspace(0., 1., 256)
x, y, z = np.ogrid[-10:10:20j, -10:10:20j, -10:10:20j]
s = np.sin(x*y*z)/(x*y*z)
volume = mlab.pipeline.volume(mlab.pipeline.scalar_field(s), vmin=0, vmax=0.8)
# save the existing colormap
c = ctf.save_ctfs(volume._volume_property)
# change it with the colors of the new colormap
# in this case 'plasma'
c['rgb']=cm.get_cmap('plasma')(values.copy())
# load the color transfer function to the volume
ctf.load_ctfs(c, volume._volume_property)
# signal for update
volume.update_ctf = True
mlab.show()

While the previous answer by like444 helped me partially with a similar problem, it leads to incorrect translation between colormaps. This is because the format in which matplotlib and tvtk store color information is slightly different: Matplotlib uses RGBA, while ColorTransferFunction uses VRGB, where V is the value in the shown data that this part of the colormap is assigned to. So by doing a 1-to-1 copy, green becomes red, blue becomes green and alpha becomes blue. The following code snippet fixes that:
def cmap_to_ctf(cmap_name):
values = list(np.linspace(0, 1, 256))
cmap = cm.get_cmap(cmap_name)(values)
transfer_function = ctf.ColorTransferFunction()
for i, v in enumerate(values):
transfer_function.add_rgb_point(v, cmap[i, 0], cmap[i, 1], cmap[i, 2])
return transfer_function

Related

Seaborn hue not showing every values [duplicate]

I am trying to plot some data with the following code
from sklearn.datasets import make_blobs
import seaborn as sns
import numpy as np
X, y = make_blobs(n_samples=1000, n_features=2, centers=10, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None)
palette = np.array(sns.color_palette("bright", 10)) #Chossing color
sns.scatterplot(X[:,0],X[:,1],legend='full',c=palette[y])
The color is beautiful, but the legend is missing.
When I check the documentation, I see:
How to draw the legend. If “brief”, numeric hue and size variables
....
So it seems I also need to include the hue argument.
But when I try the hue argument with the following codes, the following graph is created instead...
sns.scatterplot(X[:,0],X[:,1],legend='full',hue=y,c=palette[y])
The legend is showing, but the color is not what I want. After adding the hue argument, it seems it overwrite the palette argument. No matter what palette I choose, the color is still ugly as hell...
My question is:
How to show legend while maintaining the color that I want?
You would need to use the palette kwarg, and specify the hues with your y values.
from sklearn.datasets import make_blobs
import seaborn as sns
import matplotlib.pyplot as plt
X, y = make_blobs(n_samples=1000, n_features=2, centers=10, cluster_std=1.0,
center_box=(-10.0, 10.0), shuffle=True, random_state=None)
palette = sns.color_palette("bright", 10) #Choosing color
sns.scatterplot(X[:, 0], X[:, 1], palette=palette, hue=y, legend='full')
plt.show()

Is it possible to achieve a continuous color gradient with surface plot using matplotlib?

I am trying to visualise the pixel intensity of a photo by plotting a 3D graph. In the code below, lab is an image I want to analyse. The code will look at the pixel intensity of every pixels in the image and plot a graph, where the height denotes the pixel intensity.
Here is a portion of my code:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from skimage import io, color
import glob
from PIL import Image
plt.figure(dpi=1200)
ax = plt.axes(projection='3d')
y = range(lab.shape[0])
x = range(lab.shape[1])
X, Y = np.meshgrid(x, y)
ax.view_init(elev=60., azim=60)
thickness = ax.plot_surface(
X,
Y,
lab[:, :, 0], # change value here to adjust the height
cmap=cm.coolwarm,
antialiased=False)
# Add a color bar which maps values to colors.
fig.colorbar(thickness, shrink=0.5, aspect=5)
It outputs:
As you can see, the colour gradient is not continuous despite the graph having many fine details and slight fluctuations in height which is not represented by the color map.
Is it possible to achieve a continuous color gradient with surface plot using matplotlib like the image below?
Thank you.
You can use the colormap hsv to get the same result.
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from skimage import io, color
import glob
from PIL import Image
lab = cv2.imread('Lenna.png')
lab = cv2.cvtColor(lab, cv2.COLOR_BGR2LAB)
fig = plt.figure()
ax = plt.axes(projection='3d')
y = range(lab.shape[0])
x = range(lab.shape[1])
X, Y = np.meshgrid(x, y)
ax.view_init(elev=60., azim=60)
thickness = ax.plot_surface(
X,
Y,
lab[:, :, 0], # change value here to adjust the height
cmap=plt.get_cmap('hsv'),
antialiased=False)
# Add a color bar which maps values to colors.
fig.colorbar(thickness, shrink=0.5, aspect=5)
plt.show()
output :
Take a look at the documentation for more colormaps.

Making sure 0 gets white in a RdBu colorbar

I create a heatmap with the following snippet:
import numpy as np
import matplotlib.pyplot as plt
d = np.random.normal(.4,2,(10,10))
plt.imshow(d,cmap=plt.cm.RdBu)
plt.colorbar()
plt.show()
The result is plot below:
Now, since the middle point of the data is not 0, the cells in which the colormap has value 0 are not white, but rather a little reddish.
How do I force the colormap so that max=blue, min=red and 0=white?
Use a DivergingNorm.
Note: From matplotlib 3.2 onwards DivergingNorm is renamed to TwoSlopeNorm.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
d = np.random.normal(.4,2,(10,10))
norm = mcolors.DivergingNorm(vmin=d.min(), vmax = d.max(), vcenter=0)
plt.imshow(d, cmap=plt.cm.RdBu, norm=norm)
plt.colorbar()
plt.show()
A previous SO post (Change colorbar gradient in matplotlib) wanted a solution for a more complicated situation, but one of the answers talked about the MidpointNormalize subclass in the matplotlib documentation. With that, the solution becomes:
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
class MidpointNormalize(mpl.colors.Normalize):
## class from the mpl docs:
# https://matplotlib.org/users/colormapnorms.html
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
super().__init__(vmin, vmax, clip)
def __call__(self, value, clip=None):
# I'm ignoring masked values and all kinds of edge cases to make a
# simple example...
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y))
d = np.random.normal(.4,2,(10,10))
plt.imshow(d,cmap=plt.cm.RdBu,norm=MidpointNormalize(midpoint=0))
plt.colorbar()
plt.show()
Kudos to Joe Kington for writing the subclass, and to Rutger Kassies for pointing out the answer.

Plotting masked numpy array leads to incorrect colorbar

I'm trying to create a custom color bar for a matplotlib PolyCollection. Everything seems ok until I attempt to plot a masked array. The color bar no longer shows the correct colors even though the plot does. Is there a different procedure for plotting masked arrays?
I'm using matplotlib 1.4.0 and numpy 1.8.
Here's my plotting code:
import numpy
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
vertices = numpy.load('vertices.npy')
array = numpy.load('array.npy')
# Take 2d slice out of 3D array
slice_ = array[:, :, 0:1].flatten(order='F')
fig, ax = plt.subplots()
poly = PolyCollection(vertices, array=slice_, edgecolors='black', linewidth=.25)
cm = mpl.colors.ListedColormap([(1.0, 0.0, 0.0), (.2, .5, .2)])
poly.set_cmap(cm)
bounds = [.1, .4, .6]
norm = mpl.colors.BoundaryNorm(bounds, cm.N)
fig.colorbar(poly, ax=ax, orientation='vertical', boundaries=bounds, norm=norm)
ax.add_collection(poly, autolim=True)
ax.autoscale_view()
plt.show()
Here's what the plot looks like:
However, when I plot a masked array with the following change before the slicing:
array = numpy.ma.array(array, mask=array > .5)
I get a color bar that now shows only a single color. Even though both colors are (correctly) still shown in the plot.
Is there some trick to keeping a colobar consistent when plotting a masked array? I know I can use cm.set_bad to change the color of masked values, but that's not quite what I'm looking for. I want the color bar to show up the same between these two plots since both colors and the color bar itself should remain unchanged.
Pass the BoundaryNorm to the PolyCollection, poly. Otherwise, poly.norm gets set to a matplotlib.colors.Normalize instance by default:
In [119]: poly.norm
Out[119]: <matplotlib.colors.Normalize at 0x7faac4dc8210>
I have not stepped through the source code sufficiently to explain exactly what is happening in the code you posted, but I speculate that the interaction of this Normalize instance and the BoundaryNorm make the range of values seen by the fig.colorbar different than what you expected.
In any case, if you pass norm=norm to PolyCollection, then the result looks correct:
import numpy
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.collections as mcoll
import matplotlib.colors as mcolors
numpy.random.seed(4)
N, M = 3, 3
vertices = numpy.random.random((N, M, 2))
array = numpy.random.random((1, N, 2))
# vertices = numpy.load('vertices.npy')
# array = numpy.load('array.npy')
array = numpy.ma.array(array, mask=array > .5)
# Take 2d slice out of 3D array
slice_ = array[:, :, 0:1].flatten(order='F')
fig, ax = plt.subplots()
bounds = [.1, .4, .6]
cm = mpl.colors.ListedColormap([(1.0, 0.0, 0.0), (.2, .5, .2)])
norm = mpl.colors.BoundaryNorm(bounds, cm.N)
poly = mcoll.PolyCollection(
vertices,
array=slice_,
edgecolors='black', linewidth=.25, norm=norm)
poly.set_cmap(cm)
fig.colorbar(poly, ax=ax, orientation='vertical')
ax.add_collection(poly, autolim=True)
ax.autoscale_view()
plt.show()

matplotlib: Stretch image to cover the whole figure

I am quite used to working with matlab and now trying to make the shift matplotlib and numpy. Is there a way in matplotlib that an image you are plotting occupies the whole figure window.
import numpy as np
import matplotlib.pyplot as plt
# get image im as nparray
# ........
plt.figure()
plt.imshow(im)
plt.set_cmap('hot')
plt.savefig("frame.png")
I want the image to maintain its aspect ratio and scale to the size of the figure ... so when I do savefig it exactly the same size as the input figure, and it is completely covered by the image.
Thanks.
I did this using the following snippet.
#!/usr/bin/env python
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from pylab import *
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2-Z1 # difference of Gaussians
ax = Axes(plt.gcf(),[0,0,1,1],yticks=[],xticks=[],frame_on=False)
plt.gcf().delaxes(plt.gca())
plt.gcf().add_axes(ax)
im = plt.imshow(Z, cmap=cm.gray)
plt.show()
Note the grey border on the sides is related to the aspect rario of the Axes which is altered by setting aspect='equal', or aspect='auto' or your ratio.
Also as mentioned by Zhenya in the comments Similar StackOverflow Question
mentions the parameters to savefig of bbox_inches='tight' and pad_inches=-1 or pad_inches=0
You can use a function like the one below.
It calculates the needed size for the figure (in inches) according to the resolution in dpi you want.
import numpy as np
import matplotlib.pyplot as plt
def plot_im(image, dpi=80):
px,py = im.shape # depending of your matplotlib.rc you may
have to use py,px instead
#px,py = im[:,:,0].shape # if image has a (x,y,z) shape
size = (py/np.float(dpi), px/np.float(dpi)) # note the np.float()
fig = plt.figure(figsize=size, dpi=dpi)
ax = fig.add_axes([0, 0, 1, 1])
# Customize the axis
# remove top and right spines
ax.spines['right'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
# turn off ticks
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_ticklabels([])
ax.yaxis.set_ticklabels([])
ax.imshow(im)
plt.show()
Here's a minimal object-oriented solution:
fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes([0, 0, 1, 1], frameon=False, xticks=[], yticks=[])
Testing it out with
ax.imshow([[0]])
fig.savefig('test.png')
saves out a uniform purple block.
edit: As #duhaime points out below, this requires the figure to have the same aspect as the axes.
If you'd like the axes to resize to the figure, add aspect='auto' to imshow.
If you'd like the figure to resize to be resized to the axes, add
from matplotlib import tight_bbox
bbox = fig.get_tightbbox(fig.canvas.get_renderer())
tight_bbox.adjust_bbox(fig, bbox, fig.canvas.fixed_dpi)
after the imshow call. This is the important bit of matplotlib's tight_layout functionality which is implicitly called by things like Jupyter's renderer.