001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package Ch08_Binary_Regions; 010 011import ij.IJ; 012import ij.ImagePlus; 013import ij.gui.GenericDialog; 014import ij.plugin.PlugIn; 015import ij.process.ByteProcessor; 016import ij.process.ImageProcessor; 017import imagingbook.common.geometry.moments.FlusserMoments; 018import imagingbook.common.math.Matrix; 019import imagingbook.common.math.PrintPrecision; 020import imagingbook.common.math.Statistics; 021import imagingbook.common.regions.BinaryRegion; 022import imagingbook.common.regions.BinaryRegionSegmentation; 023import imagingbook.common.regions.RegionContourSegmentation; 024import imagingbook.core.jdoc.JavaDocHelp; 025import imagingbook.core.resource.ImageResource; 026import imagingbook.sampleimages.kimia.Kimia1070; 027import imagingbook.sampleimages.kimia.Kimia216; 028import imagingbook.sampleimages.kimia.Kimia99; 029 030import java.util.ArrayList; 031import java.util.List; 032 033/** 034 * <p> 035 * This ImageJ plugin calculates the covariance matrix for the 11-element Flusser moment vectors over a collection of 036 * binary images. The plugin assumes binary images (with 0 background and non-zero foreground) and calculates the 11 037 * scale and rotation invariant Flusser moments (sec. G.3 of [1] for details) for the largest contained region contained 038 * in each image of the data set . The resulting covariance matrix is output in {@code float} and bit-encoded 039 * {@code long} format to avoid precision loss. The latter can be converted to {@code double} using 040 * {@link Matrix#fromLongBits(long[][])}. The covariance matrix is used as a parameter to the Mahalanobis distance for 041 * matching moment vectors (see {@link Flusser_Moments_Matching_Demo}). 042 * </p> 043 * <p> 044 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 045 * (2022). 046 * </p> 047 * 048 * @author WB 049 * @version 2022/12/28 050 * @see Flusser_Moments_Matching_Demo 051 */ 052public class Flusser_Moments_Covariance_Matrix implements PlugIn, JavaDocHelp { 053 054 private enum DataSet { 055 Kimia99, Kimia216, Kimia1070 056 } 057 058 private static DataSet DATA_SET = DataSet.Kimia99; 059 private static int MIN_REGION_SIZE = 50; 060 private static boolean LIST_LONG_ENCODED_MATRIX = false; 061 062 public void run(String arg0) { 063 if (!runDialog()) 064 return; 065 066 Class<? extends ImageResource> clazz = null; 067 switch(DATA_SET) { 068 case Kimia99: clazz = Kimia99.class; break; 069 case Kimia216: clazz = Kimia216.class; break; 070 case Kimia1070: clazz = Kimia1070.class; break; 071 } 072 073 ImageResource[] irs = clazz.getEnumConstants(); 074 IJ.log("Processing " + clazz.getSimpleName() + " dataset (" + irs.length + " images)"); 075 076 // moment vectors 077 List<double[]> samples = new ArrayList<double[]>(); 078 079 for (int i = 0; i < irs.length; i++) { 080 ImageResource ir = irs[i]; 081 ImagePlus img = ir.getImagePlus(); 082 if (img == null) { 083 IJ.log("ERROR: could not open " + ir); 084 continue; 085 } 086 ImageProcessor ip = img.getProcessor(); // do some processing 087 if (!(ip instanceof ByteProcessor)) { 088 IJ.log("ERROR: Wrong image type: " + ir); 089 continue; 090 } 091 092 BinaryRegionSegmentation segmenter = new RegionContourSegmentation((ByteProcessor) ip); 093 List<BinaryRegion> regions = segmenter.getRegions(true); 094 if (!regions.isEmpty()) { 095 BinaryRegion r = regions.get(0); 096 if (r.getSize() >= MIN_REGION_SIZE) { 097 double[] moments = new FlusserMoments(r).getInvariantMoments(); 098 if (!isFinite(moments)) { 099 IJ.log("ERROR: non-finite moment vector for " + ir + ": " + Matrix.toString(moments)); 100 } 101 else { 102 samples.add(moments); 103 } 104 } 105 } 106 } 107 108 double[][] samplesA = samples.toArray(new double[0][]); 109 double[][] cov = Statistics.covarianceMatrix(samplesA); 110 111 PrintPrecision.set(9); 112 IJ.log("covariance matrix (double):\n" + Matrix.toString(cov)); 113 114 if ( LIST_LONG_ENCODED_MATRIX) { 115 IJ.log("covariance matrix (long):\n" + Matrix.toString(Matrix.toLongBits(cov))); 116 } 117 } 118 119 private boolean isFinite(double[] moments) { 120 for (int i = 0; i < moments.length; i++) { 121 if (Double.isFinite(moments[i])) { 122 return true; 123 } 124 } 125 return false; 126 } 127 128 // ------------------------------------------------------------------------ 129 130 private boolean runDialog() { 131 GenericDialog gd = new GenericDialog(this.getClass().getSimpleName()); 132 gd.addHelp(getJavaDocUrl()); 133 gd.addEnumChoice("Data set to use", DATA_SET); 134 gd.addNumericField("Minimum region size (pixels)", MIN_REGION_SIZE); 135 gd.addCheckbox("List long-encoded covariance matrix", LIST_LONG_ENCODED_MATRIX); 136 137 gd.showDialog(); 138 if (gd.wasCanceled()) { 139 return false; 140 } 141 142 DATA_SET = gd.getNextEnumChoice(DataSet.class); 143 MIN_REGION_SIZE = (int) gd.getNextNumber(); 144 LIST_LONG_ENCODED_MATRIX = gd.getNextBoolean(); 145 return true; 146 } 147 148} 149