在 Matplotlib 中建立色彩映射#

Matplotlib 有許多內建的色彩映射,可透過 matplotlib.colormaps 存取。 也有像 palettable 這樣的外部函式庫,其中有許多額外的色彩映射。

然而,我們也可能想要建立或操作自己的色彩映射。 這可以使用 ListedColormapLinearSegmentedColormap 類別來完成。 這兩個色彩映射類別都會將 0 到 1 之間的值對應到色彩。 然而,如下所述,它們之間存在差異。

在手動建立或操作色彩映射之前,讓我們先看看如何從現有的色彩映射類別中取得色彩映射及其色彩。

取得色彩映射並存取其值#

首先,取得已命名的色彩映射 (其中大多數列於 在 Matplotlib 中選擇色彩映射 中),可以使用 matplotlib.colormaps 來完成,該方法會傳回色彩映射物件。 用於內部定義色彩映射的色彩清單長度可以透過 Colormap.resampled 調整。 在下方,我們使用 8 這個適中的值,這樣就沒有太多值需要查看。

import matplotlib.pyplot as plt
import numpy as np

import matplotlib as mpl
from matplotlib.colors import LinearSegmentedColormap, ListedColormap

viridis = mpl.colormaps['viridis'].resampled(8)

物件 viridis 是一個可呼叫物件,當傳入 0 到 1 之間的浮點數時,會從色彩映射傳回 RGBA 值

print(viridis(0.56))
(np.float64(0.122312), np.float64(0.633153), np.float64(0.530398), np.float64(1.0))

ListedColormap#

ListedColormap 將其色彩值儲存在 .colors 屬性中。 可以使用 colors 屬性直接存取構成色彩映射的色彩清單,或者也可以透過使用與色彩映射長度相符的值陣列呼叫 viridis 間接存取。 請注意,傳回的清單格式為 RGBA (N, 4) 陣列,其中 N 是色彩映射的長度。

print('viridis.colors', viridis.colors)
print('viridis(range(8))', viridis(range(8)))
print('viridis(np.linspace(0, 1, 8))', viridis(np.linspace(0, 1, 8)))
viridis.colors [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(range(8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]
viridis(np.linspace(0, 1, 8)) [[0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

色彩映射是一個查找表,因此「過取樣」色彩映射會傳回最近鄰內插法 (請注意下方清單中的重複色彩)

print('viridis(np.linspace(0, 1, 12))', viridis(np.linspace(0, 1, 12)))
viridis(np.linspace(0, 1, 12)) [[0.267004 0.004874 0.329415 1.      ]
 [0.267004 0.004874 0.329415 1.      ]
 [0.275191 0.194905 0.496005 1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.212395 0.359683 0.55171  1.      ]
 [0.153364 0.497    0.557724 1.      ]
 [0.122312 0.633153 0.530398 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.288921 0.758394 0.428426 1.      ]
 [0.626579 0.854645 0.223353 1.      ]
 [0.993248 0.906157 0.143936 1.      ]
 [0.993248 0.906157 0.143936 1.      ]]

LinearSegmentedColormap#

LinearSegmentedColormap 沒有 .colors 屬性。 然而,仍然可以使用整數陣列或 0 到 1 之間的浮點數陣列呼叫色彩映射。

copper = mpl.colormaps['copper'].resampled(8)

print('copper(range(8))', copper(range(8)))
print('copper(np.linspace(0, 1, 8))', copper(np.linspace(0, 1, 8)))
copper(range(8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]
copper(np.linspace(0, 1, 8)) [[0.         0.         0.         1.        ]
 [0.17647055 0.1116     0.07107143 1.        ]
 [0.35294109 0.2232     0.14214286 1.        ]
 [0.52941164 0.3348     0.21321429 1.        ]
 [0.70588219 0.4464     0.28428571 1.        ]
 [0.88235273 0.558      0.35535714 1.        ]
 [1.         0.6696     0.42642857 1.        ]
 [1.         0.7812     0.4975     1.        ]]

建立列出的色彩映射#

建立色彩映射基本上是上述的反向操作,我們將色彩規格的清單或陣列提供給 ListedColormap 以建立新的色彩映射。

在繼續本教學之前,讓我們先定義一個輔助函數,該函數將一個或多個色彩映射作為輸入、建立一些隨機資料,並將色彩映射套用至該資料集的影像繪圖。

def plot_examples(colormaps):
    """
    Helper function to plot data with associated colormap.
    """
    np.random.seed(19680801)
    data = np.random.randn(30, 30)
    n = len(colormaps)
    fig, axs = plt.subplots(1, n, figsize=(n * 2 + 2, 3),
                            layout='constrained', squeeze=False)
    for [ax, cmap] in zip(axs.flat, colormaps):
        psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
        fig.colorbar(psm, ax=ax)
    plt.show()

在最簡單的情況下,我們可能會鍵入色彩名稱的清單,以從這些色彩名稱建立色彩映射。

cmap = ListedColormap(["darkorange", "gold", "lawngreen", "lightseagreen"])
plot_examples([cmap])
colormap manipulation

事實上,該清單可能包含任何有效的 Matplotlib 色彩規格。 特別是用於建立自訂色彩映射的是 (N, 4) 形狀的陣列。 因為我們可以使用各種 numpy 運算對這樣的陣列執行,所以從現有的色彩映射中製作新的色彩映射變得非常簡單。

例如,假設我們想要出於某種原因將 256 長度的「viridis」色彩映射的前 25 個項目變成粉紅色

viridis = mpl.colormaps['viridis'].resampled(256)
newcolors = viridis(np.linspace(0, 1, 256))
pink = np.array([248/256, 24/256, 148/256, 1])
newcolors[:25, :] = pink
newcmp = ListedColormap(newcolors)

plot_examples([viridis, newcmp])
colormap manipulation

我們可以縮減色彩映射的動態範圍;在這裡,我們選擇色彩映射的中間一半。 然而,請注意,由於 viridis 是一個列出的色彩映射,我們最終會得到 128 個離散值,而不是原始色彩映射中的 256 個值。 此方法不會在色彩空間中內插以新增新的色彩。

viridis_big = mpl.colormaps['viridis']
newcmp = ListedColormap(viridis_big(np.linspace(0.25, 0.75, 128)))
plot_examples([viridis, newcmp])
colormap manipulation

我們可以輕鬆地將兩個色彩映射串連起來

top = mpl.colormaps['Oranges_r'].resampled(128)
bottom = mpl.colormaps['Blues'].resampled(128)

newcolors = np.vstack((top(np.linspace(0, 1, 128)),
                       bottom(np.linspace(0, 1, 128))))
newcmp = ListedColormap(newcolors, name='OrangeBlue')
plot_examples([viridis, newcmp])
colormap manipulation

當然,我們不需要從已命名的色彩映射開始,我們只需要建立 (N, 4) 陣列傳遞給 ListedColormap。 在這裡,我們建立一個從棕色 (RGB: 90, 40, 40) 到白色 (RGB: 255, 255, 255) 的色彩映射。

N = 256
vals = np.ones((N, 4))
vals[:, 0] = np.linspace(90/256, 1, N)
vals[:, 1] = np.linspace(40/256, 1, N)
vals[:, 2] = np.linspace(40/256, 1, N)
newcmp = ListedColormap(vals)
plot_examples([viridis, newcmp])
colormap manipulation

建立線性分段色彩映射#

LinearSegmentedColormap 類別使用 RGB(A) 值在其中進行內插的錨點來指定色彩映射。

指定這些色彩映射的格式允許在錨點處存在不連續性。 每個錨點都指定為 [x[i] yleft[i] yright[i]] 形式的矩陣中的一行,其中 x[i] 是錨點,而 yleft[i]yright[i] 是錨點兩側的色彩值。

如果沒有不連續性,則 yleft[i] == yright[i]

cdict = {'red':   [[0.0,  0.0, 0.0],
                   [0.5,  1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'green': [[0.0,  0.0, 0.0],
                   [0.25, 0.0, 0.0],
                   [0.75, 1.0, 1.0],
                   [1.0,  1.0, 1.0]],
         'blue':  [[0.0,  0.0, 0.0],
                   [0.5,  0.0, 0.0],
                   [1.0,  1.0, 1.0]]}


def plot_linearmap(cdict):
    newcmp = LinearSegmentedColormap('testCmap', segmentdata=cdict, N=256)
    rgba = newcmp(np.linspace(0, 1, 256))
    fig, ax = plt.subplots(figsize=(4, 3), layout='constrained')
    col = ['r', 'g', 'b']
    for xx in [0.25, 0.5, 0.75]:
        ax.axvline(xx, color='0.7', linestyle='--')
    for i in range(3):
        ax.plot(np.arange(256)/256, rgba[:, i], color=col[i])
    ax.set_xlabel('index')
    ax.set_ylabel('RGB')
    plt.show()

plot_linearmap(cdict)
colormap manipulation

為了在錨點處產生不連續性,第三欄與第二欄不同。 「紅色」、「綠色」、「藍色」和可選的「alpha」的矩陣設定為

cdict['red'] = [...
                [x[i]      yleft[i]     yright[i]],
                [x[i+1]    yleft[i+1]   yright[i+1]],
               ...]

對於傳遞給 x[i]x[i+1] 之間的色彩映射的值,內插在 yright[i]yleft[i+1] 之間。

在以下範例中,紅色部分在 0.5 處有一個不連續點。0 到 0.5 之間的插值從 0.3 到 1,而 0.5 到 1 之間的插值則從 0.9 到 1。請注意,red[0, 1]red[2, 2] 對於插值來說都是多餘的,因為 red[0, 1] (即 yleft[0]) 是 0 左側的值,而 red[2, 2] (即 yright[2]) 是 1 右側的值,它們都位於色彩映射域之外。

cdict['red'] = [[0.0,  0.0, 0.3],
                [0.5,  1.0, 0.9],
                [1.0,  1.0, 1.0]]
plot_linearmap(cdict)
colormap manipulation

直接從列表建立分段色彩映射#

上述方法非常通用,但實作起來確實有點繁瑣。對於某些基本情況,使用 LinearSegmentedColormap.from_list 可能會更容易。這會從提供的顏色列表建立一個等間距的分段色彩映射。

colors = ["darkorange", "gold", "lawngreen", "lightseagreen"]
cmap1 = LinearSegmentedColormap.from_list("mycmap", colors)

如果需要,色彩映射的節點可以指定為 0 到 1 之間的數字。例如,可以讓色彩映射中紅色部分佔據更多空間。

nodes = [0.0, 0.4, 0.8, 1.0]
cmap2 = LinearSegmentedColormap.from_list("mycmap", list(zip(nodes, colors)))

plot_examples([cmap1, cmap2])
colormap manipulation

反轉色彩映射#

Colormap.reversed 會建立一個新的色彩映射,它是原始色彩映射的反轉版本。

colors = ["#ffffcc", "#a1dab4", "#41b6c4", "#2c7fb8", "#253494"]
my_cmap = ListedColormap(colors, name="my_cmap")

my_cmap_r = my_cmap.reversed()

plot_examples([my_cmap, my_cmap_r])
colormap manipulation

如果沒有傳入名稱,.reversed 也會透過在原始色彩映射的名稱後加上 '_r' 來命名副本。

註冊色彩映射#

色彩映射可以加入到 matplotlib.colormaps 的具名色彩映射列表中。這允許在繪圖函數中透過名稱存取色彩映射。

# my_cmap, my_cmap_r from reversing a colormap
mpl.colormaps.register(cmap=my_cmap)
mpl.colormaps.register(cmap=my_cmap_r)

data = [[1, 2, 3, 4, 5]]

fig, (ax1, ax2) = plt.subplots(nrows=2)

ax1.imshow(data, cmap='my_cmap')
ax2.imshow(data, cmap='my_cmap_r')

plt.show()
colormap manipulation

腳本總執行時間: (0 分 5.643 秒)

由 Sphinx-Gallery 產生圖庫