Image Processing I

(c) 2020 Justin Bois. This work is licensed under a Creative Commons Attribution License CC-BY 4.0. All code contained herein is licensed under an MIT license.

This document was prepared at Caltech with support financial support from the Donna and Benjamin M. Rosen Bioengineering Center.

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 usual, we will begin by importing the modules we will use.

In [1]:
import numpy as np

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

import bokeh.plotting

import colorcet

import bi1x
Loading BokehJS ...

Note: To run this tutorial, you may need to install the colorcet package. To do this, open a terminal and run conda install colorcet on the command line.

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. 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 Importantly, skimage is well-documented, and you can access the documentation at

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


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 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 [2]:
# Load images as NumPy array
grat_im ='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 is a <class 'numpy.ndarray'>
Data type is uint8
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. Bokeh allows us to do this with convenient zooming and scrolling. However, the interface is a bit low-level, so I wrote a higher level interface in the bi1x module.

In [3]: