import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.*;
import ij.gui.GenericDialog;
import ij.gui.DialogListener;
import ij.process.*;
import ij.plugin.ContrastEnhancer;
import java.awt.*;
import java.awt.event.*;
/** This plugin-Filter provides a selective mean (averaging) filter.
* In contrast to the standard mean filter, it preserves edges better
* and is less sensitive to outliers.
* Based on Lee's sigma filter algorithm and a plugin by Tony Collins.
* J.S. Lee, Digital image noise smoothing and the sigma filter, in:
* Computer Vision, Graphics and Image Processing, vol. 24, 255-269 (1983).
* The "Outlier Aware" option is a modification of Lee's algorithm introduced
* by Tony Collins.
*
* The filter smoothens an image by taking an average over the
* neighboring pixels, but only includes those pixels that have a
* value not deviating from the current pixel by more than a given
* range. The range is defined by the standard deviation of the pixel
* values within the neighborhood ("Use pixels within ... sigmas").
* If the number of pixels in this range is too low (less than "Minimum
* pixel fraction"), averaging over all neighboring pixels is performed.
* With the "Outlier Aware" option, averaging over all neighboring
* pixels excludes the center pixel. Thus, outliers having a value
* very different from the surrounding are not included in the average,
* i.e., completely eliminated.
*
* For preserving the edges, values of "Use pixels within" between
* 1 and 2 sigmas are recommended. With high values, the filter will behave
* more like a traditional averaging filter, i.e. smoothen the edges.
* Typical values of the minimum pixel fraction are around 0.2, with higher
* values resulting in more noise supression, but smoother edges.
*
* If preserving the edges is not desired, "Use pixels within" 2-3 sigmas
* and a minimum pixel fraction around 0.8-0.9, together with the "Outlier
* Aware" option will smoothen the image, similar to a traditional filter,
* but without being influenced by outliers strongly deviating from the
* surrounding pixels (hot pixels, dead pixels etc.).
*
*
* Code by Michael Schmid, 2007-10-25
*/
public class Sigma_Filter_Plus implements ExtendedPlugInFilter, DialogListener {
// Filter parameters
private static double radius = 2.; // The kernel radius, see Process>Filters>Show Circular Masks
private static double sigmaWidth = 2.; // Pixel value range in sigmas.
private static double minPixFraction = 0.2; // The fraction of pixels that need to be inside the range for selective smoothing
private static boolean outlierAware = true; // Whether outliers will be excluded from averaging
// F u r t h e r c l a s s v a r i a b l e s
int flags = DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
PARALLELIZE_STACKS;
private int nPasses = 1; // The number of passes (color channels * stack slices)
private int pass; // Current pass
protected int kRadius; // kernel radius. Size is (2*kRadius+1)^2
protected int kNPoints; // number of points in the kernel
protected int[] lineRadius; // the length of each kernel line is 2*lineRadius+1
/** Setup of the PlugInFilter. Returns the flags specifying the capabilities and needs
* of the filter.
*
* @param arg Defines type of filter operation
* @param imp The ImagePlus to be processed
* @return Flags specifying further action of the PlugInFilterRunner
*/
public int setup(String arg, ImagePlus imp) {
return flags;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
GenericDialog gd = new GenericDialog(command+"...");
gd.addNumericField("Radius", radius, 1, 6, "Pixels");
gd.addNumericField("Use Pixels Within", sigmaWidth, 1, 6, "Sigmas");
gd.addNumericField("Minimum Pixel Fraction", minPixFraction, 1);
gd.addCheckbox("Outlier Aware", outlierAware);
gd.addPreviewCheckbox(pfr); //passing pfr makes the filter ready for preview
gd.addDialogListener(this); //the DialogItemChanged method will be called on user input
gd.showDialog(); //display the dialog; preview runs in the now
if (gd.wasCanceled()) return DONE;
IJ.register(this.getClass()); //protect static class variables (filter parameters) from garbage collection
return IJ.setupDialog(imp, flags); //ask whether to process all slices of stack (if a stack)
}
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
radius = gd.getNextNumber();
sigmaWidth = gd.getNextNumber();
minPixFraction = gd.getNextNumber();
outlierAware = gd.getNextBoolean();
if (gd.invalidNumber() || radius<0.5 || radius>10 || minPixFraction<0. || minPixFraction>1.)
return false;
makeKernel(radius); //determine the kernel size once for all channels&slices
return true;
}
public void run(ImageProcessor ip) {
//copy class variables to local ones - this is necessary for preview
int[] lineRadius;
int kRadius, kNPoints, minPixNumber;
synchronized(this) { //the two following items must be consistent
lineRadius = (int[])(this.lineRadius.clone()); //cloning also required by doFiltering method
kRadius = this.kRadius; //kernel radius
kNPoints = this.kNPoints; //number of pixels in the kernel
minPixNumber = (int)(kNPoints * minPixFraction + 0.999999); //min pixels in sigma range
}
if (Thread.currentThread().isInterrupted()) return;
pass++;
doFiltering((FloatProcessor)ip, kRadius, lineRadius, sigmaWidth, minPixNumber, outlierAware);
}
/** Filter a FloatProcessor according to filterType
* @param ip The image subject to filtering
* @param kRadius The kernel radius. The kernel has a side length of 2*kRadius+1
* @param lineRadius The radius of the lines in the kernel. Line length of line i is 2*lineRadius[i]+1.
* Note that the array lineRadius
will be modified, thus call this method
* with a clone of the original lineRadius array if the array should be used again.*/
//
// Data handling: The area needed for processing a line, i.e. a stripe of width (2*kRadius+1)
// is written into the array 'cache'. This array is padded at the edges of the image so that
// a surrounding with radius kRadius for each pixel processed is within 'cache'. Out-of-image
// pixels are set to the value of the neares edge pixel. When adding a new line, the lines in
// 'cache' are not shifted but rather the smaller array with the line lengths of the kernel is
// shifted.
//
public void doFiltering(FloatProcessor ip, int kRadius, int[] lineRadius, double sigmaWidth, int minPixNumber, boolean outlierAware) {
float[] pixels = (float[])ip.getPixels(); // array of the pixel values of the input image
int width = ip.getWidth();
int height = ip.getHeight();
Rectangle roi = ip.getRoi();
int xmin = roi.x - kRadius;
int xEnd = roi.x + roi.width;
int xmax = xEnd + kRadius;
int kSize = 2*kRadius + 1;
int cacheWidth = xmax - xmin;
int xminInside = xmin>0 ? xmin : 0;
int xmaxInside = xmax=width ? width-1 : x) + width*(y<0 ? 0 : y>=height ? height-1 : y)];
int nextLineInCache = 2*kRadius; // where the next line should be written to
double[] sums = new double[2];
Thread thread = Thread.currentThread(); // needed to check for interrupted state
long lastTime = System.currentTimeMillis();
for (int y=roi.y; y 100) {
lastTime = time;
if (thread.isInterrupted()) return;
showProgress(y/(double)(roi.height));
}
int ynext = y+kRadius; // C O P Y N E W L I N E into cache
if (ynext >= height) ynext = height-1;
float leftpxl = pixels[width*ynext]; //edge pixels of the line replace out-of-image pixels
float rightpxl = pixels[width-1+width*ynext];
int iCache = cacheWidth*nextLineInCache;//where in the cache we have to copy to
for (int x=xmin; x<0; x++, iCache++)
cache[iCache] = leftpxl;
System.arraycopy(pixels, xminInside+width*ynext, cache, iCache, widthInside);
iCache += widthInside;
for (int x=width; x=sigmaBottom)&&(v<=sigmaTop)) {
sum += v;
count++;
}
}
}
//if there are too few pixels in the kernel that are within sigma range, the
//mean of the entire kernel is taken.
if (count>=minPixNumber)
pixels[p] = (float)(sum/count);
else {
if (outlierAware)
pixels[p] = (float)((sums[0]-value)/(kNPoints-1)); //assumes that the current pixel is an outlier
else
pixels[p] = (float)mean;
}
} // for x
int newLineRadius0 = lineRadius[kSize-1]; //shift kernel lineRadii one line
System.arraycopy(lineRadius, 0, lineRadius, 1, kSize-1);
lineRadius[0] = newLineRadius0;
} // for y
}
/** Get sum of values and values squared within the kernel area.
* xCache0 points to cache element equivalent to current x coordinate.
* Output is written to array sums[0] = sum; sums[1] = sum of squares */
private void getAreaSums(float[] cache, int cacheWidth, int xCache0, int[] lineRadius, int kSize, double[] sums) {
double sum=0, sum2=0;
for (int y=0; y=1.5 && radius<1.75) //this code creates the same sizes as the previous RankFilters
radius = 1.75;
else if (radius>=2.5 && radius<2.85)
radius = 2.85;
int r2 = (int) (radius*radius) + 1;
kRadius = (int)(Math.sqrt(r2+1e-10));
lineRadius = new int[2*kRadius+1];
lineRadius[kRadius] = kRadius;
kNPoints = 2*kRadius + 1;
for (int y=1; y<=kRadius; y++) {
int dx = (int)(Math.sqrt(r2-y*y+1e-10));
lineRadius[kRadius+y] = dx;
lineRadius[kRadius-y] = dx;
kNPoints += 4*dx + 2;
}
}
/** This method is called by ImageJ to set the number of calls to run(ip)
* corresponding to 100% of the progress bar */
public void setNPasses (int nPasses) {
this.nPasses = nPasses;
pass = 0;
}
private void showProgress(double percent) {
percent = (double)(pass-1)/nPasses + percent/nPasses;
IJ.showProgress(percent);
}
}