Normalizing colors in matplotlib
Asked Answered
T

3

6

I am trying to plot a surface using matplotlib using the code below:

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pylab as p

vima=0.5

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 16.67, vima)
Y = np.arange(0, 12.5, vima)
X, Y = np.meshgrid(X, Y)

Z = np.sqrt(((1.2*Y+0.6*X)**2+(0.2*Y+1.6*X)**2)/(0.64*Y**2+0.36*X**2))

surf = ax.plot_surface(X, Y, Z,rstride=1, cstride=1, alpha=1,cmap=cm.jet,  linewidth=0)
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()

If you run it you will see a blue surface, but I want to use the whole color range of jet... I know there is a class "matplotlib.colors.Normalize", but I don't know how to use it. Could you please add the necessary code in order to do it?

Thoreau answered 6/3, 2011 at 14:53 Comment(6)
When I run this code as-is I do not get a blue surface. What version of matplotlib are you using?Morganite
I am using version 1.0.1Thoreau
Looks fine to me after I remove the NaN value in Z caused by the divide by zero.Ingrained
I don't understand what's going on then... I know there are differences between versions of matplotlib, but if you run it in version 1.0.1 and there is no problem then it's not only my problem.Thoreau
That seems to be it, Josh. There seems to be a bug in scaling the colormap for arrays with a NaN in it. You should post nan_to_num or whatever you used to get rid of the NaN as a work-around.Morganite
You might be able to use masked arrays to ignore the NaNs: docs.scipy.org/doc/numpy/reference/maskedarray.htmlThinskinned
M
5

As JoshAdel noted in a comment (credit belongs to him), it appears that the surface plot is improperly ranging the colormap when a NaN is in the Z array. A simple work-around is to simply convert the NaN's to zero or very large or very small numbers so that the colormap can be normalized to the z-axis range.

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pylab as p

vima=0.5

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 16.67, vima)
Y = np.arange(0, 12.5, vima)
X, Y = np.meshgrid(X, Y)

Z = np.sqrt(((1.2*Y+0.6*X)**2+(0.2*Y+1.6*X)**2)/(0.64*Y**2+0.36*X**2))
Z = np.nan_to_num(Z) # added this line

surf = ax.plot_surface(X, Y, Z,rstride=1, cstride=1, alpha=1,cmap=cm.jet,  linewidth=0)
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()
Morganite answered 7/3, 2011 at 2:32 Comment(3)
Thanks for attribution. Just remember to up vote me some other time :-)Ingrained
Thank you both Peter and Josh for your help. I've marked this as the correct answer, but I prefer to change "X = np.arange(0, 16.67, vima)" into "X = np.arange(0.000001, 16.67, vima)" instead of adding "Z = np.nan_to_num(Z)". Thus I don't have any NaN values in Z... You can try it to see the difference near (X,Y)=(0,0)Thoreau
This however disable the usage of nan to hide (not plot) part of the data. How can one still use nan to hide some of the points yet have proper color normalization?Maybe a custom Normalize? see #36107092Angelenaangeleno
U
18

I realise that the poster's issue has already been resolved, but the question of normalizing the colors was never dealt with. Since I've figured out how I thought I'd just drop this here for anyone else who might need it.

First you create a norm and pass that to the plotting function, I've tried to add this to the OP's code.

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pylab as p
import matplotlib

vima=0.5

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 16.67, vima)
Y = np.arange(0, 12.5, vima)
X, Y = np.meshgrid(X, Y)

Z = np.sqrt(((1.2*Y+0.6*X)**2+(0.2*Y+1.6*X)**2)/(0.64*Y**2+0.36*X**2))
Z = np.nan_to_num(Z)

# Make the norm
norm = matplotlib.colors.Normalize(vmin = np.min(Z), vmax = np.max(Z), clip = False)

# Plot with the norm
surf = ax.plot_surface(X, Y, Z,rstride=1, cstride=1, norm=norm, alpha=1,cmap=cm.jet,     linewidth=0)
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()

The norm works the same way for the "imshow" command.

Urumchi answered 18/9, 2012 at 15:15 Comment(1)
Normalize() is unnecessary here and produces unwanted results. The correct answer is indeed the accepted one by Paul.Schonthal
M
5

As JoshAdel noted in a comment (credit belongs to him), it appears that the surface plot is improperly ranging the colormap when a NaN is in the Z array. A simple work-around is to simply convert the NaN's to zero or very large or very small numbers so that the colormap can be normalized to the z-axis range.

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pylab as p

vima=0.5

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 16.67, vima)
Y = np.arange(0, 12.5, vima)
X, Y = np.meshgrid(X, Y)

Z = np.sqrt(((1.2*Y+0.6*X)**2+(0.2*Y+1.6*X)**2)/(0.64*Y**2+0.36*X**2))
Z = np.nan_to_num(Z) # added this line

surf = ax.plot_surface(X, Y, Z,rstride=1, cstride=1, alpha=1,cmap=cm.jet,  linewidth=0)
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()
Morganite answered 7/3, 2011 at 2:32 Comment(3)
Thanks for attribution. Just remember to up vote me some other time :-)Ingrained
Thank you both Peter and Josh for your help. I've marked this as the correct answer, but I prefer to change "X = np.arange(0, 16.67, vima)" into "X = np.arange(0.000001, 16.67, vima)" instead of adding "Z = np.nan_to_num(Z)". Thus I don't have any NaN values in Z... You can try it to see the difference near (X,Y)=(0,0)Thoreau
This however disable the usage of nan to hide (not plot) part of the data. How can one still use nan to hide some of the points yet have proper color normalization?Maybe a custom Normalize? see #36107092Angelenaangeleno
J
4

Replying to an old question, I know, but the answers posted were at least in my case somewhat unsatisfactory. For those still stumbling here, I give a solution that worked for me.

Firstly, I did not want use zeros to replace NaNs, as for me they represent points with missing or undefined data. I'd rather not have anything plotted at these points. Secondly, the whole z range of my data was way above zero, so dotting the plot with zeros would result in an ugly and badly scaled plot.

Solution given by leifdenby was quite close, so +1 for that (though as pointed out, the explicit normalisation does not add to the earlier solution). I just dropped the NaN-to-zero replacement, and used the functions nanmin and nanmax instead of min and max in the color scale normalisation. These functions give the min and max of an array but simply ignore all NaNs. The code now reads:

# Added colors to the matplotlib import list
from matplotlib import cm, colors
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pylab as p


vima=0.5

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 16.67, vima)
Y = np.arange(0, 12.5, vima)
X, Y = np.meshgrid(X, Y)

Z = np.sqrt(((1.2*Y+0.6*X)**2+(0.2*Y+1.6*X)**2)/(0.64*Y**2+0.36*X**2))

# MAIN IDEA: Added normalisation using nanmin and nanmax functions
norm = colors.Normalize(vmin = np.nanmin(Z), 
                        vmax = np.nanmax(Z))

# Added the norm=norm parameter
surf = ax.plot_surface(X, Y, Z,rstride=1, cstride=1, alpha=1, norm=norm, cmap=cm.jet,  linewidth=0)
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()

Running this, I get a correctly scaled plot, with the (0, 0) datapoint missing. This is also the behaviour that I find most preferable, as the limit (x, y) to (0, 0) does not seem to exist for the function in question.

This has been my first contribution to StackOverflow, I hope it was a good one (wink).

Julissa answered 30/12, 2015 at 19:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.