Matplotlib sum of colors as result of plot overlapping [duplicate] - python-3.x

When dealing with overlapping high density scatter or line plots of different colors it can be convenient to implement additive blending schemes, where the RGB colors of each marker add together to produce the final color in the canvas. This is a common operation in 2D and 3D render engines.
However, in Matplotlib I've only found support for alpha/opacity blending. Is there any roundabout way of doing it or am I stuck with rendering to bitmap and then blending them in some paint program?
Edit: Here's some example code and a manual solution.
This will produce two partially overlapping random distributions:
x1 = randn(1000)
y1 = randn(1000)
x2 = randn(1000) * 5
y2 = randn(1000)
scatter(x1,y1,c='b',edgecolors='none')
scatter(x2,y2,c='r',edgecolors='none')
This will produce in matplotlib the following:
As you can see, there are some overlapping blue points that are occluded by red points and we would like to see them. By using alpha/opacity blending in matplotlib, you can do:
scatter(x1,y1,c='b',edgecolors='none',alpha=0.5)
scatter(x2,y2,c='r',edgecolors='none',alpha=0.5)
Which will produce the following:
But what I really want is the following:
I can do it manually by rendering each plot independently to a bitmap:
xlim = plt.xlim()
ylim = plt.ylim()
scatter(x1,y1,c='b',edgecolors='none')
plt.xlim(xlim)
plt.ylim(ylim)
scatter(x2,y2,c='r',edgecolors='none')
plt.xlim(xlim)
plt.ylim(ylim)
plt.savefig(r'scatter_blue.png',transparent=True)
plt.savefig(r'scatter_red.png',transparent=True)
Which gives me the following images:
What you can do then is load them as independent layers in Paint.NET/PhotoShop/gimp and just additive blend them.
Now ideal would be to be able to do this programmatically in Matplotlib, since I'll be processing hundreds of these!

If you only need an image as the result, you can get the canvas buffer as a numpy array, and then do the blending, here is an example:
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.scatter(x1,y1,c='b',edgecolors='none')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.patch.set_facecolor("none")
ax.patch.set_edgecolor("none")
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
img = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
ax.clear()
ax.scatter(x2,y2,c='r',edgecolors='none')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.patch.set_facecolor("none")
ax.patch.set_edgecolor("none")
fig.canvas.draw()
img2 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
img[img[:, :, -1] == 0] = 0
img2[img2[:, :, -1] == 0] = 0
fig.clf()
plt.imshow(np.maximum(img, img2))
plt.subplots_adjust(0, 0, 1, 1)
plt.axis("off")
plt.show()
the result:

This feature is now supported by my matplotlib backend https://github.com/anntzer/mplcairo (master only):
import matplotlib; matplotlib.use("module://mplcairo.qt")
from matplotlib import pyplot as plt
from mplcairo import operator_t
import numpy as np
x1 = np.random.randn(1000)
y1 = np.random.randn(1000)
x2 = np.random.randn(1000) * 5
y2 = np.random.randn(1000)
fig, ax = plt.subplots()
# The figure and axes background must be made transparent.
fig.patch.set(alpha=0)
ax.patch.set(alpha=0)
pc1 = ax.scatter(x1, y1, c='b', edgecolors='none')
pc2 = ax.scatter(x2, y2, c='r', edgecolors='none')
operator_t.ADD.patch_artist(pc2) # Use additive blending.
plt.show()

Related

Matplotlib - maintain plot size of uneven subplots

I've been creating uneven subplots in matplotlib based on this question. The gridspec solution (third answer) worked a little better for me as it gives a bit more flexibility for the exact sizes of the subplots.
When I add a plot of a 2D array with imshow() the affected subplot is resized to the shape of the array. Is there any way to avoid that and keep the subplot-sizes (or rather aspect-ratio) fixed?
Here's the example code and the resulting image with the subplot-sizes I'm happy with:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot
fig = plt.figure(figsize=(12, 9))
gs = gridspec.GridSpec(20, 20)
ax1 = fig.add_subplot(gs[0:5,0:11])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[6:11,0:11])
ax2.plot(y, x)
ax3 = fig.add_subplot(gs[12:20,0:11])
ax3.plot(y, x)
ax4 = fig.add_subplot(gs[0:9,13:20])
ax4.plot(x, y)
ax5 = fig.add_subplot(gs[11:20,13:20])
ax5.plot(y, x)
plt.show()
This is what happens if I additionally plot data from a 2D array with the following lines (insert before plt.show):
2Ddata = np.arange(0, 10, 0.1).reshape(10, 10)
im = ax3.imshow(2Ddata, cmap='rainbow')
How can I restore the original size of the subplot from ax3 (lower left corner)?
Including the line ax3.set_aspect('auto') seems to have solved the issue.

Python: how to create a smoothed version of a 2D binned "color map"?

I would like to create a version of this 2D binned "color map" with smoothed colors.
I am not even sure this would be the correct nomenclature for the plot, but, essentially, I want my figure to be color coded by the median values of a third variable for points that reside in each defined bin of my (X, Y) space.
Even though I am able to accomplish that to a certain degree (see example), I would like to find a way to create a version of the same plot with a smoothed color gradient. That would allow me to visualize the overall behavior of my distribution.
I tried ideas described here: Smoothing 2D map in python
and here: Python: binned_statistic_2d mean calculation ignoring NaNs in data
as well as links therein, but could not find a clear solution to the problem.
This is what I have so far:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from scipy.stats import binned_statistic_2d
import random
random.seed(999)
x = np.random.normal (0,10,5000)
y = np.random.normal (0,10,5000)
z = np.random.uniform(0,10,5000)
fig = plt.figure(figsize=(20, 20))
plt.rcParams.update({'font.size': 10})
ax = fig.add_subplot(3,3,1)
ax.set_axisbelow(True)
plt.grid(b=True, lw=0.5, zorder=-1)
x_bins = np.arange(-50., 50.5, 1.)
y_bins = np.arange(-50., 50.5, 1.)
cmap = plt.cm.get_cmap('jet_r',1000) #just a colormap
ret = binned_statistic_2d(x, y, z, statistic=np.median, bins=[x_bins, y_bins]) # Bin (X, Y) and create a map of the medians of "Colors"
plt.imshow(ret.statistic.T, origin='bottom', extent=(-50, 50, -50, 50), cmap=cmap)
plt.xlim(-40,40)
plt.ylim(-40,40)
plt.xlabel("X", fontsize=15)
plt.ylabel("Y", fontsize=15)
ax.set_yticks([-40,-30,-20,-10,0,10,20,30,40])
bounds = np.arange(2.0, 20.0, 1.0)
plt.colorbar(ticks=bounds, label="Color", fraction=0.046, pad=0.04)
# save plots
plt.savefig("Whatever_name.png", bbox_inches='tight')
Which produces the following image (from random data):
Therefore, the simple question would be: how to smooth these colors?
Thanks in advance!
PS: sorry for excessive coding, but I believe a clear visualization is crucial for this particular problem.
Thanks to everyone who viewed this issue and tried to help!
I ended up being able to solve my own problem. In the end, it was all about image smoothing with Gaussian Kernel.
This link: Gaussian filtering a image with Nan in Python gave me the insight for the solution.
I, basically, implemented the exactly same code, but, in the end, mapped the previously known NaN pixels from the original 2D array to the resulting smoothed version. Unlike the solution from the link, my version does NOT fill NaN pixels with some value derived from the pixels around. Or, it does, but then I erase those again.
Here is the final figure produced for the example I provided:
Final code, for reference, for those who might need in the future:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from scipy.stats import binned_statistic_2d
import scipy.stats as st
import scipy.ndimage
import scipy as sp
import random
random.seed(999)
x = np.random.normal (0,10,5000)
y = np.random.normal (0,10,5000)
z = np.random.uniform(0,10,5000)
fig = plt.figure(figsize=(20, 20))
plt.rcParams.update({'font.size': 10})
ax = fig.add_subplot(3,3,1)
ax.set_axisbelow(True)
plt.grid(b=True, lw=0.5, zorder=-1)
x_bins = np.arange(-50., 50.5, 1.)
y_bins = np.arange(-50., 50.5, 1.)
cmap = plt.cm.get_cmap('jet_r',1000) #just a colormap
ret = binned_statistic_2d(x, y, z, statistic=np.median, bins=[x_bins, y_bins]) # Bin (X, Y) and create a map of the medians of "Colors"
sigma=1 # standard deviation for Gaussian kernel
truncate=5.0 # truncate filter at this many sigmas
U = ret.statistic.T.copy()
V=U.copy()
V[np.isnan(U)]=0
VV=sp.ndimage.gaussian_filter(V,sigma=sigma)
W=0*U.copy()+1
W[np.isnan(U)]=0
WW=sp.ndimage.gaussian_filter(W,sigma=sigma)
np.seterr(divide='ignore', invalid='ignore')
Z=VV/WW
for i in range(len(Z)):
for j in range(len(Z[0])):
if np.isnan(U[i][j]):
Z[i][j] = np.nan
plt.imshow(Z, origin='bottom', extent=(-50, 50, -50, 50), cmap=cmap)
plt.xlim(-40,40)
plt.ylim(-40,40)
plt.xlabel("X", fontsize=15)
plt.ylabel("Y", fontsize=15)
ax.set_yticks([-40,-30,-20,-10,0,10,20,30,40])
bounds = np.arange(2.0, 20.0, 1.0)
plt.colorbar(ticks=bounds, label="Color", fraction=0.046, pad=0.04)
# save plots
plt.savefig("Whatever_name.png", bbox_inches='tight')

plt.subplot_adjust() not working correctly

I am making some density plots like so:
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import r2_score
import matplotlib
from scipy import stats
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
from matplotlib.ticker import FormatStrFormatter
import matplotlib.cm as cm
from scipy.ndimage.filters import gaussian_filter
import random
matplotlib.rcParams.update({'font.size': 16})
matplotlib.rcParams['xtick.direction'] = 'in'
matplotlib.rcParams['ytick.direction'] = 'in'
x = random.sample(range(1, 10001), 1000)
y = random.sample(range(1, 10001), 1000)
def myplot(x, y, s, bins=1000):
heatmap, xedges, yedges = np.histogram2d(x, y, bins=bins)
heatmap = gaussian_filter(heatmap, sigma=s)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
return heatmap.T, extent
cmap = cm.YlOrRd
fig, (ax, ax1, cax) = plt.subplots(ncols = 3, figsize = (15, 5),
gridspec_kw={"width_ratios":[1,1, 0.5]})
img, extent = myplot(x, y, 20)
im = ax.imshow(img, extent = extent, origin = 'lower', cmap = cmap)
ax.text(0.05, 0.92, '$R^2$ = {}'.format(np.round(r2_score(x, y), 2)), fontsize=14, color = 'k', transform = ax.transAxes)
ax.plot(ax.get_xlim(), ax.get_ylim(), ls="--", c=".3")
ax.set_xlabel("Black Sky")
ax.set_ylabel("Blue Sky")
img2, extent2 = myplot(x, y, 20)
ax1.imshow(img2, extent = extent2, origin = 'lower', cmap = cmap)
ax1.text(0.05, 0.92, '$R^2$ = {}'.format(np.round(r2_score(x, y), 2)), fontsize=14, color = 'k', transform = ax1.transAxes)
ax1.axes.get_yaxis().set_visible(False)
ax1.yaxis.set_ticks([])
ax1.plot(ax1.get_xlim(), ax1.get_ylim(), ls="--", c=".3")
ax1.set_xlabel("White Sky")
ip = InsetPosition(ax1, [1.05,0,0.05,1])
cax.set_axes_locator(ip)
fig.colorbar(im, cax=cax, ax=[ax,ax1], use_gridspec = True)
plt.subplots_adjust(wspace=0.1, hspace=0)
which gives me a plot like this:
No matter what I change wspace to the plot stays the same. I think this is because when I turn of the y-axis in ax1 I am just making the text blank instead of removing the y-axis all together. Is there a way to do this so that I can make the width spacing between the figures closer together?
As commented, wspace sets the minimal distance between plots. This distance may be larger in case of equal aspect axes. Then it will depend on the figure size, figure aspect and image aspect.
A. Use automatic aspect
You may set aspect = "auto" in your imshow plots,
ax.imshow(..., aspect = "auto")
B. Adjust the subplot parameters
You may set the left or right subplot parameter to something smaller. E.g.
plt.subplots_adjust(wspace=0.0, hspace=0, right=0.7)
C. Adjust the figure size
Using a smaller figure width, which is closer to the actual image aspect will also reduce whitespace around the figure.
E.g, making the figure only 11 inches wide and using 5% padding on the right,
plt.subplots(..., figsize = (11, 5))
plt.subplots_adjust(wspace=0.0, hspace=0, right=.95)

How to draw Scatter plot on top of background using Basemap Python

I am trying to plot a scatter plot on a background using basemap. But it's overwriting the background. How do I retain the background?
I am using this code
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80,llcrnrlon=-180,urcrnrlon=180,lat_ts=20,resolution='c')
m.bluemarble()
x, y = m(list(longitude), list(latitude))
plt.scatter(x,y,1,marker='o',color='Red')
plt.show()
But as soon as I run the scatter plot, its overwriting background image. How can I overlay the scatter plot on the image.
This is how to plot a series of points on top of a raster map. Note that the bluemarble image is huge, so a full scale (1.0 or default) plot of it should be avoided. The code is based on yours.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# make up some data for scatter plot
lats = np.random.randint(-75, 75, size=20)
lons = np.random.randint(-179, 179, size=20)
fig = plt.gcf()
fig.set_size_inches(8, 6.5)
m = Basemap(projection='merc', \
llcrnrlat=-80, urcrnrlat=80, \
llcrnrlon=-180, urcrnrlon=180, \
lat_ts=20, \
resolution='c')
m.bluemarble(scale=0.2) # full scale will be overkill
m.drawcoastlines(color='white', linewidth=0.2) # add coastlines
x, y = m(lons, lats) # transform coordinates
plt.scatter(x, y, 10, marker='o', color='Red')
plt.show()
The resulting plot:
I realize it's an old question but in case anyone comes here with the same problem as I did.
The trick is to give a higher zorder for the scatter plot than for the .bluemarble().
m.scatter(x, y, 10, marker='o', color='Red', zorder=3)
More info here: https://matplotlib.org/3.1.0/gallery/misc/zorder_demo.html
I'm not entirely sure what you mean by "overwriting the background". When you use plt.scatter(), it will plot the points over the map, so it will display the points over the background.
Just based off the code provided, I think you're issue here is m(list(longitude), list(latitude)).
If you have multiple points in a list, you want to loop over them.
lats = [32, 38, 35]
lons = [-98, -79, -94]
x, y = m(lons, lats)
for i in range(len(lats)):
plt.scatter(x, y, marker = 'o')
If it's only one single point,
lat, lon = 32, -92
x, y = m(lon, lat)
plt.scatter(x, y, marker = 'o')
The styling of the points can be found in the matplotlib documentation.

How to achieve the Fiji "HiLo" colormap in matplotlib image plots, to mark under and overexposed pixels

Matplotlib's colormaps do not provide the HiLo colormap for images, which is often used in microscopy. HiLo shows a gray-level gradient from low to high values, but values at the low-end are shown in blue and ones at the upper end in red.
How can one get this color-map for matplotlib images?
To achieve this one can use the 'set_under' and 'set_over' methods of the LinearSegmentedColormap class, of which the colormaps are inherited.
# minimal example
from matplotlib import cm
import matplotlib.pyplot as plt
from numpy import arange
im_array = arange(0, 256)
cmap = cm.gray
cmap.set_over(color='red')
cmap.set_under(color='blue')
fig = plt.figure()
ax = fig.add_subplot(111)
vmin = im_array.min() + 1
vmax = im_array.max() - 1
ax.imshow(im_array.reshape((16, 16)), cmap=cmap, vmin=vmin, vmax=vmax)
May be this helps someone.
Cheers!
S

Resources