Make reverse diagonals white in heatmap - python

I'm trying to do something as seen on the image is given below,
Just setting reverse diagonals white color is left. I couldn't set them as white. The chart takes integer values and I don't know what integer value is corresponding of white color.
Thank!
Edited:
Here is the code;
import math
from matplotlib import pyplot as plt
from matplotlib import cm as cm
import pylab
import numpy as np
from matplotlib.collections import LineCollection
class HeatMap:
def __init__(self, selectedLines):
self.selectedLines = selectedLines
def getHeapMap(self):
figure = plt.figure()
if len(self.selectedLines) != 0:
self.map = self.createTestMapData(len(self.selectedLines), len(self.selectedLines))
maxValueInMap = self.findMaxValueInMap(self.map)
x = np.arange(maxValueInMap + 1)
ys = [x + i for i in x]
ax = figure.add_subplot(111)
ax.imshow(self.map, cmap=cm.jet, interpolation='nearest')
'''
Left side label of the chart is created according to selected values
from a checkbox group.
'''
leftSideLabelSize = len(self.selectedLines)
sideLabels = []
for line in self.selectedLines:
sideLabels.append(line.text())
pos = np.arange(leftSideLabelSize)
'''
Left side labels are set with the code below.
'''
pylab.yticks(pos, sideLabels)
plt.xticks(pos, sideLabels)
self.numrows, self.numcols = self.map.shape
ax.format_coord = self.format_coord
line_segments = LineCollection([zip(x, y) for y in ys],
linewidths=(0.5, 3, 1.5, 2),
linestyles='solid')
line_segments.set_array(x)
axcb = figure.colorbar(line_segments)
return figure
def format_coord(self, x, y):
col = int(x + 0.5)
row = int(y + 0.5)
if col >= 0 and col < self.numcols and row >= 0 and row < self.numrows:
z = self.map[row, col]
return 'x=%1.4f, y=%1.4f, z=%1.4f' % (x, y, z)
else:
return 'x=%1.4f, y=%1.4f' % (x, y)
def createTestMapData(self, xSize, ySize):
resultMap = 10 * np.random.rand(xSize, ySize)
#Setting reverse diagonal is here. Now it is set with zero but it gives blue.
# I want it to be set as white
for index in range(0, int(math.sqrt(resultMap.size))):
resultMap[index][((math.sqrt(resultMap.size) - 1) - index )] = 0
return resultMap
def findMaxValueInMap(self, map):
return np.amax(map)
The values are generated randomly at the moment. The code is above gives a gui like;

You can make your own colormap, or adjust an existing one :)
Here's the code for the above plot, with explainations in the comments:
import matplotlib
from pylab import *
import numpy as np
#Create test data with zero valued diagonal:
data = np.random.random_sample((25, 25))
rows, cols = np.indices((25,25))
data[np.diag(rows, k=0), np.diag(cols, k=0)] = 0
#Create new colormap, with white for zero
#(can also take RGB values, like (255,255,255):
colors = [('white')] + [(cm.jet(i)) for i in xrange(1,256)]
new_map = matplotlib.colors.LinearSegmentedColormap.from_list('new_map', colors, N=256)
pcolor(data, cmap=new_map)
colorbar()
savefig('map.png')
show()
Alternatively, you could mask your data, and set a mask color:
#Create test data:
data = np.random.random_sample((25, 25))
#Create a diagonal mask:
mask = np.diag(np.ones(25))
#Apply mask to data:
masked_data = ma.masked_array(data, mask)
#Set mask color to white:
cm.jet.set_bad(color='white', alpha=None)
#for this to work we use pcolormesh instead of pcolor:
pcolormesh(masked_data, cmap=cm.jet)
colorbar()
show()
This produces essentially the same result, but may suit your needs better as you can set any cell to white, and also the white doesn't show up on the colorbar (see very bottom of above colorbar):

The colormap is defined by the cmap argument in ax.imshow(). You have used the jet colormap so you have cmap=cm.jet, which is just one of many built-in color maps in matplotlib. You may choose one or define your own that suits your taste.

Related

How to plot lines between points, and change their color based on specific values in Python?

Context:
3x35 values array that associates 1 value per segment
4x35x2 matpos array that gathers the coordinates of 4x35 points (hence 3x35 segments).
Question:
How can I define each segment's color based on their values from the values array ?
Code attempt:
# Array of values for each point
values = np.random.rand(3,35)
# Generate array of positions
x = np.arange(0,35)
y = np.arange(0,4)
matpos = np.array([[(y[i], x[j]) for j in range(0,len(x))] for i in range(0,len(y))])
# plot the figure
plt.figure()
for i in range(len(y)-1):
for j in range(len(x)):
# plot each segment
plt.plot(matpos[i:i+2,j,0],matpos[i:i+2,j,1]) #color = values[i,j]
If your values are just along a grid, you might as well just use plt.imshow(values).
Updated code for desired result:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# Array of values for each point
values = np.random.rand(3,35)
# Transform value to colors depending on colormap
color_norm = mpl.colors.Normalize(np.min(values), np.max(values))
color_map = mpl.cm.get_cmap('viridis')
colors = color_map(color_norm(values))
plt.close('all')
plt.figure()
for (y, x), value in np.ndenumerate(values):
plt.plot([x, x+1], [y, y], c = colors[y,x], linewidth = 10)

Matplotlib -- how to retreive polygons colors from choropleth map

I made the choropleth map using GeoPandas and Matplotlib. I want to add value labels to each polygon of the map in a way that font label color must be a contrast to polygon fill color (white on a darker color and black on a lighter).
Thus, I need to know every polygon's fill color. I found the solution (see minimal working example code below).
But I suppose that a more simple and clear solution exists, so I post this question with the hope to find it with community help.
import geopandas as gpd
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from itertools import islice, pairwise
from matplotlib.collections import PatchCollection
def contrast_color(color):
d = 0
r, g, b = (round(x*255, 0) for x in color[:3])
luminance = 1 - (0.299 * r + 0.587 * g + 0.114 * b) / 255
d = 0 if luminance < 0.5 else 255
return (d, d, d)
def get_colors(ax):
# get childrens
# to obtain a PatchCollection
_ = ax.axes.get_children()
collection = _[0] # suppose it is first element
if not isinstance(collection, PatchCollection):
raise TypeError("This is not Collection")
# get information about polygons fill colors
# .get_facecolors() returns ALL colors for ALL polygons
# that belongs to one multipolygon
# e. g. if we have two multipolygons,
# and the first consists of two polygons
# and second consists of one polygon
# we obtain THREE colors
poly_colors = collection.get_facecolors()
return poly_colors.tolist()
gpd.read_file("https://gist.githubusercontent.com/ap-Codkelden/72f988e2bcc90ea3c6c9d6d989d8eb3b/raw/c91927bdb6b199c4dd6df6759200a5a1e4b820f0/obl_sample.geojson")
dfm['coords'] = [x[0] for x in dfm['geometry'].apply(lambda x: x.representative_point().coords[:])]
fig, ax = plt.subplots(1, figsize=(10, 6))
ax.axis('off')
ax.set_title('Title', fontdict={'fontsize': '12', 'fontweight' : '3'})
dfm.plot(
ax=ax,
column='Average', cmap='Blues_r',
linewidth=0.5, edgecolor='k',
scheme='FisherJenks', k=2,
legend=True
)
out = [] # empty container for colors
# count polygons for every multipolygon
# since it can contains more than one
poly_count = dfm.geometry.apply(lambda x: len(x.geoms)).to_list()
poly_colors = get_colors(ax)
# we need split the polygon's colors list into sublists,
# where every sublist will contain all colors for
# every polygon that belongs to one multipolygon
slices = [(0, poly_count[0])] + [x for x in pairwise(np.cumsum(poly_count))]
# splitting
for s in slices:
out.append(
set(tuple(x) for x in islice(poly_colors, *s)),)
# remove transparensy info
out = [next(iter(x))[:3] for x in out]
dfm['color'] = [tuple([y/255 for y in x]) for x in map(contrast_color, out)]
for idx, row in dfm.iterrows():
plt.annotate(
f"{row['reg_en']}\n{row['Average']:.2f}",
xy=row['coords'], horizontalalignment='center',
color=row['color'], size=9)
Desired labels are:

How to ignore a color or alpha when using clusters

I am trying to find the dominant color of an image using Pil and cluster. My problem is that my images has a transparent background because these are .png and so i always get black as the dominant color. I'd like to ignore the first dominant color and pick the second most dominant color.
Is there a way to ignore alpha color or just remove it from the result?
I am afraid that by just removing the first most dominant color, i would sometimes remove the actual dominant color in case of the background being a really small part of the image.
Here is my code :
from PIL import Image
import numpy
import math
import matplotlib.pyplot as plot
from sklearn.cluster import MiniBatchKMeans
imgfile = Image.open("images/abra.png")
numarray = numpy.array(imgfile.getdata(), numpy.uint8)
X = []
Y = []
fig, axes = plot.subplots(nrows=5, ncols=2, figsize=(20,25))
xaxis = 0
yaxis = 0
cluster_count = 3
clusters = MiniBatchKMeans(n_clusters = cluster_count)
clusters.fit(numarray)
npbins = numpy.arange(0, cluster_count + 1)
histogram = numpy.histogram(clusters.labels_, bins=npbins)
labels = numpy.unique(clusters.labels_)
barlist = axes[xaxis, yaxis].bar(labels, histogram[0])
if(yaxis == 0):
yaxis = 1
else:
xaxis = xaxis + 1
yaxis = 0
for i in range(cluster_count):
barlist[i].set_color('#%02x%02x%02x' % (
math.ceil(clusters.cluster_centers_[i][0]),
math.ceil(clusters.cluster_centers_[i][1]),
math.ceil(clusters.cluster_centers_[i][2])))
plot.show()
Here is en example of my current code :
Image given :
Returned values :
You could avoid passing transparent pixels into the classifier like this, if that's what you mean:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
import math
import matplotlib.pyplot as plot
from sklearn.cluster import MiniBatchKMeans
# Open image
imgfile = Image.open("abra.png")
# Only pass through non-transparent pixels, i.e. those where A!=0 in the RGBA quad
na = np.array([f for f in imgfile.getdata() if f[3] !=0], np.uint8)
X = []
Y = []
fig, axes = plot.subplots(nrows=5, ncols=2, figsize=(20,25))
xaxis = 0
yaxis = 0
cluster_count = 3
clusters = MiniBatchKMeans(n_clusters = cluster_count)
clusters.fit(na)
npbins = np.arange(0, cluster_count + 1)
histogram = np.histogram(clusters.labels_, bins=npbins)
labels = np.unique(clusters.labels_)
barlist = axes[xaxis, yaxis].bar(labels, histogram[0])
if(yaxis == 0):
yaxis = 1
else:
xaxis = xaxis + 1
yaxis = 0
for i in range(cluster_count):
barlist[i].set_color('#%02x%02x%02x' % (
math.ceil(clusters.cluster_centers_[i][0]),
math.ceil(clusters.cluster_centers_[i][1]),
math.ceil(clusters.cluster_centers_[i][2])))
plot.show()

How can I change colors in contours (obtained from non-Python) with Python?

I am trying to convert the color map of a contour generated from non-Python application. I tried using Matthias Bussonnier's code available here, but is unable to give me a full conversion. I tried to truncate the color map to give me a full conversion, but again does not give me a complete conversion.
MWE
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as colors
import matplotlib.image as mpimg
from scipy.spatial import cKDTree
import matplotlib
import matplotlib.cm as mplcm
def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
new_cmap = colors.LinearSegmentedColormap.from_list(
'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),
cmap(np.linspace(minval, maxval, n)))
return new_cmap
cmap = plt.get_cmap('jet')
cmap = truncate_colormap(cmap, 0.1, 0.9)
img = mpimg.imread('./test.png')[:,:,:3]
##interact(sub=(0, 500), d=(0,1,0.05))
def convert(sub=256,d=0.1, cin=cmap, cout='viridis'):
viridis = plt.get_cmap(cout)
jet = plt.get_cmap(cin)
jet256 = colors.makeMappingArray(sub, jet)[:, :3]
K = cKDTree(jet256)
oshape = img.shape
img_data = img.reshape((-1,3))
res = K.query(img_data, distance_upper_bound=d)
indices = res[1]
l = len(jet256)
indices = indices.reshape(oshape[:2])
remapped = indices
indices.max()
mask = (indices == l)
remapped = remapped / (l-1)
mask = np.stack( [mask]*3, axis=-1)
blend = np.where(mask, img, viridis(remapped)[:,:,:3])
fig, ax = plt.subplots()
fig.set_figheight(10)
fig.set_figwidth(10)
ax.imshow(blend)
fig.savefig('viridize.pdf')
convert()
Input image
Output image
How do I get a complete conversion of the color map (jet in this case) to viridis with Python?
As commented, the solution from How I can specify how rainbow color scheme should be converted to grayscale
will work, but with some small modifications.
I.e. you need to apply your target colormap to the values optained from that solution and hence modify the resulting array size to be 3D.
The conditions for this to work are:
You know the colormap that the original image has been produced with (origin_cmap)
All colors in that image are either grey scale (axes, text etc.) or part of that origin_cmap. I.e. there should not be any other line plot or similar in addition in the figure.
The original colormap is unambiguous, i.e. does not contain the same color twice.
The full range of the original colormap has been used to create the input image and the full range of the target colormap will be aimed for. (This condition can be weakend though if needed, by specifying a different norm and/or range)
The following will hence "viridify" a given image.
import numpy as np
import matplotlib.colors
import matplotlib.pyplot as plt
image = plt.imread("https://i.stack.imgur.com/NyLq2.png")
def changecolormap(image, origin_cmap, target_cmap):
r = np.linspace(0,1, 256)
norm = matplotlib.colors.Normalize(0,1)
mapvals = origin_cmap(norm(r))[:,:3]
def get_value_from_cm(color):
color=matplotlib.colors.to_rgb(color)
#if color is already gray scale, dont change it
if np.std(color) < 0.1:
return color
#otherwise return value from colormap
distance = np.sum((mapvals - color)**2, axis=1)
return target_cmap(r[np.argmin(distance)])[:3]
newim = np.zeros_like(image)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
c = image[i,j,:3]
newim[i,j, :3] = get_value_from_cm(c)
return newim
fig, (ax,ax2) = plt.subplots(ncols=2)
ax.imshow(image)
ax2.imshow(changecolormap(image, plt.cm.jet, plt.cm.viridis))
ax.axis("off")
ax2.axis("off")
plt.show()

How do I reliably scale matplotlib pcolormesh plots for large data sets?

I'm trying to plot some data using a pcolormesh from the matplotlib.pyplot but I'm having some difficulty when saving the output (specifically, in scaling the image appropriately).
I'm using Python v3.4 with matplotlib v1.51 if that makes a difference.
This is what my code currently looks like:
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
def GetData(data_entries, num_of_channels):
data_dict = {'timestamp' : np.linspace(1, data_entries*21, data_entries, endpoint=True)}
for chan in range(0, num_of_channels, 1):
data_dict['random%03d'%chan] = np.random.rand(data_entries, 1).flatten()
num_at_each_end_to_highlight = 10
data_dict['random%03d'%chan][0:num_at_each_end_to_highlight] = 1.5
data_dict['random%03d'%chan][-num_at_each_end_to_highlight:] = 1.5
for chan in range(0, num_of_channels, 1):
data_dict['periodic%03d' % chan] = np.zeros(data_entries)#.flatten()
data_dict['periodic%03d' % chan][::65] = 5000
return pd.DataFrame(data_dict)
def GetSubPlotIndex(totalRows, totalCols, row):
return totalRows*100+totalCols*10+row
def PlotData(df, num_of_channels, field_names):
# Calculate the range of data to plot
data_entries = len(df.index)
# Create the x/y mesh that the data will be plotted on
x = df['timestamp']
y = np.linspace(0, num_of_channels - 1, num_of_channels)
X,Y = np.meshgrid(x,y)
# Iterate through all of the field types and produce one plot for each but share the X axis
for idx, field_name in enumerate(field_names):
# Create this sub-plot
subPlotIndex = GetSubPlotIndex(len(field_names), 1, idx + 1)
ax = plt.subplot(subPlotIndex)
if idx is 0:
ax.set_title('Raw Data Time Series')
# Set the axis scale to exactly meet the limits of the data set.
ax.set_autoscale_on(False)
plt.axis([x[0], x[data_entries-1], 0, num_of_channels - 1])
# Set up the colour palette used to render the data.
# Make bad results (those that are masked) invisible so the background shows instead.
palette = plt.cm.get_cmap('autumn')
palette.set_bad(alpha=0.0)
ax.set_axis_bgcolor('black') # Set the background to zero
# Grab the data and transpose it so we can stick it in the time series running along the X axis.
firstFftCol = df.columns.get_loc(field_name + "%03d"%(0))
lastFftCol = df.columns.get_loc(field_name + "%03d"%(num_of_channels - 1))
data = df.ix[:,firstFftCol:lastFftCol]
data = data.T # Transpose so that time runs along the X axis and bin index is on the Y
# Mask off data with zero's so that it doesn't obscure the data we're actually interested in.
data = np.ma.masked_where(data == 0.0, data)
# Actually create the data mesh so we can plot it
z_min, z_max = data.min().min(), data.max().max()
p = ax.pcolormesh(X,Y, data, cmap=palette, vmin=z_min, vmax=z_max)
# Render it
plt.plot()
# Label the plot and add a key
plt.ylabel(field_name)
plt.colorbar(p)
# Label the plot
plt.xlabel('Time (ms)')
# Record the result
plt.savefig('test.png', edgecolor='none', transparent=False)
if __name__ == '__main__':
data_entries = 30000 # Large values here cause issues
num_of_channels = 255
fields_to_plot = ('random', 'periodic')
data = GetData(data_entries, num_of_channels)
width_in_pixels = len(data.index)+200
additional_vertical_space_per_plot = 50
num_of_plots = len(fields_to_plot)
height_in_pixels = (num_of_channels+additional_vertical_space_per_plot)*num_of_plots
dpi = 80 # The default according to the documentation.
fig = plt.figure(1,figsize=(width_in_pixels/dpi, height_in_pixels/dpi), dpi=dpi)
PlotData(data, num_of_channels, fields_to_plot)
With 1000 entries, the result looks fine:
If I increase the number of samples to the sort of size I actually want to plot (30000), the image is the correct size (30200 pixels wide) but I see a lot of dead space. This is a zoomed-out summary of the issues I see:
Is there a way to more accurately fill the image with the data?
Thanks to the prompt from #Dusch, this seems to solve things rather neatly:
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
def GetData(data_entries, num_of_channels):
data_dict = {'timestamp' : np.linspace(1, data_entries*21, data_entries, endpoint=True)}
for chan in range(0, num_of_channels, 1):
data_dict['random%03d'%chan] = np.random.rand(data_entries, 1).flatten()
num_at_each_end_to_highlight = 10
data_dict['random%03d'%chan][0:num_at_each_end_to_highlight] = 1.5
data_dict['random%03d'%chan][-num_at_each_end_to_highlight:] = 1.5
for chan in range(0, num_of_channels, 1):
data_dict['periodic%03d' % chan] = np.zeros(data_entries)#.flatten()
data_dict['periodic%03d' % chan][::65] = 5000
return pd.DataFrame(data_dict)
def GetSubPlotIndex(totalRows, totalCols, row):
return totalRows*100+totalCols*10+row
def PlotData(df, num_of_channels, field_names):
# Calculate the range of data to plot
data_entries = len(df.index)
# Create the x/y mesh that the data will be plotted on
x = df['timestamp']
y = np.linspace(0, num_of_channels - 1, num_of_channels)
X,Y = np.meshgrid(x,y)
# Iterate through all of the field types and produce one plot for each but share the X axis
for idx, field_name in enumerate(field_names):
# Create this sub-plot
subPlotIndex = GetSubPlotIndex(len(field_names), 1, idx + 1)
ax = plt.subplot(subPlotIndex)
if idx is 0:
ax.set_title('Raw Data Time Series')
# Set the axis scale to exactly meet the limits of the data set.
ax.set_autoscale_on(False)
plt.axis([x[0], x[data_entries-1], 0, num_of_channels - 1])
# Set up the colour palette used to render the data.
# Make bad results (those that are masked) invisible so the background shows instead.
palette = plt.cm.get_cmap('autumn')
palette.set_bad(alpha=0.0)
ax.set_axis_bgcolor('black') # Set the background to zero
# Grab the data and transpose it so we can stick it in the time series running along the X axis.
firstFftCol = df.columns.get_loc(field_name + "%03d"%(0))
lastFftCol = df.columns.get_loc(field_name + "%03d"%(num_of_channels - 1))
data = df.ix[:,firstFftCol:lastFftCol]
data = data.T # Transpose so that time runs along the X axis and bin index is on the Y
# Mask off data with zero's so that it doesn't obscure the data we're actually interested in.
data = np.ma.masked_where(data == 0.0, data)
# Actually create the data mesh so we can plot it
z_min, z_max = data.min().min(), data.max().max()
p = ax.pcolormesh(X,Y, data, cmap=palette, vmin=z_min, vmax=z_max)
# Render it
plt.plot()
# Label this sub-plot
plt.ylabel(field_name)
# Sort out the color bar
fig = plt.gcf()
image_width = fig.get_size_inches()[0] * fig.dpi # size in pixels
colorbar_padding_width_in_pixels = 20
colorbar_padding = colorbar_padding_width_in_pixels/image_width
plt.colorbar(p, pad=colorbar_padding)
# Label the plot
plt.xlabel('Time (ms)')
# Record the result
plt.savefig('test.png', edgecolor='none', transparent=False, bbox_inches='tight')
plt.tight_layout()
if __name__ == '__main__':
data_entries = 30000 # Large values here cause issues
num_of_channels = 255
fields_to_plot = ('random', 'periodic')
data = GetData(data_entries, num_of_channels)
width_in_pixels = len(data.index)+200
additional_vertical_space_per_plot = 50
num_of_plots = len(fields_to_plot)
height_in_pixels = (num_of_channels+additional_vertical_space_per_plot)*num_of_plots
dpi = 80 # The default according to the documentation.
fig = plt.figure(1,figsize=(width_in_pixels/dpi, height_in_pixels/dpi), dpi=dpi)
PlotData(data, num_of_channels, fields_to_plot)
The secret sauce in the end was:
Add plt.tight_layout() immediately before the plt.savefig call.
Add bbox_inches='tight' to the plt.savefig call.
Add , pad=colorbar_padding after calculating colorbar_padding by checking what proportion of the overall image width a 20 pixel padding equates to.

Categories

Resources