使用自定义颜色绘制一个二维的numpy数组。

9

我有一个2D的numpy数组,我想绘制它,以便我可以看到每个类别在网格上的位置。矩阵(mat)看起来像这样:

156 138 156
1300 137 156
138 138 1300
137 137 137

我这样绘制出来:

我这样绘制出来:

 plt.imshow(mat, cmap='tab20', interpolation='none')

然而,我希望拥有自定义颜色。我有一个csv文件,其中id对应矩阵中的值:

id,R,G,B
156,200,200,200
138,170,255,245
137,208,130,40
1300,63,165,76

有没有办法让矩阵中的值对应csv文件中的R、G、B值?每行都有一个ID和三列,分别表示相应的R、G和B值。因此,第一行具有ID 156(一个特定领域的代码),其R、G和B值为200,200和200(即灰色)。现在我有一个二维矩阵要绘制,在每个坐标处,如果值为156,则希望该像素为灰色。同样,如果ID为1300,则颜色63、165和76表示我想在矩阵中使用的绿色。

2
在我看来,imshow 会导致对数据的错误理解,因为我将其与某种连续性联系起来。我更喜欢使用非常大的点来传达数据的离散性,因此我更倾向于使用 scatter。如果您同意,我可以发布另一个答案,使用 scatter。请让我知道您是否有兴趣。 - gboffi
但这是否会改变图像的外观呢?我不确定我完全理解你的观点,但我仍然很感兴趣。 - Bram Zijlstra
1
当然,图像的外观会改变。这正是我的观点:当前的绘图(通过imshow)_暗示_您正在表示一个连续的、通常可微分的场——另一方面,据我所知,您的数据是离散的,离散数据经常使用scatter显示,颜色代表_z_值。 - gboffi
好的,对不起,那是一个有点愚蠢的评论,我的意思是我不确定它如何改变。但我很想看看你的例子。图片与我预期的略有不同,所以也许散点图可以解决这个问题。 - Bram Zijlstra
4个回答

7

使用色图

原则上,RGB值矩阵就是一种色图。在 matplotlib 中使用色图来绘制图表是有意义的。但这里稍微复杂一些的是,这些值的间距并不均匀。因此,一个想法是将它们映射为从 0 开始的整数。然后用这些值创建一个色图,并使用 BoundaryNorm 将其与之配合以获得等间隔的颜色条。最后,可以将颜色条的刻度标签设置回初始值。

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

a =np.array([[156, 138, 156],
             [1300, 137, 156],
             [138, 138, 1300],
             [137, 137, 137]])

ca = np.array([[156,200,200,200],
               [138,170,255,245],
               [137,208,130,40],
               [1300,63,165,76]])

u, ind = np.unique(a, return_inverse=True)
b = ind.reshape((a.shape))

colors = ca[ca[:,0].argsort()][:,1:]/255.
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(np.arange(len(ca)+1)-0.5, len(ca))

plt.imshow(b, cmap=cmap, norm=norm)

cb = plt.colorbar(ticks=np.arange(len(ca)))
cb.ax.set_yticklabels(np.unique(ca[:,0]))

plt.show()

绘制RGB数组

您可以从数据中创建一个RGB数组,直接作为imshow进行绘制。为此,您可以使用来自颜色数组的颜色对原始数组进行索引,并重塑生成的数组,使其处于适合使用imshow绘图的正确形状。

输入图像描述

import numpy as np
import matplotlib.pyplot as plt


a =np.array([[156, 138, 156],
             [1300, 137, 156],
             [138, 138, 1300],
             [137, 137, 137]])

ca = np.array([[156,200,200,200],
               [138,170,255,245],
               [137,208,130,40],
               [1300,63,165,76]])

u, ind = np.unique(a, return_inverse=True)
c = ca[ca[:,0].argsort()][:,1:]/255.

b =  np.moveaxis(c[ind][:,:,np.newaxis],1,2).reshape((a.shape[0],a.shape[1],3))
plt.imshow(b)
plt.show()

结果与上述相同,但没有颜色条(因为此处没有要映射的数量)。

你的第二部分出现了错误:AttributeError: module 'numpy' has no attribute 'moveaxis'但是你的第一部分完美地运行了。我只是删除了cb变量以删除colorbar。 - Bram Zijlstra
moveaxis 函数在 numpy 版本 1.12 或更高版本中可用,可能您安装的是旧版本。 - ImportanceOfBeingErnest

6

虽然不是特别优雅,但它很简单。

In [72]: import numpy as np
In [73]: import matplotlib.pyplot as plt
In [74]: a = np.mat("156 138 156;1300 137 156;138 138 1300;137 137 137")
In [75]: d = {   156:  [200,  200,  200],
    ...:         138:  [170,  255,  245],
    ...:         137:  [208,  130,   40],
    ...:        1300:   [63,  165,   76]}
In [76]: image = np.array([[d[val] for val in row] for row in a], dtype='B')
In [77]: plt.imshow(image);

地图在此处

重点是生成包含正确(并解压缩的)RGB元组的正确dtype数组(“B”编码短无符号整数)。


附加说明

原始问题之后的评论交流后,在本补充中,我将提出可能的解决方案来使用plt.scatter()绘制相同类型的数据(这个问题比我预期的还要棘手......)。

import numpy as np
import matplotlib.pyplot as plt
from random import choices, randrange

######## THIS IS FOR IMSHOW ######################################
# the like of my previous answer
values = [20,150,900,1200]
rgb = lambda x=255:(randrange(x), randrange(x), randrange(x))
colord = {v:rgb() for v in values}

nr, nc = 3, 5
data = np.array(choices(values, k=nr*nc)).reshape((nr,nc))
c = np.array([[colord[v] for v in row] for row in data], dtype='B')

######## THIS IS FOR SCATTER ######################################
# This is for having the coordinates of the scattered points, note that rows' indices
# map to y coordinates and columns' map to x coordinates
y, x = np.array([(i,j) for i in range(nr) for j in range(nc)]).T
# Scatter does not expect a 3D array of uints but a 2D array of RGB floats
c1 = (c/255.0).reshape(nr*nc,3)

######## THIS IS FOR PLOTTING ######################################
# two subplots, plot immediately the imshow
f, (ax1, ax2) = plt.subplots(nrows=2)
ax1.imshow(c)
# to make a side by side comparison we set the boundaries and aspect
# of the second plot to mimic imshow's
ax2.set_ylim(ax1.get_ylim())
ax2.set_xlim(ax1.get_xlim())
ax2.set_aspect(1)
# and finally plot the data --- the size of dots `s=900` was by trial and error
ax2.scatter(x, y, c=c1, s=900)
plt.show()

enter image description here


我想补充一下(正如许多SO帖子中所指出的,只需搜索“numpy fromfunction”,第一个结果就足以让您信服),即np.fromfunction的文档字符串真的很误导人... - gboffi

3

Pandas可以帮助您收集数据:

im = pd.read_clipboard(header=None)  # from your post
colours = pd.read_clipboard(index_col=0,sep=',')  # from your post

Pandas 还可以帮助处理色图:
colordf = colours.reindex(arange(1301)).fillna(0).astype(np.uint8)

使用numpy.take构建图像:

rgbim = colordf.values.take(im,axis=0))

plt.imshow(rgbim):

enter image description here


1
使用pandas和numpy,(编辑n x m矩阵):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

n = 2
m = 2
df = pd.read_csv('matrix.txt')
id = df.id.values
id = np.reshape(id, (n, m))
R = df.R.values
R = np.reshape(R/255, (n, m))
G = df.R.values
G = np.reshape(G/255, (n, m))
B = df.B.values
B = np.reshape(B/255, (n, m))
img = []

for i in range(n):
    img.append([])
    for j in range(m):
        img[i].append((R[i][j], G[i][j], B[i][j]))

plt.imshow(img)
plt.show()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接