老问题:如何在matplotlib中创建一个具有恒定亮度的HSL颜色图?
根据matplotlib的彩色地图文档的说法,它们的默认共垫的亮度值不是常数。但是,我想从HSL颜色空间创建一个颜色图,它具有恒定的亮度。我怎么能这么做?
一般情况下,创建你自己的网格并不难,我不知道如何做到这一点,同时满足轻盈的标准。也许这可以通过从彩色地图文档中逆向工程代码来完成?
解决方案
我想我找到了一种基于这个职位的方法。首先,在HSL颜色空间工作对我的总体目标来说并不是最好的主意,所以我转而使用HSV。这样,我就可以从matplotlib加载首选颜色映射,从它创建一组RGB颜色,将它们转换为HSV,设置它们的颜色值常数,将它们转换回RGB,最后再从它们创建一个颜色映射(例如,我可以使用它来进行2d直方图)。
背景
我需要一个HSV的颜色映射与一个恒定的颜色值,因为然后我可以唯一地映射颜色到RGB空间,从托盘是跨越色调和饱和度。这反过来将允许我创建一个2d直方图,在那里,我可以对计数(通过饱和度)和第三个变量(通过色调)进行彩色编码。
例如,在下面的MWE中(与这里略有不同),颜色值不变的彩色地图,在每个bin i中,可以使用饱和度来表示计数的数量(例如,颜色越轻,数目越低),并使用色调来表示平均z值。这将使我基本上可以把下面的两个情节合并成一个。(在2d直方图中也有添加alpha值的本教程,但在这种情况下,我认为这是行不通的。)
目前,您仍然需要这两幅图才能获得完整的图片,因为如果没有直方图,您将无法判断一个bin中的某个z值可能有多重要,因为同一颜色是独立于对其贡献的数据点使用的(因此,从颜色判断,一个只有一个数据点的bin看起来可能与具有相同颜色的bin一样重要,但包含更多的数据点;因此存在偏向于离群者的偏见)。

import matplotlib.pyplot as plt
import numpy as np
# make data: correlated + noise
n = 1000
x, y = np.random.uniform(-2, 2, (2, n))
z = np.sqrt(x**2 + y**2) + np.random.uniform(0, 1, n)
bins = 20
fig, axs = plt.subplots(1, 2, figsize=(7, 3), constrained_layout=True)
_, _, _, img = axs[0].hist2d(x, y, bins=bins)
fig.colorbar(img, ax=axs[0])
axs[0].set(xlabel='x', ylabel='y', title='histogram')
sums, xbins, ybins = np.histogram2d(x, y, bins=bins, weights=z)
counts, _, _ = np.histogram2d(x, y, bins=bins)
with np.errstate(divide='ignore', invalid='ignore'):
# suppress possible divide-by-zero warnings
img = axs[1].pcolormesh(xbins, ybins, sums / counts, cmap='inferno')
fig.colorbar(img, ax=axs[1], label='z')
axs[1].set(xlabel='x', ylabel='y', title='weighed by z')
fig.show()问题的其余部分
现在我找到了一种方法来创建具有恒定颜色值的渐变图,剩下的就是如何从二维颜色图中绘制二维直方图。因为2d直方图创建了一个QuadMesh的实例,而且很明显您可以设置它的外观颜色,也许这是一种方法,但我还没有弄清楚如何设置它。下面是我至少创建2d彩色地图的实现:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.colors import hsv_to_rgb, rgb_to_hsv, ListedColormap
# make data: correlated + noise
np.random.seed(100)
n = 1000
x, y = np.random.uniform(-2, 2, (2, n))
z = np.sqrt(x**2 + y**2) + np.random.uniform(0, 1, n)
bins = 20
fig, axs = plt.subplots(1, 3, figsize=(8, 3), constrained_layout=True)
_, _, _, img = axs[0].hist2d(x, y, bins=bins)
fig.colorbar(img, ax=axs[0], label='N')
axs[0].set(xlabel='x', ylabel='y', title='histogram')
# creating the colormap
inferno = cm.get_cmap('inferno')
hsv_inferno = rgb_to_hsv(inferno(np.linspace(0, 1, 300))[:, :3])
hsv_inferno[:, 2] = 1
rgb_inferno = hsv_to_rgb(hsv_inferno)
# plotting the data
sums, xbins, ybins = np.histogram2d(x, y, bins=bins, weights=z)
counts, _, _ = np.histogram2d(x, y, bins=bins)
with np.errstate(divide='ignore', invalid='ignore'):
# suppress possible divide-by-zero warnings
img = axs[1].pcolormesh(
xbins, ybins, sums / counts, cmap=ListedColormap(rgb_inferno)
)
axs[1].set(xlabel='x', ylabel='y', title='weighed by z')
# adding the custom colorbar
S, H = np.mgrid[0:1:100j, 0:1:300j]
V = np.ones_like(S)
HSV = np.dstack((H, S, V))
HSV[:, :, 0] = hsv_inferno[:, 0]
# HSV[:, :, 2] = hsv_inferno[:, 2]
RGB = hsv_to_rgb(HSV)
z_min, z_max = np.min(img.get_array()), np.max(img.get_array())
c_min, c_max = np.min(counts), np.max(counts)
axs[2].imshow(
np.rot90(RGB), origin='lower', extent=[c_min, c_max, z_min, z_max],
aspect=14
)
axs[2].set_xlabel("N")
axs[2].set_ylabel("z")
axs[2].yaxis.set_label_position("right")
axs[2].yaxis.tick_right()
# readjusting the axes a bit
fig.show() # necessary to get the proper positions
pos = axs[1].get_position()
pos.x0 += 0.065
pos.x1 += 0.065
axs[1].set_position(pos)
fig.show()

发布于 2022-03-09 17:48:11
我想到的是在你已经定义的2D颜色空间中进行插值。使用n=100000在上一个示例之后运行下面的代码,以获得更平滑的图像。
from scipy import interpolate
z = np.divide(sums, counts, where=counts != 0);
points = np.mgrid[
0:np.max(counts):1j*RGB.shape[0], # use counts for the first axis
0:np.max(z):1j*RGB.shape[1], # use sum in for the second axis
]
# arrange points in a N x 2 array
points = np.stack(points, axis=2).reshape(-1, 2)
# arrange the colors in a N x 3 array
values = RGB.reshape(-1, 3) # use your 2D colormap as values
# Creates an interpolator from (..., 2) to (..., 3)
cmap2d = interpolate.LinearNDInterpolator(
points, values
)
# stack counts and sums in an array of (n1, n2, 2)
cpoints = np.stack([counts, z], axis=2)
# gets an (n1, n2, 3) array
img = cmap2d(cpoints)
# plot the img as a RGB image
plt.imshow(img, extent=[xbins[0], xbins[-1], ybins[0], ybins[-1]])这就是你得到的

对于对数比例尺,您可以将对数应用到限制值上,但要使用相同的空间网格。插值时使用坐标的对数。
from scipy import interpolate
z = np.divide(sums, counts, where=counts != 0);
points = np.mgrid[
# apply log to the limits from 1/e to max(count)
-1:np.log(np.max(counts)):1j*RGB.shape[0], # use counts for the first axis
0:np.max(z):1j*RGB.shape[1], # use sum in for the second axis
]
# arrange points in a N x 2 array
points = np.stack(points, axis=2).reshape(-1, 2)
# arrange the colors in a N x 3 array
values = RGB.reshape(-1, 3) # use your 2D colormap as values
# Creates an interpolator from (..., 2) to (..., 3)
cmap2d = interpolate.LinearNDInterpolator(
points, values
)
# stack counts and sums in an array of (n1, n2, 2)
# apply log to the values
cpoints = np.stack([np.log(np.maximum(counts, 1)), z], axis=2)
# gets an (n1, n2, 3) array
img = cmap2d(cpoints)
# plot the img as a RGB image
plt.imshow(img, extent=[xbins[0], xbins[-1], ybins[0], ybins[-1]])

https://stackoverflow.com/questions/71384228
复制相似问题