2D noisy object segmentation and filtering

Prerequisites

Before starting this lesson, you should be familiar with:

Learning Objectives

After completing this lesson, learners should be able to:
  • Create an image analysis workflow comprising image denoising and object filtering.

Motivation

Finding objects in images typically presents itself with two challenges. First, the input image may not lend itseld to a simple intensity thresholding operation for binarisation. Second, there may be unwanted objects in the image such as hot pixels or objects that are not fully in the image. The first challenge typically is tackled by applying appropriate image filters to the raw data. The second challenge is tackled by defining and applying reproducible criteria to remove certain objects from the image.

Concept map

graph TD GI["Grayscale input image"] --> FGI["Filtered grayscale image"] FGI -->|has property|P["Interesting stuff is bright"] FGI --> BI["Binary image"] BI --> LI["Label image"] LI --> FLI["Subset label image"] FLI -->|has property|U["Unwanted labels are removed"] FLI --> S("Shape measurement") S --> SFT["Object feature table"]



Figure


Nuclei segmentation and area measurement, including image denoising and object filtering.



Activities

Segment 2d noisy nuclei and filter objects


Show activity for:  

ImageJ Macro & GUI

/**
 * 2D nuclei segmentation, including
 *  - image denoising by a mean filter
 *  - removal of small and boundary objects
 *  
 * Requirements:
 *   - Update site: IJPB-Plugins (MorpholibJ)
 * 
 */

run("Close All");
run("Options...", "iterations=1 count=1 black do=Nothing");

analyseNuclei( "Ctrl", "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_noisy_small.tif" );
analyseNuclei( "Treat", "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_noisy_large.tif" );
run("Tile");

function analyseNuclei( name, filePath )
{
	// File › Open...
	open(filePath);
	// Image › Rename...
	rename(name);
	// Image › Adjust › Brightness/Contrast...
	setMinAndMax(0, 100);
	// Image › Duplicate...
	run("Duplicate...", "title=" + name + "_denoise" );
	// Process › Filters › Mean...
	run("Mean...", "radius=3");
	// Image › Duplicate...
	run("Duplicate...", "title=" + name + "_binary" );
	// Image › Adjust › Threshold...  [ Apply ]
	setThreshold(25, 255);
	run("Convert to Mask");
	// Plugins › MorphoLibJ › Binary Images › Connected Components Labeling
	run("Connected Components Labeling", "connectivity=4 type=[8 bits]");
	// Image › Lookup Tables › glasbey_on_dark
	run("glasbey_on_dark");
	// Plugins › MorphoLibJ › Label Images › Label Size Opening
	run("Label Size Opening", "min=100");
	// Plugins › MorphoLibJ › Label Images › Remove Border Labels
	run("Remove Border Labels", "left right top bottom");
	// Plugins › MorphoLibJ › Analyze › Analyze Regions
	run("Analyze Regions", "area");
}

skimage and napari

# %%
# Import modules
import napari
from OpenIJTIFF import open_ij_tiff
from skimage.filters.rank import mean
from skimage.morphology import disk, remove_small_objects, remove_small_holes
from skimage.measure import label, regionprops_table
from skimage.segmentation import clear_border
import pandas as pd

# %% [markdown]
# ### Process the first image

# %%
# Instantiate the napari viewer
napari_viewer1 = napari.Viewer()

# Read and inspect the image:
fpath = 'https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_noisy_small.tif'
image1, axes_image1, scales_image1, units_image1 = open_ij_tiff(fpath)
napari_viewer1.add_image(image1, name='image1')

# %%
# Apply local mean filter
radius = 3

disk_radius = disk(radius)
image_denoise = mean(image1, disk_radius)
napari_viewer1.add_image(image_denoise, name='denoised')

# %%
# Binarize the image:
thr = 25

image_binary = image_denoise > thr
napari_viewer1.add_labels(image_binary, name='binary')

# %%
# Fill small holes
min_size_holes = 100

img_binary_fill = remove_small_holes(image_binary, area_threshold=min_size_holes)
napari_viewer1.add_labels(img_binary_fill, name='filled')

# %%
# Find labels
connectivity = 2

img_labels = label(img_binary_fill, connectivity=connectivity)
napari_viewer1.add_labels(img_labels, name='labels')

# %%
# Remove small regions
min_size_regions = 100

img_labels_filt = remove_small_objects(img_labels, min_size=min_size_regions)
napari_viewer1.add_labels(img_labels_filt, name='labels_filt')

# %%
# Remove regions touching the borders
img_labels_filt_no_borders = clear_border(img_labels_filt)
napari_viewer1.add_labels(img_labels_filt_no_borders, name='labels_filt_no_borders')

# %%
# Obtain cell properties:
properties = regionprops_table(
    img_labels_filt_no_borders,
    properties = {'label', 'area'}
)

# %%
# Print areas for each cell:
properties = pd.DataFrame(properties)
areas = properties
print(areas)

# %% [markdown]
# ### Define a workflow using function

# %%
# define workflow function

def noisy_nuclei_quantification(
    image, 
    viewer, 
    radius=3,
    thr=25,
    min_size_holes=100,
    connectivity=2,
    min_size_regions=100,
):
    
    # Apply local mean filter
    disk_radius = disk(radius)
    image_denoise = mean(image, disk_radius)
    viewer.add_image(image_denoise, name='denoise')
    
    # Binarize the image:
    image_binary = image_denoise > thr
    viewer.add_labels(image_binary, name='binary')
    
    # Fill small holes
    img_binary_fill = remove_small_holes(image_binary, area_threshold=min_size_holes)
    viewer.add_labels(img_binary_fill, name='filled')
    
    # Find labels:
    img_labels = label(img_binary_fill, connectivity=connectivity)
    viewer.add_labels(img_labels, name='labels')
    
    # Remove small regions
    img_labels_filt = remove_small_objects(img_labels, min_size=min_size_regions)
    viewer.add_labels(img_labels_filt, name='labels_filt')
    
    # Remove regions touching the borders
    img_labels_filt_no_borders = clear_border(img_labels_filt)
    viewer.add_labels(img_labels_filt_no_borders, name='labels_filt_no_borders')
    
    # Obtain cell properties:
    properties = regionprops_table(
        img_labels_filt_no_borders,
        properties = {'label', 'area'}
    )
    
    # Print areas for each cell:
    properties = pd.DataFrame(properties)
    areas = properties
    print(areas)

    return properties

# %%
# Instantiate the napari viewer
napari_viewer2 = napari.Viewer()

# Read and inspect the image
fpath = 'https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_noisy_large.tif'
image2, axes_image2, scales_image2, units_image2 = open_ij_tiff(fpath)
napari_viewer2.add_image(image2, name='image2')

# %%
# Run workflow
properties = noisy_nuclei_quantification(image2, napari_viewer2)

# %%






Assessment


Explanations




Follow-up material

Recommended follow-up modules:

Learn more: