如何在matplotlib中添加2D色条或色轮?

时间:2017-08-11 03:25:38

标签: python matplotlib

我正在分析样品的磁化映射。获得梯度及其方向后,我将它们绘制为HSV(从-π到π的方向从0到1映射到Hue,而Value是标准化梯度)由img_rgb = mpl.colors.hsv_to_rgb(img_hsv)转换为RGB。

我设法使用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
enter image description here

理想情况下,我想添加一个色轮,它可以对方向和幅度进行编码(可能就像极坐标图一样?)。如果无法做到这一点,请添加一个2D图,该图扩展当前颜色条以包含x轴上的渐变幅度。

子图显然是可能的,但它们看起来像一个kludge。还有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

首先,如果您想要同时显示两个不同的参数,可以通过为它们分配两个不同的通道(例如红色和绿色)来实现。这可以通过规范化两个2d阵列并将其馈送到与this answer类似的imshow堆叠来完成。

如果您满足于方形的2d色彩映射,则可以通过创建meshgrid然后再次堆叠并提供给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()

结果如下:

square-shaped 2d colorbar

原则上同样的事情也应该是极地Axes,但根据this github ticket中的评论,imshow不支持极轴,我无法做{ {1}}填写整张光盘。

修改

感谢ImportanceOfBeingErnest和his answer另一个问题(imshow关键字做了它),现在这里使用color在极轴上显示2d色图。有一些警告,最值得注意的是,pcolormesh尺寸需要比colors方向的meshgrid小一个,否则颜色图有螺旋形式:

theta

这产生了这个数字:

working round 2d colormap

答案 1 :(得分:0)

在尝试可视化表面梯度的径向分量和绝对分量时,我遇到了类似的问题。

我正在通过 hsv 将渐变的绝对值加上角度转换为颜色(使用色调作为角度,使用饱和度和值作为绝对值)。这与磁化图中的相同,因为可以使用任何矢量场代替梯度。下面的函数说明了这个想法。完整代码在答案末尾提供。

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]))

结果图如下。

Resulting image


显示表面梯度场的完整示例代码:

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()