import ij.*; import ij.process.*; import ij.gui.*; import ij.measure.*; import ij.plugin.ContrastEnhancer; import java.awt.*; import java.util.*; import ij.plugin.filter.PlugInFilter; /** This class implements the Process/FFT/bandpass Filter command. It started out as Joachim Walter's FFT Filter plugin at "http://rsb.info.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 BandpassFilter implements PlugInFilter, Measurements { private ImagePlus imp; private String arg; private static int filterIndex = 1; private FHT fht; private int slice; private int stackSize = 1; public static double filterLargeDia = 40.0; public static double filterSmallDia = 3.0; public static int choiceIndex = 0; public static String[] choices = {"None","Horizontal","Vertical"}; public static String choiceDia = choices[0]; public static double toleranceDia = 5.0; public static boolean doScalingDia = true; public static boolean saturateDia = true; public static boolean displayFilter; public 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: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(); System.gc(); 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(); System.gc(); 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. */ ImageProcessor tileMirror(ImageProcessor ip, int width, int height, int x, int y) { 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.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; } }