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.
import numpy as np import skimage.io import skimage.exposure import skimage.filters import skimage.morphology import bokeh.io import bokeh.plotting import colorcet import bi1x bokeh.io.output_notebook()
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.
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 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.
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.
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.
# 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
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
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