package ij.plugin.filter;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.plugin.ContrastEnhancer;
import ij.util.ThreadUtil;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class RankFilters implements ExtendedPlugInFilter, DialogListener {
public static final int MEAN=0, MIN=1, MAX=2, VARIANCE=3, MEDIAN=4, OUTLIERS=5, DESPECKLE=6, REMOVE_NAN=7,
OPEN=8, CLOSE=9, TOP_HAT=10; public static final int BRIGHT_OUTLIERS = 0, DARK_OUTLIERS = 1;
private static final String[] outlierStrings = {"Bright","Dark"};
private static int HIGHEST_FILTER = TOP_HAT;
private int filterType;
private double radius;
private double threshold; private int whichOutliers;
private boolean lightBackground = Prefs.get("bs.background", true); private boolean dontSubtract;
private static double[] lastRadius = new double[HIGHEST_FILTER+1]; private static double lastThreshold = 50.;
private static int lastWhichOutliers = BRIGHT_OUTLIERS;
private static boolean lastLightBackground = false;
private static boolean lastDontSubtract = false;
int flags = DOES_ALL|SUPPORTS_MASKING|KEEP_PREVIEW;
private ImagePlus imp;
private int nPasses = 1; private PlugInFilterRunner pfr;
private int pass;
private boolean previewing = false;
private int numThreads = Prefs.getThreads();
private AtomicInteger highestYinCache = new AtomicInteger(Integer.MIN_VALUE); private AtomicInteger nThreadsWaiting = new AtomicInteger(0); private AtomicBoolean copyingToCache = new AtomicBoolean(false);
private boolean isMultiStepFilter(int filterType) {
return filterType>=OPEN && filterType<=TOP_HAT;
}
public int setup(String arg, ImagePlus imp) {
this.imp = imp;
if (arg.equals("mean"))
filterType = MEAN;
else if (arg.equals("min"))
filterType = MIN;
else if (arg.equals("max"))
filterType = MAX;
else if (arg.equals("variance")) {
filterType = VARIANCE;
flags |= FINAL_PROCESSING;
} else if (arg.equals("median"))
filterType = MEDIAN;
else if (arg.equals("outliers"))
filterType = OUTLIERS;
else if (arg.equals("despeckle"))
filterType = DESPECKLE;
else if (arg.equals("tophat"))
filterType = TOP_HAT;
else if (arg.equals("nan")) {
filterType = REMOVE_NAN;
if (imp!=null && imp.getBitDepth()!=32) {
IJ.error("RankFilters","\"Remove NaNs\" requires a 32-bit image");
return DONE;
}
} else if (arg.equals("final")) { setDisplayRange(imp.getProcessor());
} else if (arg.equals("masks")) {
showMasks();
return DONE;
} else {
IJ.error("RankFilters","Argument missing or undefined: "+arg);
return DONE;
}
if (isMultiStepFilter(filterType) && imp!=null) { Roi roi = imp.getRoi();
if (roi!=null && !roi.getBounds().contains(new Rectangle(imp.getWidth(), imp.getHeight())))
flags |= SNAPSHOT; }
return flags;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
if (filterType == DESPECKLE) {
filterType = MEDIAN;
radius = 1.0;
} else {
GenericDialog gd = GUI.newNonBlockingDialog(command,imp);
radius = lastRadius[filterType]<=0 ? 2 : lastRadius[filterType];
gd.addNumericField("Radius", radius, 1, 6, "pixels");
if (filterType==OUTLIERS) {
int digits = imp.getType() == ImagePlus.GRAY32 ? 2 : 0;
gd.addNumericField("Threshold", lastThreshold, digits);
gd.addChoice("Which outliers", outlierStrings, outlierStrings[lastWhichOutliers]);
gd.addHelp(IJ.URL+"/docs/menus/process.html#outliers");
} else if (filterType==REMOVE_NAN) {
gd.addHelp(IJ.URL+"/docs/menus/process.html#nans");
} else if (filterType==TOP_HAT) {
gd.addCheckbox("Light Background", lastLightBackground);
gd.addCheckbox("Don't subtract (grayscale open)", lastDontSubtract);
}
gd.addPreviewCheckbox(pfr); gd.addDialogListener(this); previewing = true;
gd.showDialog(); previewing = false;
if (gd.wasCanceled()) return DONE;
IJ.register(this.getClass()); if (Macro.getOptions() == null) { lastRadius[filterType] = radius;
if (filterType == OUTLIERS) {
lastThreshold = threshold;
lastWhichOutliers = whichOutliers;
} else if (filterType==TOP_HAT) {
lastLightBackground = lightBackground;
lastDontSubtract = dontSubtract;
}
}
}
this.pfr = pfr;
flags = IJ.setupDialog(imp, flags); if ((flags&DOES_STACKS)!=0) {
int size = imp.getWidth() * imp.getHeight();
Roi roi = imp.getRoi();
if (roi != null) {
Rectangle roiRect = roi.getBounds();
size = roiRect.width * roiRect.height;
}
double workToDo = size*(double)radius; if (filterType==MEAN || filterType==VARIANCE) workToDo *= 0.5;
else if (filterType==MEDIAN) workToDo *= radius*0.5;
if (workToDo < 1e6 && imp.getImageStackSize()>=2*numThreads) {
numThreads = 1; flags |= PARALLELIZE_STACKS;
}
}
return flags;
}
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
radius = gd.getNextNumber();
if (filterType == OUTLIERS) {
threshold = gd.getNextNumber();
whichOutliers = gd.getNextChoiceIndex();
} else if (filterType==TOP_HAT || filterType==OPEN || filterType==CLOSE) {
lightBackground = gd.getNextBoolean();
dontSubtract = gd.getNextBoolean();
}
int maxRadius = (filterType==MEDIAN || filterType==OUTLIERS || filterType==REMOVE_NAN) ? 100 : 1000;
if (gd.invalidNumber() || radius<0 || radius>maxRadius || (filterType==OUTLIERS && threshold <0))
return false;
if (filterType == TOP_HAT) {
if (dontSubtract)
flags &= ~FINAL_PROCESSING; else
flags |= FINAL_PROCESSING; }
return true;
}
public void run(ImageProcessor ip) {
rank(ip, radius, filterType, whichOutliers, (float)threshold, lightBackground, dontSubtract);
if (IJ.escapePressed()) ip.reset();
else if (previewing && (flags&FINAL_PROCESSING)!=0)
setDisplayRange(ip);
}
public void rank(ImageProcessor ip, double radius, int filterType) {
rank(ip, radius, filterType, 0, 50f);
}
public void rank(ImageProcessor ip, double radius, int filterType, int whichOutliers, float threshold) {
rank(ip, radius, filterType, whichOutliers, threshold, false, false);
}
public void rank(ImageProcessor ip, double radius, int filterType, int whichOutliers, float threshold, boolean lightBackground, boolean dontSubtract) {
Rectangle roi = ip.getRoi();
ImageProcessor mask = ip.getMask();
Rectangle roi1 = null;
int[] lineRadii = makeLineRadii(radius);
boolean snapshotRequired = (filterType==TOP_HAT && !dontSubtract) ||
(isMultiStepFilter(filterType) && (roi.width!=ip.getWidth() || roi.height!=ip.getHeight()));
if (snapshotRequired && ip.getSnapshotPixels()==null)
ip.snapshot();
float minMaxOutliersSign = filterType==MIN || filterType==OPEN ? -1f : 1f; if (filterType == OUTLIERS) minMaxOutliersSign = (ip.isInvertedLut()==(whichOutliers==DARK_OUTLIERS)) ? -1f : 1f;
if (filterType == TOP_HAT) {
boolean invertedLut = ip.isInvertedLut();
boolean invert = (invertedLut && !lightBackground) || (!invertedLut && lightBackground);
minMaxOutliersSign = invert ? 1f : -1f;
}
ImageProcessor snapIp = null;
FloatProcessor fp = null, snapFp = null;
boolean isImagePart = (roi.width<ip.getWidth()) || (roi.height<ip.getHeight());
AtomicInteger nextY = new AtomicInteger(); if (filterType == TOP_HAT && !dontSubtract) {
snapIp = ip.createProcessor(ip.getWidth(), ip.getHeight());
snapIp.setPixels(ip.getSnapshotPixels());
}
for (int ch=0; ch<ip.getNChannels(); ch++) {
int filterType1 = filterType;
if (isMultiStepFilter(filterType)) { filterType1 = (minMaxOutliersSign == -1f) ? MIN : MAX;
if (isImagePart) { int kRadius = kRadius(lineRadii);
int kHeight = kHeight(lineRadii);
Rectangle roiClone = (Rectangle)roi.clone();
roiClone.grow(kRadius, kHeight/2);
roi1 = roiClone.intersection(new Rectangle(ip.getWidth(), ip.getHeight()));
ip.setRoi(roi1);
}
}
doFiltering(ip, lineRadii, filterType1, minMaxOutliersSign, threshold, ch, nextY);
if (isMultiStepFilter(filterType)) { ip.setRoi(roi);
ip.setMask(mask);
if (nextY.get() < 0) break;
int filterType2 = (minMaxOutliersSign == -1f) ? MAX : MIN;
doFiltering(ip, lineRadii, filterType2, -minMaxOutliersSign, threshold, ch, nextY);
if (isImagePart)
resetRoiBoundary(ip, roi, roi1);
}
if (nextY.get() < 0) break;
if (filterType == TOP_HAT && !dontSubtract) { float offset = 0;
if (minMaxOutliersSign == 1f && !(ip instanceof FloatProcessor))
offset = (float)ip.maxValue(); fp = ip.toFloat(ch, fp);
snapFp = snapIp.toFloat(ch, snapFp);
float[] pixels = (float[])fp.getPixels();
float[] snapPixels = (float[])snapFp.getPixels();
int width = ip.getWidth();
for (int y=roi.y; y<roi.y+roi.height; y++)
for (int ix=0, p=y*width+roi.x; ix<roi.width; ix++, p++)
pixels[p] = snapPixels[p] - pixels[p] + offset;
ip.setPixels(ch, fp);
}
}
}
private void doFiltering(final ImageProcessor ip, final int[] lineRadii, final int filterType,
final float minMaxOutliersSign, final float threshold, final int colorChannel, final AtomicInteger nextY) {
Rectangle roi = ip.getRoi();
int width = ip.getWidth();
Object pixels = ip.getPixels();
int numThreads = Math.min(roi.height, this.numThreads);
if (numThreads==0)
return;
int kHeight = kHeight(lineRadii);
int kRadius = kRadius(lineRadii);
final int cacheWidth = roi.width+2*kRadius;
final int cacheHeight = kHeight + (numThreads>1 ? 2*numThreads : 0);
final float[] cache = new float[cacheWidth*cacheHeight];
highestYinCache.set(Math.max(roi.y-kHeight/2, 0) - 1);
final AtomicIntegerArray yForThread = new AtomicIntegerArray(numThreads); for (int i=0; i<numThreads; i++)
yForThread.set(i, -1);
nextY.set(roi.y); Callable[] callables = new Callable[numThreads-1];
for (int i=0; i<numThreads-1; i++) {
final int threadNumber = i+1; callables[i] = new Callable<Void>() {
final public Void call() {
doFiltering(ip, lineRadii, cache, cacheWidth, cacheHeight,
filterType, minMaxOutliersSign, threshold, colorChannel,
yForThread, threadNumber, nextY);
return null;
}
};
}
Future[] futures = ThreadUtil.start(callables);
doFiltering(ip, lineRadii, cache, cacheWidth, cacheHeight,
filterType, minMaxOutliersSign, threshold, colorChannel,
yForThread, 0, nextY);
ThreadUtil.joinAll(futures);
showProgress(1.0, ip instanceof ColorProcessor);
pass++;
}
private void doFiltering(ImageProcessor ip, int[] lineRadii, float[] cache, int cacheWidth, int cacheHeight,
int filterType, float minMaxOutliersSign, float threshold, int colorChannel,
AtomicIntegerArray yForThread, int threadNumber, AtomicInteger nextY) {
if (nextY.get() < 0 || Thread.currentThread().isInterrupted()) return;
int width = ip.getWidth();
int height = ip.getHeight();
Rectangle roi = ip.getRoi();
int kHeight = kHeight(lineRadii);
int kRadius = kRadius(lineRadii);
int kNPoints = kNPoints(lineRadii);
int xmin = roi.x - kRadius;
int xmax = roi.x + roi.width + kRadius;
int[]cachePointers = makeCachePointers(lineRadii, cacheWidth);
int padLeft = xmin<0 ? -xmin : 0;
int padRight = xmax>width? xmax-width : 0;
int xminInside = xmin>0 ? xmin : 0;
int xmaxInside = xmax<width ? xmax : width;
int widthInside = xmaxInside - xminInside;
boolean minOrMax = filterType == MIN || filterType == MAX;
boolean minOrMaxOrOutliers = minOrMax || filterType == OUTLIERS;
boolean sumFilter = filterType == MEAN || filterType == VARIANCE;
boolean medianFilter = filterType == MEDIAN || filterType == OUTLIERS;
double[] sums = sumFilter ? new double[2] : null;
float[] medianBuf1 = (medianFilter||filterType==REMOVE_NAN) ? new float[kNPoints] : null;
float[] medianBuf2 = (medianFilter||filterType==REMOVE_NAN) ? new float[kNPoints] : null;
boolean smallKernel = kRadius < 2;
Object pixels = ip.getPixels();
boolean isFloat = pixels instanceof float[];
float maxValue = isFloat ? Float.NaN : (float)ip.maxValue();
float[] values = isFloat ? (float[])pixels : new float[roi.width];
int numThreads = yForThread.length();
long lastTime = System.currentTimeMillis();
int previousY = kHeight/2-cacheHeight;
boolean rgb = ip instanceof ColorProcessor;
while (true) {
int y = nextY.getAndIncrement(); if (y >= 0)
yForThread.set(threadNumber, y);
boolean threadFinished = y >= roi.y+roi.height || y < 0; if (numThreads>1 && (nThreadsWaiting.get()>0 || threadFinished)) synchronized(this) {
notifyAll(); }
if (threadFinished)
return;
if (threadNumber==0) { long time = System.currentTimeMillis();
if (time-lastTime>100) {
lastTime = time;
showProgress((y-roi.y)/(double)(roi.height), rgb);
if (Thread.currentThread().isInterrupted() || (imp!= null && IJ.escapePressed())) {
nextY.set(Integer.MIN_VALUE);
synchronized(this) {notifyAll();}
return;
}
}
}
for (int i=0; i<cachePointers.length; i++) cachePointers[i] = (cachePointers[i] + cacheWidth*(y-previousY))%cache.length;
previousY = y;
if (numThreads>1) { int slowestThreadY = arrayMinNonNegative(yForThread);
if (y - slowestThreadY + kHeight > cacheHeight) { nThreadsWaiting.incrementAndGet();
synchronized(this) {
slowestThreadY = arrayMinNonNegative(yForThread); if (y - slowestThreadY + kHeight > cacheHeight) {
do {
try {
wait();
if (nextY.get() < 0) return;
} catch (InterruptedException e) {
nextY.set(Integer.MIN_VALUE);
notifyAll();
Thread.currentThread().interrupt(); return;
}
slowestThreadY = arrayMinNonNegative(yForThread);
} while (y - slowestThreadY + kHeight > cacheHeight);
} }
nThreadsWaiting.decrementAndGet();
}
}
if (numThreads==1) { int yStartReading = y==roi.y ? Math.max(roi.y-kHeight/2, 0) : y+kHeight/2;
for (int yNew = yStartReading; yNew<=y+kHeight/2; yNew++) { readLineToCacheOrPad(pixels, width, height, roi.y, xminInside, widthInside,
cache, cacheWidth, cacheHeight, padLeft, padRight, colorChannel, kHeight, yNew);
}
} else { if (!copyingToCache.get() || highestYinCache.get() < y+kHeight/2) synchronized(cache) {
copyingToCache.set(true); while (highestYinCache.get() < arrayMinNonNegative(yForThread) - kHeight/2 + cacheHeight - 1) {
int yNew = highestYinCache.get() + 1;
readLineToCacheOrPad(pixels, width, height, roi.y, xminInside, widthInside,
cache, cacheWidth, cacheHeight, padLeft, padRight, colorChannel, kHeight, yNew);
highestYinCache.set(yNew);
}
copyingToCache.set(false);
}
}
int cacheLineP = cacheWidth * (y % cacheHeight) + kRadius; filterLine(values, width, cache, cachePointers, kNPoints, cacheLineP, roi, y, sums, medianBuf1, medianBuf2, minMaxOutliersSign, maxValue, isFloat, filterType,
smallKernel, sumFilter, minOrMax, minOrMaxOrOutliers, threshold);
if (!isFloat) writeLineToPixels(values, pixels, roi.x+y*width, roi.width, colorChannel); } }
private int arrayMinNonNegative(AtomicIntegerArray array) {
int min = Integer.MAX_VALUE;
for (int i=0; i<array.length(); i++) {
int v = array.get(i);
if (v < min) min = v;
}
return min<0 ? 0 : min;
}
private void filterLine(float[] values, int width, float[] cache, int[] cachePointers, int kNPoints, int cacheLineP, Rectangle roi, int y,
double[] sums, float[] medianBuf1, float[] medianBuf2, float minMaxOutliersSign, float maxValue, boolean isFloat, int filterType,
boolean smallKernel, boolean sumFilter, boolean minOrMax, boolean minOrMaxOrOutliers, float threshold) {
int valuesP = isFloat ? roi.x+y*width : 0;
float max = 0f;
float median = Float.isNaN(cache[cacheLineP]) ? 0 : cache[cacheLineP]; boolean fullCalculation = true;
for (int x=0; x<roi.width; x++, valuesP++) { if (fullCalculation) {
fullCalculation = smallKernel; if (minOrMaxOrOutliers)
max = getAreaMax(cache, x, cachePointers, 0, -Float.MAX_VALUE, minMaxOutliersSign);
if (minOrMax) {
values[valuesP] = max*minMaxOutliersSign;
continue;
}
else if (sumFilter)
getAreaSums(cache, x, cachePointers, sums);
} else {
if (minOrMaxOrOutliers) {
float newPointsMax = getSideMax(cache, x, cachePointers, true, minMaxOutliersSign);
if (newPointsMax >= max) { max = newPointsMax;
} else {
float removedPointsMax = getSideMax(cache, x, cachePointers, false, minMaxOutliersSign);
if (removedPointsMax >= max)
max = getAreaMax(cache, x, cachePointers, 1, newPointsMax, minMaxOutliersSign);
}
if (minOrMax) {
values[valuesP] = max*minMaxOutliersSign;
continue;
}
} else if (sumFilter) {
addSideSums(cache, x, cachePointers, sums);
if (Double.isNaN(sums[0])) fullCalculation = true;
}
}
if (sumFilter) {
if (filterType == MEAN)
values[valuesP] = (float)(sums[0]/kNPoints);
else { float value = (float)((sums[1] - sums[0]*sums[0]/kNPoints)/kNPoints);
if (value>maxValue) value = maxValue;
if (value < 0) value = 0; values[valuesP] = value;
}
} else if (filterType == MEDIAN) {
if (isFloat) {
median = Float.isNaN(values[valuesP]) ? Float.NaN : values[valuesP]; median = getNaNAwareMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
} else
median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
values[valuesP] = median;
} else if (filterType == OUTLIERS) {
float v = cache[cacheLineP+x];
if (v*minMaxOutliersSign+threshold < max) { median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
if (v*minMaxOutliersSign+threshold < median*minMaxOutliersSign)
v = median; }
values[valuesP] = v;
} else if (filterType == REMOVE_NAN) { if (Float.isNaN(values[valuesP]))
values[valuesP] = getNaNAwareMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median);
else
median = values[valuesP]; }
} }
private static void readLineToCacheOrPad(Object pixels, int width, int height, int roiY, int xminInside, int widthInside,
float[]cache, int cacheWidth, int cacheHeight, int padLeft, int padRight, int colorChannel,
int kHeight, int y) {
int lineInCache = y%cacheHeight;
if (y < height) {
readLineToCache(pixels, y*width, xminInside, widthInside,
cache, lineInCache*cacheWidth, padLeft, padRight, colorChannel);
if (y==0) for (int prevY = roiY-kHeight/2; prevY<0; prevY++) { int prevLineInCache = cacheHeight+prevY;
System.arraycopy(cache, 0, cache, prevLineInCache*cacheWidth, cacheWidth);
}
} else
System.arraycopy(cache, cacheWidth*((height-1)%cacheHeight), cache, lineInCache*cacheWidth, cacheWidth);
}
private static void readLineToCache(Object pixels, int pixelLineP, int xminInside, int widthInside,
float[] cache, int cacheLineP, int padLeft, int padRight, int colorChannel) {
if (pixels instanceof byte[]) {
byte[] bPixels = (byte[])pixels;
for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
cache[cp] = bPixels[pp]&0xff;
} else if (pixels instanceof short[]){
short[] sPixels = (short[])pixels;
for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
cache[cp] = sPixels[pp]&0xffff;
} else if (pixels instanceof float[]) {
System.arraycopy(pixels, pixelLineP+xminInside, cache, cacheLineP+padLeft, widthInside);
} else { int[] cPixels = (int[])pixels;
int shift = 16 - 8*colorChannel;
int byteMask = 255<<shift;
for (int pp=pixelLineP+xminInside, cp=cacheLineP+padLeft; pp<pixelLineP+xminInside+widthInside; pp++,cp++)
cache[cp] = (cPixels[pp]&byteMask)>>shift;
}
for (int cp=cacheLineP; cp<cacheLineP+padLeft; cp++)
cache[cp] = cache[cacheLineP+padLeft];
for (int cp=cacheLineP+padLeft+widthInside; cp<cacheLineP+padLeft+widthInside+padRight; cp++)
cache[cp] = cache[cacheLineP+padLeft+widthInside-1];
}
private static void writeLineToPixels(float[] values, Object pixels, int pixelP, int length, int colorChannel) {
if (pixels instanceof byte[]) {
byte[] bPixels = (byte[])pixels;
for (int i=0, p=pixelP; i<length; i++,p++)
bPixels[p] = (byte)(((int)(values[i] + 0.5f))&0xff);
} else if (pixels instanceof short[]) {
short[] sPixels = (short[])pixels;
for (int i=0, p=pixelP; i<length; i++,p++)
sPixels[p] = (short)(((int)(values[i] + 0.5f))&0xffff);
} else { int[] cPixels = (int[])pixels;
int shift = 16 - 8*colorChannel;
int resetMask = 0xffffffff^(0xff<<shift);
for (int i=0, p=pixelP; i<length; i++,p++)
cPixels[p] = (cPixels[p]&resetMask) | (((int)(values[i] + 0.5f))<<shift);
}
}
private static float getAreaMax(float[] cache, int xCache0, int[] kernel, int ignoreRight, float max, float sign) {
for (int kk=0; kk<kernel.length; kk++) { for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0-ignoreRight; p++) {
float v = cache[p]*sign;
if (max < v) max = v;
}
}
return max;
}
private static float getSideMax(float[] cache, int xCache0, int[] kernel, boolean isRight, float sign) {
float max = -Float.MAX_VALUE;
if (!isRight) xCache0--;
for (int kk= isRight ? 1 : 0; kk<kernel.length; kk+=2) { float v = cache[xCache0 + kernel[kk]]*sign;
if (max < v) max = v;
}
return max;
}
private static void getAreaSums(float[] cache, int xCache0, int[] kernel, double[] sums) {
double sum=0, sum2=0;
for (int kk=0; kk<kernel.length; kk++) { for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
double v = cache[p];
sum += v;
sum2 += v*v;
}
}
sums[0] = sum;
sums[1] = sum2;
return;
}
private static void addSideSums(float[] cache, int xCache0, int[] kernel, double[] sums) {
double sum=0, sum2=0;
for (int kk=0; kk<kernel.length; ) {
double v = cache[kernel[kk++]+(xCache0-1)]; sum -= v;
sum2 -= v*v;
v = cache[kernel[kk++]+xCache0]; sum += v;
sum2 += v*v;
}
sums[0] += sum;
sums[1] += sum2;
return;
}
private static float getMedian(float[] cache, int xCache0, int[] kernel,
float[] aboveBuf, float[]belowBuf, int kNPoints, float guess) {
int nAbove = 0, nBelow = 0;
for (int kk=0; kk<kernel.length; kk++) {
for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
float v = cache[p];
if (v > guess) {
aboveBuf[nAbove] = v;
nAbove++;
}
else if (v < guess) {
belowBuf[nBelow] = v;
nBelow++;
}
}
}
int half = kNPoints/2;
if (nAbove>half)
return findNthLowestNumber(aboveBuf, nAbove, nAbove-half-1);
else if (nBelow>half)
return findNthLowestNumber(belowBuf, nBelow, half);
else
return guess;
}
private static float getNaNAwareMedian(float[] cache, int xCache0, int[] kernel,
float[] aboveBuf, float[]belowBuf, int kNPoints, float guess) {
int nAbove = 0, nBelow = 0;
for (int kk=0; kk<kernel.length; kk++) {
for (int p=kernel[kk++]+xCache0; p<=kernel[kk]+xCache0; p++) {
float v = cache[p];
if (Float.isNaN(v)) {
kNPoints--;
} else if (v > guess) {
aboveBuf[nAbove] = v;
nAbove++;
}
else if (v < guess) {
belowBuf[nBelow] = v;
nBelow++;
}
}
}
if (kNPoints == 0) return Float.NaN; int half = kNPoints/2;
if (nAbove>half)
return findNthLowestNumber(aboveBuf, nAbove, nAbove-half-1);
else if (nBelow>half)
return findNthLowestNumber(belowBuf, nBelow, half);
else
return guess;
}
public final static float findNthLowestNumber(float[] buf, int bufLength, int n) {
int i,j;
int l=0;
int m=bufLength-1;
float med=buf[n];
float dum ;
while (l<m) {
i=l ;
j=m ;
do {
while (buf[i]<med) i++ ;
while (med<buf[j]) j-- ;
dum=buf[j];
buf[j]=buf[i];
buf[i]=dum;
i++ ; j-- ;
} while ((j>=n) && (i<=n)) ;
if (j<n) l=i ;
if (n<i) m=j ;
med=buf[n] ;
}
return med ;
}
private void resetRoiBoundary(ImageProcessor ip, Rectangle roi, Rectangle roi1) {
int width = ip.getWidth();
Object pixels = ip.getPixels();
Object snapshot = ip.getSnapshotPixels();
for (int y=roi1.y, p = roi1.x+y*width; y<roi.y; y++,p+=width)
System.arraycopy(snapshot, p, pixels, p, roi1.width);
int leftWidth = roi.x - roi1.x;
int rightWidth = roi1.x+roi1.width - (roi.x+roi.width);
for (int y=roi.y, pL=roi1.x+y*width, pR=roi.x+roi.width+y*width; y<roi.y+roi.height; y++,pL+=width,pR+=width) {
if (leftWidth > 0)
System.arraycopy(snapshot, pL, pixels, pL, leftWidth);
if (rightWidth > 0)
System.arraycopy(snapshot, pR, pixels, pR, rightWidth);
}
for (int y=roi.y+roi.height, p = roi1.x+y*width; y<roi1.y+roi1.height; y++,p+=width)
System.arraycopy(snapshot, p, pixels, p, roi1.width);
}
public void makeKernel(double radius) {
this.radius = radius;
}
private void setDisplayRange(ImageProcessor ip) {
if ((ip instanceof ByteProcessor) || (ip instanceof ColorProcessor)) return;
new ContrastEnhancer().stretchHistogram(ip, 0.5);
}
protected int[] makeLineRadii(double radius) {
if (radius>=1.5 && radius<1.75) radius = 1.75;
else if (radius>=2.5 && radius<2.85)
radius = 2.85;
int r2 = (int) (radius*radius) + 1;
int kRadius = (int)(Math.sqrt(r2+1e-10));
int kHeight = 2*kRadius + 1;
int[] kernel = new int[2*kHeight + 2];
kernel[2*kRadius] = -kRadius;
kernel[2*kRadius+1] = kRadius;
int nPoints = 2*kRadius+1;
for (int y=1; y<=kRadius; y++) { int dx = (int)(Math.sqrt(r2-y*y+1e-10));
kernel[2*(kRadius-y)] = -dx;
kernel[2*(kRadius-y)+1] = dx;
kernel[2*(kRadius+y)] = -dx;
kernel[2*(kRadius+y)+1] = dx;
nPoints += 4*dx+2; }
kernel[kernel.length-2] = nPoints;
kernel[kernel.length-1] = kRadius;
return kernel;
}
private int kHeight(int[] lineRadii) {
return (lineRadii.length-2)/2;
}
private int kRadius(int[] lineRadii) {
return lineRadii[lineRadii.length-1];
}
private int kNPoints(int[] lineRadii) {
return lineRadii[lineRadii.length-2];
}
private int[] makeCachePointers(int[] lineRadii, int cacheWidth) {
int kRadius = kRadius(lineRadii);
int kHeight = kHeight(lineRadii);
int[] cachePointers = new int[2*kHeight];
for (int i=0; i<kHeight; i++) {
cachePointers[2*i] = i*cacheWidth+kRadius + lineRadii[2*i];
cachePointers[2*i+1] = i*cacheWidth+kRadius + lineRadii[2*i+1];
}
return cachePointers;
}
void showMasks() {
int w=150, h=150;
ImageStack stack = new ImageStack(w, h);
for (double r=0.5; r<50; r+=0.5) {
ImageProcessor ip = new FloatProcessor(w,h,new int[w*h]);
float[] pixels = (float[])ip.getPixels();
int[] lineRadii = makeLineRadii(r);
int kHeight = kHeight(lineRadii);
int kRadius = kRadius(lineRadii);
int y0 = h/2-kHeight/2;
for (int i = 0, y = y0; i<kHeight; i++, y++)
for (int x = w/2+lineRadii[2*i], p = x+y*w; x <= w/2+lineRadii[2*i+1]; x++, p++)
pixels[p] = 1f;
stack.addSlice("radius="+r+", size="+(2*kRadius+1), ip);
}
new ImagePlus("Masks", stack).show();
}
public void setNPasses (int nPasses) {
this.nPasses = nPasses;
pass = 0;
}
private void showProgress(double percent, boolean rgb) {
if (nPasses==0) return;
int nPasses2 = rgb?nPasses*3:nPasses;
percent = (double)pass/nPasses2 + percent/nPasses2;
IJ.showProgress(percent);
}
}