Stretching axis ticks to fit compressed image - matplotlib

I have a PyQt4 Application in which I am representing a 16bit grayscale image using matplotlib. The image I'm representing are quite large. Due to memory limitations I'm unfortunately not able to represent the bigger images, so I'm slicing them in this way:
ImageDataArray[::ratio, ::ratio]
When showing the plots, the axis are compressed depending on the ratio. Hence the coordinates of the image is of importance to know where the information of interest is loacated, I want to stretch the axis again by the factor of ratio.
How can I manage this, so the correct coordinates are shown even if I use the zoom function from the matplotlib toolbar?
Thanks in advance.
Code:
from numpy import fromfile, uint16, shape
import matplotlib.pyplot as plt
data = fromfile('D:\\ImageData.raw', dtype=uint16)
data.resize((ysize,xsize))
xmin = 0
ymin = 0
xmax = shape(data)[1]
ymax = shape(data)[0]
ratio = max(max(shape(data)[0], shape(data)[1])/2000, 1)
data_slice = data[ymin:ymax:ratio, xmin:xmax:ratio]
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.imshow(data_slice, cmap='gray', interpolation='nearest')
plt.show()

You want to use the extent keyword argument of imshow (doc)
ax.imshow(...,extent=[xmin,xmax,ymin,ymax])

Related

Function to define an area in a colormap and return average pixel value

I have a .txt of delimited data, about 600 x 600 cells in size. I have graphed it using matplotlib's imshow and now I want to analyze a specific part of interest in it. Particularly, I'm interested in drawing a defined region around a specific (x, y) point and returning the average value (from the file) within that region. I've done some searching but I can't find anything that will let me manipulate this as such. The closest thing I've come to is patches or the patches.Circle function, but all that does is let me draw a circle around the defined point. I can't extract (or can't figure out how to) any information from there. Here is my working example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
#Setup Plots
fig, ax = plt.subplots(figsize=(20, 10))
plt.rcParams.update({'font.size': 20})
#Open Data
filename = "smalltest.txt"
data = np.genfromtxt(filename, delimiter = ";", skip_header = 0)
#Colourmap
ax.imshow(data, cmap ='hot', interpolation='nearest')
#-------------------------------------
#Average values around point
#Create circle
circ = patches.Circle((345, 195), 60, alpha=0.8, fc='none',
edgecolor = 'yellow')
ax.add_patch(circ)
fig.colorbar(ax.imshow(data, cmap ='hot', interpolation='nearest'),
label = "Temperature (K)")
plt.show()
Here is a picture as an example:
Graph produced. Trying to get average value within the circle that is drawn

Matplotlib plot over imshow while keeping axis size

Whenever I use imshow() to plot an image, plotting 1D data over it in a twinned bottom x-axis changes the size and aspect ratio of the initial x-axis created with for imshow(). How do I avoid this behavior? Here is how to reproduce the issue:
import numpy as np
import matplotlib
matplotlib.use('macosx')
import matplotlib.pyplot as plt
im = np.random.rand(2856, 4290)
light_curve = im[1000, :]
fig = plt.figure(1, figsize=(10,10))
ax1 = plt.subplot(2,1,1)
ax1.imshow(im, cmap='gray', origin='lower')
ax2 = plt.subplot(2,1,2)
ax2.imshow(im, cmap='gray', origin='lower')
# Setting aspect ratio to equal does not help
ax2.set_aspect('equal')
ax21 = ax2.twinx()
ax21.plot(light_curve, alpha=0.7)
# Setting axis limits does not help
ax1.axis([0, im.shape[1], 0, im.shape[0]])
ax21.set_xlim([0, im.shape[1]])
And here is what it looks like with my graphical backend (macosx, if that is of any relevance)
Isn't it the purpose of twinx() used above to help with this in the first place?
So how may I keep the initial imshow() x-axis fixed and have the subsequent axis of the 1D plot simply fit, without resizing or messing with the aspect ratio, without going completely manual with building my axes?
It is indeed a bit unfortunate that the aspect does not propagate to the twin axes in the sense that it would have the same box around it.
I think the only way to overcome this is to calculate the aspect manually and set it for the twin axes as well.
import numpy as np
import matplotlib.pyplot as plt
im = np.random.rand(285, 429)
light_curve = im[100, :]
fig = plt.figure(1, figsize=(8,8))
ax1 = plt.subplot(2,1,1)
ax1.imshow(im, cmap='gray', origin='lower')
ax2 = plt.subplot(2,1,2)
ax2.imshow(im, cmap='gray', origin='lower')
ax2.set_aspect("equal", "box-forced")
ax21 = ax2.twinx()
ax21.plot(light_curve, alpha=0.7)
# Setting axis limits does not help
ax21.set_xlim(ax1.get_xlim())
a = np.diff(ax21.get_ylim())[0]/np.diff(ax1.get_xlim())*im.shape[1]/im.shape[0]
ax21.set_aspect(1./a, "box-forced")
plt.show()

How to expand matplolib window without stretching the plot?

I want to increase the grey area around the plot, but keeping the plot the same size. I've already tried changing the figure size, which ends up stretching the plot.
The axes inside the figure is positionned relative to the figure. Per default you have e.g. a fraction of 0.125 of figure width as space at the left. This means that resizing the figure, scales the axes as well.
You may calculate how much the spacings need to change such that if the figure is rescaled, the axes size remains constant. The new spacings then need to be set using fig.subplots_adjust.
import matplotlib.pyplot as plt
def set_figsize(figw,figh, fig=None):
if not fig: fig=plt.gcf()
w, h = fig.get_size_inches()
l = fig.subplotpars.left
r = fig.subplotpars.right
t = fig.subplotpars.top
b = fig.subplotpars.bottom
hor = 1.-w/float(figw)*(r-l)
ver = 1.-h/float(figh)*(t-b)
fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
fig, ax=plt.subplots()
ax.plot([1,3,2])
set_figsize(9,7)
plt.show()
You may then also use this function to update the subplot params when the figure window is resized.
import matplotlib.pyplot as plt
class Resizer():
def __init__(self,fig=None):
if not fig: fig=plt.gcf()
self.fig=fig
self.w, self.h = self.fig.get_size_inches()
self.l = self.fig.subplotpars.left
self.r = self.fig.subplotpars.right
self.t = self.fig.subplotpars.top
self.b = self.fig.subplotpars.bottom
def set_figsize(self, figw,figh):
hor = 1.-self.w/float(figw)*(self.r-self.l)
ver = 1.-self.h/float(figh)*(self.t-self.b)
self.fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
def resize(self, event):
figw = event.width/self.fig.dpi
figh = event.height/self.fig.dpi
self.set_figsize( figw,figh)
fig, ax=plt.subplots()
ax.plot([1,3,2])
r = Resizer()
cid = fig.canvas.mpl_connect("resize_event", r.resize)
plt.show()
In the window of a matplotlib figure, there's a button called 'Configure subplots' (see below picture, screenshot on Windows 10 with matplotlib version 1.5.2). Try to change the parameters 'left' and 'right'. You can also change these parameters with plt.subplots_adjust(left=..., bottom=..., right=..., top=..., wspace=..., hspace=...).

plot several image files in matplotlib subplots

I would like to create a matrix subplot and display each BMP files, from a directory, in a different subplot, but I cannot find the appropriate solution for my problem, could somebody helping me?.
This the code that I have:
import os, sys
from PIL import Image
import matplotlib.pyplot as plt
from glob import glob
bmps = glob('*trace*.bmp')
fig, axes = plt.subplots(3, 3)
for arch in bmps:
i = Image.open(arch)
iar = np.array(i)
for i in range(3):
for j in range(3):
axes[i, j].plot(iar)
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()
I am having the following error after executing:
natively matplotlib only supports PNG images, see http://matplotlib.org/users/image_tutorial.html
then the way is always read the image - plot the image
read image
img1 = mpimg.imread('stinkbug1.png')
img2 = mpimg.imread('stinkbug2.png')
plot image (2 subplots)
plt.figure(1)
plt.subplot(211)
plt.imshow(img1)
plt.subplot(212)
plt.imshow(img2)
plt.show()
follow the tutorial on http://matplotlib.org/users/image_tutorial.html (because of the import libraries)
here is a thread on plotting bmps with matplotlib: Why bmp image displayed as wrong color with plt.imshow of matplotlib on IPython-notebook?
The bmp has three color channels, plus the height and width, giving it a shape of (h,w,3). I believe plotting the image gives you an error because the plot only accepts two dimensions. You could grayscale the image, which would produce a matrix of only two dimensions (h,w).
Without knowing the dimensions of the images, you could do something like this:
for idx, arch in enumerate(bmps):
i = idx % 3 # Get subplot row
j = idx // 3 # Get subplot column
image = Image.open(arch)
iar_shp = np.array(image).shape # Get h,w dimensions
image = image.convert('L') # convert to grayscale
# Load grayscale matrix, reshape to dimensions of color bmp
iar = np.array(image.getdata()).reshape(iar_shp[0], iar_shp[1])
axes[i, j].plot(iar)
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()

Draw colorbar with twin scales

I'd like to draw a (vertical) colorbar, which has two different scales (corresponding to two different units for the same quantity) on each side. Think Fahrenheit on one side and Celsius on the other side. Obviously, I'd need to specify the ticks for each side individually.
Any idea how I can do this?
That should get you started:
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10))
plt.pcolormesh(x)
# create the colorbar
# the aspect of the colorbar is set to 'equal', we have to set it to 'auto',
# otherwise twinx() will do weird stuff.
cbar = plt.colorbar()
pos = cbar.ax.get_position()
cbar.ax.set_aspect('auto')
# create a second axes instance and set the limits you need
ax2 = cbar.ax.twinx()
ax2.set_ylim([-2,1])
# resize the colorbar (otherwise it overlays the plot)
pos.x0 +=0.05
cbar.ax.set_position(pos)
ax2.set_position(pos)
plt.show()
If you create a subplot for the colorbar, you can create a twin axes for that subplot and manipulate it like a normal axes.
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1,2.7)
X,Y = np.meshgrid(x,x)
Z = np.exp(-X**2-Y**2)*.9+0.1
fig, (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios":[15,1]})
im =ax.imshow(Z, vmin=0.1, vmax=1)
cbar = plt.colorbar(im, cax=cax)
cax2 = cax.twinx()
ticks=np.arange(0.1,1.1,0.1)
iticks=1./np.array([10,3,2,1.5,1])
cbar.set_ticks(ticks)
cbar.set_label("z")
cbar.ax.yaxis.set_label_position("left")
cax2.set_ylim(0.1,1)
cax2.set_yticks(iticks)
cax2.set_yticklabels(1./iticks)
cax2.set_ylabel("1/z")
plt.show()
Note that in newer version of matplotlib, the above answers no long work (as #Ryan Skene pointed out). I'm using v3.3.2. The secondary_yaxis function works for the colorbars in the same way as for regular plot axes and gives one colorbar with two scales: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib.axes.Axes.secondary_yaxis
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10)) #let's assume these are temperatures in Fahrenheit
im = plt.imshow(x)
# create the colorbar
cbar = plt.colorbar(im,pad=0.1) #you may need to adjust this padding for the secondary colorbar label[enter image description here][1]
cbar.set_label('Temperature ($^\circ$F)')
# define functions that relate the two colorbar scales
# e.g., Celcius to Fahrenheit and vice versa
def F_to_C(x):
return (x-32)*5/9
def C_to_F(x):
return (x*9/5)+32
# create a second axes
cbar2 = cbar.ax.secondary_yaxis('left',functions=(F_to_C,C_to_F))
cbar2.set_ylabel('Temperatrue ($\circ$C)')
plt.show()
I am using an inset axis for my colorbar and, for some reason, I found the above to answers no longer worked as of v3.4.2. The twinx took up the entire original subplot.
So I just replicated the inset axis (instead of using twinx) and increased the zorder on the original inset.
axkws = dict(zorder=2)
cax = inset_axes(
ax, width="100%", height="100%", bbox_to_anchor=bbox,
bbox_transform=ax.transAxes, axes_kwargs=axkws
)
cbar = self.fig.colorbar(mpl.cm.ScalarMappable(cmap=cmap), cax=cax)
cbar.ax.yaxis.set_ticks_position('left')
caxx = inset_axes(
ax, width="100%", height="100%",
bbox_to_anchor=bbox, bbox_transform=ax.transAxes
)
caxx.yaxis.set_ticks_position('right')