package ij.plugin.filter;
import ij.*;
import ij.plugin.*;
import ij.process.*;
import ij.gui.*;
public class EDM implements ExtendedPlugInFilter {
public static final int BYTE_OVERWRITE = 0;
public static final int BYTE = 1;
public static final int SHORT = 2;
public static final int FLOAT = 3;
public static final int ONE = 41;
public static final int SQRT2 = 58;
public static final int SQRT5 = 92;
private ImagePlus imp; private ImagePlus outImp; private PlugInFilterRunner pfr; private String command; private int outImageType; private ImageStack outStack; private int processType; private MaximumFinder maxFinder = new MaximumFinder(); private double progressDone; private int nPasses; private boolean interrupted;
private boolean background255; private int flags = DOES_8G | PARALLELIZE_STACKS | FINAL_PROCESSING;
private static final int EDM = 0, WATERSHED = 1, UEP = 2, VORONOI = 3;
private static final boolean[] USES_MAX_FINDER = new boolean[] {
false, true, true, true };
private static final boolean[] USES_WATERSHED = new boolean[] {
false, true, false, true };
private static final String[] TITLE_PREFIX = new String[] {
"EDM of ", null, "UEPs of ", "Voronoi of "};
private static final int NO_POINT = -1; private static final double MAXFINDER_TOLERANCE = 0.5;
private static int outputType = BYTE_OVERWRITE;
public int setup (String arg, ImagePlus imp) {
if (arg.equals("final")) {
showOutput();
return DONE;
}
this.imp = imp;
if (arg.equals("watershed")) {
processType = WATERSHED;
flags += KEEP_THRESHOLD;
} else if (arg.equals("points"))
processType = UEP;
else if (arg.equals("voronoi"))
processType = VORONOI;
if (processType != WATERSHED) outImageType = outputType; if (outImageType != BYTE_OVERWRITE)
flags |= NO_CHANGES;
if (imp != null) {
ImageProcessor ip = imp.getProcessor();
if (!ip.isBinary()) {
IJ.error("8-bit binary image (0 and 255) required.");
return DONE;
}
ip.resetRoi();
boolean invertedLut = imp.isInvertedLut();
background255 = (invertedLut && Prefs.blackBackground) || (!invertedLut && !Prefs.blackBackground);
}
return flags;
}
public int showDialog (ImagePlus imp, String command, PlugInFilterRunner pfr) {
this.pfr = pfr;
int width = imp.getWidth();
int height= imp.getHeight();
flags = IJ.setupDialog(imp, flags);
if ((flags&DOES_STACKS)!=0 && outImageType!=BYTE_OVERWRITE) {
outStack = new ImageStack(width, height, imp.getStackSize());
maxFinder.setNPasses(imp.getStackSize());
}
return flags;
}
public void run (ImageProcessor ip) {
if (interrupted) return;
int width = ip.getWidth();
int height = ip.getHeight();
int backgroundValue = (processType==VORONOI) ?
(background255 ? 0 : (byte)255) : (background255 ? (byte)255 : 0); if (USES_WATERSHED[processType]) nPasses = 0; FloatProcessor floatEdm = makeFloatEDM(ip, backgroundValue, false);
ByteProcessor maxIp = null;
if (USES_MAX_FINDER[processType]) {
if (processType == VORONOI) floatEdm.multiply(-1); int maxOutputType = USES_WATERSHED[processType] ? MaximumFinder.SEGMENTED : MaximumFinder.SINGLE_POINTS;
boolean isEDM = processType!=VORONOI;
maxIp = maxFinder.findMaxima(floatEdm, MAXFINDER_TOLERANCE,
ImageProcessor.NO_THRESHOLD, maxOutputType, false, isEDM);
if (maxIp == null) { interrupted = true;
return;
} else if (processType != WATERSHED) {
if (processType == VORONOI) floatEdm.multiply(-1);
resetMasked(floatEdm, maxIp, processType == VORONOI ? -1 : 0);
}
}
ImageProcessor outIp = null;
if (processType==WATERSHED) {
if (background255) maxIp.invert();
ip.copyBits(maxIp, 0, 0, Blitter.COPY);
ip.setBinaryThreshold();
} else switch (outImageType) { case FLOAT:
outIp = floatEdm;
break;
case SHORT:
floatEdm.setMinAndMax(0., 65535.);
outIp = floatEdm.convertToShort(true);
break;
case BYTE:
floatEdm.setMinAndMax(0., 255.);
outIp = floatEdm.convertToByte(true);
break;
case BYTE_OVERWRITE:
ip.setPixels(0, floatEdm);
if (floatEdm.getMax() > 255.)
ip.resetMinAndMax(); }
if (outImageType != BYTE_OVERWRITE) { if (outStack==null) {
outImp = new ImagePlus(TITLE_PREFIX[processType]+imp.getShortTitle(), outIp);
} else
outStack.setPixels(outIp.getPixels(), pfr.getSliceNumber());
}
}
public void setNPasses (int nPasses) {
this.nPasses = nPasses;
progressDone = 0;
if (USES_MAX_FINDER[processType]) maxFinder.setNPasses(nPasses);
}
public void toEDM (ImageProcessor ip) {
ip.setPixels(0, makeFloatEDM(ip, 0, false));
ip.resetMinAndMax();
}
public void toWatershed (ImageProcessor ip) {
FloatProcessor floatEdm = makeFloatEDM(ip, 0, false);
ByteProcessor maxIp = maxFinder.findMaxima(floatEdm, MAXFINDER_TOLERANCE,
ImageProcessor.NO_THRESHOLD, MaximumFinder.SEGMENTED, false, true);
if (maxIp != null) ip.copyBits(maxIp, 0, 0, Blitter.AND);
}
public ShortProcessor make16bitEDM (ImageProcessor ip) {
FloatProcessor floatEdm = makeFloatEDM(ip, 0, false);
floatEdm.setMinAndMax(0, 65535./ONE);
return (ShortProcessor)floatEdm.convertToShort(true);
}
public FloatProcessor makeFloatEDM (ImageProcessor ip, int backgroundValue, boolean edgesAreBackground) {
int width = ip.getWidth();
int height = ip.getHeight();
FloatProcessor fp = new FloatProcessor(width, height);
byte[] bPixels = (byte[])ip.getPixels();
float[] fPixels = (float[])fp.getPixels();
final int progressInterval = 100;
int nProgressUpdates = height/progressInterval; double progressAddendum = (nProgressUpdates>0) ? 0.5/nProgressUpdates : 0;
for (int i=0; i<width*height; i++)
if (bPixels[i]!=backgroundValue) fPixels[i] = Float.MAX_VALUE;
int[][] pointBufs = new int[2][width]; int yDist = Integer.MAX_VALUE; for (int x=0; x<width; x++) {
pointBufs[0][x] = NO_POINT;
pointBufs[1][x] = NO_POINT;
}
for (int y=0; y<height; y++) {
if (edgesAreBackground) yDist = y+1; edmLine(bPixels, fPixels, pointBufs, width, y*width, y, backgroundValue, yDist);
if (y%progressInterval == 0) {
if (Thread.currentThread().isInterrupted()) return null;
addProgress(progressAddendum);
}
}
for (int x=0; x<width; x++) {
pointBufs[0][x] = NO_POINT;
pointBufs[1][x] = NO_POINT;
}
for (int y=height-1; y>=0; y--) {
if (edgesAreBackground) yDist = height-y;
edmLine(bPixels, fPixels, pointBufs, width, y*width, y, backgroundValue, yDist);
if (y%progressInterval == 0) {
if (Thread.currentThread().isInterrupted()) return null;
addProgress(progressAddendum);
}
}
fp.sqrt();
return fp;
}
private void edmLine(byte[] bPixels, float[] fPixels, int[][] pointBufs, int width,
int offset, int y, int backgroundValue, int yDist) {
int[] points = pointBufs[0]; int pPrev = NO_POINT;
int pDiag = NO_POINT; int pNextDiag;
boolean edgesAreBackground = yDist != Integer.MAX_VALUE;
int distSqr = Integer.MAX_VALUE; for (int x=0; x<width; x++, offset++) {
pNextDiag = points[x];
if (bPixels[offset] == backgroundValue) {
points[x] = x | y<<16; } else { if (edgesAreBackground)
distSqr = (x+1 < yDist) ? (x+1)*(x+1) : yDist*yDist; float dist2 = minDist2(points, pPrev, pDiag, x, y, distSqr);
if (fPixels[offset] > dist2) fPixels[offset] = dist2;
}
pPrev = points[x];
pDiag = pNextDiag;
}
offset--; points = pointBufs[1]; pPrev = NO_POINT;
pDiag = NO_POINT;
for (int x=width-1; x>=0; x--, offset--) {
pNextDiag = points[x];
if (bPixels[offset] == backgroundValue) {
points[x] = x | y<<16; } else { if (edgesAreBackground)
distSqr = (width-x < yDist) ? (width-x)*(width-x) : yDist*yDist;
float dist2 = minDist2(points, pPrev, pDiag, x, y, distSqr);
if (fPixels[offset] > dist2) fPixels[offset] = dist2;
}
pPrev = points[x];
pDiag = pNextDiag;
}
}
private float minDist2 (int[] points, int pPrev, int pDiag, int x, int y, int distSqr) {
int p0 = points[x]; int nearestPoint = p0;
if (p0 != NO_POINT) {
int x0 = p0& 0xffff; int y0 = (p0>>16)&0xffff;
int dist1Sqr = (x-x0)*(x-x0)+(y-y0)*(y-y0);
if (dist1Sqr < distSqr)
distSqr = dist1Sqr;
}
if (pDiag!=p0 && pDiag!=NO_POINT) {
int x1 = pDiag&0xffff; int y1 = (pDiag>>16)&0xffff;
int dist1Sqr = (x-x1)*(x-x1)+(y-y1)*(y-y1);
if (dist1Sqr < distSqr) {
nearestPoint = pDiag;
distSqr = dist1Sqr;
}
}
if (pPrev!=pDiag && pPrev!=NO_POINT) {
int x1 = pPrev& 0xffff; int y1 = (pPrev>>16)&0xffff;
int dist1Sqr = (x-x1)*(x-x1)+(y-y1)*(y-y1);
if (dist1Sqr < distSqr) {
nearestPoint = pPrev;
distSqr = dist1Sqr;
}
}
points[x] = nearestPoint;
return (float)distSqr;
}
private void byteFromFloat(ImageProcessor ip, FloatProcessor floatEdm) {
int width = ip.getWidth();
int height = ip.getHeight();
byte[] bPixels = (byte[])ip.getPixels();
float[] fPixels = (float[])floatEdm.getPixels();
for (int i=0; i<width*height; i++) {
float v = fPixels[i];
bPixels[i] = v<255f ? (byte)(v+0.5) : (byte)255;
}
}
private void resetMasked(FloatProcessor floatEdm, ImageProcessor mask, int resetOnThis) {
int width = mask.getWidth();
int height = mask.getHeight();
byte[] mPixels = (byte[])mask.getPixels();
float[] fPixels = (float[])floatEdm.getPixels();
for (int i=0; i<width*height; i++)
if (mPixels[i] == resetOnThis) fPixels[i] = 0;
}
private void showOutput() {
if (interrupted) return;
if (outStack!=null) {
outImp = new ImagePlus(TITLE_PREFIX[processType]+imp.getShortTitle(), outStack);
int[] d = imp.getDimensions();
outImp.setDimensions(d[2], d[3], d[4]);
for (int i=1; i<=imp.getStackSize(); i++)
outStack.setSliceLabel(imp.getStack().getSliceLabel(i), i);
}
if (outImageType != BYTE_OVERWRITE) {
ImageProcessor ip = outImp.getProcessor();
if (!Prefs.blackBackground) ip.invertLut();
ip.resetMinAndMax();
outImp.show();
}
}
private void addProgress(double deltaProgress) {
if (nPasses == 0) return;
progressDone += deltaProgress;
IJ.showProgress(progressDone/nPasses);
}
public static void setOutputType(int type) {
if (type<BYTE_OVERWRITE || type>FLOAT)
throw new IllegalArgumentException("Invalid type: "+type);
outputType = type;
}
public static int getOutputType() {
return outputType;
}
}