package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.ContrastEnhancer;
import java.awt.*;
import java.util.*;

/** 
This class implements the Process/FFT/Bandpass Filter command. It is based on 
Joachim Walter's FFT Filter plugin at "http://imagej.nih.gov/ij/plugins/fft-filter.html".
2001/10/29: First Version (JW)
2003/02/06: 1st bugfix (works in macros/plugins, works on stacks, overwrites image(=>filter)) (JW)
2003/07/03: integrated into ImageJ, added "Display Filter" option (WSR)
2007/03/26: 2nd bugfix (Fixed incorrect calculation of filter from structure sizes, which caused
            the real structure sizes to be wrong by a factor of 0.75 to 1.5 depending on the image size.)
*/
public class FFTFilter implements  PlugInFilter, Measurements {

	private ImagePlus imp;
	private String arg;
	private static int filterIndex = 1;
	private FHT fht;
	private int slice;
	private int stackSize = 1;	
	
	private static double filterLargeDia = 40.0;
	private static double  filterSmallDia = 3.0;
	private static int choiceIndex = 0;
	private static String[] choices = {"None","Horizontal","Vertical"};
	private static String choiceDia = choices[0];
	private static double toleranceDia = 5.0;
	private static boolean doScalingDia = true;
	private static boolean saturateDia = true;
	private static boolean displayFilter;
	private static boolean processStack;

	public int setup(String arg, ImagePlus imp) {
		this.arg = arg;
 		this.imp = imp;
 		if (imp==null)
 			{IJ.noImage(); return DONE;}
 		stackSize = imp.getStackSize();
		fht  = (FHT)imp.getProperty("FHT");
		if (fht!=null) {
			IJ.error("FFT Filter", "Spatial domain image required");
			return DONE;
		}
		if (!showBandpassDialog(imp))
			return DONE;
		else
			return processStack?DOES_ALL+DOES_STACKS+PARALLELIZE_STACKS:DOES_ALL;
	}

	public void run(ImageProcessor ip) {
		slice++;
		filter(ip);
	}
	
	void filter(ImageProcessor ip) {
		ImageProcessor ip2 = ip;
		if (ip2 instanceof ColorProcessor) {
			showStatus("Extracting brightness");
			ip2 = ((ColorProcessor)ip2).getBrightness();
		} 
		Rectangle roiRect = ip2.getRoi();		
		int maxN = Math.max(roiRect.width, roiRect.height);
		double sharpness = (100.0 - toleranceDia) / 100.0;
		boolean doScaling = doScalingDia;
		boolean saturate = saturateDia;
		
		IJ.showProgress(1,20);

		/* 	tile mirrored image to power of 2 size		
			first determine smallest power 2 >= 1.5 * image width/height
		  	factor of 1.5 to avoid wrap-around effects of Fourier Trafo */

		int i=2;
		while(i<1.5 * maxN) i *= 2;		
        
        // Calculate the inverse of the 1/e frequencies for large and small structures.
        double filterLarge = 2.0*filterLargeDia / (double)i;
        double filterSmall = 2.0*filterSmallDia / (double)i;
        
		// fit image into power of 2 size 
		Rectangle fitRect = new Rectangle();
		fitRect.x = (int) Math.round( (i - roiRect.width) / 2.0 );
		fitRect.y = (int) Math.round( (i - roiRect.height) / 2.0 );
		fitRect.width = roiRect.width;
		fitRect.height = roiRect.height;
		
		// put image (ROI) into power 2 size image
		// mirroring to avoid wrap around effects
		showStatus("Pad to "+i+"x"+i);
		ip2 = tileMirror(ip2, i, i, fitRect.x, fitRect.y);
		IJ.showProgress(2,20);
		
		// transform forward
		showStatus(i+"x"+i+" forward transform");
		FHT fht = new FHT(ip2);
		fht.setShowProgress(false);
		fht.transform();
		IJ.showProgress(9,20);
		//new ImagePlus("after fht",ip2.crop()).show();	

		// filter out large and small structures
		showStatus("Filter in frequency domain");
		filterLargeSmall(fht, filterLarge, filterSmall, choiceIndex, sharpness);
		//new ImagePlus("filter",ip2.crop()).show();
		IJ.showProgress(11,20);

		// transform backward
		showStatus("Inverse transform");
		fht.inverseTransform();
		IJ.showProgress(19,20);
		//new ImagePlus("after inverse",ip2).show();	
		
		// crop to original size and do scaling if selected
		showStatus("Crop and convert to original type");
		fht.setRoi(fitRect);
		ip2 = fht.crop();
		if (doScaling) {
			ImagePlus imp2 = new ImagePlus(imp.getTitle()+"-filtered", ip2);
			new ContrastEnhancer().stretchHistogram(imp2, saturate?1.0:0.0);
			ip2 = imp2.getProcessor();
		}

		// convert back to original data type
		int bitDepth = imp.getBitDepth(); 
		switch (bitDepth) {
			case 8: ip2 = ip2.convertToByte(doScaling); break;
			case 16: ip2 = ip2.convertToShort(doScaling); break;
			case 24:
				ip.snapshot();
				showStatus("Setting brightness");
				((ColorProcessor)ip).setBrightness((FloatProcessor)ip2);
				break;
			case 32: break;
		}

		// copy filtered image back into original image
		if (bitDepth!=24) {
			ip.snapshot();
			ip.copyBits(ip2, roiRect.x, roiRect.y, Blitter.COPY);
		}
		ip.resetMinAndMax();
		IJ.showProgress(20,20);
	}
	
	void showStatus(String msg) {
		if (stackSize>1 && processStack)
			IJ.showStatus("FFT Filter: "+slice+"/"+stackSize);
		else
			IJ.showStatus(msg);
	}

	/** Puts ImageProcessor (ROI) into a new ImageProcessor of size width x height y at position (x,y).
	The image is mirrored around its edges to avoid wrap around effects of the FFT. */
	public ImageProcessor tileMirror(ImageProcessor ip, int width, int height, int x, int y) {
		if (IJ.debugMode) IJ.log("FFT.tileMirror: "+width+"x"+height+" "+ip);
		if (x < 0 || x > (width -1) || y < 0 || y > (height -1)) {
			IJ.error("Image to be tiled is out of bounds.");
			return null;
		}
		
		ImageProcessor ipout = ip.createProcessor(width, height);
		
		ImageProcessor ip2 = ip.crop();
		int w2 = ip2.getWidth();
		int h2 = ip2.getHeight();
				
		//how many times does ip2 fit into ipout?
		int i1 = (int) Math.ceil(x / (double) w2);
		int i2 = (int) Math.ceil( (width - x) / (double) w2);
		int j1 = (int) Math.ceil(y / (double) h2);
		int j2 = (int) Math.ceil( (height - y) / (double) h2);		

		//tile		
		if ( (i1%2) > 0.5)
			ip2.flipHorizontal();
		if ( (j1%2) > 0.5)
			ip2.flipVertical();
					
		for (int i=-i1; i1)
			gd.addCheckbox("Process entire stack", processStack);	
		gd.addHelp(IJ.URL+"/docs/menus/process.html#fft-bandpass");
		gd.showDialog();
		if(gd.wasCanceled())
			return false;
		if(gd.invalidNumber()) {
			IJ.error("Error", "Invalid input number");
			return false;
		}				
		filterLargeDia = gd.getNextNumber();
		filterSmallDia = gd.getNextNumber();	
		choiceIndex = gd.getNextChoiceIndex();
		choiceDia = choices[choiceIndex];
		toleranceDia = gd.getNextNumber();
		doScalingDia = gd.getNextBoolean();
		saturateDia = gd.getNextBoolean();
		displayFilter = gd.getNextBoolean();
		if (stackSize>1)
			processStack = gd.getNextBoolean();
		return true;
	}

}


 

Ads help maintain this website.