Bi 1x 2016: Image Processing I

This tutorial was generated from an Jupyter notebook. You can download the notebook here.

In this tutorial, we will learn some basic techniques for image processing using scikit-image with Python. As with the Introduction to Python tutorial, do not cut and paste the commands. Type them in by hand. I intentionally have omitted all outputs in this tutorial. You will learn much more effectively that way.

As usual, we will begin by importing the modules we will use.

In [18]:
import numpy as np
import matplotlib.pyplot as plt

import skimage.io
import skimage.exposure
import skimage.filters
import skimage.morphology

import seaborn as sns

# Magic function used to make this Jupyter notebook
%matplotlib inline

# This enables high res graphics inline
%config InlineBackend.figure_formats = {'png', 'retina'}

# JB's favorite Seaborn settings for notebooks (without grid for image viewing)
rc = {'lines.linewidth': 2, 
      'axes.labelsize': 18, 
      'axes.titlesize': 18}
sns.set_context('notebook', rc=rc)
sns.set_style('darkgrid', rc=rc)

Image processing tools for Python

There are many image processing tools available for Python. Some of them, such as ITK and OpenCV are mature image processing packages that have bindings for Python, allowing easy use of their functionality. Others were developed specifically for Python. Some of the many packages are

The first two packages are standard with Anaconda. They provide a set of basic image processing tools, with more sophisticated packages such as ITK and Fiji supplying many more bells and whistles. If in the future you have more demanding image processing requirements, the other packages can prove very useful.

We will almost exclusively use scikit-image along with the standard tools from NumPy. The package scipy.ndimage is quite useful, but we will use scikit-image, since it has expanded functionality. We will call is skimage for short (which is how the package is imported anyhow). A potential annoyance with skimage is that the main package has minimal functionality, and you must import subpackages as needed. For example, to load and view images, you will need to import skimage.io. Importantly, skimage is well-documented, and you can access the documentation at http://scikit-image.org/.

We will explore skimage’s capabilities and some basic image processing techniques through example. We will start with examining a graticule (stage micrometer), then put together an multi-color image, and finally analyze fluorescence and phase contrast images of growing bacteria.

Calibrating an objective using a graticule

You should have downloaded the file 2014-03-24_graticule.tif. We will use this as our graticule sample image. Note that I have given it a descriptive name. A trick I find useful is to have the first 10 characters in the file name be the date. (I often include the time as well.) That way, images you are analyzing appear in chronological order when viewed on your computer. Following the date is a descriptive title for the image. I kept the name short for the purposes of this tutorial, but for my own files, I would name the file 2014-03-24-1456_graticule_10micron_increments_10x_wheels.tif, which specifies that I took this image at 2:56 PM on March 24, 2014 using the 10$\times$ objective on the microscope Wheels, and that the gradations on the graticule are ten microns apart. This is important to know going forward: we are calibrating a 10$\times$ objective using a graticule with 10 µm increments.

Loading and viewing the image

We load the image using the skimage.io.imread. The image is stored as a NumPy array. Each entry in the array is a pixel value. This is an important point: a digital image is data. It is a set of numbers with spatial positions.

In [19]:
# Load images as NumPy array
grat_im = skimage.io.imread('2014-03-24_graticule.tif')

# Show that grat_im is a NumPy array
print('grat_im is a', type(grat_im))

# Show that the data type is unsigned int
print('Data type is', grat_im.dtype)

# Let's look at grat_im (only works in the IPython console)
grat_im
grat_im is a <class 'numpy.ndarray'>
Data type is uint8
Out[19]:
array([[198, 201, 201, ..., 196, 195, 198],
       [200, 198, 200, ..., 197, 197, 197],
       [200, 198, 195, ..., 195, 196, 198],
       ..., 
       [199, 194, 197, ..., 193, 192, 193],
       [197, 198, 195, ..., 193, 194, 193],
       [199, 197, 197, ..., 193, 194, 194]], dtype=uint8)

An image is nothing more than a 2D array, or a matrix. Any operation we can do to a matrix, we can do to an image. That includes slicing, assignment, any mathematical operations; anything. Note also that the data type of grat_im is uint8, or unsigned 8 bit integer. This is the assumed bit depth of the image. Bit depth is the number of bits used to indicate the intensity of a single pixel in an image. For an 8-bit image, the pixel values for from $0$ to $2^8 − 1$, or from $0$ to $255$. (In general, low pixel values are dark and high pixel values are light.) skimage evaluates the pixel values in the image and then chooses which of its allowed data types are commensurate with the pixel values. skimage allows 8, 16, or 32 bit images, in addition to float images (with pixel values between 0 and 1). We will see the consequences of this later. For now, it suffices to know that our graticule image is 8 bit.

We have looked at the image as a set of numbers. As is often the case, it is useful to "plot" data. A common way to "plot" an image is to display it as we're used to seeing images. We can do this with the skimage.io.imshow function.

In [21]:
skimage.io.imshow(grat_im);

Finally, I note that the axes are useful to have on the plot, but we can easily get rid of them.

In [22]:
skimage.io.imshow(grat_im)
plt.axis('off');