How display one white pixel with mathplot imshow - python

I want to display one white pixel with mathplot:
import numpy as np
import matplotlib.pyplot as plt
plt.imshow([[0.99]], cmap='gray', interpolation='nearest')
plt.show()
but it shows black. Why?

The problem is that you only give imshow one value, so the colour scale is set around that value and it gets painted as the minimum value of the scale (thus black).
Specify vmin and vmax, as shown here:
import numpy as np
import matplotlib.pyplot as plt
plt.imshow([[0.99]], cmap='gray', interpolation='nearest', vmin=0, vmax=1)
plt.show()
More importantly, you need vmax, which will be mapped to white, to be the value you give imshow, and vmin to be smaller than that:
import numpy as np
import matplotlib.pyplot as plt
max_value = np.random.random()
min_value = -max_value # for instance
plt.imshow([[max_value]], cmap='gray', interpolation='nearest',
vmin=min_value, vmax=max_value)
plt.show()

Related

Convert cmap values to RGB for PIL.Image

I want to use PIL.Image to save a figure and I want to use matplotlib cmaps to map the data to a color. I have tried the following:
import matplotlib
matplotlib.use('TkAgg')
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from PIL import Image
M, N = 255, 255
data = np.arange(M*N).reshape((M, N))
cmap_name = 'autumn_r'
cmap_name = cmap_name
cmap = plt.get_cmap(cmap_name)
norm = mpl.colors.Normalize()
scalarMap = cm.ScalarMappable(norm=norm, cmap=cmap)
plt.imshow(data, cmap=cmap)
plt.show()
colors = scalarMap.to_rgba(data)
image = Image.fromarray((colors[:, :, :3]*256).astype(np.uint8))
image.show()
Which plots this in matplotlib:
However, it plots this in the Image:
How can I get PIL.Image to show the same colors as matplotlib?
If its possible to also add the alpha channel, that will be useful
You need to give PIL the same normalisation and cmap you give matplotlib, so it can do the same mapping from 2D array -> normalised -> mapped to cmap.
I rewrote your sample code to be a bit simpler:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from PIL import Image
M, N = 255, 255
data = np.arange(M*N).reshape((M, N))
cmap = cm.autumn_r
plt.imshow(data, cmap=cmap)
norm = mpl.colors.Normalize()
Then your answer is:
Image.fromarray(np.uint8(cmap(norm(data))*255)).show()
(Found the solution here, might be a dupe.)

Seaborn plot with colorbar, centered around 0

I am trying to change the default behaviour of seaborn by adding a colormap (a continuous color palette) instead of using the hue argument, which creates bins from a continuous variable. I have found the following code to work, however, I would like to add one more option, to center the color bar at 0, that is 0 gets the color white, and the colors diverge from zero to negative/positive.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
y=np.random.normal(30,30,100)
x=np.random.uniform(0,50,100)
s=sns.scatterplot(
y=y,
x=x,
hue=y,
size=y,
palette='RdBu',
sizes=(50,50)
)
norm=plt.Normalize(y.min(),y.max())
sm=plt.cm.ScalarMappable(cmap="RdBu",norm=norm)
sm.set_array([])
s.get_legend().remove()
s.figure.colorbar(sm)
As can be seen from the image 0 gets a slightly reddish color, because the data is not symmetric about zero. How can I center the colormap around 0? I am completely fine with an inflated colormap from say -80 to 80 (because of the asymmetry) if the center is at 0.
Using the c, norm, and cmap key-word arguments which are passed through from seaborn to matplotlib.axes.Axes.scatter (used to colour the points instead of palette) and create a mcolors.TwoSlopeNorm to create the normalisation centred around zero you can generate the plot like so:
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as mcolors
fig, ax = plt.subplots()
y=np.random.normal(30,30,100)
x=np.random.uniform(0,50,100)
vcenter = 0
vmin, vmax = y.min(), y.max()
normalize = mcolors.TwoSlopeNorm(vcenter=vcenter, vmin=vmin, vmax=vmax)
colormap = cm.RdBu
s=sns.scatterplot(
y=y,
x=x,
c=y,
norm=normalize,
cmap=colormap,
ax=ax,
)
scalarmappaple = cm.ScalarMappable(norm=normalize, cmap=colormap)
scalarmappaple.set_array(y)
fig.colorbar(scalarmappaple)

Matplotlib image plot - nan values shown as lowest color of colormaps instead of transparent

I am using matplotlib 3.0.3 and want to create an animation of image plots using the FuncAnimation module. For plotting speed, I update the image data using im.set_data for imshow and im.set_array() for plt.pcolormesh (set_data is not available as an attribute). If I update the data partially with NaN values, imshow displays them as blank pixels, while pcolormesh shows them as the lowest color from the colormap (blue for viridis).
Is this intended and if not, why is this the behavior? It seems related to set_array(), since pcolormesh normally does plot NaN as blank pixels.
Minimal example:
import numpy as np
import matplotlib.pyplot as plt
fig1 = plt.figure()
im1 = plt.pcolormesh(np.random.rand(10,10))
im1.set_array((np.zeros((10,10)) * np.nan).ravel())
fig2 = plt.figure()
im2 = plt.imshow(np.random.rand(10,10))
im2.set_data(np.zeros((10,10)) * np.nan)
Matplotlib images do not work well with nans. One should instead use masked arrays. Then both cases are the same (except for the need to flatten the pcolormesh array).
import numpy as np
import matplotlib.pyplot as plt
A = np.random.rand(10,10)
B = np.ma.array(A, mask=np.ones((10,10)))
fig1 = plt.figure()
im1 = plt.pcolormesh(A)
im1.set_array(B.ravel())
plt.colorbar()
fig2 = plt.figure()
im2 = plt.imshow(A)
im2.set_array(B)
plt.colorbar()
plt.show()

How to make the color of one end of colorbar darker in matplotlib?

Say I have the following plot:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
data = np.sort(np.random.rand(8,12))
plt.figure()
c = plt.pcolor(data, edgecolors='k', linewidths=4, cmap='Blues', vmin=0.0, vmax=1.0)
plt.colorbar(c)
plt.show()
The colorbar has the (almost) white color assigned to the lowest values. How do I make it slightly darker? I want that instead of the colorbar ranging from white to blue, it should range from light blue to dark blue. Like, the color for the value 0 should be something like what it is for the value 0.4 in the plot above.
I found this when searching about it, but the question (and the solutions) is about making all the colors darker, which is not what I am looking for.
Although the suggestion of #user3483203 is very good, you do re-interpolate the colormap. You could avoid this by first getting the colormap as a matrix of colors (based on the original interpolation) and then select a part of this matrix as your new colormap:
import matplotlib as mpl
cmap = mpl.cm.Blues(np.linspace(0,1,20))
cmap = mpl.colors.ListedColormap(cmap[10:,:-1])
Your example then becomes
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
cmap = mpl.cm.Blues(np.linspace(0,1,20))
cmap = mpl.colors.ListedColormap(cmap[10:,:-1])
np.random.seed(1)
data = np.sort(np.random.rand(8,12))
plt.figure()
c = plt.pcolor(data, edgecolors='k', linewidths=4, cmap=cmap, vmin=0.0, vmax=1.0)
plt.colorbar(c)
plt.show()
which gives
which is in this case probably equivalent to re-interpolated colormap, as Blues itself comes from some interpolation.
For other colormaps the results may be quite different. For example, for jet:
No new interpolation, but just a subset of the original colormap (i.e. current solution):
Using re-interpolation (i.e. #user3483203's solution):
Simply define your own custom colormap:
from matplotlib.colors import LinearSegmentedColormap
colors = [(0.6, 0.76, 0.98), (0, 0.21, 0.46)] # Experiment with this
cm = LinearSegmentedColormap.from_list('test', colors, N=10)
Then just plug it in for the cmap parameter:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
data = np.sort(np.random.rand(8,12))
plt.figure()
c = plt.pcolor(data, edgecolors='k', linewidths=4, cmap=cm, vmin=0.0, vmax=1.0)
plt.colorbar(c)
plt.show()
And the result:
Using set_clim is a simple way to get your colors adjusted the way you probably want:
c.set_clim(-0.5, 1.0)
This sets the color limit (first value is vmin and second is vmax).
↳ https://matplotlib.org/api/_as_gen/matplotlib.pyplot.clim.html

How to detect circlular region in images and centre it with Python?

I have a figure flame of the form shown below:
I am trying to detect the outer edge of the camera's view and centre the figure so that circular view of the flame is exactly at the centre of the plot. As the position of the circle might change with the image capture date. Sometimes it might be at the upper half, sometimes lower half, etc.
Are there any modules in Python that can detect the view and centre it?
Reproducible code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img=mpimg.imread('flame.png')
lum_img = img[:,:,0]
img_plot = plt.imshow(lum_img)
img_plot.set_cmap('jet')
plt.axis('Off')
plt.show()
Adapted from this answer, do an edge detection and robustly fit a circle to the outline using RANSAC:
from __future__ import print_function
from skimage import io, feature, color, measure, draw, img_as_float
import numpy as np
image = img_as_float(color.rgb2gray(io.imread('flame.png')))
edges = feature.canny(image)
coords = np.column_stack(np.nonzero(edges))
model, inliers = measure.ransac(coords, measure.CircleModel,
min_samples=3, residual_threshold=1,
max_trials=1000)
print(model.params)
rr, cc = draw.circle_perimeter(int(model.params[0]),
int(model.params[1]),
int(model.params[2]),
shape=image.shape)
image[rr, cc] = 1
import matplotlib.pyplot as plt
plt.imshow(image, cmap='gray')
plt.scatter(model.params[1], model.params[0], s=50, c='red')
plt.axis('off')
plt.savefig('/tmp/flame_center.png', bbox_inches='tight')
plt.show()
This yields:
I think you have plenty of options. Two easy approaches that come to my mind would be to threshold your input image on a low intensity value which will give you a white circle. Then you could run the Hough transform for circles on it to find the center.
Or you can use the distance transform of the thresholded white pixels and take the maximum of this distance transform:
# code derived from watershed example of scikit-image
# http://scikit-image.org/docs/dev/auto_examples/plot_watershed.html
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage as ndi
from skimage.morphology import watershed
from skimage.feature import peak_local_max
from skimage.color import rgb2gray
from skimage.io import imread
img = imread('flame.png')
image = rgb2gray(img) > 0.01
# Now we want to separate the two objects in image
# Generate the markers as local maxima of the distance to the background
distance = ndi.distance_transform_edt(image)
# get global maximum like described in
# http://stackoverflow.com/a/3584260/2156909
max_loc = unravel_index(distance.argmax(), distance.shape)
fig, axes = plt.subplots(ncols=4, figsize=(10, 2.7))
ax0, ax1, ax2, ax3 = axes
ax0.imshow(img,interpolation='nearest')
ax0.set_title('Image')
ax1.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax1.set_title('Thresholded')
ax2.imshow(-distance, cmap=plt.cm.jet, interpolation='nearest')
ax2.set_title('Distances')
ax3.imshow(rgb2gray(img), cmap=plt.cm.gray, interpolation='nearest')
ax3.set_title('Detected centre')
ax3.scatter(max_loc[1], max_loc[0], color='red')
for ax in axes:
ax.axis('off')
fig.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
right=1)
plt.show()
Just to give you an idea how robust this method is, if I pick a very bad threshold (image = rgb2gray(img) > 0.001 -- far too low to get a nice circle), the result is almost the same:

Categories