How to achieve the Fiji "HiLo" colormap in matplotlib image plots, to mark under and overexposed pixels - python-3.x

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

Related

Align frequency scale on specgram and PSD plot

I would like to know if the frequency scale on the PSD plot to the left of the image is a 1:1 mapping of the specgram spectrogram frequency scale to the right?
The code that I am using to plot this is:
import matplotlib.pyplot as plt
from matplotlib import transforms
import numpy as np
import librosa
import librosa.display
x2=np.random.random(10000)
fs2=1000
Pxx2, freq2 = plt.psd(x, 512, fs2)
fig = plt.figure(figsize=(9, 8),constrained_layout=True)
gs = GridSpec(6,5, figure=fig)
ax2 = plt.subplot(gs.new_subplotspec((0, 0), rowspan=4))
ax2.plot(10*np.log10(Pxx2), freq2)
x_ticks = (min(10*np.log10(Pxx2)), max(10*np.log10(Pxx2)))
ax2.set_xticks(x_ticks)
ax2.invert_xaxis()
plt.xlabel("(dB/Hz)")
plt.ylabel('Frequency (Hz)')
# plt.yticks(np.arange(0,22050, 5000))
plt.ylim([0, max(freq2)])
ax3 = plt.subplot(gs.new_subplotspec((0, 1), rowspan=4, colspan=4))
ax3.specgram(x2, Fs=2);
plt.show()
If the scales is not, how do I go about matching the frequency scale on the PSD and the spectrogram plots?
Thanks for your help and time!

Seaborn, how to gradient color distplot depending on the x-axis value

I'd like to gradient-color the plot line in the Seaborn's distplot, depending on the x-axis value. For example if the value is 1, then the colour is blue, when 1.1 then it's blue and goes toward green, and so on, and so on. For example like on the plot-draft below:
The problem is, that I don't how to set colour map manually in Seaborn or how to force x-dependend coloring of the plot's curve.
Note that distplot has been deprecated. In the current seaborn version, kdeplot draws a kde curve.
You can grab the generated line with ax.get_lines(). And then create a multicolored line similar to this tutorial example.
Here is some code to demonstrate the idea (currently it would also still work with distplot):
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import seaborn as sns
import numpy as np
np.random.seed(1234)
data = np.random.uniform(-1, 1.1, (5, 1000)).cumsum(axis=1).ravel()
ax = sns.kdeplot(x=data)
x, y = ax.get_lines()[0].get_data()
segments = np.array([x[:-1], y[:-1], x[1:], y[1:]]).T.reshape(-1, 2, 2)
norm = plt.Normalize(x.min(), x.max())
lc = LineCollection(segments, cmap='turbo_r', norm=norm)
lc.set_array(x[:-1])
lc.set_linewidth(2)
ax.get_lines()[0].remove()
line = ax.add_collection(lc)
ax.fill_between(x, y, color='purple', alpha=0.1, hatch='xx')
ax.margins(x=0)
ax.set_ylim(ymin=0)
plt.show()

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')

Matplotlib sum of colors as result of plot overlapping [duplicate]

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()

How to invert color of seaborn heatmap colorbar

I use an heatmap to visualize a confusion matrix. I like the standard colors, but I would like to have 0s in light orange and highest values in dark purple.
I managed to do so only with another set of colors (light to dark violet), setting:
colormap = sns.cubehelix_palette(as_cmap=True)
ax = sns.heatmap(cm_prob, annot=False, fmt=".3f", xticklabels=print_categories, yticklabels=print_categories, vmin=-0.05, cmap=colormap)
But I want to keep these standard ones. This is my code and the image I get.
ax = sns.heatmap(cm_prob, annot=False, fmt=".3f", xticklabels=print_categories, yticklabels=print_categories, vmin=-0.05)
The default cmap is sns.cm.rocket. To reverse it set cmap to sns.cm.rocket_r
Using your code:
cmap = sns.cm.rocket_r
ax = sns.heatmap(cm_prob,
annot=False,
fmt=".3f",
xticklabels=print_categories,
yticklabels=print_categories,
vmin=-0.05,
cmap = cmap)
To expand on Ben's answer, you can do this with most if not any color map.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
X = np.random.random((4, 4))
sns.heatmap(X,cmap="Blues")
plt.show()
sns.heatmap(X,cmap="Blues_r")
plt.show()
sns.heatmap(X,cmap="YlGnBu")
plt.show()
sns.heatmap(X,cmap="YlGnBu_r")
plt.show()
only add cmap="rocket_r" to sns.heatmap
cmap="rocket": is the default palette of heatmap
add_r: to reverse the colors of palette
ax = sns.heatmap(cm_prob, annot=False, fmt=".3f", xticklabels=print_categories, yticklabels=print_categories, vmin=-0.05,cmap="rocket_r")
we can now quickly achieve reverse color just by putting _r in the end.
For example: for viridis => viridis_r
sns.heatmap(corr_matrix, annot=True, cmap='viridis_r');
Did you try to invert the colormap?
sns.cubehelix_palette(as_cmap=True, reverse=True)

Resources