ImageDev

- Getting Started
- ImageDev Python
- ImageDev C#
- ImageDev C++
- Licensing Information
- Redistributable Files
- Processing
- Image Manipulation
- - Synthetic Images
  - - RandomImage2d
    - RandomImage3d
    - RandomGaussianImage2d
    - RandomGaussianImage3d
    - RadialFunctionImage2d
    - RadialFunctionImage3d
    - OrientedLinesImage2d
    - OrientedLinesImage3d
    - RandomSphereImage3d
    - BoxGridImage2d
    - BoxGridImage3d
    - WedgeMaskImage3d
    - PointSpreadFunctionImage3d
  - Image Editing
  - - ConvertImage
    - ResetImage
    - CopyImage
    - PasteImage2d
    - PasteImage3d
    - CropImage2d
    - CropImage3d
    - SampleImage2d
    - SampleImage3d
    - InterlaceFrames2d
    - DeinterlaceFrames2d
  - Stack Operations
  - - SequenceConcatenation
    - SequenceToMultispectral
    - SequenceToVolume
    - VolumeToSequence
    - MultispectralToSequence
    - GetSliceFromVolume3d
    - SetSliceToVolume3d
    - GetObliqueSliceFromVolume3d
    - ImageStackProjection3d
    - VolumeProjection3d
- Geometry And Matching
- - Geometric Transforms
  - - Shearing3d
    - TranslateImage2d
    - TranslateImage3d
    - Flip2d
    - Flip3d
    - FlipImage2d
    - FlipImage3d
    - TransposeImage2d
    - TransposeImage3d
    - SwapQuadrants
    - Rotate2d
    - Rotate3d
    - RotationGenerator3d
    - Resize2d
    - Resize3d
    - RescaleByFactor
    - RescaleImageByFactor
    - ResampleAffine2d
    - ResampleAffine3d
    - ResampleElastic2d
    - SurfaceUnfolding3d
  - Pattern Recognition
  - - BinaryCorrelation2d
    - CrossCorrelation2d
    - CorrelationByDifference2d
    - CorrelationBySignChange2d
  - Registration
  - - ImagePrealignment3d
    - AffineRegistration
    - ElasticRegistration2d
- Arithmetic And Logic
- - Arithmetical Operations
  - - AbsoluteValue
    - AbsoluteMaximumWithImage
    - ArithmeticOperationWithImage
    - ArithmeticOperationWithValue
    - BlendWithImage
    - BlendWithValue
    - DirectionalBlending2d
    - CombineByMask
    - ImageFormula
    - EigenDecomposition2d
    - EigenDecomposition3d
  - Logical Operations
  - - MaskImage
    - LogicalOperationWithImage
    - LogicalOperationWithValue
    - LogicalNot
    - InvertImage
    - BitShift
- Image Filtering
- - Grayscale Transforms
  - - RescaleIntensity
    - StackRescaleIntensity
    - MatchContrast
    - HistogramEqualization
    - Clahe2d
    - Clahe3d
    - AdaptiveHistogramEqualization
    - ApplyGrayscaleLut
    - BackgroundImage2d
    - RadialBackgroundImage2d
    - ShadingCorrection
    - Exponential
    - Logarithm
    - Square
    - SquareRoot
    - RingArtifactRemoval3d
    - BeamHardeningCorrection3d
  - Color Transforms
  - - ColorToLightness
    - GetColorChannel
    - ColorToGrayscale
    - GrayscaleToColor
    - ColorSpaceConversion
    - ColorAntialiasing2d
    - BayerToRgb2d
    - DecorrelationStretch2d
    - BlendAlphaChannelWithImage
    - BlendAlphaChannelWithValue
  - Smoothing And Denoising
  - - BoxFilter2d
    - BoxFilter3d
    - MedianFilter2d
    - MedianFilter3d
    - GaussianFilter2d
    - GaussianFilter3d
    - ExponentialFilter2d
    - RecursiveExponentialFilter2d
    - RecursiveExponentialFilter3d
    - NonLocalMeansFilter2d
    - NonLocalMeansFilter3d
    - CurvatureDrivenDiffusion
    - BilateralFilter2d
    - BilateralFilter3d
    - MajorityFilter2d
    - MajorityFilter3d
    - SigmaFilter2d
    - SigmaFilter3d
    - Despeckle2d
    - Despeckle3d
    - NagaoFilter2d
    - NagaoFilter3d
    - SnnFilter2d
    - SnnFilter3d
    - FlowInpainting
  - Sharpening
  - - Deblurring2d
    - Delineate2d
    - Delineate3d
    - UnsharpMasking2d
    - UnsharpMasking3d
    - Shade2d
    - RichardsonLucyDeconvolution
  - Texture Filters
  - - MeanFilter2d
    - MeanFilter3d
    - VarianceFilter2d
    - VarianceFilter3d
    - SkewnessFilter2d
    - SkewnessFilter3d
    - KurtosisFilter2d
    - KurtosisFilter3d
    - ContrastFilter2d
    - ContrastFilter3d
    - VariationFilter2d
    - VariationFilter3d
    - EnergyFilter2d
    - EnergyFilter3d
    - EntropyFilter2d
    - EntropyFilter3d
    - AutocorrelationFilter
    - LocalAutocorrelationFilter2d
    - ImageCurvature2d
    - ImageCurvature3d
    - StructureEnhancementFilter2d
    - StructureEnhancementFilter3d
    - ScaleSpaceLogFilter2d
    - ScaleSpaceLogFilter3d
  - Frequency Domain
  - - ComplexFft
    - ComplexFftInverse
    - ComplexCenteredFft
    - ComplexCenteredFftInverse
    - CartesianToPolar2d
    - PolarToCartesian2d
    - ConvolutionWithImage
    - ConvolutionWithImage2d
    - RadialFrequencyFilter2d
    - GaborFiltering2d
- Edge Detection
- - Gradient
  - - GradientOperator2d
    - GradientOperator3d
    - VectorToMagnitude2d
    - VectorToMagnitude3d
    - GradientVector2d
    - GradientVector3d
    - GradientMagnitude2d
    - GradientMagnitude3d
    - MorphologicalGradient2d
    - MorphologicalGradient3d
    - RadialGradientCentered2d
    - RadialGradientCentered3d
    - RadialGradientContour2d
    - RadialGradientContour3d
    - RadialGradientLabel2d
    - RadialGradientLabel3d
  - Laplacian
  - - DobFilter2d
    - DobFilter3d
    - MorphologicalLaplacian
    - RecursiveLaplacian2d
    - RecursiveLaplacian3d
  - Other Derivatives
  - - GaussianDerivative2d
    - GaussianDerivative3d
    - GaussianHessianMatrix2d
    - GaussianHessianMatrix3d
    - GaussianGradientTensor2d
    - GaussianGradientTensor3d
  - Edge Marking
  - - ImageLocalMaxima2d
    - ImageLocalMaxima3d
    - RidgeDetection
    - ZeroCrossings2d
    - DijkstraShortestPath2d
    - EigenvaluesToStructureness2d
    - EigenvaluesToStructureness3d
    - TensorVoting2d
    - CannyEdgeDetector2d
    - CannyEdgeDetector3d
- Image Segmentation
- - Binarization
  - - Thresholding
    - ThresholdingByCriterion
    - AutoThresholdingBright
    - AutoThresholdingDark
    - ColorThresholding
    - TopHat
    - FeatureAdaptiveThresholding
    - AdaptiveThresholding2d
    - AdaptiveThresholding3d
    - HysteresisThresholding
  - Labeling
  - - Labeling2d
    - Labeling3d
    - ReorderLabels
    - AssignLabel
    - AddObjectToLabel
    - GroupCloseLabelsl2d
  - Feature Selection
  - - KillBorder2d
    - KillBorder3d
    - RemoveSmallSpots
    - ObjectBoundaries
    - BinarySmoothing2d
    - BinarySmoothing3d
    - LabelInterfaces
  - Separating And Filling
  - - FillHoles2d
    - FillHoles3d
    - RemoveSmallHoles
    - FillImageBorder3d
    - ExpandLabelBoundaries
    - SeparateObjects
    - HExtremaWatershed
    - SeparateObjectsByBlock
    - HExtremaWatershedByBlock
    - ConvexHull2d
  - Region Growing
  - - MarkerBasedWatershed2d
    - MarkerBasedWatershed3d
    - MarkerBasedWatershedByBlock
    - MarkerBasedWatershedBasinsByBlock
    - ExpandLabelsOnBackground
    - FloodFillThreshold2d
    - FloodFillThreshold3d
    - Waterpixel
  - Computational Geometry
  - - PolylineResampler2d
    - PolylineResampler3d
    - PolylineExtrusion3d
    - SurfaceFitting3d
- Mathematical Morphology
- - Erosion And Dilation
  - - Erosion2d
    - Erosion3d
    - Dilation2d
    - Dilation3d
    - ErosionDisk2d
    - ErosionDisk3d
    - ErosionBall3d
    - DilationDisk2d
    - DilationDisk3d
    - DilationBall3d
    - ErosionLine2d
    - ErosionLine3d
    - DilationLine2d
    - DilationLine3d
    - ErosionColor2d
    - DilationColor2d
    - SelectiveErosion2d
    - SelectiveErosion3d
    - SelectiveDilation2d
    - SelectiveDilation3d
  - Opening And Closing
  - - Opening2d
    - Opening3d
    - Closing2d
    - Closing3d
    - OpeningDisk2d
    - OpeningDisk3d
    - OpeningBall3d
    - ClosingDisk2d
    - ClosingDisk3d
    - ClosingBall3d
    - OpeningLine2d
    - OpeningLine3d
    - ClosingLine2d
    - ClosingLine3d
    - OpeningLineMaximum
    - ClosingLineMinimum
    - OpeningColor2d
    - ClosingColor2d
    - OpeningByReconstruction2d
    - OpeningByReconstruction3d
    - ClosingByReconstruction2d
    - ClosingByReconstruction3d
    - OpeningDiskByReconstruction2d
    - OpeningBallByReconstruction3d
    - ClosingDiskByReconstruction2d
    - ClosingBallByReconstruction3d
    - SelectiveOpening2d
    - SelectiveOpening3d
    - SelectiveClosing2d
    - SelectiveClosing3d
  - Hit Or Miss And Skeleton
  - - Skeleton
    - Centerline3d
    - InfluenceZones
    - Centroid2d
    - EndPoints2d
    - EndPoints3d
    - Pruning2d
    - Pruning3d
    - IsolatedPoints2d
    - IsolatedPoints3d
    - TriplePoints2d
    - InteriorPoints2d
    - ApplyMorphologicalLut2d
    - ApplyMorphologicalLut3d
  - Geodesic Transformations
  - - ReconstructionFromMarkers2d
    - ReconstructionFromMarkers3d
    - GrayscaleReconstruction2d
    - GrayscaleReconstruction3d
    - GrayscaleFillHoles2d
    - GrayscaleFillHoles3d
    - HExtrema2d
    - HExtrema3d
    - RegionalExtrema2d
    - RegionalExtrema3d
    - UltimateErosion
  - Distance Maps
  - - DistanceMap2d
    - DistanceMap3d
    - EuclideanDistanceMap3d
    - EuclideanDistanceMap2d
    - GeodesicDistanceMap
    - ClosestBoundaryPoints2d
    - ClosestBoundaryPoints3d
    - TimeMap
    - LocalThicknessMap3d
    - GeodesicPropagation2d
    - GeodesicMaxima
    - GeodesicMinima
- Image Analysis
- - Image Measurements
  - - BinaryAnalysis
    - Area2d
    - Area3d
    - IntensityIntegral2d
    - IntensityIntegral3d
    - ObjectCount
    - DistanceMean2d
    - EulerNumber2d
    - EulerNumber3d
    - FractalDimension
    - CurvatureIntegrals3d
  - Image Statistics
  - - IntensityExtrema
    - IntensityStatistics
    - IntensityMaskedStatistics
    - IntensityHistogram
    - IntensityBinHistogram
    - AutoThresholdingValue
    - CompareImage
    - CompareValue
    - SimilarityMetricValue
    - SsimValue2d
    - SsimValue3d
    - RegionSimilarity
    - RadialIntensityProfile2d
    - RadialIntensityProfile3d
    - CylindricalIntensityProfile3d
    - RadialAutocorrelationProfile
    - Cooccurrence2d
    - MeasureGaussianNoise
    - LocalMaxima2d
    - LocalMaxima3d
    - PeakExtractor
    - LocalDensityMap2d
    - LocalDensityMap3d
    - SegmentationMetrics
  - Individual Measurements
  - - VolumeFractionByLabel
    - LabelCountByRegion
    - VolumeFractionByRegion
    - LabelAnalysis
    - LabelFiltering
    - LabelFilteringAnalysis
    - FilterAnalysis
    - MeasurementToImage
    - FilterByMeasurement
    - SieveLabeling
  - Moments And Orientations
  - - MomentsOfInertia2d
    - MomentsOfInertia3d
    - InertiaMoment2d
    - Eccentricity2d
    - OrientationMapFourier3d
    - OrientationMatchingFourier3d
  - Morphometry
  - - ObjectVolume
    - TotalImageVolume
    - ObjectBoundaryArea
    - TotalImageBoundaryArea
    - AverageObjectAreaPerSlice3d
    - AverageObjectCountPerSlice3d
    - ObjectImageRatio
    - ObjectSpecificSurface
    - ObjectSurfaceDensity
    - PorosityPercentage3d
    - FragmentationIndex
    - StructureModelIndex3d
    - AverageObjectThickness3d
    - AverageSpaceThickness3d
    - ObjectLinearDensity3d
    - DegreeOfAnisotropy
  - Propagation
  - - AxisPropagationBinary3d
    - AxisPropagationBinaryWithSeeds3d
    - AxisPropagationLabel3d
    - AxisPropagationLabelWithSeeds3d
    - AxisPropagationDistance3d
    - EstimateShortestPath3d
    - PercolationThreshold3d
    - AxisConnectivity3d
    - CentroidPathTortuosity3d
- Machine Learning
- - Classification
  - - AutoSegmentation3Phases
    - AutoIntensityClassification
    - SupervisedTextureClassification2d
    - SupervisedTextureClassification3d
    - TextureClassificationCreate
    - TextureClassificationTrain
    - TextureClassificationApply
  - Deep learning
  - - OnnxPredictionFiltering2d
    - OnnxPredictionFiltering3d
    - OnnxPredictionSegmentation2d
    - OnnxPredictionSegmentation3d
    - OnnxPredictionTwoOutputs2d
    - OnnxPredictionTwoOutputs3d
- Business Application
- - Dental
  - - DentalWallSegmentation3d
    - DentalMeshExtraction3d
    - DentalUnfolding3d
- IO
- - Image Data
  - - ReadVipImage
    - WriteVipImage
  - Algebraic Data
  - - ReadArrayXd
    - ReadArrayXi64
    - WriteArrayXd
    - WriteArrayXi64
  - Model
  - - ReadOnnxModel
- CUDA
- - Geometry And Matching
  - - CudaCrossCorrelation2d
    - CudaTemplateMatching3d
  - Image Filtering
  - - CudaBoxFilter2d
    - CudaMedianFilter2d
    - CudaGaussianFilter3d
    - CudaAdaptiveManifoldNlmFilter
  - Mathematical Morphology
  - - CudaErosion2d
    - CudaDilation2d
    - CudaOpening2d
    - CudaClosing2d
- Utilities
- Initialization
- Processing
- Syntax Check
- Visualization
- Data Object
- Image Data
- Kernels
- - MorphologicalKernel2d
  - MorphologicalKernel3d
  - MorphologicalLut2d
  - MorphologicalLut3d
- Model
- - TextureClassificationModel
  - OnnxModel
- Global Measurements
- - Image Measurements
  - - Area2dMsr
    - Area3dMsr
    - IntensityIntegral2dMsr
    - IntensityIntegral3dMsr
    - ObjectCountMsr
    - DistanceMeanMsr
    - Euler2dMsr
    - Euler3dMsr
    - FractalMsr
    - CurvatureIntegralsMsr
  - Image Statistics
  - - IntensityExtremaMsr
    - StatisticsMsr
    - IntensityHistogramMsr
    - IntensityBinHistogramMsr
    - CompareMsr
    - SimilarityMetricValueMsr
    - SsimValueMsr
    - RegionSimilarityMsr
    - SegmentationMetricsObjectMsr
    - SegmentationMetricsImageMsr
    - CylindricalIntensityProfileMsr
    - RadialIntensityProfileMsr
    - CorrelationMsr
    - CoocurrrenceMsr
    - GaussianNoiseMsr
    - LocalMaximaMsr
    - PeakExtractorMsr
  - Individual Measurements
  - - VolumeFractionByLabelMsr
    - LabelCountByRegionMsr
    - VolumeFractionByRegionMsr
  - Moments And Orientations
  - - MomentsOfInertia2dMsr
    - MomentsOfInertia3dMsr
    - MeanInertiaMomentMsr
    - Eccentricity2dMsr
    - LocalOrientation3dMsr
  - Morphometry
  - - ObjectVolumeMsr
    - ImageVolumeMsr
    - ObjectBoundaryAreaMsr
    - ImageBoundaryAreaMsr
    - AverageObjectAreaPerSliceMsr
    - AverageObjectCountPerSliceMsr
    - ObjectImageRatioMsr
    - ObjectSpecificSurfaceMsr
    - ObjectSurfaceDensityMsr
    - PorosityPercentageMsr
    - FragmentationMsr
    - StructureModelIndexMsr
    - AverageObjectThicknessMsr
    - AverageSpaceThicknessMsr
    - ObjectLinearDensityMsr
    - AnisotropyDegreeMsr
  - Propagation
  - - AxisPropagationBinary3dMsr
    - AxisPropagationLabel3dMsr
    - AxisPropagationDistance3dMsr
    - PercolationThreshold3dMsr
    - CentroidTortuosity3dMsr
  - Geometry And Matching
  - - CrossCorrelation2dMsr
  - Edge Detection
  - - CannyEdgeDetectorMsr
  - Image Segmentation
  - - AutoThresholdingMsr
    - AutoSegmentation3PhasesMsr
  - Mathematical Morphology
  - - ApplyMorphologicalLutMsr
- Analysis Measurements
- - AnalysisMsr
  - Measurement
  - Native Measurements
  - - Geometry
    - Feret
    - Intensity
    - Histogram
    - Inertia
    - Locate
    - Segment
    - Cooccurrence
  - CustomMeasurement
  - MeasurementAttributes
  - - Quantile
    - Histogram
    - Feret2d
    - Feret3d
    - NeighborCount
    - Cooccurrence
    - Breadth3d
  - MeasurementGroup
- Measurement Information
- - FieldInformation
  - CustomMeasurementInfo
- CUDA
- - CudaContext
- User Guide
- First Steps
- - Hello ImageDev
  - Initialize ImageDev
- Image Management
- - Create 2D Image
  - Create 3D Image
  - Data Transfer
- Standard Processing
- - Denoising
  - Thresholding
- Image Analysis
- - Global Analysis
  - Label Analysis
  - Measurement Browsing
  - Measurement Group
  - Custom Analysis
- Advanced Processing
- - Algorithm Tracking
- Typical Use Cases
- - Particle Analysis
  - Pore Analysis

# Pore Analysis

This example chains several classical image processing algorithms on a three-dimensional digital rocks image. It extracts several features characterizing the pores contained in shale rock volume.
The input image is a 3D sample of shale rock made up of minerals, organic matter, and pores.
Figure 1. Original image with (red) pores, (green) organic-matter grains, and (blue) minerals
First, this example applies the BilateralFilter3d to denoise the input image while preserving the edges of the different materials.
Figure 2. Denoised image
A first thresholding operation is done to isolate the darker objects corresponding to the pores.
Figure 3. Rough binarization of the pores
An erosion is performed to isolate the pores from the organic matters and build pore markers.
Figure 4. Eroded pore markers
A second thresholding is applied to detect the minerals.
Figure 5. Rough binarization of the minerals
Then, the AddObjectToLabel algorithm is applied to create a label image that merges pore markers as label 1 with background markers as label 2.
Figure 6. Merged markers: (light blue) pores and (dark blue) organic matters and minerals
The denoised image is processed by the GradientOperator3d algorithm to reveal the edges of the different phases.
Figure 7. Gradient of the denoised image
This gradient image can be used as a landscape image for the watershed algorithm. MarkerBasedWatershed3d is applied on this image with the merged markers image to detect the separation surfaces between pores and background.
The FillHoles2d algorithm is applied on each 2D slice of the resulting volume to fill the pores. A BorderKill operation is also applied to remove pores that are not entirely visible in the image.
Figure 8. Result after filling pores and rejecting pores intersecting the image borders
The image is labeled to assign the same intensity to all voxels belonging to the same connected pore.
Figure 9. Original image and labeled pores
Some global measurements are computed to extract the number of detected pores and the phase fraction represented by the pores.

- Pore volume fraction = 1.84%
- Pore number = 114

Finally, three measurements are computed for each pore: its volume, the diameter of a sphere of the same volume, and a shape factor indicating if the pore is spherical or not. The corresponding results are displayed in the console for the first pores of the volume.

Pore | EquivalentDiameter | Volume3d | InverseSphericity3d
1 | 11.43 | 781.90 | 2.12
2 | 13.42 | 1266.16 | 1.72
3 | 6.91 | 172.88 | 1.14
4 | 9.27 | 558.92 | 1.89

    #include <ImageDev/ImageDev.h>
    #include <ioformat/IOFormat.h>
    #include <string.h>

    using namespace imagedev;
    using namespace ioformat;
    using namespace iolink;

    int
    main( int argc, char* argv[] )
    {
        int status = 0;

        try
        {
            // Initialize the ImageDev library if not done
            if ( imagedev::isInitialized() == false )
                imagedev::init();

            // Open a standard tif file and display the image properties
            auto imageInput = readImage( std::string( IMAGEDEVDATA_IMAGES_FOLDER ) + "shale.am" );

            // Preprocess the input with a bilateral filter for denoising
            auto imageDenoised = bilateralFilter3d( imageInput, 3, 3, 3, 20, BilateralFilter3d::BILATERAL );

            // STEP 1: Pore segmentation with the watershed algorithm
            // Threshold the low graylevels (pore markers)
            auto imageLowBin =
                thresholdingByCriterion( imageDenoised, ThresholdingByCriterion::ComparisonCriterion::LESS_THAN, 40 );

            // Slightly erode these markers in order to avoid contact with the high level markers
            auto imageLowEro = erosion3d( imageLowBin, 1, Erosion3d::CONNECTIVITY_26 );

            // Initialize the marker image (label 1)
            auto imageLowLab = convertImage( imageLowEro, ConvertImage::LABEL_16_BIT );

            // Threshold the high graylevels (mineral markers)
            auto imageHighBin =
                thresholdingByCriterion( imageDenoised, ThresholdingByCriterion::ComparisonCriterion::GREATER_THAN, 90 );

            // Merge high intensity markers with label 2
            auto imageMarkers = addObjectToLabel( imageHighBin, imageLowLab, 2 );

            // Extract the gradient to build the relief image for watershed
            auto resultGrad = gradientMagnitude3d( imageDenoised,
                                                   GradientMagnitude3d::GAUSSIAN,
                                                   { 0.9, 0.9, 0.9 },
                                                   GradientMagnitude3d::FilterMode::RECURSIVE,
                                                   GradientMagnitude3d::OutputType::SAME_AS_INPUT,
                                                   GradientMagnitude3d::GradientMode::AMPLITUDE_EUCLIDEAN );

            // Perform the Watershed to detect pore boundaries
            auto imageWatershed = markerBasedWatershed3d( resultGrad,
                                                          imageMarkers,
                                                          MarkerBasedWatershed3d::REPEATABLE,
                                                          MarkerBasedWatershed3d::LINES,
                                                          MarkerBasedWatershed3d::CONNECTIVITY_26 );

            // Fill all pores in each 2D slice of the volume
            imageWatershed->setAxesInterpretation( ImageTypeId::IMAGE_SEQUENCE );
            auto imageFilled = fillHoles2d( imageWatershed, FillHoles2d::CONNECTIVITY_4 );
            imageFilled->setAxesInterpretation( ImageTypeId::VOLUME );

            // Remove pores touching the image border
            auto imageInside = killBorder3d( imageFilled, KillBorder3d::Neighborhood::CONNECTIVITY_26 );

            // Assign labels to the pores
            auto imagePores = labeling3d( imageInside, Labeling3d::LABEL_16_BIT, Labeling3d::CONNECTIVITY_26 );

            // STEP 2: Pore quantification
            // Compute the pore volume percentage
            auto poreVolume = intensityIntegral3d( imageInside );
            std::cout << "  - Pore volume fraction = " << ( 100.0 * poreVolume->volumeFraction() ) << "%" << std::endl;

            // Compute the pore number
            auto poreCount = objectCount( imagePores );
            std::cout << "  - Pore number = " << poreCount.outputMeasurement->count( 0 ) << std::endl;

            // Define the analysis features to be computed
            AnalysisMsr::Ptr analysis = AnalysisMsr::Ptr( new AnalysisMsr() );
            auto diameter = analysis->select( NativeMeasurements::equivalentDiameter );
            auto volume = analysis->select( NativeMeasurements::volume3d );
            auto shape = analysis->select( NativeMeasurements::inverseSphericity3d );

            // Launch the feature extraction on the segmented image
            labelAnalysis( imagePores, NULL, analysis );
            std::cout << "Pore\t" << diameter->name() << "\t" << volume->name() << "\t" << shape->name() << std::endl;
            // Print the analysis results for 5% of the pores
            for ( int i = 0; i < ( int )( analysis->labelCount() / 20 ); i++ )
            {
                std::cout << ( i + 1 ) << "\t\t" << diameter->value( i ) << "\t\t\t" << volume->value( i ) << "\t\t"
                          << shape->value( i ) << std::endl;
            }

            // Save the segmented image with IOFormat
            writeView( imagePores, "T06_02_output.tif" );

            std::cout << "This example ran successfully." << std::endl;
        }
        catch ( const imagedev::Exception& error )
        {
            // Print potential exception in the standard output
            std::cerr << "ImageDev exception: " << error.what() << std::endl;
            status = -1;
        }

        // ImageDev library finalization
        imagedev::finish();

        // Check if we must ask for an enter key to close the program
        if ( !( ( argc == 2 ) && strcmp( argv[1], "--no-stop-at-end" ) == 0 ) )
            std::cout << "Press Enter key to close this window." << std::endl, getchar();

        return status;
    }

    using System;
    using ImageDev;
    using IOLink;
    using IOFormat;

    namespace T06_02_PoreAnalysis
    {
        class Program
        {
            static void Main(string[] args)
            {
                int status = 0;

                try
                {
                    // Initialize the ImageDev library if not done
                    if (Initialization.IsInitialized() == false)
                        Initialization.Init();

                    // Open a standard tif file and display the image properties
                    ImageView imageInput = IOFormat.ViewIO.ReadImage("Data/images/shale.am") as ImageView;

                    // Preprocess the input with a bilateral filter for denoising
                    ImageView imageDenoised =
                        Processing.BilateralFilter3d(imageInput, 3, 3, 3, 20, BilateralFilter3d.FilterMode.BILATERAL)
                            as ImageView;

                    // STEP 1: Pore segmentation with the watershed algorithm
                    // Threshold the low graylevels (pore markers)
                    ImageView imageLowBin = Processing.ThresholdingByCriterion(
                        imageDenoised, ThresholdingByCriterion.ComparisonCriterion.LESS_THAN, 40) as ImageView;

                    // Slightly erode these markers in order to avoid contact with the high level markers
                    ImageView imageLowEro =
                        Processing.Erosion3d(imageLowBin, 1, Erosion3d.Neighborhood.CONNECTIVITY_26) as ImageView;

                    // Initialize the marker image (label 1)
                    ImageView imageLowLab =
                        Processing.ConvertImage(imageLowEro, ConvertImage.OutputType.LABEL_16_BIT) as ImageView;

                    // Threshold the high graylevels (mineral markers)
                    ImageView imageHighBin = Processing.ThresholdingByCriterion(
                        imageDenoised, ThresholdingByCriterion.ComparisonCriterion.GREATER_THAN, 90) as ImageView;

                    // Merge high intensity markers with label 2
                    ImageView imageMarkers = Processing.AddObjectToLabel(imageHighBin, imageLowLab, 2) as ImageView;

                    // Extract the gradient to build the relief image for watershed
                    var resultGrad = Processing.GradientMagnitude3d(imageDenoised,
                                                   GradientMagnitude3d.GradientOperator.GAUSSIAN,
                                                   new double[] { 0.9, 0.9, 0.9 },
                                                   GradientMagnitude3d.FilterMode.RECURSIVE,
                                                   GradientMagnitude3d.OutputType.SAME_AS_INPUT,
                                                   GradientMagnitude3d.GradientMode.AMPLITUDE_EUCLIDEAN);

                    // Perform the Watershed to detect pore boundaries
                    ImageView imageWatershed =
                        Processing.MarkerBasedWatershed3d(resultGrad,
                                                           imageMarkers,
                                                           MarkerBasedWatershed3d.AlgorithmMode.REPEATABLE,
                                                           MarkerBasedWatershed3d.OutputType.LINES,
                                                           MarkerBasedWatershed3d.Neighborhood.CONNECTIVITY_26) as ImageView;

                    // Fill all pores in each 2D slice of the volume
                    imageWatershed.AxesInterpretation = ImageTypeId.IMAGE_SEQUENCE;
                    ImageView imageFilled = Processing.FillHoles2d(imageWatershed, FillHoles2d.Neighborhood.CONNECTIVITY_4);
                    imageFilled.AxesInterpretation = ImageTypeId.VOLUME;

                    // Remove pores touching the image border
                    ImageView imageInside =
                        Processing.KillBorder3d(imageFilled, KillBorder3d.Neighborhood.CONNECTIVITY_26) as ImageView;

                    // Assign labels to the pores
                    ImageView imagePores = Processing.Labeling3d(imageInside,
                                                                  Labeling3d.LabelType.LABEL_16_BIT,
                                                                  Labeling3d.Neighborhood.CONNECTIVITY_26) as ImageView;

                    // STEP 2: Pore quantification
                    // Compute the pore volume percentage
                    var poreVolume = Processing.IntensityIntegral3d(imageInside);
                    Console.WriteLine(
                        "  - Pore volume fraction = " + (100.0 * poreVolume.volumeFraction()).ToString("0.00") + " %");

                    // Compute the pore number
                    var poreCount = Processing.ObjectCount(imagePores);
                    Console.WriteLine("  - Pore number = " + poreCount.outputMeasurement.count(0));

                    // Define the analysis features to be computed
                    AnalysisMsr analysis = new AnalysisMsr();
                    var diameter = analysis.Select(NativeMeasurements.EquivalentDiameter);
                    var volume = analysis.Select(NativeMeasurements.Volume3d);
                    var shape = analysis.Select(NativeMeasurements.InverseSphericity3d);

                    // Launch the feature extraction on the segmented image
                    Processing.LabelAnalysis(imagePores, null, analysis);
                    Console.WriteLine("Pore\t" + diameter.Name() + "\t" + volume.Name() + "\t" + shape.Name());
                    // Print the analysis results for 5% of the pores
                    for (int i = 0; i < (int)(analysis.LabelCount() / 20); i++)
                    {
                        Console.WriteLine((i + 1) + "\t\t" + diameter.Value(i).ToString("0.00") + "\t\t\t" +
                                           volume.Value(i).ToString("0.00") + "\t\t" +
                                           shape.Value(i).ToString("0.00"));
                    }

                    // Save the segmented image with IOFormat
                    IOFormat.ViewIO.WriteView(imagePores, "T06_02_output.tif");

                }
                catch (Exception error)
                {
                    // Print potential exception in the standard output
                    System.Console.WriteLine("HelloImageDev exception: " + error.ToString());
                    status = -1;
                }

                // ImageDev library finalization
                Initialization.Finish();

                // Check if we must ask for an enter key to close the program
                if (!((args.Length >= 1) && (args[0] == "--no-stop-at-end")))
                {
                    System.Console.WriteLine("Press Enter key to close this window.");
                    System.Console.ReadKey();
                }

                System.Environment.Exit(status);
            }
        }
    }

    import imagedev
    import imagedev_data
    import ioformat
    import iolink

    # Open and display a tif file
    image_input = ioformat.read_image(imagedev_data.get_image_path("shale.am"))

    # Preprocess the input with a bilateral filter for denoising
    image_denoised = imagedev.bilateral_filter_3d(image_input)

    # STEP 1: Pore segmentation with the watershed algorithm
    # Threshold the low graylevels (pore markers)
    image_low_bin = imagedev.thresholding_by_criterion(\
      image_denoised, imagedev.ThresholdingByCriterion.ComparisonCriterion.LESS_THAN, 40)

    # Slightly erode these markers in order to avoid contact with the high level markers
    image_low_ero = imagedev.erosion_3d(image_low_bin, 1)

    # Initialize the marker image (label 1)
    image_low_lab = imagedev.convert_image(image_low_ero, imagedev.ConvertImage.LABEL_16_BIT)

    # Threshold the high graylevels (mineral markers)
    image_high_bin = imagedev.thresholding_by_criterion(\
      image_denoised, imagedev.ThresholdingByCriterion.ComparisonCriterion.GREATER_THAN, 90)

    # Merge high intensity markers with label 2
    image_markers = imagedev.add_object_to_label(image_high_bin, image_low_lab, 2)

    # Extract the gradient to build the relief image for watershed
    image_grad = imagedev.gradient_magnitude_3d(image_denoised,
                                                imagedev.GradientMagnitude3d.GAUSSIAN, [0.9, 0.9, 0.9],
                                                imagedev.GradientMagnitude3d.RECURSIVE,
                                                imagedev.GradientMagnitude3d.SAME_AS_INPUT,
                                                imagedev.GradientMagnitude3d.AMPLITUDE_EUCLIDEAN)

    # Perform the Watershed to detect pore boundaries
    image_watershed = imagedev.marker_based_watershed_3d(image_grad, image_markers,\
                                                         output_type=imagedev.MarkerBasedWatershed3d.OutputType.LINES)

    # Fill all pores in each slice of the volume
    image_watershed.axes_interpretation = iolink.ImageTypeId.IMAGE_SEQUENCE
    image_filled = imagedev.fill_holes_2d(image_watershed, imagedev.FillHoles2d.CONNECTIVITY_4)
    image_filled.axes_interpretation = iolink.ImageTypeId.VOLUME

    # Remove pores touching the image border
    image_inside = imagedev.kill_border_3d(image_filled)

    # Assign labels to the pores
    image_pores = imagedev.labeling_3d(image_inside)

    # STEP 2: Pore quantification
    # Compute the pore volume percentage
    pore_volume = imagedev.intensity_integral_3d(image_inside)
    print("  - Pore volume fraction = " + "{:.2f}".format(pore_volume.volume_fraction()*100) + "%")

    # Compute the pore number
    _, pore_count = imagedev.object_count(image_pores)
    print("  - Pore number = " + str(pore_count.count(0)))

    # Define the analysis features to be computed
    analysis = imagedev.AnalysisMsr()
    diameter = analysis.select(imagedev.native_measurements.EquivalentDiameter)
    volume = analysis.select(imagedev.native_measurements.Volume3d)
    shape = analysis.select(imagedev.native_measurements.InverseSphericity3d)

    # Launch the feature extraction on the segmented image
    imagedev.label_analysis(image_pores, None, analysis)
    print("Pore\t" + diameter.name + "\t" + volume.name + "\t" + shape.name)
    # Print the analysis results for 5% of the pores
    for i in range(int(analysis.label_count/20)):
      print(str(i+1) + '\t\t\t' + "{:.2f}".format(diameter.value(i)) +'\t\t\t\t' + "{:.2f}".format(volume.value(i)) +\
        '\t\t' + "{:.2f}".format(shape.value(i)))

    # Save the segmented image with IOFormat
    ioformat.write_view(image_pores, "T06_02_output.tif")

See also

- ThresholdingByCriterion
- BilateralFilter3d
- Erosion3d
- ConvertImage
- AddObjectToLabel
- MarkerBasedWatershed3d
- FillHoles2d
- KillBorder3d
- ObjectCount
- IntensityIntegral3d
- Labeling3d
- LabelAnalysis

© 2025 Thermo Fisher Scientific Inc. All rights reserved.