pyplot contourf with custom colormap repeats color instead of changing

|▌冷眼眸甩不掉的悲伤 提交于 2020-01-24 12:52:10

问题


I want to plot some data with a logarithmic color code where a decade limit is indicated by a white/black interface. Grey levels are used to show some subdivisions of a decade. My problem is that there are two white neighboring regions in each decade even though the color map has the right number of entries (at least I think). Could someone help please?

In the meantime I made some tests and I found that it's the second color of the repeating pattern that is not used (the gray(0.25)), but I still have no idea why.

Here is the short version of the code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors

# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize

# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
                 1e-2,2e-2,4e-2,6e-2,8e-2,
                 0.1,0.2,0.4,0.6,0.8,
                 1])
# (5 intervals in one decade )

# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()

# make a list of 5 colors to create a colormap from
mapColS = [plt.cm.gray(0),plt.cm.gray(0.25),plt.cm.gray(0.50), 
           plt.cm.gray(0.7),plt.cm.gray(0.99)]
# repeat 3 times for the three decades
mapColS = mapColS + mapColS + mapColS
MyCmap=colors.ListedColormap(mapColS) # make color map

fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)

# plot lines
cax = plt.contour(im, levS, linewidths = 0.5,
                   norm=colors.LogNorm(), colors = 'k')
# fill with colors
cax = plt.contourf(im, levS, norm=colors.LogNorm(),
                   cmap=MyCmap)  # plt.cm.jet  OR  MyCmap

# show log color bar
cbar = fig13g.colorbar(cax, orientation='vertical',
                       spacing='regular',ticks= levS)

Here are the results:

For comparisson, using 'jet' there is no problem:


回答1:


The problem is that different values end up producing the same color. This is due to the non-linear norm in use.
For a linear norm, the colors for the layers of the contourf plot would be taken at the arithmetic mean between the levels. While this may also cause problems when comparing images and contour plots (as shown in How does pyplot.contourf choose colors from a colormap?), it would still leed to N unique colors being used for N+1 levels.

For a LogNorm, the geometric mean is used instead of the arithmetic mean.

In the following the values used to produce the colors from the colormap are shown. As can be seen several end up in the same bin.

Increasing the number of colors will allow each value to be in its own colorbin.

This is in principle exactly why the use of the 'jet' colormap works fine, because you have 256 different colors.

Hence a possible solution is to use more colors for the colormap creation,

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors

# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize

# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
                 1e-2,2e-2,4e-2,6e-2,8e-2,
                 0.1,0.2,0.4,0.6,0.8,
                 1])
# (5 intervals in one decade )

# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()

# make a list of N colors to create a colormap from
N = 20
mapColS = list(plt.cm.gray(np.linspace(0,1,N)))
# repeat 3 times for the three decades
mapColR = mapColS + mapColS + mapColS
MyCmap=colors.ListedColormap(mapColR) # make color map

fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)

# plot lines
c = plt.contour(im, levS, linewidths = 0.5,
                   norm=colors.LogNorm(), colors = 'k')
# fill with colors
cf = plt.contourf(im, levS, norm=colors.LogNorm(),
                   cmap=MyCmap)  # plt.cm.jet  OR  MyCmap

cbar = fig13g.colorbar(cf, orientation='vertical',
                       spacing='regular',ticks= levS)                 
plt.show()

The drawback of this is that you loose dynamic range because the lowest color is not black but dark grey.

A different option would hence be to calculate those layer values and create a colormap with the respective colors at exactly those positions.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors

# generate data
x = y = np.linspace(-3, 3, 200)
im = 1800*np.exp(-(np.outer(x,x) + y**2))
im = im / im.max() # normalize

# set logarithic levels (with small steps)
levS = np.array([1e-3,2e-3,4e-3,6e-3,8e-3,
                 1e-2,2e-2,4e-2,6e-2,8e-2,
                 0.1,0.2,0.4,0.6,0.8,
                 1])
# (5 intervals in one decade )

# avoid white patches by filling up to lowest level
im[ im < levS.min() ] = levS.min()

# make a list of N colors to create a colormap from
N = 5
mapColS = list(plt.cm.gray(np.linspace(0,1,N)))
# repeat 3 times for the three decades
mapColR = mapColS + mapColS + mapColS

#calculate layer values for lognorm
layers = np.sqrt(levS[:-1]) * np.sqrt(levS[1:])
norm = colors.LogNorm(levS.min(), levS.max())
#add outmost values and colors
lvals = np.concatenate(([0.], norm(layers), [1.]))
cvals = [mapColR[0]] + mapColR + [mapColR[-1]]

# make the colormap from values and colors
MyCmap=colors.LinearSegmentedColormap.from_list("", list(zip(lvals,cvals)))

fig13g = plt.figure(1000) #create figure
ax13g = fig13g.add_subplot(111)

# plot lines
c = plt.contour(im, levS, linewidths = 0.5,
                   norm=norm, colors = 'k')
# fill with colors
cf = plt.contourf(im, levS, norm=norm,
                   cmap=MyCmap)

cbar = fig13g.colorbar(cf, orientation='vertical',
                       spacing='regular',ticks= levS)                 
plt.show()




回答2:


The problem is that you were repeating the same color levels mapColS 3 times by using mapColS = mapColS + mapColS + mapColS. The straight forward solution is to create a single continuous grayscale by dividing linearly the scale between plt.cm.gray(0) and plt.cm.gray(0.99) into 15 equal levels as

mapColS = [plt.cm.gray(i) for i in np.linspace(0, 0.99, 15)]

MyCmap=colors.ListedColormap(mapColS) # make color map

Output



来源:https://stackoverflow.com/questions/52498858/pyplot-contourf-with-custom-colormap-repeats-color-instead-of-changing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!