(c) 2016 Justin Bois and Griffin Chure. 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 tutorial was generated from a Jupyter notebook. You can download the notebook here.
import numpy as np
# Our image processing tools
import skimage.filters
import skimage.io
import skimage.morphology
import skimage.exposure
import matplotlib.pyplot as plt
import seaborn as sns
rc={'lines.linewidth': 2, 'axes.labelsize': 18, 'axes.titlesize': 18}
sns.set(rc=rc)
# The following is specific Jupyter notebooks
%matplotlib inline
%config InlineBackend.figure_formats = {'png', 'retina'}
In this tutorial, we will learn some basic techniques for image processing using scikit-image
with 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
scikit-image
scipy.ndimage
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 it "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. In this lesson, we will take a brightfield and a fluorescent image of bacteria and perform segmentation, that is the identification of each pixel in the image as being bacterial or background.
We will now load and view the test images we will use for segmentation. 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.
Today, we'll be looking at some images of Bacillus subtilis, a gram-positive bacterium famous for its ability to enter a form of 'suspended animation' known as sporulation when environmental conditions get rough. In these images, all cells have been engineered to express Cyan Fluorescent Protein (CFP) once they enter a particular genetic state known as comptency. These cells have been imaged under phase contrast (bsub_100x_phase.tif
) and epifluorescence (bsub_100x_cfp.tif
) microscopy. These images were acquired by Caltech graduate student (and 2016 bootcamp TA) Griffin Chure.
To display the images, we do not want Seaborn's default white gridlines, so we can use context management with Seaborn by setting the axes style to 'dark'
.
# Load the phase contrast image.
im_phase = skimage.io.imread('data/bsub_100x_phase.tif')
# Display the image, set Seaborn style 'dark' to avoid grid lines
with sns.axes_style('dark'):
skimage.io.imshow(im_phase)
We get a warning and then a strange looking picture! The warning is that we have a low dynamic range. We get this warning because skimage
assumes that images with integer pixel values are either 8-bit, 16-bit, or 32-bit. This image, as is often the case with images acquired with scientific cameras, were acquired with a 12-bit camera. This means that the pixel values range from 0 to $2^{12}-1=4095$. This is much less than what we would expect for a maximal pixel value for a 16-bit image, $2^{16}-1 = 65535$. Opening these images using the standard photo viewing tools on your computer (such as Preview on macOS or Windows Photo Viewer on Windows) will show a black image.
So, to view the images in their full range, we should divide by the maximal pixel value.
# Display the image
with sns.axes_style('dark'):
skimage.io.imshow(im_phase / im_phase.max())
As an alternative to stretching the image and using skimage.io.imshow
to view the image, we can use matploblib
's image viewing function, which automatically adjusts the image. We will need to specify a gray
colormap to look at it. In fact, while we're at it, we can specify whatever colormap we want.
with sns.axes_style('dark'):
# Get subplots
fig, ax = plt.subplots(2, 2, figsize=(8,6))
# Display various LUTs
ax[0,0].imshow(im_phase, cmap=plt.cm.gray)
ax[0,1].imshow(im_phase, cmap=plt.cm.RdBu_r)
ax[1,0].imshow(im_phase, cmap=plt.cm.viridis)
ax[1,1].imshow(im_phase, cmap=plt.cm.copper)