简体   繁体   English

如何向 matplotlib 添加 2D 颜色条或色轮?

[英]How can I add a 2D colorbar, or a color wheel, to matplotlib?

I am analyzing the magnetization mapping of a sample.我正在分析样品的磁化映射。 After getting the gradient and its direction, I plotted them as an HSV (the direction from -π to π was mapped to Hue from 0 to 1, and Value was the normalized gradient) converted to RGB by img_rgb = mpl.colors.hsv_to_rgb(img_hsv) .获得梯度及其方向后,我将它们绘制为 HSV(从 -π 到 π 的方向映射到色调从 0 到 1,值是归一化梯度)通过img_rgb = mpl.colors.hsv_to_rgb(img_hsv)

I managed to add an HSV colorbar by using vmin and vmax, but this does not show the magnitude of the gradient:我设法使用 vmin 和 vmax 添加了一个 HSV 颜色条,但这并没有显示渐变的大小:

plt.imshow(img_rgb, cmap='hsv', vmin=-180, vmax=180, extent=(0, 100, 0,100))
plt.xlabel('μm')
plt.ylabel('μm')
plt.colorbar()

My current plot :我目前的情节
在此处输入图片说明

Ideally, I would like to add a color wheel which encodes both the direction and the magnitude (maybe as something like a polar plot?).理想情况下,我想添加一个色轮来编码方向和幅度(可能类似于极坐标图?)。 If that is not possible, adding a 2D plot which extends the current colorbar to include the gradient magnitude on the x-axis.如果这是不可能的,请添加一个 2D 图,它扩展当前颜色条以包括 x 轴上的梯度幅度。

Subplots are obviously possible, but they seem like a kludge.次要情节显然是可能的,但它们似乎是杂七杂八的。 Is there a better way?有更好的方法吗?

First off, if you have two different parameters that you want to visualise simultaneously, you can do that by assigning two different channels to them (say red and green).首先,如果您想同时显示两个不同的参数,您可以通过为它们分配两个不同的通道(例如红色和绿色)来实现。 This can be done by normalising your two 2d arrays and feeding them to imshow stacked similarly to this answer .这可以通过规范化您的两个 2d 数组并将它们提供给imshow堆叠来imshow类似于这个答案

If you are content with a square-shaped 2d colormap, you can then get this colormap in the same way, by creating a meshgrid that you then again stack and feed to imshow :如果您对方形 2d 颜色图meshgrid ,那么您可以通过创建一个meshgrid ,然后再次堆叠并馈送到imshow以相同的方式获得此颜色imshow

from matplotlib import pyplot as plt
import numpy as np

##generating some  data
x,y = np.meshgrid(
    np.linspace(0,1,100),
    np.linspace(0,1,100),
)
directions = (np.sin(2*np.pi*x)*np.cos(2*np.pi*y)+1)*np.pi
magnitude = np.exp(-(x*x+y*y))


##normalize data:
def normalize(M):
    return (M-np.min(M))/(np.max(M)-np.min(M))

d_norm = normalize(directions)
m_norm = normalize(magnitude)

fig,(plot_ax, bar_ax) = plt.subplots(nrows=1,ncols=2,figsize=(8,4))

plot_ax.imshow(
    np.dstack((d_norm,m_norm, np.zeros_like(directions))),
    aspect = 'auto',
    extent = (0,100,0,100),
)

bar_ax.imshow(
    np.dstack((x, y, np.zeros_like(x))),
    extent = (
        np.min(directions),np.max(directions),
        np.min(magnitude),np.max(magnitude),
    ),
    aspect = 'auto',
    origin = 'lower',
)
bar_ax.set_xlabel('direction')
bar_ax.set_ylabel('magnitude')

plt.show()

The result looks like this:结果如下所示:

方形二维颜色条

In principle the same thing should also be doable with a polar Axes , but according to a comment in this github ticket , imshow does not support polar axes and I couldn't make imshow fill the entire disc.原则上,使用 polar Axes也应该可以做同样的事情,但根据此 github 票证中的评论, imshow不支持极轴,我无法让imshow填满整个光盘。

EDIT :编辑

Thanks to ImportanceOfBeingErnest and his answer to another question (the color keyword did it), here now a 2d colormap on a polar axis using pcolormesh .感谢 ImportanceOfBeingErnest 和对另一个问题的回答( color关键字做到了),现在这里使用pcolormesh极轴上的二维颜色pcolormesh There were a few caveats, most notable, the colors dimension needs to be one smaller than the meshgrid in theta direction, otherwise the colormap has a spiral form:有一些警告,最值得注意的是, colors维度需要在theta方向上比meshgrid小一,否则颜色图具有螺旋形式:

fig= plt.figure(figsize=(8,4))
plot_ax = fig.add_subplot(121)
bar_ax = fig.add_subplot(122, projection = 'polar')

plot_ax.imshow(
    np.dstack((d_norm,m_norm, np.zeros_like(directions))),
    aspect = 'auto',
    extent = (0,100,0,100),
)

theta, R = np.meshgrid(
    np.linspace(0,2*np.pi,100),
    np.linspace(0,1,100),
)

t,r = np.meshgrid(
    np.linspace(0,1,99),
    np.linspace(0,1,100),
)    

image = np.dstack((t, r, np.zeros_like(r)))

color = image.reshape((image.shape[0]*image.shape[1],image.shape[2]))

bar_ax.pcolormesh(
    theta,R,
    np.zeros_like(R),
    color = color,
)

bar_ax.set_xticks(np.linspace(0,2*np.pi,5)[:-1])
bar_ax.set_xticklabels(
    ['{:.2}'.format(i) for i in np.linspace(np.min(directions),np.max(directions),5)[:-1]]
)
bar_ax.set_yticks(np.linspace(0,1,5))
bar_ax.set_yticklabels(
    ['{:.2}'.format(i) for i in np.linspace(np.min(magnitude),np.max(magnitude),5)]
)
bar_ax.grid('off')

plt.show()

This produces this figure:这产生了这个数字:

工作轮 2d 颜色图

I am having a similar problem when trying to visualize the radial and absolute components of a surface gradient.在尝试可视化表面梯度的径向和绝对分量时,我遇到了类似的问题。

I am converting the absolute value of the gradient plus the angle to a color via hsv (using the hue as the angle and the saturation and the value as the absolute value).我正在通过 hsv 将渐变的绝对值加上角度转换为颜色(使用色调作为角度,使用饱和度和值作为绝对值)。 This is the same as in magnetization plots as any vector field can be used replacing the gradient.这与磁化图中的相同,因为可以使用任何矢量场代替梯度。 The following function illustrates the idea.下面的函数说明了这个想法。 The full code is provided in the end of the answer.答案末尾提供了完整代码。

import matplotlib.colors

# gradabs is the absolute gradient value, 
# gradang is the angle direction, z the vector field
# the gradient was calculated of

max_abs = np.max(gradabs) 

def grad_to_rgb(angle, absolute):
    """Get the rgb value for the given `angle` and the `absolute` value

    Parameters
    ----------
    angle : float
        The angle in radians
    absolute : float
        The absolute value of the gradient

    Returns
    -------
    array_like
        The rgb value as a tuple with values [0..1]
    """
    global max_abs

    # normalize angle
    angle = angle % (2 * np.pi)
    if angle < 0:
        angle += 2 * np.pi

    return matplotlib.colors.hsv_to_rgb((angle / 2 / np.pi, 
                                         absolute / max_abs, 
                                         absolute / max_abs))

# convert to colors via hsv
grad = np.array(list(map(grad_to_rgb, gradang.flatten(), gradabs.flatten())))

# reshape
grad = grad.reshape(tuple(list(z.shape) + [3]))

The resulting graph is the following.结果图如下。

结果图像


The full example code on showing the gradient field of a surface:显示表面梯度场的完整示例代码:

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

r = np.linspace(0, np.pi, num=100)
x, y = np.meshgrid(r, r)
z = np.sin(y) * np.cos(x)

fig = plt.figure()

ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.plot_surface(x, y, z)
# ax.imshow(z)
ax.set_title("Surface")

ax = fig.add_subplot(1, 3, 2)
ax.set_title("Gradient")

# create gradient
grad_y, grad_x = np.gradient(z)

# calculate length
gradabs = np.sqrt(np.square(grad_x) + np.square(grad_y))
max_abs = np.max(gradabs)

# calculate angle component
gradang = np.arctan2(grad_y, grad_x)

def grad_to_rgb(angle, absolute):
    """Get the rgb value for the given `angle` and the `absolute` value

    Parameters
    ----------
    angle : float
        The angle in radians
    absolute : float
        The absolute value of the gradient
    
    Returns
    -------
    array_like
        The rgb value as a tuple with values [0..1]
    """
    global max_abs

    # normalize angle
    angle = angle % (2 * np.pi)
    if angle < 0:
        angle += 2 * np.pi

    return matplotlib.colors.hsv_to_rgb((angle / 2 / np.pi, 
                                         absolute / max_abs, 
                                         absolute / max_abs))

# convert to colors via hsv
grad = np.array(list(map(grad_to_rgb, gradang.flatten(), gradabs.flatten())))

# reshape
grad = grad.reshape(tuple(list(z.shape) + [3]))

ax.imshow(grad)

n = 5
gx, gy = np.meshgrid(np.arange(z.shape[0] / n), np.arange(z.shape[1] / n))
ax.quiver(gx * n, gy * n, grad_x[::n, ::n], grad_y[::n, ::n])

# plot color wheel
# Generate a figure with a polar projection, inspired by
# https://stackoverflow.com/a/48253413/5934316
ax = fig.add_subplot(1, 3, 3, projection='polar')

n = 200  # the number of secants for the mesh
t = np.linspace(0, 2 * np.pi, n)
r = np.linspace(0, max_abs, n)
rg, tg = np.meshgrid(r, t)
c = np.array(list(map(grad_to_rgb, tg.T.flatten(), rg.T.flatten())))
cv = c.reshape((n, n, 3))

m = ax.pcolormesh(t, r, cv[:,:,1], color=c, shading='auto')
m.set_array(None)
ax.set_yticklabels([])

plt.show()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM