π The CoCalc Library - books, templates and other resources
License: OTHER
EuroSciPy 2019 - 3D image processing with scikit-image
Alexandre de Siqueira, [email protected]
BIDS @ University of California, Berkeley
Support material for the tutorial 3D image processing with scikit-image.
This tutorial will introduce how to analyze three dimensional stacked and volumetric images in Python, mainly using scikit-image. Here we will learn how to:
pre-process data using filtering, binarization and segmentation techniques.
inspect, count and measure attributes of objects and regions of interest in the data.
visualize large 3D data.
For more info:
To cite this material, please use the reference below (this is a Chicago-like style):
de Siqueira, Alexandre Fioravante. "Support material for the tutorial 3D image processing with scikit-image." EuroSciPy 2019. 2019, Sep 02. Access date: < ACCESS DATE>
What is scikit-image?
scikit-image is a collection of image processing algorithms which aims to integrate well with for the SciPy ecosystem.
It is well documented, and provides well-tested code to quickly build sophisticated image processing pipelines.
Checking the system
First, we'll check if your system have the necessary packages.
Importing the base Scientific Python ecossystem
Let's start importing the basics.
Then, let's set a nice, monospace
font for matplotlib's figures.
Introduction to three-dimensional image processing
In scikit-image, images are represented as numpy
arrays.
A grayscale image is a 2D matrix of pixel intensities of shape (row, column)
. They are also called single-channel images. Multi-channel data has an extra dimension, channel
, in the final position. channel
contains color information.
We can construct a 3D volume as a series of 2D planes
, giving 3D images the shape (plane, row, column)
.
Summarizing:
Image type | Coordinates |
---|---|
2D grayscale | (row, column) |
2D multichannel | (row, column, channel) |
3D grayscale | (plane, row, column) |
3D multichannel | (plane, row, column, channel) |
Some 3D images are constructed with equal resolution in each dimension. An example would be a computer generated rendering of a sphere with dimensions (30, 30, 30)
: 30 planes, 30 rows and 30 columns.
However, most experimental data captures one dimension at a lower resolution than the other two. For example, photographing thin slices to approximate a 3D structure as a stack of 2D images. We will work with one example of such data in this tutorial.
skimage.io - utilities to read and write images in various formats
This module helps us on reading images and saving the results. There are multiple plugins available, which support multiple formats. The most commonly used functions include:
io.imread
: read an image to a numpy array.io.imsave
: write an image to disk.io.imread_collection
: read multiple images which match a common pattern.
Data can be loaded with io.imread
, as in the following example.
First let's check its shape, data type and range.
We see that cells
has 60 planes, each with 256 rows and 256 columns. Let's try visualizing the image with skimage.io.imshow
.
skimage.io.imshow
can only display grayscale and RGB(A) 2D images. We can use skimage.io.imshow
to visualize 2D planes. Let's use some helping functions for checking 3D data, then.
All supplementary functions we will use during this tutorial are stored within supplementary_code.py
. First, we import this file:
By fixing one axis, we can observe three different views of the image. Let's use the helper function show_plane
to do that.
Three-dimensional images can be viewed as a series of two-dimensional ones. The slice_explorer
helper presents a slider to check the 2D planes.
The display
helper function, on the other hand, displays 30 planes of the provided image. By default, every other plane is displayed.
Exercise: (3 min, shall we? π) there is another dataset within the folder image
, called bead_pack.tif
.
Now, using what we saw so far, there's some tasks for you:
Read this data and check its shape, data type, minimum and maximum values.
Check the slices using the function
slice_explorer
.Display each six slices using the function
display
(you will use the variablestep
for that).
skimage.exposure - evaluating or changing the exposure of an image
This module contains a number of functions for adjusting image contrast. We will use some of them:
exposure.adjust_gamma
: gamma correction.exposure.equalize_hist
: histogram equalization.
Gamma correction, also known as Power Law Transform, brightens or darkens an image. The function is applied to each pixel in the image. A gamma < 1
will brighten an image, while a gamma > 1
will darken an image.
One of the most common tools to evaluate exposure is the histogram, which plots the number of points which have a certain value against the values in order from lowest (dark) to highest (light).
Histogram equalization improves contrast in an image by redistributing pixel intensities. The most common pixel intensities are spread out, allowing areas of lower local contrast to gain a higher contrast. This may enhance background noise.
Most experimental images are affected by salt and pepper noise. A few bright artifacts can decrease the relative intensity of the pixels of interest. A simple way to improve contrast is to clip the pixel values on the lowest and highest extremes. Clipping the darkest and brightest 0.5% of pixels will increase the overall contrast of the image.
We'll call our dataset cells_rescaled
from now on. In this cell, you can choose any of the previous results to continue working with.
In the next steps, we'll use the cells_clipped
version.
Exercise: (7-ish min? π) now, using our variable beadpack
, let's repeat the process, ok?
Now, using what we saw so far, there's some tasks for you:
Obtain a nice
gamma_val
to adjust the gamma ofbeadpack
.Equalize
beadpack
's histogram usingequalize_hist
and CLAHE (given byequalize_adapthist
).Increase
beadpack
's contrast by clipping the darkest/brightest pixels there. Try different percentages.Choose the data you think is best, and call it
beadpack_rescaled
.
Edge detection
Edge detection highlights regions in the image where a sharp change in contrast occurs. The intensity of an edge corresponds to the steepness of the transition from one intensity to another. A gradual shift from bright to dark intensity results in a dim edge. An abrupt shift results in a bright edge.
The Sobel operator is an edge detection algorithm which approximates the gradient of the image intensity, and is fast to compute.
skimage.filters - apply filters to an image
Filtering applies whole-image modifications such as sharpening or blurring. In addition to edge detection, skimage.filters
provides functions for filtering and thresholding images.
Notable functions include (links to relevant gallery examples):
filters.threshold_*
(multiple different functions with this prefix)filters.try_all_threshold
to compare various methods
filters.sobel
- not adapted for 3D images. It can be applied planewise to approximate a 3D result.filters.prewitt
filters.scharr
filters.roberts
filters.laplace
filters.hessian
filters.meijering
filters.sato
filters.frangi
Inverse filtering (see also skimage.restoration):
filters.weiner
filters.inverse
Directional:
filters.gabor
Blurring/denoising
filters.gaussian
filters.median
Sharpening:
filters.unsharp_mask
Define your own filter:
LPIFilter2D
The sub-submodule skimage.filters.rank
contains rank filters. These filters are nonlinear and operate on the local histogram.
skimage.transform - transforms & warping
This submodule has multiple features which fall under the umbrella of transformations.
Forward (radon
) and inverse (iradon
) radon transforms, as well as some variants (iradon_sart
) and the finite versions of these transforms (frt2
and ifrt2
). These are used for reconstructing medical computed tomography (CT) images.
Hough transforms for identifying lines, circles, and ellipses.
Changing image size, shape, or resolution with resize
, rescale
, or downscale_local_mean
.
warp
, and warp_coordinates
which take an image or set of coordinates and translate them through one of the defined *Transforms
in this submodule. estimate_transform
may be assist in estimating the parameters.
Numerous gallery examples are available illustrating these functions. The panorama tutorial also includes warping via SimilarityTransform
with parameter estimation via measure.ransac
.
We created the illustration below to illustrate the downsampling operation. The red dots show the pixels within each image.
The distance between pixels in each dimension, called spacing
, is encoded in a tuple and is accepted as a parameter by some skimage
functions and can be used to adjust contributions to filters.
The distance between pixels was reported by the microscope used to image the cells. This spacing
information will be used to adjust contributions to filters and helps decide when to apply operations planewise. We've chosen to downsample each slice by a factor of 4 in the row
and column
dimensions to make the data smaller, thus reducing computational time. We also normalize it to 1.0
in the row
and column
dimensions.
Exercise: (3-ish min? π) now, using our variable beadpack_rescaled
, let's check its edges.
Your tasks right now are:
Use the Sobel edge filter to obtain the edges of
beadpack_rescaled
.Explore the edges at each depth.
Check 2D and 3D Sobel filters when row and column are equal to 100.
Filters
Gaussian filter applies a Gaussian function to an image, creating a smoothing effect. skimage.filters.gaussian
takes as input sigma
which can be a scalar or a sequence of scalar. This sigma
determines the standard deviation of the Gaussian along each axis. When the resolution in the plane
dimension is much worse than the row
and column
dimensions, dividing base_sigma
by the image spacing
will balance the contribution to the filter along each axis.
Median filter is a noise removal filter. It is particularly effective against salt and pepper noise. An additional feature of the median filter is its ability to preserve edges. This is helpful in segmentation because the original shape of regions of interest will be preserved.
skimage.filters.median
does not support three-dimensional images and needs to be applied planewise.
skimage.util - utility functions
These are generally useful functions which have no definite other place in the package.
util.img_as_*
are convenience functions for datatype conversion.util.invert
is a convenient way to invert any image, accounting for its datatype.util.random_noise
is a comprehensive function to apply any amount of many different types of noise to images. The seed may be set, resulting in pseudo-random noise for testing.util.view_as_*
allows for overlapping views into the same memory array, which is useful for elegant local computations with minimal memory impact.util.apply_parallel
uses Dask to apply a function across subsections of an image. This can result in dramatic performance or memory improvements, but depending on the algorithm edge effects or lack of knowledge of the remainder of the image may result in unexpected results.util.pad
andutil.crop
pads or crops the edges of images.util.pad
is now a direct wrapper fornumpy.pad
.
skimage.restoration - restoration of an image
This submodule includes routines to restore images. Currently these routines fall into four major categories. Links lead to topical gallery examples.
restoration.denoise_*
- Reducing noise.Deconvolution, or reversing a convolutional effect which applies to the entire image. This can be done in an unsupervised way.
restoration.weiner
restoration.unsupervised_weiner
restoration.richardson_lucy
restoration.inpaint_biharmonic
- Inpainting, or filling in missing areas of an image.restoration.unwrap_phase
- Phase unwrapping.
A bilateral filter is another edge-preserving, denoising filter. Each pixel is assigned a weighted average based on neighboring pixels. The weight is determined by spatial and radiometric similarity (e.g., distance between two colors).
skimage.restoration.denoise_bilateral
requires a multichannel
parameter. This determines whether the last axis of the image is to be interpreted as multiple channels or another spatial dimension. While the function does not yet support 3D data, the multichannel
parameter will help distinguish multichannel 2D data from grayscale 3D data.
Exercise: (5-ish min? π) let's filter beadpack_rescaled
now.
Your tasks are:
Use Gaussian, median and bilateral filters on
beadpack_rescaled
.Check the results; choose one and call it
beadpack_denoised
.
Thresholding
Thresholding is used to create binary images. A threshold value determines the intensity value separating foreground pixels from background pixels. Foregound pixels are pixels brighter than the threshold value, background pixels are darker. Thresholding is a form of image segmentation.
Different thresholding algorithms produce different results. Otsu's method and Li's minimum cross entropy threshold are two common algorithms. The example below demonstrates how a small difference in the threshold value can visibly alter the binarized image.
Exercise: (5-ish min? π) let's binarize beadpack_denoised
, but using different tools!
Your tasks are:
Use the function
filters.try_all_threshold
to check the binary version of the 100th plane ofbeadpack_denoised
.Choose one of the thresholds, apply it on the data and call it
beadpack_binary
.
skimage.morphology - binary and grayscale morphology
Morphological image processing is a collection of non-linear operations related to the shape or morphology of features in an image, such as boundaries, skeletons, etc. In any given technique, we probe an image with a small shape or template called a structuring element, which defines the region of interest or neighborhood around a pixel.
Mathematical morphology operations and structuring elements are defined in skimage.morphology
. Structuring elements are shapes which define areas over which an operation is applied. The response to the filter indicates how well the neighborhood corresponds to the structuring element's shape.
There are a number of two and three dimensional structuring elements defined in skimage.morphology
. Not all 2D structuring element have a 3D counterpart. The simplest and most commonly used structuring elements are the disk
/ball
and square
/cube
.
The most basic mathematical morphology operations are dilation
and erosion
. Dilation enlarges bright regions and shrinks dark regions. Erosion shrinks bright regions and enlarges dark regions. Other morphological operations are composed of dilation
and erosion
.
The closing
of an image is defined as a dilation
followed by an erosion
. Closing can remove small dark spots (i.e. βpepperβ) and connect small bright cracks. This tends to βcloseβ up (dark) gaps between (bright) features. Morphological opening
on an image is defined as an erosion
followed by a dilation
. Opening can remove small bright spots (i.e. βsaltβ) and connect small dark cracks. This tends to βopenβ up (dark) gaps between (bright) features.
These operations in skimage.morphology
are compatible with 3D images and structuring elements. A 2D structuring element cannot be applied to a 3D image, nor can a 3D structuring element be applied to a 2D image.
These four operations (closing
, dilation
, erosion
, opening
) have binary counterparts which are faster to compute than the grayscale algorithms.
Morphology operations can be chained together to denoise an image. For example, a closing
applied to an opening
can remove salt and pepper noise from an image.
Functions operating on connected components can remove small undesired elements while preserving larger shapes.
skimage.morphology.remove_small_holes
fills holes and skimage.morphology.remove_small_objects
removes bright regions. Both functions accept a min_size
parameter, which is the minimum size (in pixels) of accepted holes or objects. The min_size
can be approximated by a cube.
Exercise: (5-ish min? π) let's perform some operations on beadpack_binary
and check the results.
Your tasks are:
Apply opening, closing, dilation and erosion on
beadpack_binary
.Generate binary histogram-equalized and CLAHE versions of
beadpack
, according to the threshold you chose previously.Remove small holes and objects on
beadpack_binary
.
skimage.measure - measuring image or region properties
Multiple algorithms to label images, or obtain information about discrete regions of an image.
measure.label
- Label an image, i.e. identify discrete regions in the image using unique integers.measure.regionprops
- In a labeled image, as returned bylabel
, find various properties of the labeled regions.
Finding paths from a 2D image, or isosurfaces from a 3D image.
measure.find_contours
measure.marching_cubes_lewiner
measure.marching_cubes_classic
measure.mesh_surface_area
- Surface area of 3D mesh from marching cubes.measure.compare_*
- Quantify the difference between two whole images; often used in denoising or restoration.
RANDom Sample Consensus fitting (RANSAC) - a powerful, robust approach to fitting a model to data. It exists here because its initial use was for fitting shapes, but it can also fit transforms.
measure.ransac
measure.CircleModel
measure.EllipseModel
measure.LineModelND
Image segmentation partitions images into regions of interest. Integer labels are assigned to each region to distinguish regions of interest.
Connected components of the binary image are assigned the same label via skimage.measure.label
. Tightly packed cells connected in the binary image are assigned the same label.
A better segmentation would assign different labels to disjoint regions in the original image.
Watershed segmentation can distinguish touching objects. Markers are placed at local minima and expanded outward until there is a collision with markers from another region. The inverse intensity image transforms bright cell regions into basins which should be filled.
In declumping, markers are generated from the distance function. Points furthest from an edge have the highest intensity and should be identified as markers using skimage.feature.peak_local_max
. Regions with pinch points should be assigned multiple markers.
skimage.feature - extract features from an image
This submodule presents a diverse set of tools to identify or extract certain features from images, including tools for
Edge detection:
feature.canny
Corner detection:
feature.corner_kitchen_rosenfeld
feature.corner_harris
feature.corner_shi_tomasi
feature.corner_foerstner
feature.subpix
feature.corner_moravec
feature.corner_fast
feature.corner_orientations
Blob detection
feature.blob_dog
feature.blob_doh
feature.blob_log
Texture
feature.greycomatrix
feature.greycoprops
feature.local_binary_pattern
feature.multiblock_lbp
Peak finding:
feature.peak_local_max
Object detction
feature.hog
feature.match_template
Stereoscopic depth estimation:
feature.daisy
Feature matching
feature.ORB
feature.BRIEF
feature.CENSURE
feature.match_descriptors
feature.plot_matches
After watershed, we have better disambiguation between internal cells.
When cells simultaneous touch the border of the image, they may be assigned the same label. In pre-processing, we typically remove these cells.
Note: This is 3D data -- you may not always be able to see connections in 2D!
The watershed algorithm falsely detected subregions in a few cells. This is referred to as oversegmentation.
Plotting the markers on the distance image reveals the reason for oversegmentation. Cells with multiple markers will be assigned multiple labels, and oversegmented. It can be observed that cells with a uniformly increasing distance map are assigned a single marker near their center. Cells with uneven distance maps are assigned multiple markers, indicating the presence of multiple local maxima.
Exercise: (5-ish min? π) now it's time to label beadpack_remove_objects
and separate the beads!
Your tasks are:
Label
beadpack_remove_objects
usingmeasure.label
, and obtain the distance between the pixels.Try different footprints and obtain its max local peaks for
morphology.watershed
.
skimage.segmentation - identification of regions of interest
One of the key image analysis tasks is identifying regions of interest. These could be a person, an object, certain features of an animal, microscopic image, or stars. Segmenting an image is the process of determining where these things you want are in your images.
Segmentation has two overarching categories:
Supervised - must provide some guidance (seed points or initial conditions)
segmentation.random_walker
segmentation.active_contour
segmentation.watershed
segmentation.flood_fill
segmentation.flood
Unsupervised - no human input
segmentation.slic
segmentation.felzenszwalb
segmentation.chan_vese
There are also some supervised and unsupervised thresholding algorithms in filters
. There is a segmentation lecture (and its solution) you may peruse, as well as many gallery examples which illustrate all of these segmentation methods.
Feature extraction reduces data required to describe an image or objects by measuring informative features. These include features such as area or volume, bounding boxes, and intensity statistics.
Before measuring objects, it helps to clear objects from the image border. Measurements should only be collected for objects entirely contained in the image.
After clearing the border, the object labels are no longer sequentially increasing. The labels can be renumbered such that there are no jumps in the list of image labels:
skimage.measure.regionprops
automatically measures many labeled image features. Optionally, an intensity_image
can be supplied and intensity features are extracted per object. It's good practice to make measurements on the original image.
Not all properties are supported for 3D data. Below are lists of supported and unsupported 3D measurements.
skimage.measure.regionprops
ignores the 0 label, which represents the background.
Collected measurements can be further reduced by computing per-image statistics such as total, minimum, maximum, mean, and standard deviation.
Exercise: (5-ish min? π) let's clean the beads and prepare them to visualization!
Here are your tasks:
Clear the borders and remove small objects on
beadpack_labels
.Show the volume information for the beads.
Visualization
After cleaning, separating and studying the regions within the data, it's time to visualize them.
We can use the perimeters of a region to generate their plots. However, perimeter measurements are not computed for 3D objects. Using the fact that 3D extension of perimeter is surface area, we can measure the surface of an object by generating a surface mesh with skimage.measure.marching_cubes
and computing the surface area of the mesh with skimage.measure.mesh_surface_area
. The function plot_3d_surface
has it covered:
Now let's generate a full, interactive 3D plot using ITK and itkwidgets
:
To generate a 3D plot using ITK, we need to reformat the numpy array into an ITK matrix. Then, we use itkwidgets.view
:
Exercise: (3-ish min? π) now, using our variable beadpack_relabeled
, let's check its edges.
Your tasks right now are:
Downscale
beadpack_relabeled
by a factor of 4.Convert
beadpack_relabeled
to ITK's image.Use ITK's
view
to check the results.
ββ BONUS! ββ Parallelizing image loops
In image processing, we frequently apply the same algorithm on a large batch of images. Some of these image loops can take a while to be processed. Here we'll see how to use joblib
to parallelize loops.
Our bilateral application during this tutorial, for example:
Now, let's convert this loop to a joblib
one:
Going beyond
[1] A tour/guide on scikit-image's submodules: https://github.com/scikit-image/skimage-tutorials/blob/master/lectures/tour_of_skimage.ipynb
[2] scikit-image's gallery examples: https://scikit-image.org/docs/stable/auto_examples/
[3] ITK's ikwidgets
: https://github.com/InsightSoftwareConsortium/itkwidgets
[4] joblib.Parallel
: https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html