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 ******************************************************************************/ 009 010package More_; 011 012import ij.IJ; 013import ij.ImagePlus; 014import ij.gui.GenericDialog; 015import ij.gui.Roi; 016import ij.plugin.filter.PlugInFilter; 017import ij.process.ByteProcessor; 018import ij.process.ImageProcessor; 019import imagingbook.common.geometry.basic.Pnt2d; 020import imagingbook.common.geometry.fd.FourierDescriptor; 021import imagingbook.common.geometry.fd.FourierDescriptorTrigonometric; 022import imagingbook.common.ij.DialogUtils; 023import imagingbook.common.ij.RoiUtils; 024import imagingbook.common.ij.overlay.ColoredStroke; 025import imagingbook.common.ij.overlay.ShapeOverlayAdapter; 026import imagingbook.common.math.Complex; 027import imagingbook.core.jdoc.JavaDocHelp; 028import imagingbook.sampleimages.GeneralSampleImage; 029 030import java.awt.Color; 031import java.awt.geom.Path2D; 032import java.util.Arrays; 033 034import static imagingbook.common.ij.IjUtils.noCurrentImage; 035 036/** 037 * <p> 038 * This ImageJ plugin demonstrates the "trigonometric" construction of elliptic Fourier descriptors. See Sec. 26.3.7 of 039 * [1] for details. The input is a user-defined polygon selection (ROI). The number of Fourier coefficient pairs can be 040 * specified in the user dialog. The plugin then displays the original polygon and the approximate contour 041 * reconstruction from the Fourier descriptor using all specified coefficient pairs. Increasing the number of 042 * coefficient pairs improves the reconstruction. 043 * </p> 044 * <p> 045 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction Using Java</em>, 2nd ed, 046 * Springer (2016). 047 * </p> 048 * 049 * @author WB 050 * @version 2022/10/28 051 */ 052public class Fourier_Descriptor_Trigonometic implements PlugInFilter, JavaDocHelp { 053 054 private static int FourierCoefficientPairs = 3; 055 056 // visualization-related settings 057 private static boolean ShowOriginalContour = true; 058 private static boolean ShowFullReconstruction = true; 059 private static int ReconstructionPoints = 100; 060 061 private static Color ContourColor = new Color(0, 60, 255); 062 private static double ContourStrokeWidth = 0.25; 063 private static Color ReconstructionColor = new Color(0, 185, 15); 064 private static double ReconstructionStrokeWidth = 0.5; 065 066 private ImagePlus im; 067 068 // ---------------------------------------------------------------- 069 070 /** 071 * Constructor, asks to open a predefined sample image if no other image 072 * is currently open. 073 */ 074 public Fourier_Descriptor_Trigonometic() { 075 if (noCurrentImage()) { 076 DialogUtils.askForSampleImage(GeneralSampleImage.HouseRoi); 077 } 078 } 079 080 // ---------------------------------------------------------------- 081 082 @Override 083 public int setup(String arg, ImagePlus im) { 084 this.im = im; 085 return DOES_ALL + ROI_REQUIRED + NO_CHANGES; 086 } 087 088 @Override 089 public void run(ImageProcessor ip) { 090 091 if (!runDialog()) { 092 return; 093 } 094 095 Roi anyRoi = im.getRoi(); 096 Pnt2d[] V = RoiUtils.getOutlinePointsFloat(anyRoi); // toPointArray(anyRoi); 097 IJ.log("V=" + Arrays.toString(V)); 098 FourierDescriptor fd = FourierDescriptorTrigonometric.from(V, FourierCoefficientPairs); 099 100 Complex ctr = fd.getCoefficient(0); 101 102 ImagePlus imA = makeBackgroundImage(); 103 imA.show(); 104 105 ColoredStroke contourStroke = new ColoredStroke(ContourStrokeWidth, ContourColor); 106 ColoredStroke reconstructionStroke = new ColoredStroke(ReconstructionStrokeWidth, ReconstructionColor); 107 108 ShapeOverlayAdapter ola = new ShapeOverlayAdapter(); 109 110 if (ShowOriginalContour) { 111 ola.addShape(toClosedPath(V), contourStroke); 112 ola.addShape(Pnt2d.from(ctr.re, ctr.im).getShape()); 113 } 114 115 if (true) { 116 for (Pnt2d v : V) { 117 ola.addShape(v.getShape(3), contourStroke); 118 } 119 } 120 121 if (ShowFullReconstruction) { // draw the shape reconstructed from all FD-pairs 122 Path2D rec = FourierDescriptor.toPath(fd.getShapeFull(ReconstructionPoints)); 123 ola.addShape(rec, reconstructionStroke); 124 } 125 126 imA.setOverlay(ola.getOverlay()); 127 imA.updateAndDraw(); 128 129 } 130 131 // ------------------------------------------------------------- 132 133 private ImagePlus makeBackgroundImage() { 134 ByteProcessor bp = im.getProcessor().convertToByteProcessor(); 135 String title = String.format("%s-partial-%03d", im.getShortTitle(), FourierCoefficientPairs); 136 137 if (bp.isInvertedLut()) { 138 bp.invert(); 139 bp.invertLut(); 140 } 141 brighten(bp, 220); 142 return new ImagePlus(title, bp); 143 } 144 145 private Path2D toClosedPath(Pnt2d[] pnts) { 146 Path2D path = new Path2D.Float(); 147 path.moveTo(pnts[0].getX(), pnts[0].getY()); 148 for (int i = 1; i < pnts.length; i++) { 149 path.lineTo(pnts[i].getX(), pnts[i].getY()); 150 } 151 path.closePath(); 152 return path; 153 } 154 155 private void brighten(ByteProcessor ip, int minGray) { 156 float scale = (255 - minGray) / 255f; 157 int[] table = new int[256]; 158 for (int i=0; i<256; i++) { 159 table[i] = Math.round(minGray + scale * i); 160 161 } 162 ip.applyTable(table); 163 } 164 165 // ------------------------------------------------------------- 166 167 private boolean runDialog() { 168 GenericDialog gd = new GenericDialog(this.getClass().getSimpleName()); 169 gd.addHelp(getJavaDocUrl()); 170 gd.addNumericField("Number of FD pairs (min. 1)", FourierCoefficientPairs, 0); 171 gd.addNumericField("Reconstruction Points", ReconstructionPoints, 0); 172 gd.addCheckbox("Show Original Contour", ShowOriginalContour); 173 gd.addCheckbox("Show Full Reconstruction", ShowFullReconstruction); 174 175 gd.showDialog(); 176 if(gd.wasCanceled()) 177 return false; 178 179 FourierCoefficientPairs = Math.max(1, (int) gd.getNextNumber()); 180 ReconstructionPoints = (int) gd.getNextNumber(); 181 ShowOriginalContour = gd.getNextBoolean(); 182 ShowFullReconstruction = gd.getNextBoolean(); 183 return true; 184 } 185 186}