Display a colorbar without associated image map - python

I have matplotlib figure that displays an image, histogram and a colobar under the histogram, generated by the following code:
import numpy as np
import imageio
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
image = imageio.imread('imageio:camera.png')
fig, axes = plt.subplots(2,1,figsize=(8,4))
divider = make_axes_locatable(axes[1])
cbar_ax = divider.append_axes("bottom", size='15%', pad=0.02)
im = axes[0].imshow(image,cmap='gray')
values, bins, patches = axes[1].hist(image.ravel(), 255, color='#cccccc', density=True)
cbar = plt.colorbar(im, cax=cbar_ax, orientation='horizontal')
fig.savefig('test.png', dpi=300)
plt.show()
This code generates exactly this image below
Since it's a recurrent image (I'm studying the histogram), I would like to create a figure just displaying the histogram and the associated colorbar below it.
But to display a colorbar is mandatory provides a image_map as argument and I need to call ax.imshow() to have a image_map. The exact output I want is something like
And I don't know how to achieve that.
Of course I could edit my images in some editor (as I did), but this isn't acceptable, since every update takes me a huge effort editing many image.

Related

How to plot a numpy array over a pcolor image in matplotlib?

I would like to plot a matrix as image and a vector as a line in this image.
something like that:
I manage to do the code for the matrix image, but I'm not able to make the black line (here I did just
an example in powerpoint).
this is my code so far:
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.ticker import LogLocator
from matplotlib import rcParams
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
import numpy as np
rcParams['font.size']=35
x = np.arange(1,16,1)
y = np.arange(-50,0,1)
z = 100 * np.random.random_sample((15, 50))
line = np.linspace(0,100,50)
fig, ax = plt.subplots(figsize=(25,25))
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.1)
im = ax.pcolor(x,y,z.T,norm=LogNorm(0.1, 100),cmap= 'jet')
cbar = fig.colorbar(im,cax=cax, orientation='vertical')
cbar.ax.yaxis.set_major_locator(LogLocator()) # <- Why? See above.
cbar.ax.set_ylabel('Resistividade \u03C1 [ohm.m]', rotation=270)
#ax2=ax.twinx()
#ax2.plot(line,y,'k--',linewidth=10)
ax.set_xlabel('Aquisição')
ax.set_ylabel('Profundidade [m]')
plt.savefig('mrec_1'+'.png',bbox_inches = "tight", format='png', dpi=300)
plt.show()
I have tried to use the ax.twinx() but since the order of magnetude is different the values on x-axis doesn' match.
Would someone help me please?
I tried this but it depends on the kind of data you have for the black line "track". I think you can just make an array of different (x,y) coordinates for the vertices (where the line changes direction + point of origin and point of end). Basing on what I saw in the image, you can add these two lines (is an approximation)
coordinates=np.array([[7,0],[7,-5],[9,-5],[9,-13], [7,-13],[7,-23],[13,-23],[13,-60]])
ax.plot(coordinates[:,0], coordinates[:,1], c='k', linewidth=10)
where every element of coordinates is the [x,y] couple. But if you have x and y from a well log, for example, you can just use those as arrays instead of coordinates[:,0] and coordinates[:,1]
As #BlueScr33n mentioned I used the twiny and works fine.

plt.savefig() partially crops subscript character from colorbar label, despite using bbox_inches="tight"

I have a figure which contains a labelled colourbar below the x axis of the main plot. When I attempt to save this using plt.savefig(), the very bottom of the subscript character in the label is cropped from the saved image, like this, despite using bbox_inches="tight". However, if I simply save the figure manually in the pop-up window, the subscript character is not cropped, like this.
Although the latter image could be manually cropped, or cropped using additional lines in the code, I would be grateful for any advice on how to resolve this issue without the need for this additional work.
I have tried to add a line break to the colourbar label like so:
label="$U/U_{"+(u"\u221e")+"}$\n"
But this simply adds white space below the label; the bottom of the subscript character is still cropped.
I have also tried to add the line:
cb.set_label(label,labelpad=5)
But this simply offsets the label from the bottom of the colourbar; no additional padding is provided below the label to fully display the subscript character.
The code is below:
import numpy
import random
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as mcolors
import matplotlib.colorbar as cbar
from matplotlib import cm
##########################################################
# Centre colourmap so 0=white
class MidpointNormalize(mpl.colors.Normalize):
def __init__(self,vmin=None,vmax=None,midpoint=None,clip=False):
self.midpoint=midpoint
mpl.colors.Normalize.__init__(self,vmin,vmax,clip)
def __call__(self,value,clip=None):
x,y=[self.vmin,self.midpoint,self.vmax],[0,0.5,1]
return numpy.ma.masked_array(numpy.interp(value,x,y),numpy.isnan(value))
##########################################################
# Set min and max values
xymin=0
xymax=10
valmin=-5
valmax=5
val=numpy.zeros((xymax,xymax),dtype=float)
# Configure plot
fig,ax=plt.subplots()
ax.set_xlim([xymin,xymax])
ax.set_ylim([xymin,xymax])
# Configure colour bar
colours=plt.cm.RdBu(numpy.linspace(0,1,256))
colourmap=mcolors.LinearSegmentedColormap.from_list('colourmap',colours)
normalisecolors=mpl.colors.Normalize(vmin=valmin,vmax=valmax)
scalecolors=cm.ScalarMappable(norm=normalisecolors,cmap=colourmap)
label="$U/U_{"+(u"\u221e")+"}$"
for ix in range(xymin,xymax):
for iy in range(xymin,xymax):
xlow=ix*+1 # Calculate vertices of patch
xhigh=(ix*1)+1
ylow=iy*1
yhigh=(iy*1)+1
val[ix][iy]=random.randint(valmin,valmax) # Patch value
rgbacolor=scalecolors.to_rgba(val[ix][iy]) # Calculate RGBA colour for value
ax.add_patch(patches.Polygon([(xlow,ylow),(xlow,yhigh),(xhigh,yhigh),(xhigh,ylow)],fill=True,facecolor=rgbacolor)) # Add value as polygon patch
cax,_=cbar.make_axes(ax,orientation="horizontal")
cb=cbar.ColorbarBase(cax,cmap=colourmap,norm=MidpointNormalize(midpoint=0,vmin=valmin,vmax=valmax),orientation="horizontal",label=label)
plt.savefig("C:/Users/Christopher/Desktop/test.png",dpi=1200,bbox_inches="tight")
plt.clf
plt.close()
I'm afraid I don't really have a good answer for you. This appears to be related to this bug https://github.com/matplotlib/matplotlib/issues/15313
The good news is that it is being worked on, the bad news is that there is no fix as of yet.
Two points to consider anyway (based on reading the thread on github):
the higher the dpi, the worst it is. So you may want to save at a lower dpi (300 works fine for me)
the problem is not present on the pdf backend, so you could save your plot in pdf (and eventually convert to png if needed)
BTW (this is unrelated to the bug in question): I'm confused by the complexity of your code. It seems to me the following code produces the same output:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm
N=10
valmin=-5
valmax=5
valmid=0
val=np.random.randint(low=valmin, high=valmax, size=(N,N))
cmap = 'RdBu'
norm = TwoSlopeNorm(vcenter=valmid, vmin=valmin, vmax=valmax)
label="$U/U_{"+(u"\u221e")+"}$"
# Configure plot
fig, ax=plt.subplots()
im = ax.imshow(val, cmap=cmap, norm=norm, aspect='auto', origin='lower')
cbar = fig.colorbar(im, orientation='horizontal', label=label)
fig.savefig('./test-1200.png',dpi=1200,bbox_inches="tight") # subscript is cut
fig.savefig('./test-300.png',dpi=300,bbox_inches="tight") # subscript is not cut
fig.savefig('./test-pdf.pdf',dpi=1200,bbox_inches="tight") # subscript is not cut
1200 dpi:
300 dpi:
pdf:

Figure size changes with colorbar

The following question arose while toying around with figure sizes. I am trying to create figures of the same size (let's say 5 by 5 inches). Thing is, when I change the notation of the colorbar, the figure size seems to change. In the code below, this can be achieved by changing the final if to tick_check=False. How can I force the figures to be the same size, regardless of the colorbar notation?
Here's my MWE:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def foo(arr, sub, fig, _str, vmin, vmax, ticks, fontsize, check, tick_check):
cm=plt.cm.jet
P2A=0.12; box_size=5
image=sub.matshow(arr,origin='low',cmap=cm,vmin=vmin,vmax=vmax,extent=[-10.92,10.92,-10.92,10.92],interpolation='bilinear')
sub.axis([-box_size,box_size,-box_size,box_size])
divider = make_axes_locatable(sub)
sub.set_title(_str,fontsize=fontsize); sub.set_xlabel(r"A",fontsize=fontsize); sub.set_ylabel(r"B",fontsize=fontsize)
if tick_check: cbar=fig.colorbar(image, cax=divider.append_axes("right", size="5%", pad=0.05), format='%.1E')
else: cbar=fig.colorbar(image, cax=divider.append_axes("right", size="5%", pad=0.05), format='%.1f')
cbar.set_ticks(ticks); sub.xaxis.tick_bottom(); sub.tick_params(labelsize=10);sub.set_title(_str, y=1.01); plt.tight_layout(h_pad=1)
d_arr=np.random.rand(182,182)
fig0, (a1) = plt.subplots(ncols=1,figsize=(5,5))
im=foo(d_arr,a1,fig0,r'Test',np.min(d_arr),np.max(d_arr),np.arange(np.min(d_arr),np.max(d_arr),0.1),10,False,True)
plt.savefig('Foo.eps',bbox_inches='tight',dpi=100)
Any help is appreciated.
Turns out, the solution to this problem is the keyword aspect, which needs to be set to auto. So:
image=sub.matshow(arr,origin='low',cmap=cm,vmin=vmin,vmax=vmax,extent=[-10.92,10.92,-10.92,10.92],interpolation='bilinear', aspect='auto')

How to rasterize a color bar axis spines in Matplotlib?

I am generating a large PDF with numerous pages. I trying to rasterize everything I can within it to save some disk space. However, the color bars are particularly tough to deal with.
Can someone manage to rasterize the color bar axis spines in the example below?
import matplotlib.pyplot as plt
import numpy as np
ax = plt.subplot(111)
im = ax.imshow(np.arange(100).reshape((10, 10)))
cb = plt.colorbar(im)
ax.set_rasterized(True)
plt.suptitle('Title')
plt.savefig('test.pdf')
As it is now, when zooming in, the axes of the image look pixelated but not the axes of the color bar. I want both to be pixelated.
I thought im.colorbar.solids.set_rasterized(True) would make the trick but it did not. Saving the image as a PNG file is not an option since I want to keep the figure title not rasterized (this is the only thing I do not want rasterized)
It turns out it is necessary to access the axis of the color bar directly. The for loop below makes the trick
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure() # <--- New line
ax = plt.subplot(111)
im = ax.imshow(np.arange(100).reshape((10, 10)))
cb = plt.colorbar(im)
for ax in fig.get_axes(): # <--- New line
ax.set_rasterized(True) # <--- New line
plt.suptitle('Title')
plt.savefig('test.pdf')

Matplotlib canvas as numpy array artefacts

I want to convert a matplotlib figure into a numpy array. I have been able to do this by accessing the contents of the renderer directly. However, when I call imshow on the numpy array it has what looks like aliasing artefacts along the edges which aren't present in the original figure.
I've tried playing around with various parameters but can't figure out how to fix the artefacts from imshow. The differences in the images remain if I save the figures to an image file.
Note that what I want to achieve is a way to confirm that the content of the array is the same as the figure I viewed before. I think probably these artefacts are not present in the numpy array but are created during the imshow call. Perhaps approriate configuration of imshow can resolve the problem.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
import math
fig = plt.figure(frameon=False)
ax = plt.gca()
ax.add_patch(Rectangle((0,0), 1, 1, angle=45, color="red"))
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
ax.set_aspect(1)
plt.axis("off")
fig.canvas.draw()
plt.savefig("rec1.png")
plt.show()
X = np.array(fig.canvas.renderer._renderer)
fig = plt.figure(frameon=False)
ax = plt.gca()
plt.axis("off")
plt.imshow(X)
plt.savefig("rec2.png")
plt.show()
These are clearly resampling artefacts, which can be avoided by using plt.figimage which specifically adds a non-resampled image to the figure.
plt.figimage(X)
plt.show()
Note that this will not work with the %matplotlib inline in Jupyter Notebook, but it does work fine with %matplotlib notebook and with GUI backends.
By adding the fig.tight_layout with padding of -1.08, I was able to get the exact image as the real image.
X = np.array(fig.canvas.renderer._renderer)
fig = plt.figure(frameon=False)
ax = plt.gca()
plt.axis("off")
plt.imshow(X)
fig.tight_layout(pad=-1.08)
plt.savefig("rec2.png")
plt.show()
Real Image
From numpy array
I hope that solves your problem, atleast till you find a better way. Cheers.
The best one I can think of is by using cv2 (openCV-python) library. My solution does require saving the image and in the case of color images, the decoded images will have the channels stored in B G R order.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
import math
import cv2 #import openCV
fig = plt.figure(frameon=False)
ax = plt.gca()
ax.add_patch(Rectangle((0,0), 1, 1, angle=45, color="red"))
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
ax.set_aspect(1)
plt.axis("off")
fig.canvas.draw()
plt.savefig("rec1.png")
plt.show()`
im = cv2.imread("rec1.png")
print(type(im)) #prints numpy.ndarray
cv2.imshow("pic",im) #creates a window named pic, loads im
cv2.waitKey(0) #has no time limit, window destroyed on any key press
cv2.destroyAllWindows()
Final result looks like
Since it is a numpy array, you can call methods on it for your comparison.
print(im.shape) #prints (288, 432, 3)
The image that is shown in the second plot is plotted smaller than the first image; the reason is that the complete first figure's image is squeezed into a newly created smaller axes -- this would be obvious when not turning the axes off.
In order to make sure the second figure only shows the image itself, you may adjust the margins, such that there is no spacing between the figure edge and the axes, using subplots_adjust.
fig = plt.figure(frameon=False)
fig.subplots_adjust(0,0,1,1)
ax = plt.gca()
plt.axis("off")
plt.imshow(X)
This produces the desired plot.
Note however that the array is not exactly the same due to antialiasing being applied when saving the png file. You may find out via
X = np.array(fig.canvas.renderer._renderer)/255.
Y = plt.imread("rec1.png")
print(np.all(X==Y))
## This prints False
Inversely speaking, if you want to have the same numpy array as the one that is saved, you should make sure to use the saved image itself.
plt.savefig("rec1.png")
X = plt.imread("rec1.png")
# use X from here onwards
Thanks to the comments who pointed out interpolation as the cause. I found the following code (adapted for Python 3) which displays the image in the way I want; identical to the first image but via the numpy array.
import PIL.Image
from io import BytesIO
import IPython.display
import numpy as np
def showarray(a, fmt='png'):
a = np.uint8(a)
f = BytesIO()
PIL.Image.fromarray(a).save(f, fmt)
IPython.display.display(IPython.display.Image(data=f.getvalue()))
source: https://gist.github.com/kylemcdonald/2f1b9a255993bf9b2629

Categories