Particle Analysis
This example chains several classic image processing algorithms, and extracts features of particles contained in a grayscale image.
The first step of this example consists in binarizing the input grayscale image.
The ThresholdingByCriterion algorithm is used to set all pixels with an intensity greater than 128 to 1
and the rest to 0.
Then the SeparateObjects algorithm is applied to disconnect the connected particles. This method computes a watershed on the distance map to detect isthmuses joining adjacent particles.
Many particles are touching the border of the image field. Since their entire shapes are unknown, these objects would generate a bias when measuring individual features on the particles. Therefore, the BorderKill algorithm is used to remove these objects.
Last, a labelization step is performed to assign a unique value to each set of connected pixels. In this way all particles are identified.
Figure 1. Particle segmentation (a) the initial image, (b) the thresholding result,
(c) watershed based separation and (d) final image after rejecting border objects and labelization
Once this segmentation phase is done, the analysis algorithm is launched after having selected four measurements to compute:
Note:
More than 250 other measurements are also available and described in the Native Measurements section of the ImageDev Reference Guide.
See also
Then the SeparateObjects algorithm is applied to disconnect the connected particles. This method computes a watershed on the distance map to detect isthmuses joining adjacent particles.
Many particles are touching the border of the image field. Since their entire shapes are unknown, these objects would generate a bias when measuring individual features on the particles. Therefore, the BorderKill algorithm is used to remove these objects.
Last, a labelization step is performed to assign a unique value to each set of connected pixels. In this way all particles are identified.
![]() (a) |
![]() (b) |
![]() (c) |
![]() (d) |
Once this segmentation phase is done, the analysis algorithm is launched after having selected four measurements to compute:
- The X and Y center positions of the particles (the origin is the image top-left corner).
- The equivalent diameter of the particles (that is, the diameter of disks of same area).
- Their mean intensity in the original image.
| Number of particles = 58 | ||||
| Particle | BarycenterX | BarycenterY | EquivalentDiameter | IntensityMean |
| 1 | 395.75 | 39.01 | 49.46 | 206.28 |
| 2 | 105.89 | 48.58 | 50.75 | 225.12 |
| 3 | 248.53 | 56.56 | 61.04 | 227.68 |
| 4 | 192.00 | 63.94 | 52.13 | 212.79 |
| 5 | 469.05 | 74.31 | 63.93 | 229.77 |
Note:
More than 250 other measurements are also available and described in the Native Measurements section of the ImageDev Reference Guide.
#include <ImageDev/Data/AnalysisMsr.h>
#include <ImageDev/Data/NativeMeasurements.h>
#include <ImageDev/ImageDev.h>
#include <ioformat/IOFormat.h>
#include <iolink/view/ImageViewProvider.h>
using namespace imagedev;
using namespace ioformat;
using namespace iolink;
int
main()
{
// ImageDev library initialization
if ( imagedev::isInitialized() == false )
imagedev::init();
// Open a standard tif file and display the image properties
auto imageInput = readImage( std::string( IMAGEDEVDATA_IMAGES_FOLDER ) + "balls.tif" );
// Threshold the grayscale image
auto imageThr =
thresholdingByCriterion( imageInput, ThresholdingByCriterion::ComparisonCriterion::GREATER_THAN, 128 );
// Separate connected particles
auto imageSep = separateObjects( imageThr,
2,
SeparateObjects::OutputType::SEPARATED_OBJECTS,
SeparateObjects::AlgorithmMode::REPEATABLE,
SeparateObjects::Neighborhood::CONNECTIVITY_26 );
imageThr.reset();
// Remove particles touching image borders
auto imageBdk = borderKill( imageSep, BorderKill::Neighborhood::CONNECTIVITY_26 );
imageSep.reset();
// Connected component labeling of a binary image
auto imageLab =
labeling2d( imageBdk, Labeling2d::LabelType::LABEL_8_BIT, Labeling2d::Neighborhood::CONNECTIVITY_8 );
// The number of particles is the maximum label
auto extrema = intensityExtrema( imageLab, 0 );
int particleCount = ( int )extrema->maximum( 0, 0, 0 );
// Define the analysis features to be computed
AnalysisMsr::Ptr analysis = std::make_shared< AnalysisMsr >();
auto centerX = analysis->select( NativeMeasurements::barycenterX );
auto centerY = analysis->select( NativeMeasurements::barycenterY );
auto diameter = analysis->select( NativeMeasurements::equivalentDiameter );
auto intensity = analysis->select( NativeMeasurements::intensityMean );
// Launch the feature extraction on the segmented image
labelAnalysis( imageLab, imageInput, analysis );
std::cout << "Number of particles = " << particleCount << std::endl;
std::cout << "Particle\t" << centerX->name() << "\t" << centerY->name() << "\t"
<< diameter->name() + "\t" + intensity->name() << std::endl;
// Print the analysis results for 10% of the particles
for ( int i = 0; i < ( int )( particleCount / 10 ); i++ )
{
std::cout << ( i + 1 ) << "\t\t" << centerX->value( i ) << "\t\t" << centerY->value( i ) << "\t\t"
<< diameter->value( i ) << "\t\t\t" << intensity->value( i ) << std::endl;
}
// Save the created image with IOFormat
writeView( imageLab, R"(T06_01_output.png)" );
// ImageDev library finalization
imagedev::finish();
return 0;
}
using System;
using ImageDev;
using IOLink;
using IOFormat;
namespace T06_01_ParticleAnalysis
{
class Program
{
static void Main( string[] args )
{
// ImageDev library initialization
Initialization.Init();
// Open a standard tif file and display the image properties
ImageView imageInput = ViewIO.ReadImage( @"Data/images/polystyrene.tif" );
// Threshold the grayscale image
ImageView imageThr = Processing.ThresholdingByCriterion(
imageInput, ThresholdingByCriterion.ComparisonCriterion.GREATER_THAN, 128 );
// Separate connected particles
ImageView imageSep = Processing.SeparateObjects( imageThr, 2 );
imageThr.Dispose();
// Remove particles touching image borders
ImageView imageBdk = Processing.BorderKill( imageSep );
imageSep.Dispose();
// Connected component labeling of a binary image
var imageLab = Processing.Labeling2d( imageBdk,
Labeling2d.LabelType.LABEL_8_BIT,
Labeling2d.Neighborhood.CONNECTIVITY_8 );
// The number of particles is the maximum label
var extrema = Processing.IntensityExtrema( imageLab, 0 ) as IntensityExtremaMsr;
int particleCount = ( int )extrema.maximum( 0, 0, 0 );
// Define the analysis features to be computed
AnalysisMsr analysis = new AnalysisMsr();
var centerX = analysis.Select( NativeMeasurements.BarycenterX );
var centerY = analysis.Select( NativeMeasurements.BarycenterY );
var diameter = analysis.Select( NativeMeasurements.EquivalentDiameter );
var intensity = analysis.Select( NativeMeasurements.IntensityMean );
// Launch the feature extraction on the segmented image
Processing.LabelAnalysis( imageLab, imageInput, analysis );
Console.WriteLine( "Number of particles = " + particleCount );
Console.WriteLine( "Particle\t" + centerX.Name() + "\t" + centerY.Name() + "\t" + diameter.Name() + "\t" +
intensity.Name() );
// Print the analysis results for 10% of the particles
for ( int i = 0; i < ( int )( particleCount / 10 ); i++ )
{
Console.WriteLine( ( i + 1 ) + "\t\t" + centerX.Value( i ).ToString( "0.00" ) + "\t\t" +
centerY.Value( i ).ToString( "0.00" ) + "\t\t" +
diameter.Value( i ).ToString( "0.00" ) + "\t\t\t" +
intensity.Value( i ).ToString( "0.00" ) );
}
// Save the created image with IOFormat
ViewIO.WriteView( imageBdk, @"T06_01_output.png" );
// ImageDev library finalization
Initialization.Finish();
}
}
}
import imagedev
import imagedev_data
import ioformat
# Initialize the ImageDev library if not done
if (imagedev.is_initialized() == False): imagedev.init()
# Open and display a tif file
image_input = ioformat.read_image(imagedev_data.get_image_path("polystyrene.tif"))
# Threshold the grayscale image
image_thr = imagedev.thresholding_by_criterion(\
image_input, imagedev.ThresholdingByCriterion.ComparisonCriterion.GREATER_THAN, 128)
# Separate connected particles
image_sep = imagedev.separate_objects(image_thr, 2)
image_thr = None
# Remove particles touching image borders
image_bdk = imagedev.border_kill(image_sep)
image_sep = None
# Connected component labeling of a binary image
image_lab = imagedev.labeling_2d(\
image_bdk, imagedev.Labeling2d.LabelType.LABEL_8_BIT, imagedev.Labeling2d.Neighborhood.CONNECTIVITY_8)
# The number of particles is the maximum label
extrema = imagedev.intensity_extrema(image_lab, 0)
particle_count = int(extrema.maximum(0, 0, 0))
# Define the analysis features to be computed
analysis = imagedev.AnalysisMsr()
center_x = analysis.select(imagedev.native_measurements.BarycenterX)
center_y = analysis.select(imagedev.native_measurements.BarycenterY)
diameter = analysis.select(imagedev.native_measurements.EquivalentDiameter)
intensity = analysis.select(imagedev.native_measurements.IntensityMean)
# Launch the feature extraction on the segmented image
imagedev.label_analysis(image_lab, image_input, analysis)
print("Number of particles = " + str(particle_count))
print("Particle\t" + center_x.name + "\t" + center_y.name + "\t" + diameter.name + "\t" + intensity.name)
# Print the analysis results for 10% of the particles
for i in range(int(particle_count/10)):
print(str(i+1) + '\t\t\t' + "{:.2f}".format(center_x.value(i)) +'\t\t' + "{:.2f}".format(center_y.value(i)) +\
'\t\t' + "{:.2f}".format(diameter.value(i)) + '\t\t\t\t' + "{:.2f}".format(intensity.value(i)))
# Save the created image with IOFormat
ioformat.write_view(image_lab, "T06_01_output.png")
# ImageDev library finalization
imagedev.finish() See also



