7

I'm trying to plot a small image in python using matplotlib and would like the displayed axes to have the same shape as the numpy array it was generated from, i.e. the data should not be resampled. In other words, each entry in the array should correspond to a pixel (or thereabouts) on the screen. This seems trivial, but even after trawling the internet for while, I can't seem to get it to work:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

X = np.random.rand(30,40)

fig = plt.figure()
fig.add_axes(aspect="equal",extent=[0, X.shape[1], 0, X.shape[0]])
ax = fig.gca()
ax.autoscale_view(True, False, False)
ax.imshow(X, cmap = cm.gray)

plt.show()
user588241
  • 239
  • 3
  • 10
  • 2
    looks like the code you provided does the trick you want, the rectangular shape (40x30) is conserved in the plotted image, and the axis limits are matched to the image shape. Do I miss something in your question? – gcalmettes Mar 28 '12 at 15:55
  • Sorry my phrasing is confusing. What I'm interested in is something like the imshow functionality in matlab, where the number of pixels on the screen matches the number of entries in the matrix, i.e. the axes are not resampled to match the figure size. – user588241 Mar 28 '12 at 15:59
  • 1
    Do you mean to have an image without interpolation? If so, just change in your code `imshow` with `matshow`, or add `interpolation = 'nearest'` as a parameter to imshow function. – carla Mar 28 '12 at 16:13
  • Thanks, that stops smooth interpolation, but how can I reduce the size of the axes so that the figure reflects the low resolution of the image? Currently it is still seems to be rescaled to match the figure size. – user588241 Mar 28 '12 at 17:12

4 Answers4

5

I've had the same problem myself. If the interpolation='nearest' option to imshow isn't good enough, well if your main objective is to see raw, un-scaled, non-interpolated, un-mucked about pixels in matplotlib, then you can't beat figimage IMHO. Demo:

import numpy as np
import numpy.random
import matplotlib.pyplot as plt

a=256*np.random.rand(64,64)

f0=plt.figure()
plt.imshow(a,cmap=plt.gray())
plt.suptitle("imshow")

f1=plt.figure()
plt.figimage(a,cmap=plt.gray())
plt.suptitle("figimage")

plt.show()

Of course it means giving up the axes (or drawing them yourself somehow). There are some options to figimage which let you move the image around the figure so I suppose it might be possible to manoeuvre them on top of some axes created by other means.

timday
  • 24,582
  • 12
  • 83
  • 135
  • 2
    In case anyone's wondering, the only way I was able to get figimage to work without anything on the plot except the image in Ipython Notebook was by adding an empty ("turned-off") set of axes before the plt.show(), akin to this S/O post: http://stackoverflow.com/questions/9295026/matplotlib-plots-removing-axis-legends-and-white-spaces. Otherwise, it just didn't plot anything. – Greg Kramida Jul 17 '13 at 22:31
3

You can use the following code snippet to convert an array into a PIL (Python Imaging Library). The resulting image will have the same size as the input array. It can be displayed or saved as an image.

from PIL import Image
from numpy import linspace, array, fromfunction, sin, cos
from matplotlib import cm


# scale array between vmin and vmax and encode it to uint8 (256 values)
def scale(arr, vmin, vmax):
    return (255*(arr - vmin)/(vmax - vmin)).clip(0, 255).astype('uint8')

# convert a matplotlib colormap into a PIL palette
def getpalette(cmap):
    return (255.*array(map(lambda x: cmap(x)[0:3], linspace(0., 1.,256))).ravel()).astype('int')


# a sample array
data = fromfunction(lambda i,j: cos((i+j)/50)*sin(i/50.), (200, 300), dtype='float')

# convert the float array to a PIL image
im = Image.fromarray(scale(data, 0, 1))
im.putpalette(getpalette(cm.jet))
im.save('test.png') # or im.show()

The only thing is that im.show() is not very good, because it requires to have the image viewer xv and it writes a temporary image. So you can as well write a file and load it with your favorite image viewer.

François
  • 7,988
  • 2
  • 21
  • 17
1

I'm not sure I completely understand your question. Does this mean you want the x axis between 0 and X.shape[1] and the y axis between 0 and X.shape[0]? In this case, this code should do the trick:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

X = np.random.rand(30,40)

fig = plt.figure()

plt.xlim(0, X.shape[1])
plt.ylim(0, X.shape[0])

ax = fig.gca()
ax.autoscale_view(True, False, False)
ax.imshow(X, cmap = cm.gray)

plt.show()

Hope it helps

nay
  • 864
  • 2
  • 8
  • 12
  • Thanks, I'm sorry if my question was confusing. I'm trying to do something like imshow in Matlab, where the axes inside the figure are the same size as the array, e.g. each pixel on the screen corresponds to an entry in the matrix. The above rescales and resamples the array to match the figure size. – user588241 Mar 28 '12 at 15:53
  • I just found a smiliar post on stackoverflow, they use the interpolation argument of imshow: ax.imshow(X, cmap = cm.gray, extent=(0, X.shape[1], 0, X.shape[0]), interpolation='nearest') Here is the link http://stackoverflow.com/questions/6323737/make-a-2d-pixel-plot-with-matplotlib – nay Mar 28 '12 at 16:33
  • Thanks, using nearest neighbour interpolation is good, but also I would like the axes to be small, i.e. so it more closely reflects the true size of the array. Currently it seems like the axes is still rescaled to match the figure size. – user588241 Mar 28 '12 at 17:14
1

If you know the native resolution of your image, you can set the dpi argument to what you want in the plt.figure(dpi=value).

You can find more information about adjusting image size on this link.

gcalmettes
  • 8,474
  • 1
  • 32
  • 28