Automatic thresholding

Prerequisites

Before starting this lesson, you should be familiar with:

Learning Objectives

After completing this lesson, learners should be able to:
  • Understand how an image histogram can be used to derive a threshold

  • Apply automatic threshold to distinguish foreground and background pixels

Motivation

The manual determination of a threshold value is tedious and subjective. This is problematic as it reduces the reproducibility of the results and may preclude determining threshold values for many different images as the dataset becomes large. It is therefore important to know about reproducible mathematical approaches to automatically determine threshold values for image segmentation.

Concept map

graph TD I("Image") --> H("Histogram") H -- algorithm --> T("Threshold value")



Figure


Input images, histograms (Huang threshold - blue, Otsu threshold - orange), binary images (Huang), binary images (Otsu).



Key points

  • Most auto thresholding methods do two class clustering
  • If the histogram is bimodal, most automated methods will perform well
  • If the histogram has more than two peaks, automated methods could produce noisy results



Activities

Apply manual and automated thresholds


Show activity for:  

ImageJ GUI

  • Manual vs. auto thresholding
    • Open xy_8bit__nuclei_without_offset.tif
    • [ Image > Adjust > Threshold… ]
      • [X] Dark Background
      • Selecting Lower threshold level = 20 is a good example. Note down this value
      • Press Reset
      • Don’t press Set or Apply
    • Open xy_8bit__nuclei_with_offset.tif
    • [ Image > Adjust > Threshold… ]
      • [X] Dark Background
      • Selecting Lower threshold level = 20 now does not work (i.e. all pixels are foreground)
      • Here, selecting Lower threshold level = 40 works
      • Press Reset
    • Select window xy_8bit__nuclei_without_offset.tif
    • [ Image > Adjust > Auto Threshold ]
      • Method = Try all
      • [X] White objects on black background
      • Keep all other options unchecked
      • Press OK
    • Select window xy_8bit__nuclei_with_offset.tif
    • [ Image > Adjust > Auto Threshold ]
      • Method = Try all
      • [X] White objects on black background
      • Keep all other options unchecked
      • Press OK
    • In auto thresholding several methods produce acceptable results and one can get rid of selecting manual threshold values for each different image

skimage napari

# %% 
# Apply manual and automated thresholds

# %%
# initial imports
import napari
import numpy as np
from OpenIJTIFF import open_ij_tiff

# %%
# Read two images
image1, *_ = open_ij_tiff('https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_without_offset.tif')
image2, *_ = open_ij_tiff('https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_with_offset.tif')

# %%
# Inspect image data type and value range
print(image1.dtype, image1.shape, image1.min(), image1.min())
print(image2.dtype, image2.shape, image2.min(), image2.max())

# %%
# Instantiate the napari viewer and display the images
viewer = napari.Viewer()

# %%
# Add the images
viewer.add_image(image1, name="image1")
viewer.add_image(image2, name="image2")

# %%
# Show the histogram
import matplotlib.pyplot as plt
plt.hist(image1.flatten(), bins=np.arange(image1.max()+1), log=True);
plt.hist(image2.flatten(), bins=np.arange(image2.max()+1), log=True);

# %%
# Try manual thresholding
thr1 = 25
thr2 = 75

manual_thresholded1 = image1>thr1
manual_thresholded2 = image2>thr2

viewer.add_image(manual_thresholded1, name='manual_thresholded1', opacity = 0.4, colormap='magenta')
viewer.add_image(manual_thresholded2, name='manual_thresholded2', opacity = 0.4, colormap='magenta')
# Identify possible problems with this solution

# %% [markdown]
# Explore auto-thresholding options on:
# https://scikit-image.org/docs/stable/api/skimage.filters.html

# %%
# Obtain the thresholding values
from skimage.filters import threshold_mean

thr1 = threshold_mean(image1)
print(thr1)
mean_thresholded1 = image1>thr1

thr2 = threshold_mean(image2)
print(thr2)
mean_thresholded2 = image2>thr2

# %%
# Visualize auto-thresholded images
viewer.add_image(mean_thresholded1, name='mean_thresholded1', opacity = 0.4, colormap='magenta')
viewer.add_image(mean_thresholded2, name='mean_thresholded2', opacity = 0.4, colormap='magenta')

# %% 
# Explore other thresholding options
# Note that there exists a threshold_multiotsu function to handle cases with multi-peaks histograms

Galaxy

  • Navigate to Galaxy
  • Upload an image
    • In the Tools panel on the left side, click Upload Data.
    • Click the Paste/Fetch data button.
    • Paste the URLs of the images : xy_8bit__nuclei_without_offset.tif and xy_8bit__nuclei_with_offset.tif
    • Click the Start button and wait for the upload to complete.
    • Once the upload is finished, click the Close button. The image will now be available in your Galaxy history.
  • Apply a threshhold
    • In the Tools panel on the left side, search Threshold image.
    • Choose the tool named Threshold image with scikit-image, and click on it.
      • Manual threshold
        • xy_8bit__nuclei_without_offset.tif
          • Select the image xy_8bit__nuclei_without_offset.tif from the Select image dropdown list.
          • Select Manual from the Thresholding method dropdown list.
          • Set Threshold value to 20.
          • Toggle Invert output labels to Yes
          • Click the Run Tool button and wait for the job to finish (The job will turn green).
          • Click on the job in your Galaxy history to download the resulting image.
        • xy_8bit__nuclei_with_offset.tif
          • Select the image xy_8bit__nuclei_with_offset.tif from the Select image dropdown list.
          • Select Manual from the Thresholding method dropdown list.
          • Set Threshold value to 40.
          • Toggle Invert output labels to Yes
          • Click the Run Tool button and wait for the job to finish (The job will turn green).
          • Click on the job in your Galaxy history to retrieve the results.
      • Auto threshold
        • xy_8bit__nuclei_without_offset.tif
          • Select the image xy_8bit__nuclei_without_offset.tif from the Select image dropdown list.
          • Select Otsu from the Thresholding Method dropdown list.
          • Leave Offset value unchanged.
          • Toggle Invert output labels to Yes
          • Click the Run Tool button and wait for the job to finish (The job will turn green).
          • Click on the job in your Galaxy history to retrieve the results.
        • Repeat the steps for a different image xy_8bit__nuclei_with_offset.tif



Apply automated thresholds in 3D


Show activity for:  

ImageJ GUI

  • Open xyz_8bit__nuclei_autothresh.tif
  • Try all available methods
    • [ Image > Adjust > Auto Threshold ]
      • Method = Try all
      • [X] White objects on black background
      • [X] Stack
      • [X] Use stack histogram
      • Press OK
    • Observe that the different methods give different outputs
    • Appreciate that this montage view is not suited for further analysis of the binary output
  • Apply one method to properly segment the stack, e.g. Otsu
    • [ Image > Duplicate… ]
      • Title = Otsu
      • [X] Duplicate stack
    • [ Image > Adjust > Auto Threshold ]
      • Method = Otsu
      • [X] Stack
      • [X] Use stack histogram
      • [X] Show threshold values in log window
      • Press OK

skimage napari

# %% [markdown]
# ## Apply automated thresholds in 3D


# %%
# initial imports
import numpy as np
import napari
from OpenIJTIFF import open_ij_tiff

# %%
# Read the images
image, axes, scales, units = open_ij_tiff('https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_8bit__nuclei_autothresh.tif')

# %%
scales

# %%
# Inspect image data type and values
print(f'Type: {image.dtype} \nShape: {image.shape} \nMin: {np.min(image)} \nMax: {np.max(image)}')

# %%
# Instantiate the napari viewer and display the image
viewer = napari.Viewer()
viewer.add_image(image, name='image', scale=scales)

# %%
# Obtain threshold value using Otsu's algorithm
from skimage.filters import threshold_otsu
thresholded_otsu = image > threshold_otsu(image)

viewer.add_labels(thresholded_otsu, name='otsu', num_colors=1, color={1: 'green'},  scale=scales)

# %% [markdown]
# **Napari GUI** Explore the results in the napari viewer. For 3D data one can change the order 
# of visible axes (bottom left in napari viewer window). If not satisfied by the
# results, we can explore other threshold algorithms

# %%
# Additional threshold methods
from skimage.filters import threshold_li
thresholded_li = image > threshold_li(image)

viewer.add_labels(thresholded_li, name='li', num_colors=1, color={1: 'orange'}, scale=scales)

# %%






Assessment

True or False

Solution

  • True
  • False




Follow-up material

Recommended follow-up modules:

Learn more: