This notebook goes with a blog post:
In general, you can go one of two ways with images:
PIL, pillow (a port of PIL).matplotlib or scipy.In the first category, we'll take a look at the Python Imaging Library. The main points about this library:
In the second category, we'll look at reading and writing images with matplotlib.image, scipy, and scikit-image. All of these tools use PIL behind the scenes for some of their functionality. The main features of matplotlib.image:
PIL.scipy.This post uses data from this tweet by Prof Chris Jackson (Imperial College London). I don't know anything about the data. I doubt it has an open licence, but it's on Twitter, so...
We'll start with the usual prerequisites for our notebooks:
In [1]:
    
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
    
In [2]:
    
from PIL import Image
im = Image.open("../data/EChTISYWkAA6_DV.jpeg")
    
In the notebook, the repr of this Image object is a convenient display of the image:
In [3]:
    
im
    
    Out[3]:
Notice that the size (not shape!) is reported as columns × rows, so it's different from a NumPy array, which is rows × cols.
In [4]:
    
im.size
    
    Out[4]:
In [5]:
    
np.array(im).shape
    
    Out[5]:
(The 3rd dimension there is the channel: one each for red, green and blue.)
We can resize this image, but doing this while maintaining the aspect ratio is a bit fiddly becaue you have to compute the new dimensions yourself.
In [6]:
    
w, h = im.size
aspect = h / w
new_w = 200
new_h = int(new_w * aspect)   # Has to be an int.
im = im.resize((new_w, new_h), Image.ANTIALIAS)
im
    
    Out[6]:
You can save having to compute the new image size with the thumbnail method but be careful — it resizes the image in place. So I'l do it on a copy:
In [7]:
    
temp = im.copy()
temp.thumbnail((64, 64), Image.ANTIALIAS)
    
In [8]:
    
temp
    
    Out[8]:
We can plot this little image and see that it's now pixellated at any reasonable size:
In [9]:
    
plt.imshow(temp, interpolation='none')
    
    Out[9]:
    
(Note in passing that we can pass an Image object to imshow. This is because it presents a NumPy-like interface. It's not an array though.)
We can ask imshow for some more sensible interpolation:
In [10]:
    
plt.imshow(temp, interpolation='bicubic')
    
    Out[10]:
    
In [11]:
    
rgb = np.array(im)
red_channel = rgb[:, :, 0]
plt.imshow(red_channel, cmap='gray')
    
    Out[11]:
    
Note that NumPy doesn't implicitly care about the values:
In [12]:
    
np.max(red_channel)
    
    Out[12]:
In [13]:
    
red_max1 = red_channel / 255
plt.imshow(red_max1, cmap='gray')
    
    Out[13]:
    
But if you convert back to a PIL Image, it cares. In fact, it won't even accept our decimal numbers in the 0–1 range:
In [14]:
    
im_red =  Image.fromarray(red_max1)
im_red
# This should throw an error.
    
    
    Out[14]:
We have to cast them to unsigned 8-bit integers (i.e. integers in the range 0 to 255):
In [15]:
    
im_red = Image.fromarray(np.uint8(red_max1 * 255))
im_red
    
    Out[15]:
In [16]:
    
# Your code here...
    
In [17]:
    
import requests
from io import BytesIO
url = "https://pbs.twimg.com/media/EChTISYWkAA6_DV?format=jpg&name=large"
r = requests.get(url)
im = Image.open(BytesIO(r.content))
    
In [18]:
    
im
    
    Out[18]:
In [19]:
    
import matplotlib.image as mpimage
img = mpimage.imread("../data/EChTISYWkAA6_DV.png")
    
In [20]:
    
type(img)
    
    Out[20]:
In [21]:
    
img.dtype
    
    Out[21]:
A nice feature of imread is that it will accept a web URL as well as a filename:
In [22]:
    
img = mpimage.imread("https://pbs.twimg.com/media/EChTISYWkAA6_DV?format=png&name=large")
img.dtype
    
    Out[22]:
You can load a JPEG, but matplotlib will use PIL behind the scenes. PIL loads images as 8-bit unsigned integers in [0, 255], so that's what you'll end up with.
In [23]:
    
img = mpimage.imread("../data/EChTISYWkAA6_DV.jpeg")
    
In [24]:
    
img.dtype
    
    Out[24]:
Notice we have an h × w × 3 array — this is an RGB image. PNGs often have a 4th channel, alpha or A, which holds opacity.
plt.imshow() plots 3-channel arrays like this in colour:
In [25]:
    
plt.figure(figsize=(6, 10))
plt.imshow(img)
    
    Out[25]:
    
We can plot only the red channel (say), and apply false colour via a lookup table:
In [26]:
    
plt.figure(figsize=(6, 10))
plt.imshow(img[..., 0], cmap='gray')
plt.colorbar(shrink=0.67)
    
    Out[26]:
    
Let's look at the histogram for this channel:
In [27]:
    
_ = plt.hist(img[..., 0].ravel(), bins=128)
    
    
In [5]:
    
import skimage
skimage.io.imread("https://pbs.twimg.com/media/EChTISYWkAA6_DV?format=png&name=large")
    
    Out[5]:
© 2019 Agile Scientific, licensed CC-BY, please share this work.