package ij.plugin;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.ContrastEnhancer;
import ij.measure.Calibration;
import ij.util.Tools;
import java.awt.*;
import java.util.*;
public class FFT implements PlugIn, Measurements {
static boolean displayFFT = true;
public static boolean displayRawPS;
public static boolean displayFHT;
public static boolean displayComplex;
public static String fileName;
private ImagePlus imp;
private boolean padded;
private int originalWidth;
private int originalHeight;
private int stackSize = 1;
private int slice = 1;
private boolean doFFT;
public void run(String arg) {
if (arg.equals("options")) {
showDialog();
if (doFFT)
arg="fft";
else
return;
}
imp = IJ.getImage();
if (arg.equals("fft") && imp.isComposite()) {
if (!GUI.showCompositeAdvisory(imp,"FFT"))
return;
}
if (arg.equals("redisplay"))
{redisplayPowerSpectrum(); return;}
if (arg.equals("swap"))
{swapQuadrants(imp.getStack()); imp.updateAndDraw(); return;}
if (arg.equals("inverse")) {
if (imp.getTitle().startsWith("FHT of"))
{doFHTInverseTransform(); return;}
if (imp.getStackSize()==2)
{doComplexInverseTransform(); return;}
}
ImageProcessor ip = imp.getProcessor();
Object obj = imp.getProperty("FHT");
FHT fht = (obj instanceof FHT)?(FHT)obj:null;
stackSize = imp.getStackSize();
boolean inverse;
if (fht==null && arg.equals("inverse")) {
IJ.error("FFT", "Frequency domain image required");
return;
}
if (fht!=null) {
inverse = true;
imp.deleteRoi();
} else {
if (imp.getRoi()!=null)
ip = ip.crop();
fht = newFHT(ip);
inverse = false;
}
if (inverse)
doInverseTransform(fht);
else {
fileName = imp.getTitle();
doForwardTransform(fht);
}
IJ.showProgress(1.0);
}
void doInverseTransform(FHT fht) {
fht = fht.getCopy();
doMasking(fht);
showStatus("Inverse transform");
fht.inverseTransform();
if (fht.quadrantSwapNeeded)
fht.swapQuadrants();
fht.resetMinAndMax();
ImageProcessor ip2 = fht;
if (fht.originalWidth>0) {
fht.setRoi(0, 0, fht.originalWidth, fht.originalHeight);
ip2 = fht.crop();
}
int bitDepth = fht.originalBitDepth>0?fht.originalBitDepth:imp.getBitDepth();
switch (bitDepth) {
case 8: ip2 = ip2.convertToByte(false); break;
case 16: ip2 = ip2.convertToShort(false); break;
case 24:
showStatus("Setting brightness");
if (fht.rgb==null || ip2==null) {
IJ.error("FFT", "Unable to set brightness");
return;
}
ColorProcessor rgb = (ColorProcessor)fht.rgb.duplicate();
rgb.setBrightness((FloatProcessor)ip2);
ip2 = rgb;
fht.rgb = null;
break;
case 32: break;
}
if (bitDepth!=24 && fht.originalColorModel!=null)
ip2.setColorModel(fht.originalColorModel);
String title = imp.getTitle();
if (title.startsWith("FFT of "))
title = title.substring(7, title.length());
ImagePlus imp2 = new ImagePlus("Inverse FFT of "+title, ip2);
imp2.setCalibration(imp.getCalibration());
imp2.show();
}
void doForwardTransform(FHT fht) {
showStatus("Forward transform");
fht.transform();
showStatus("Calculating power spectrum");
long t0 = System.currentTimeMillis();
ImageProcessor ps = fht.getPowerSpectrum();
if (!(displayFHT||displayComplex||displayRawPS))
displayFFT = true;
if (displayFFT) {
ImagePlus imp2 = new ImagePlus("FFT of "+imp.getTitle(), ps);
imp2.show((System.currentTimeMillis()-t0)+" ms");
imp2.setProperty("FHT", fht);
imp2.setCalibration(imp.getCalibration());
String properties = "Fast Hartley Transform\n";
properties += "width: "+fht.originalWidth + "\n";
properties += "height: "+fht.originalHeight + "\n";
properties += "bitdepth: "+fht.originalBitDepth + "\n";
imp2.setProperty("Info", properties);
}
}
FHT newFHT(ImageProcessor ip) {
FHT fht;
if (ip instanceof ColorProcessor) {
showStatus("Extracting brightness");
ImageProcessor ip2 = ((ColorProcessor)ip).getBrightness();
fht = new FHT(pad(ip2));
fht.rgb = (ColorProcessor)ip.duplicate(); } else
fht = new FHT(pad(ip));
if (padded) {
fht.originalWidth = originalWidth;
fht.originalHeight = originalHeight;
}
int bitDepth = imp.getBitDepth();
fht.originalBitDepth = bitDepth;
if (bitDepth!=24)
fht.originalColorModel = ip.getColorModel();
return fht;
}
ImageProcessor pad(ImageProcessor ip) {
originalWidth = ip.getWidth();
originalHeight = ip.getHeight();
int maxN = Math.max(originalWidth, originalHeight);
int i = 2;
while(i<maxN) i *= 2;
if (i==maxN && originalWidth==originalHeight) {
padded = false;
return ip;
}
maxN = i;
showStatus("Padding to "+ maxN + "x" + maxN);
ImageStatistics stats = ImageStatistics.getStatistics(ip, MEAN, null);
ImageProcessor ip2 = ip.createProcessor(maxN, maxN);
ip2.setValue(stats.mean);
ip2.fill();
ip2.insert(ip, 0, 0);
padded = true;
Undo.reset();
return ip2;
}
void showStatus(String msg) {
if (stackSize>1)
IJ.showStatus("FFT: " + slice+"/"+stackSize);
else
IJ.showStatus(msg);
}
void doMasking(FHT ip) {
if (stackSize>1)
return;
float[] fht = (float[])ip.getPixels();
ImageProcessor mask = imp.getProcessor();
mask = mask.convertToByte(false);
if (mask.getWidth()!=ip.getWidth() || mask.getHeight()!=ip.getHeight())
return;
ImageStatistics stats = ImageStatistics.getStatistics(mask, MIN_MAX, null);
if (stats.histogram[0]==0 && stats.histogram[255]==0)
return;
boolean passMode = stats.histogram[255]!=0;
IJ.showStatus("Masking: "+(passMode?"pass":"filter"));
mask = mask.duplicate();
if (passMode)
changeValuesAndSymmetrize(mask, (byte)255, (byte)0); else
changeValuesAndSymmetrize(mask, (byte)0, (byte)255); for (int i=0; i<3; i++)
smooth(mask);
if (IJ.debugMode || IJ.altKeyDown())
new ImagePlus("mask", mask.duplicate()).show();
ip.swapQuadrants(mask);
byte[] maskPixels = (byte[])mask.getPixels();
for (int i=0; i<fht.length; i++) {
fht[i] = (float)(fht[i]*(maskPixels[i]&255)/255.0);
}
}
void changeValuesAndSymmetrize(ImageProcessor ip, byte v1, byte v2) {
byte[] pixels = (byte[])ip.getPixels();
int n = ip.getWidth();
for (int i=0; i<pixels.length; i++) {
if (pixels[i] == v1) { if (i%n==0) { if (i>0) pixels[n*n-i] = v1;
} else if (i<n) pixels[n-i] = v1;
else pixels[n*(n+1)-i] = v1;
} else
pixels[i] = v2; }
}
static void smooth(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
byte[] pixels2 = (byte[])pixels.clone();
int n = ip.getWidth();
int[] iMinus = new int[n]; int[] iPlus = new int[n]; for (int i=0; i<n; i++) { iMinus[i] = (i-1+n)%n;
iPlus[i] = (i+1)%n;
}
for (int y=0; y<n; y++) {
int offset1 = n*iMinus[y];
int offset2 = n*y;
int offset3 = n*iPlus[y];
for (int x=0; x<n; x++) {
int sum = (pixels2[offset1+iMinus[x]]&255)
+ (pixels2[offset1+x]&255)
+ (pixels2[offset1+iPlus[x]]&255)
+ (pixels2[offset2+iMinus[x]]&255)
+ (pixels2[offset2+x]&255)
+ (pixels2[offset2+iPlus[x]]&255)
+ (pixels2[offset3+iMinus[x]]&255)
+ (pixels2[offset3+x]&255)
+ (pixels2[offset3+iPlus[x]]&255);
pixels[offset2 + x] = (byte)((sum+4)/9);
}
}
}
void redisplayPowerSpectrum() {
FHT fht = (FHT)imp.getProperty("FHT");
if (fht==null)
{IJ.error("FFT", "Frequency domain image required"); return;}
ImageProcessor ps = fht.getPowerSpectrum();
imp.setProcessor(null, ps);
}
void swapQuadrants(ImageStack stack) {
FHT fht = new FHT(new FloatProcessor(1, 1));
for (int i=1; i<=stack.getSize(); i++)
fht.swapQuadrants(stack.getProcessor(i));
}
void showDialog() {
GenericDialog gd = new GenericDialog("FFT Options");
gd.setInsets(0, 20, 0);
gd.addMessage("Display:");
gd.setInsets(5, 35, 0);
gd.addCheckbox("FFT window", displayFFT);
gd.setInsets(0, 35, 0);
gd.addCheckbox("Raw power spectrum", displayRawPS);
gd.setInsets(0, 35, 0);
gd.addCheckbox("Fast Hartley Transform", displayFHT);
gd.setInsets(0, 35, 0);
gd.addCheckbox("Complex Fourier Transform", displayComplex);
gd.setInsets(8, 20, 0);
gd.addCheckbox("Do forward transform", false);
gd.addHelp(IJ.URL+"/docs/menus/process.html#fft-options");
gd.showDialog();
if (gd.wasCanceled())
return;
displayFFT = gd.getNextBoolean();
displayRawPS = gd.getNextBoolean();
displayFHT = gd.getNextBoolean();
displayComplex = gd.getNextBoolean();
doFFT = gd.getNextBoolean();
}
void doFHTInverseTransform() {
FHT fht = new FHT(imp.getProcessor().duplicate());
fht.inverseTransform();
fht.resetMinAndMax();
String name = WindowManager.getUniqueName(imp.getTitle().substring(7));
new ImagePlus(name, fht).show();
}
void doComplexInverseTransform() {
ImageStack stack = imp.getStack();
if (!stack.getSliceLabel(1).equals("Real"))
return;
int maxN = imp.getWidth();
swapQuadrants(stack);
float[] rein = (float[])stack.getPixels(1);
float[] imin = (float[])stack.getPixels(2);
float[] reout= new float[maxN*maxN];
float[] imout = new float[maxN*maxN];
c2c2DFFT(rein, imin, maxN, reout, imout);
ImageStack stack2 = new ImageStack(maxN, maxN);
swapQuadrants(stack);
stack2.addSlice("Real", reout);
stack2.addSlice("Imaginary", imout);
stack2 = unpad(stack2);
String name = WindowManager.getUniqueName(imp.getTitle().substring(10));
ImagePlus imp2 = new ImagePlus(name, stack2);
imp2.getProcessor().resetMinAndMax();
imp2.show();
}
ImageStack unpad(ImageStack stack) {
Object w = imp.getProperty("FFT width");
Object h = imp.getProperty("FFT height");
if (w==null || h==null) return stack;
int width = (int)Tools.parseDouble((String)w, 0.0);
int height = (int)Tools.parseDouble((String)h, 0.0);
if (width==0 || height==0 || (width==stack.getWidth()&&height==stack.getHeight()))
return stack;
StackProcessor sp = new StackProcessor(stack, null);
ImageStack stack2 = sp.crop(0, 0, width, height);
return stack2;
}
void c2c2DFFT(float[] rein, float[] imin, int maxN, float[] reout, float[] imout) {
FHT fht = new FHT(new FloatProcessor(maxN,maxN));
float[] fhtpixels = (float[])fht.getPixels();
for (int iy = 0; iy < maxN; iy++)
cplxFHT(iy, maxN, rein, imin, false, fhtpixels);
fht.inverseTransform();
float[] hlp = new float[maxN*maxN];
System.arraycopy(fhtpixels, 0, hlp, 0, maxN*maxN);
for (int iy = 0; iy < maxN; iy++)
cplxFHT(iy, maxN, rein, imin, true, fhtpixels);
fht.inverseTransform();
System.arraycopy(hlp, 0, reout, 0, maxN*maxN);
System.arraycopy(fhtpixels, 0, imout, 0, maxN*maxN);
}
void cplxFHT(int row, int maxN, float[] re, float[] im, boolean reim, float[] fht) {
int base = row*maxN;
int offs = ((maxN-row)%maxN) * maxN;
if (!reim) {
for (int c=0; c<maxN; c++) {
int l = offs + (maxN-c)%maxN;
fht[base+c] = ((re[base+c]+re[l]) - (im[base+c]-im[l]))*0.5f;
}
} else {
for (int c=0; c<maxN; c++) {
int l = offs + (maxN-c)%maxN;
fht[base+c] = ((im[base+c]+im[l]) + (re[base+c]-re[l]))*0.5f;
}
}
}
}