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 Ch19_Discrete_Fourier_Transform; 010 011import ij.ImagePlus; 012import ij.gui.GenericDialog; 013import ij.plugin.filter.PlugInFilter; 014import ij.process.FloatProcessor; 015import ij.process.ImageProcessor; 016import imagingbook.common.ij.DialogUtils; 017import imagingbook.common.math.Matrix; 018import imagingbook.core.jdoc.JavaDocHelp; 019import imagingbook.sampleimages.GeneralSampleImage; 020import imagingbook.spectral.dft.Dft2d; 021import imagingbook.spectral.dft.Dft2dDirect; 022import imagingbook.spectral.dft.Dft2dFast; 023import imagingbook.spectral.dft.ScalingMode; 024 025import static imagingbook.common.ij.IjUtils.noCurrentImage; 026 027 028/** 029 * <p> 030 * This ImageJ plugin computes the 2-dimensional DFT (magnitude spectrum) from an image of arbitrary size using 031 * {@code float} or {@code double} data. Optionally, either a direct DFT or a fast FFT implementation is used. See 032 * Chapters 18-19 of [1] for additional details. 033 * </p> 034 * <p> 035 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 036 * (2022). 037 * </p> 038 * 039 * @author WB 040 * @version 2022/10/23 041 */ 042public class DFT_2D_Demo implements PlugInFilter, JavaDocHelp { 043 044 private static boolean ShowLogSpectrum = true; 045 private static boolean UseDoublePrecision = false; 046 private static boolean UseFastMode = true; 047 private static boolean CenterSpectrum = true; 048 private static boolean ReconstructImage = true; 049 050 private ImagePlus im; 051 private int w, h; 052 private FloatProcessor reconstructionRe = null; 053 private FloatProcessor reconstructionIm = null; 054 055 /** 056 * Constructor, asks to open a predefined sample image if no other image is currently open. 057 */ 058 public DFT_2D_Demo() { 059 if (noCurrentImage()) { 060 DialogUtils.askForSampleImage(GeneralSampleImage.IrishManor); 061 } 062 } 063 064 @Override 065 public int setup(String arg, ImagePlus im) { 066 this.im = im; 067 return DOES_8G + DOES_32 + NO_CHANGES; 068 } 069 070 @Override 071 public void run(ImageProcessor ip) { 072 if (!runDialog()) 073 return; 074 075 w = ip.getWidth(); 076 h = ip.getHeight(); 077 078 FloatProcessor fp = ip.convertToFloatProcessor(); 079 FloatProcessor magSpectrum = (UseDoublePrecision) ? runDouble(fp) : runFloat(fp); 080 081 if (ShowLogSpectrum) { 082 magSpectrum.add(1.0); 083 magSpectrum.log(); 084 } 085 086 magSpectrum.resetMinAndMax(); 087 088 if (CenterSpectrum) { // strange this only works here (not before) - bug in Blitter? 089 swapQuadrants(magSpectrum); 090 } 091 092 String name = im.getShortTitle(); 093 String title = (ShowLogSpectrum) ? 094 name + "-DFT (log. magnitude)" : name + "-DFT (magnitude)"; 095 new ImagePlus(title, magSpectrum).show(); 096 097 // show reconstructed image if available 098 if (ReconstructImage) { 099// reIp.resetMinAndMax(); 100// imIp.resetMinAndMax(); 101 new ImagePlus(name + "-reconstructed (real part)", reconstructionRe).show(); 102 new ImagePlus(name + "-reconstructed (imag. part = zero!)", reconstructionIm).show(); 103 } 104 105 } 106 107 private FloatProcessor runFloat(FloatProcessor fp) { 108 float[][] re = fp.getFloatArray(); 109 float[][] im = new float[w][h]; 110 111 Dft2d.Float dft2 = (UseFastMode) ? 112 new Dft2dFast.Float(w, h, ScalingMode.DEFAULT) : 113 new Dft2dDirect.Float(w, h, ScalingMode.DEFAULT); 114 115 dft2.forward(re, im); 116 117 float[][] mag = dft2.getMagnitude(re, im); 118 FloatProcessor ms = new FloatProcessor(mag); 119 120 if (ReconstructImage) { 121 dft2.inverse(re, im); 122 this.reconstructionRe = new FloatProcessor(re); 123 this.reconstructionIm = new FloatProcessor(im); 124 } 125 126 return ms; 127 } 128 129 private FloatProcessor runDouble(FloatProcessor fp) { 130 double[][] re = Matrix.toDouble(fp.getFloatArray()); 131 double[][] im = new double[w][h]; 132 133 Dft2d.Double dft2 = (UseFastMode) ? 134 new Dft2dFast.Double(w, h, ScalingMode.DEFAULT) : 135 new Dft2dDirect.Double(w, h, ScalingMode.DEFAULT); 136 137 dft2.forward(re, im); 138 139 double[][] mag = dft2.getMagnitude(re, im); 140 FloatProcessor ms = new FloatProcessor(Matrix.toFloat(mag)); 141 142 if (ReconstructImage) { 143 dft2.inverse(re, im); 144 this.reconstructionRe = new FloatProcessor(Matrix.toFloat(re)); 145 this.reconstructionIm = new FloatProcessor(Matrix.toFloat(im)); 146 } 147 148 return ms; 149 } 150 // ------------------------------------------------------------- 151 152 private boolean runDialog() { 153 GenericDialog gd = new GenericDialog(getClass().getSimpleName()); 154 gd.addHelp(getJavaDocUrl()); 155 gd.addCheckbox("Use fast mode (FFT)", UseFastMode); 156 gd.addCheckbox("Use double precision", UseDoublePrecision); 157 gd.addCheckbox("Show logarithmic spectrum", ShowLogSpectrum); 158 gd.addCheckbox("Show centered spectrum", CenterSpectrum); 159 gd.addCheckbox("Reconstruct image (re/im)", ReconstructImage); 160 161 gd.showDialog(); 162 if (gd.wasCanceled()) 163 return false; 164 165 UseFastMode = gd.getNextBoolean(); 166 UseDoublePrecision = gd.getNextBoolean(); 167 ShowLogSpectrum = gd.getNextBoolean(); 168 CenterSpectrum = gd.getNextBoolean(); 169 ReconstructImage = gd.getNextBoolean(); 170 return true; 171 } 172 173 // ------------------------------------------------------------- 174 175 /** 176 * Centers a 2D DFT spectrum. 177 * Modifies the given image by moving the origin of the image to its center 178 * (circularly). 179 * TODO: Check for possible bug when applied to a {@link FloatProcessor}! 180 * 181 * @param ip an {@link ImageProcessor} instance 182 */ 183 private void swapQuadrants(ImageProcessor ip) { 184 // swap quadrants Q1 <-> Q3, Q2 <-> Q4 185 // Q2 Q1 186 // Q3 Q4 187 ImageProcessor t1, t2; 188 int w = ip.getWidth(); 189 int h = ip.getHeight(); 190 int wc = w / 2; 191 int hc = h / 2; 192 193 ip.setRoi(wc, 0, w - wc, hc); // Q1 194 t1 = ip.crop(); 195 ip.setRoi(0, hc, wc, h - hc); // Q3 196 t2 = ip.crop(); 197 198 ip.insert(t1, 0, hc); // swap Q1 <-> Q3 199 ip.insert(t2, wc, 0); 200 201 ip.setRoi(0, 0, wc, hc); // Q2 202 t1 = ip.crop(); 203 ip.setRoi(wc, hc, w - wc, h - hc); // Q4 204 t2 = ip.crop(); 205 206 ip.insert(t1, wc, hc); 207 ip.insert(t2, 0, 0); 208 } 209 210}