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.filter.PlugInFilter; 015import ij.process.ByteProcessor; 016import ij.process.ImageProcessor; 017import imagingbook.common.geometry.basic.Pnt2d; 018import imagingbook.common.geometry.moments.FlusserMoments; 019import imagingbook.common.ij.DialogUtils; 020import imagingbook.common.ij.overlay.ColoredStroke; 021import imagingbook.common.ij.overlay.ShapeOverlayAdapter; 022import imagingbook.common.regions.BinaryRegion; 023import imagingbook.common.regions.BinaryRegionSegmentation; 024import imagingbook.common.regions.Contour; 025import imagingbook.common.regions.RegionContourSegmentation; 026import imagingbook.core.jdoc.JavaDocHelp; 027import imagingbook.sampleimages.kimia.KimiaCollage; 028 029import java.awt.Color; 030import java.awt.Font; 031import java.util.Comparator; 032import java.util.List; 033 034import static imagingbook.common.ij.IjUtils.noCurrentImage; 035 036/** 037 * <p> 038 * This ImageJ plugin calculates and lists the 11 scale and rotation invariant Flusser moments for the binary regions 039 * contained in the given image. See Sec. 8.6.5 of [1] for additional details. The plugin expects a binary image (with 0 040 * background and non-zero foreground). A sample image is automatically loaded if no other image is currently open. 041 * </p> 042 * <p> 043 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 044 * (2022). 045 * </p> 046 * 047 * @author WB 048 * @version 2022/12/28 049 */ 050public class Flusser_Moments_From_Binary_Regions implements PlugInFilter, JavaDocHelp { 051 052 private static int MIN_REGION_SIZE = 100; 053 private static int MAX_REGION_COUNT = 30; 054 private static boolean MARK_OUTER_CONTOURS = true; 055 056 private static final Font MarkerFont = new Font(Font.SANS_SERIF, Font.BOLD, 12); 057 private static final Color MarkerColor = Color.blue; 058 059 private ImagePlus im; 060 061 /** 062 * Constructor, asks to open a predefined sample image if no other image is currently open. 063 */ 064 public Flusser_Moments_From_Binary_Regions() { 065 if (noCurrentImage()) { 066 DialogUtils.askForSampleImage(KimiaCollage.ShapeCollage1); // Kimia216.bird02 067 } 068 } 069 070 public int setup(String arg0, ImagePlus im) { 071 this.im = im; 072 return DOES_8G + DOES_8C; 073 } 074 075 public void run(ImageProcessor ip) { 076 if (!runDialog()) 077 return; 078 079 BinaryRegionSegmentation segmenter = new RegionContourSegmentation((ByteProcessor) ip); 080 List<BinaryRegion> regions = segmenter.getRegions(true); 081 if (regions.isEmpty()) { 082 IJ.log("No regions found!"); 083 return; 084 } 085 086 IJ.log("Regions found: " + regions.size()); 087 regions.sort(Comparator.comparingDouble(r -> r.getCenter().getY())); // sort regions by Y-coordinates 088 089 ShapeOverlayAdapter ola = new ShapeOverlayAdapter(); 090 ola.setStroke(new ColoredStroke(0.5, Color.green)); 091 ola.setFont(MarkerFont); 092 ola.setTextColor(MarkerColor); 093 int i = 0; 094 for (BinaryRegion r : regions) { 095 if (r.getSize() >= MIN_REGION_SIZE) { 096 IJ.log("Region " + i + ", size = " + r.getSize()); 097 Contour oc = r.getOuterContour(); 098 ola.addShape(oc.getPolygonPath()); 099 Pnt2d c = r.getCenter(); 100 ola.addText(c.getX(), c.getY(), "R" + i); 101 double[] moments = new FlusserMoments(r).getInvariantMoments(); 102 print(moments); 103 i++; 104 if (i > MAX_REGION_COUNT) 105 break; 106 } 107 } 108 im.setOverlay(ola.getOverlay()); 109 } 110 111 private void print(double[] moments) { 112 for (int i = 0; i < moments.length; i++) { 113 IJ.log(" p" + (i+1) + " = " + moments[i]); 114 } 115 } 116 117 // ------------------------------------------------------ 118 119 private boolean runDialog() { 120 GenericDialog gd = new GenericDialog(this.getClass().getSimpleName()); 121 gd.addHelp(getJavaDocUrl()); 122 if (im.isInvertedLut()) { 123 gd.setInsets(0, 0, 0); 124 gd.addMessage("NOTE: Image has inverted LUT (0 = white)!"); 125 } 126 gd.addNumericField("Minimum region size (pixels)", MIN_REGION_SIZE); 127 gd.addNumericField("Maximum region count", MAX_REGION_COUNT); 128 gd.addCheckbox("Mark outer contours", MARK_OUTER_CONTOURS); 129 130 gd.showDialog(); 131 if (gd.wasCanceled()) { 132 return false; 133 } 134 135 MIN_REGION_SIZE = (int) gd.getNextNumber(); 136 MAX_REGION_COUNT = (int) gd.getNextNumber(); 137 MARK_OUTER_CONTOURS = gd.getNextBoolean(); 138 return true; 139 } 140 141}