33

I want to display an image (say 800x800) with Matplotlib.pyplot imshow() function but I want to display it so that one pixel of the image occupies one pixel on the screen (zoom factor = 1, no shrink, no stretch).

I'm a beginner, so do you know how to proceed?

dom_beau
  • 2,437
  • 3
  • 30
  • 59

5 Answers5

31

Matplotlib isn't optimized for this. You'd be a bit better off with simpler options if you just want to display an image at one-pixel-to-one-pixel. (Have a look at Tkinter, for example.)

That having been said:

import matplotlib.pyplot as plt
import numpy as np

# DPI, here, has _nothing_ to do with your screen's DPI.
dpi = 80.0
xpixels, ypixels = 800, 800

fig = plt.figure(figsize=(ypixels/dpi, xpixels/dpi), dpi=dpi)
fig.figimage(np.random.random((xpixels, ypixels)))
plt.show()

Or, if you really want to use imshow, you'll need to be a bit more verbose. However, this has the advantage of allowing you to zoom in, etc if desired.

import matplotlib.pyplot as plt
import numpy as np

dpi = 80
margin = 0.05 # (5% of the width/height of the figure...)
xpixels, ypixels = 800, 800

# Make a figure big enough to accomodate an axis of xpixels by ypixels
# as well as the ticklabels, etc...
figsize = (1 + margin) * ypixels / dpi, (1 + margin) * xpixels / dpi

fig = plt.figure(figsize=figsize, dpi=dpi)
# Make the axis the right size...
ax = fig.add_axes([margin, margin, 1 - 2*margin, 1 - 2*margin])

ax.imshow(np.random.random((xpixels, ypixels)), interpolation='none')
plt.show()
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks Joe, I'll take a look at that. Frankly, I hadn't though about Tkinter but it could do the job. I'll look at it. Maybe it will solve my problem. – dom_beau Nov 09 '11 at 01:34
  • 2
    I suggested Tk mostly because it's in the standard library... If your image is already a numpy array, a great option is `glumpy`. http://code.google.com/p/glumpy/ I think `pygame` also has a lot of similar functionality, if you don't want to take the route of a full-blown gui toolkit. Good luck, at any rate! – Joe Kington Nov 09 '11 at 02:16
  • 2
    Joe, the first method with figimage() do **perfectly** the job. The key words are "adds a non-resampled array *X* to the figure." in the help... I need to display non-resampled black-and-white dithered image. – dom_beau Nov 10 '11 at 03:26
  • 1
    the other options don't have the ability to zoom or view coordinates as you hover mouse though? – endolith May 24 '15 at 05:07
  • @endolith - True! That's one of the large advantages to the second example. – Joe Kington May 26 '15 at 14:16
  • I tried both methods with a 360-by-631 ndarray of float64 values, with matplotlib 1.5 in jupyter notebook. Neither method produced a non-resampled, non-interpolated graph. What am I missing? – Kal Nov 17 '15 at 04:02
  • 1
    @Kal - If you're using the inline or nbagg backends in an IPython notebook, they're completely different than "normal" matplotlib with regards to figure size. The figure size controls work a bit differently. I'd recommend avoiding inline figures in notebooks, unless you're wanting to make a static document. (Actually, I'd recommend avoiding notebooks altogether and just use "regular" IPython instead, but I'm an outlier in that regard.) – Joe Kington Nov 17 '15 at 17:54
  • 1
    @JoeKington I see. Ah well, I was so frustrated that I have resorted to using the matplotlib colormaps directly and displaying the array using Pillow instead. – Kal Nov 18 '15 at 03:40
  • a small mistake: figsize = x, y (not y,x) so in the figsize line xpixels and ypixels should be replaced – idoo Dec 13 '15 at 14:12
  • I wish I could downvote this answer but my upvote is locked in. Be _very careful_ if you use this. The `800, 800` need to be set to _precisely_ the size of your image plus the margin for this to give the one-to-one pixel behavior. If they're too large, you'll get stretching. If they're too small, it will drop pixels from your image. I wasted an entire day trying to debug a gap in my data that turned out to be an artifact of this display code (see [this question](https://stackoverflow.com/questions/69398683/extract-street-network-from-a-raster-image)). Just use `Image.fromarray` instead. – danvk Oct 05 '21 at 15:21
  • I think the margin needs to be multiplied by 2 in the figsize too. Also, you need to add `1` to `xpixels` and `ypixels` to account for the black border, which it seems also counts towards the axes size. – nog642 Nov 01 '22 at 20:34
  • Also you should be multiplying by `1/(1 + 2 * margin)` not `1 - 2 * margin`. The latter makes it slightly off – nog642 Nov 01 '22 at 20:43
7

If you don't really need matlibplot, here is the best way for me

import PIL.Image
from io import BytesIO
import IPython.display
import numpy as np
def showbytes(a):
    IPython.display.display(IPython.display.Image(data=a))

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

use showbytes() for show a image bytes string, and showarray() for show a numpy array.

yjmade
  • 441
  • 4
  • 9
0

If you're working in a Jupyter notebook, have pillow (Python Imaging Library) installed, and don't need a colormap, then Image.fromarray is convenient. You'll just need to get your data into a form it can use (np.uint8 or bool):

import numpy as np
from PIL import Image
data = np.random.random((512, 512))

Image.fromarray((255 * data).astype(np.uint8))

Jupyter cell showing noise

or if you have a boolean array:

Image.fromarray(data > 0.5)

Jupyter cell showing binary noise

danvk
  • 15,863
  • 5
  • 72
  • 116
0

Here's a modified version of the accepted answer using imshow, which worked for me, at least for square images (it seems to not always work for non-square images). The math seems to be off for the accepted answer.

import matplotlib.pyplot as plt
import numpy as np

image = ...

dpi = 100
margin = 0.05
ypixels, xpixels = image.shape
fig = plt.figure(figsize=((1 + margin * 2) * (xpixels + 1) / dpi,
                          (1 + margin * 2) * (ypixels + 1) / dpi),
                 dpi=dpi)
ax = fig.add_axes([margin, margin,
                   1 / (1 + margin * 2), 1 / (1 + margin * 2)])
ax.imshow(image)
plt.show()

The differences between this and the accepted answer are the width and height of the axes being different, as well as the figsize adding 1 to each dimension as well as multiplying the margin by 2.

nog642
  • 569
  • 3
  • 15
-2

If you are trying to zoom in your image, then:

import matplotlib.pyplot as plt

import numpy as np

dpi = 80
margin = 0.01 # The smaller it is, the more zoom you have
xpixels, ypixels = your_image.shape[0], your_image.shape[1] ## 

figsize = (1 + margin) * ypixels / dpi, (1 + margin) * xpixels / dpi

fig = plt.figure(figsize=figsize, dpi=dpi)
ax = fig.add_axes([margin, margin, 1 - 2*margin, 1 - 2*margin])

ax.imshow(your_image)
plt.show()
vagitus
  • 264
  • 2
  • 10