cmap not behaving like it should although the values of the array are all in the cmap indices
Question:
MRE:
import numpy as np
import matplotlib.pyplot as plt
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())
From my understanding, I don’t need to use norm
because the mask values (my array values) are already within the range of the colormap indices.
However, I should get purple and green.
My intuition is that because there is no other values than 4 and 5, it maps the 4 to 0? But I don’t understand why it would work like that? And how to fix it easily?
Answers:
Matplotlib is using the range of colours you give to cover the range of numbers it sees
You can verify this explanation by putting in more gradations of values into your numerical array:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())
The result is:
You do need to set the normalization
But use the vmin
and vmax
, rather than norm
, to tell Matplotlib what range of values you want it to associate with the list of colours you have given.
Otherwise, it has no way of knowing that you want the list of colours you have presented to map to 1,2,3 etc. For example, how does it know you did not want them to be 0, 1, 2 etc, which might be more Pythonic?
Rather than having an arbitrary fixed default assumption, its default is just to use the the range of numerical values it actually sees.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap, vmin=1.0, vmax=5.0)
show_legend(plt.gca())
The result is what you wanted:
MRE:
import numpy as np
import matplotlib.pyplot as plt
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())
From my understanding, I don’t need to use norm
because the mask values (my array values) are already within the range of the colormap indices.
However, I should get purple and green.
My intuition is that because there is no other values than 4 and 5, it maps the 4 to 0? But I don’t understand why it would work like that? And how to fix it easily?
Matplotlib is using the range of colours you give to cover the range of numbers it sees
You can verify this explanation by putting in more gradations of values into your numerical array:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())
The result is:
You do need to set the normalization
But use the vmin
and vmax
, rather than norm
, to tell Matplotlib what range of values you want it to associate with the list of colours you have given.
Otherwise, it has no way of knowing that you want the list of colours you have presented to map to 1,2,3 etc. For example, how does it know you did not want them to be 0, 1, 2 etc, which might be more Pythonic?
Rather than having an arbitrary fixed default assumption, its default is just to use the the range of numerical values it actually sees.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)
# Function to display the legend (for the masks images)
def show_legend(ax):
patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)
arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
[5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)
plt.imshow(arr, cmap=cmap, vmin=1.0, vmax=5.0)
show_legend(plt.gca())
The result is what you wanted: