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, save_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
import os
import numpy as np

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

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

# Read and inspect the image:
fpath = 'https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit__nuclei_noisy_small.tif'
image, axes_image, scales_image, units_image = open_ij_tiff(fpath)
napari_viewer.add_image(image)

# %%
# Apply local mean filter
radius = 3

disk_radius = disk(radius)
denoised_image = mean(image, disk_radius)
napari_viewer.add_image(denoised_image)

# %%
# Binarize the image:
thr = 25

binary_image = denoised_image > thr
napari_viewer.add_labels(binary_image)

# %%
# Fill small holes
min_size_holes = 100

filled_binary_image = remove_small_holes(binary_image, area_threshold=min_size_holes)
napari_viewer.add_labels(filled_binary_image)

# %%
# Find labels
connectivity = 2

label_mask = label(filled_binary_image, connectivity=connectivity)
napari_viewer.add_labels(label_mask)

# %%
# Remove small regions
min_size_regions = 100

filtered_label_mask = remove_small_objects(label_mask, min_size=min_size_regions)
napari_viewer.add_labels(filtered_label_mask)

# %%
# Remove regions touching the borders
filtered_label_mask_no_borders = clear_border(filtered_label_mask)
napari_viewer.add_labels(filtered_label_mask_no_borders)

# %%
# Print areas for each cell:
properties = pd.DataFrame(regionprops_table(filtered_label_mask_no_borders,
                properties = {'label', 'area'}))
print(properties)

# %%
# Find the basename of the input file from input file path
file_name = os.path.basename(fpath)
base_name = os.path.splitext(file_name)[0]
print(base_name)

# %%
# Save final label mask as tif file
mask_save_name = base_name+"_labelmask.tif"
save_ij_tiff(mask_save_name, filtered_label_mask_no_borders.astype(np.uint8), axes_image, scales_image, units_image)
print(f"The label mask is saved as : {mask_save_name}")

# %%
# Save object properties dataframe as csv file
table_save_name = base_name+"_intensity_measurements.csv"
properties.to_csv(table_save_name, index=False)
print(f"The object properties are saved as : {table_save_name}")
# %%
# Repeat the steps on the second image

# %%






Assessment


Explanations




Follow-up material

Recommended follow-up modules:

Learn more: