package ij.plugin.filter;
import ij.plugin.filter.*;
import ij.*;
import ij.gui.*;
import ij.measure.*;
import ij.process.*;
import ij.util.Tools;
import java.awt.*;
import java.util.*;
public class MaximumFinder implements ExtendedPlugInFilter, DialogListener {
private static double tolerance = 10;
private static boolean strict = false;
public final static int SINGLE_POINTS=0;
public final static int IN_TOLERANCE=1;
public final static int SEGMENTED=2;
public final static int POINT_SELECTION=3;
public final static int LIST=4;
public final static int COUNT=5;
private static int outputType;
private static int dialogOutputType = POINT_SELECTION;
final static String[] outputTypeNames = new String[]
{"Single Points", "Maxima Within Tolerance", "Segmented Particles", "Point Selection", "List", "Count"};
private static boolean excludeOnEdges;
private static boolean useMinThreshold;
private static boolean lightBackground;
private boolean oldMacro = false; private ImagePlus imp; private int flags = DOES_ALL|NO_CHANGES|NO_UNDO; private boolean thresholded; private boolean roiSaved; private boolean previewing; private Vector checkboxes; private boolean thresholdWarningShown = false; private Label messageArea; private double progressDone; private int nPasses = 0; private int width, height; private int intEncodeXMask; private int intEncodeYMask; private int intEncodeShift;
private int[] dirOffset; private Polygon xyCoordinates; final static int[] DIR_X_OFFSET = new int[] { 0, 1, 1, 1, 0, -1, -1, -1 };
final static int[] DIR_Y_OFFSET = new int[] { -1, -1, 0, 1, 1, 1, 0, -1 };
final static byte MAXIMUM = (byte)1; final static byte LISTED = (byte)2; final static byte PROCESSED = (byte)4; final static byte MAX_AREA = (byte)8; final static byte EQUAL = (byte)16; final static byte MAX_POINT = (byte)32; final static byte ELIMINATED = (byte)64;
final static byte[] outputTypeMasks = new byte[] {MAX_POINT, MAX_AREA, MAX_AREA};
final static float SQRT2 = 1.4142135624f;
public int setup(String arg, ImagePlus imp) {
this.imp = imp;
return flags;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
ImageProcessor ip = imp.getProcessor();
ip.resetBinaryThreshold(); thresholded = ip.getMinThreshold()!=ImageProcessor.NO_THRESHOLD;
String options = Macro.getOptions();
if (options!=null && options.indexOf("noise=") >= 0) { oldMacro = true; Macro.setOptions(options.replaceAll("noise=", "prominence="));
}
GenericDialog gd = new GenericDialog(command);
String unit = (imp.getCalibration()!=null)?imp.getCalibration().getValueUnit():null;
int digits = (ip instanceof FloatProcessor || unit != null) ? 2 : 0;
if (unit.equals("Gray Value")) unit = null;
gd.addNumericField("Prominence >",tolerance, digits, 6, unit);
gd.addCheckbox("Strict", strict);
gd.addCheckbox("Exclude edge maxima", excludeOnEdges);
if (thresholded)
gd.addCheckbox("Above lower threshold", useMinThreshold);
gd.addCheckbox("Light background", lightBackground);
gd.addChoice("Output type:", outputTypeNames, outputTypeNames[dialogOutputType]);
gd.addPreviewCheckbox(pfr, "Preview point selection");
gd.addMessage(" "); messageArea = (Label)gd.getMessage();
gd.addDialogListener(this);
checkboxes = gd.getCheckboxes();
previewing = true;
gd.addHelp(IJ.URL+"/docs/menus/process.html#find-maxima");
gd.showDialog(); if (gd.wasCanceled())
return DONE;
previewing = false;
if (!dialogItemChanged(gd, null)) return DONE;
IJ.register(this.getClass()); return flags;
}
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
tolerance = gd.getNextNumber();
if (tolerance<0) tolerance = 0;
outputType = previewing ? POINT_SELECTION : dialogOutputType;
strict = gd.getNextBoolean();
excludeOnEdges = gd.getNextBoolean();
if (thresholded)
useMinThreshold = gd.getNextBoolean();
else
useMinThreshold = false;
lightBackground = gd.getNextBoolean();
dialogOutputType = gd.getNextChoiceIndex();
boolean invertedLut = imp.isInvertedLut();
if (useMinThreshold && ((invertedLut&&!lightBackground) || (!invertedLut&&lightBackground))) {
if (!thresholdWarningShown)
if (!IJ.showMessageWithCancel(
"Find Maxima",
"\"Above Lower Threshold\" option cannot be used\n"+
"when finding minima (image with light background\n"+
"or image with dark background and inverting LUT).")
&& !previewing)
return false; thresholdWarningShown = true;
useMinThreshold = false;
((Checkbox)(checkboxes.elementAt(1))).setState(false); }
if (!gd.isPreviewActive() && messageArea!=null)
messageArea.setText(""); return (!gd.invalidNumber());
}
public void setNPasses(int nPasses) {
this.nPasses = nPasses;
}
public void run(ImageProcessor ip) {
Roi roi = imp.getRoi();
if (outputType == POINT_SELECTION && !roiSaved) {
imp.saveRoi(); roiSaved = true;
}
if (roi!=null && (!roi.isArea() || outputType==SEGMENTED)) {
imp.deleteRoi();
roi = null;
}
boolean invertedLut = imp.isInvertedLut();
double threshold = useMinThreshold?ip.getMinThreshold():ImageProcessor.NO_THRESHOLD;
if ((invertedLut&&!lightBackground) || (!invertedLut&&lightBackground)) {
threshold = ImageProcessor.NO_THRESHOLD; float[] cTable = ip.getCalibrationTable();
ip = ip.duplicate();
if (cTable==null) { ip.invert();
} else { float[] invertedCTable = new float[cTable.length];
for (int i=cTable.length-1; i>=0; i--)
invertedCTable[i] = -cTable[i];
ip.setCalibrationTable(invertedCTable);
}
ip.setRoi(roi);
}
ByteProcessor outIp = null;
boolean strictMode = oldMacro ? excludeOnEdges : strict;
outIp = findMaxima(ip, tolerance, strictMode, threshold, outputType, excludeOnEdges, false); if (outIp == null) return; if (!Prefs.blackBackground) outIp.invertLut();
String resultName;
if (outputType == SEGMENTED) resultName = " Segmented";
else
resultName = " Maxima";
String outname = imp.getTitle();
if (imp.getNSlices()>1)
outname += "("+imp.getCurrentSlice()+")";
outname += resultName;
if (WindowManager.getImage(outname)!=null)
outname = WindowManager.getUniqueName(outname);
ImagePlus maxImp = new ImagePlus(outname, outIp);
Calibration cal = imp.getCalibration().copy();
cal.disableDensityCalibration();
maxImp.setCalibration(cal); maxImp.show();
}
public Polygon getMaxima(ImageProcessor ip, double tolerance, boolean excludeOnEdges) {
return getMaxima(ip, tolerance, excludeOnEdges, excludeOnEdges);
}
public Polygon getMaxima(ImageProcessor ip, double tolerance, boolean strict, boolean excludeOnEdges) {
findMaxima(ip, tolerance, strict, ImageProcessor.NO_THRESHOLD,
MaximumFinder.POINT_SELECTION, excludeOnEdges, false);
if (xyCoordinates==null)
return new Polygon();
else
return xyCoordinates;
}
public static int[] findMaxima(double[] xx, double tolerance, int edgeMode ) {
final int INCLUDE_EDGE = 0;
final int CIRCULAR = 2;
int len = xx.length;
int origLen = len;
if (len<2)
return new int[0];
if (tolerance < 0)
tolerance = 0;
if(edgeMode==CIRCULAR){
double[] cascade3 = new double[len * 3];
for (int jj = 0; jj <len; jj++){
cascade3[jj] = xx[jj];
cascade3[jj + len] = xx[jj];
cascade3[jj + 2*len] = xx[jj];
}
len *= 3;
xx = cascade3;
}
int[] maxPositions = new int[len];
double max = xx[0];
double min = xx[0];
int maxPos = 0;
int lastMaxPos = -1;
boolean leftValleyFound = (edgeMode == INCLUDE_EDGE);
int maxCount = 0;
for (int jj = 1; jj < len; jj++) {
double val = xx[jj];
if (val > min + tolerance)
leftValleyFound = true;
if (val > max && leftValleyFound) {
max = val;
maxPos = jj;
}
if (leftValleyFound)
lastMaxPos = maxPos;
if (val < max - tolerance && leftValleyFound) {
maxPositions[maxCount] = maxPos;
maxCount++;
leftValleyFound = false;
min = val;
max = val;
}
if (val < min) {
min = val;
if (!leftValleyFound)
max = val;
}
}
if (edgeMode == INCLUDE_EDGE) {
if (maxCount > 0 && maxPositions[maxCount - 1] != lastMaxPos)
maxPositions[maxCount++] = lastMaxPos;
if (maxCount == 0 && max - min >= tolerance)
maxPositions[maxCount++] = lastMaxPos;
}
int[] cropped = new int[maxCount];
System.arraycopy(maxPositions, 0, cropped, 0, maxCount);
maxPositions = cropped;
double[] maxValues = new double[maxCount];
for (int jj = 0; jj < maxCount; jj++) {
int pos = maxPositions[jj];
double midPos = pos;
while (pos < len - 1 && xx[pos] == xx[pos + 1]) {
midPos += 0.5;
pos++;
}
maxPositions[jj] = (int) midPos;
maxValues[jj] = xx[maxPositions[jj]];
}
int[] rankPositions = Tools.rank(maxValues);
int[] returnArr = new int[maxCount];
for (int jj = 0; jj < maxCount; jj++) {
int pos = maxPositions[rankPositions[jj]];
returnArr[maxCount - jj - 1] = pos; }
if(edgeMode == CIRCULAR){
int count = 0;
for(int jj = 0; jj < returnArr.length;jj++){
int pos = returnArr[jj] - origLen;
if(pos >= 0 && pos < origLen ) returnArr[count++] = pos;
}
int[] returrn2Arr = new int[count];
System.arraycopy(returnArr, 0, returrn2Arr, 0, count);
returnArr = returrn2Arr;
}
return returnArr;
}
public static int[] findMaxima(double[] xx, double tolerance, boolean excludeOnEdges) {
int edgeBehavior = (excludeOnEdges) ? 1 : 0;
return findMaxima(xx, tolerance, edgeBehavior);
}
public static int[] findMinima(double[] xx, double tolerance, boolean excludeEdges ) {
int edgeMode = (excludeEdges) ? 1 : 0;
return findMinima(xx, tolerance, edgeMode);
}
public static int[] findMinima(double[] xx, double tolerance, int edgeMode) {
int len = xx.length;
double[] negArr = new double[len];
for (int jj = 0; jj < len; jj++)
negArr[jj] = -xx[jj];
int[] minPositions = findMaxima(negArr, tolerance, edgeMode);
return minPositions;
}
public ByteProcessor findMaxima(ImageProcessor ip, double tolerance, int outputType, boolean excludeOnEdges) {
return findMaxima(ip, tolerance, ImageProcessor.NO_THRESHOLD, outputType, excludeOnEdges, false);
}
public ByteProcessor findMaxima(ImageProcessor ip, double tolerance, double threshold,
int outputType, boolean excludeOnEdges, boolean isEDM) {
return findMaxima(ip, tolerance, excludeOnEdges, threshold, outputType, excludeOnEdges, isEDM);
}
public ByteProcessor findMaxima(ImageProcessor ip, double tolerance, boolean strict, double threshold,
int outputType, boolean excludeOnEdges, boolean isEDM) {
if (dirOffset == null) makeDirectionOffsets(ip);
Rectangle roi = ip.getRoi();
byte[] mask = ip.getMaskArray();
if (threshold!=ImageProcessor.NO_THRESHOLD && ip.getCalibrationTable()!=null &&
threshold>0 && threshold<ip.getCalibrationTable().length)
threshold = ip.getCalibrationTable()[(int)threshold]; ByteProcessor typeP = new ByteProcessor(width, height); byte[] types = (byte[])typeP.getPixels();
float globalMin = Float.MAX_VALUE;
float globalMax = -Float.MAX_VALUE;
for (int y=roi.y; y<roi.y+roi.height; y++) { for (int x=roi.x; x<roi.x+roi.width; x++) { float v = ip.getPixelValue(x, y);
if (globalMin>v) globalMin = v;
if (globalMax<v) globalMax = v;
}
}
boolean maximumPossible = globalMax>globalMin;
if (strict && globalMax - globalMin <= tolerance)
maximumPossible = false;
if (threshold !=ImageProcessor.NO_THRESHOLD)
threshold -= (globalMax-globalMin)*1e-6; boolean excludeEdgesNow = excludeOnEdges && outputType!=SEGMENTED;
if (Thread.currentThread().isInterrupted()) return null;
IJ.showStatus("Getting sorted maxima...");
long[] maxPoints = maximumPossible ?
getSortedMaxPoints(ip, typeP, excludeEdgesNow, isEDM, globalMin, globalMax, threshold) : new long[0];
if (Thread.currentThread().isInterrupted()) return null;
IJ.showStatus("Analyzing maxima...");
float maxSortingError = 0;
if (ip instanceof FloatProcessor) maxSortingError = 1.1f * (isEDM ? SQRT2/2f : (globalMax-globalMin)/2e9f);
analyzeAndMarkMaxima(ip, typeP, maxPoints, excludeEdgesNow, isEDM, globalMin, tolerance, strict, outputType, maxSortingError);
if (outputType==POINT_SELECTION || outputType==LIST || outputType==COUNT)
return null;
ByteProcessor outIp;
byte[] pixels;
if (outputType == SEGMENTED) {
outIp = make8bit(ip, typeP, isEDM, globalMin, globalMax, threshold);
cleanupMaxima(outIp, typeP, maxPoints); if (!watershedSegment(outIp)) return null; if (!isEDM) cleanupExtraLines(outIp); watershedPostProcess(outIp); if (excludeOnEdges) deleteEdgeParticles(outIp, typeP);
} else { for (int i=0; i<width*height; i++)
types[i] = (byte)(((types[i]&outputTypeMasks[outputType])!=0)?255:0);
outIp = typeP;
}
byte[] outPixels = (byte[])outIp.getPixels();
if (roi!=null) {
for (int y=0, i=0; y<outIp.getHeight(); y++) { for (int x=0; x<outIp.getWidth(); x++, i++) {
if (x<roi.x || x>=roi.x+roi.width || y<roi.y || y>=roi.y+roi.height) outPixels[i] = (byte)0;
else if (mask !=null && (mask[x-roi.x + roi.width*(y-roi.y)]==0)) outPixels[i] = (byte)0;
}
}
}
return outIp;
}
long[] getSortedMaxPoints(ImageProcessor ip, ByteProcessor typeP, boolean excludeEdgesNow,
boolean isEDM, float globalMin, float globalMax, double threshold) {
Rectangle roi = ip.getRoi();
byte[] types = (byte[])typeP.getPixels();
int nMax = 0; boolean checkThreshold = threshold!=ImageProcessor.NO_THRESHOLD;
Thread thread = Thread.currentThread();
for (int y=roi.y; y<roi.y+roi.height; y++) { if (y%50==0 && thread.isInterrupted()) return null;
for (int x=roi.x, i=x+y*width; x<roi.x+roi.width; x++, i++) { float v = ip.getPixelValue(x,y);
float vTrue = isEDM ? trueEdmHeight(x,y,ip) : v; if (v==globalMin) continue;
if (excludeEdgesNow && (x==0 || x==width-1 || y==0 || y==height-1)) continue;
if (checkThreshold && v<threshold) continue;
boolean isMax = true;
boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); for (int d=0; d<8; d++) { if (isInner || isWithin(x, y, d)) {
float vNeighbor = ip.getPixelValue(x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d]);
float vNeighborTrue = isEDM ? trueEdmHeight(x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d], ip) : vNeighbor;
if (vNeighbor > v && vNeighborTrue > vTrue) {
isMax = false;
break;
}
}
}
if (isMax) {
types[i] = MAXIMUM;
nMax++;
}
} } if (thread.isInterrupted()) return null;
float vFactor = (float)(2e9/(globalMax-globalMin)); long[] maxPoints = new long[nMax]; int iMax = 0;
for (int y=roi.y; y<roi.y+roi.height; y++) for (int x=roi.x, p=x+y*width; x<roi.x+roi.width; x++, p++)
if (types[p]==MAXIMUM) {
float fValue = isEDM?trueEdmHeight(x,y,ip):ip.getPixelValue(x,y);
int iValue = (int)((fValue-globalMin)*vFactor); maxPoints[iMax++] = (long)iValue<<32|p;
}
if (thread.isInterrupted()) return null;
Arrays.sort(maxPoints); return maxPoints;
}
void analyzeAndMarkMaxima(ImageProcessor ip, ByteProcessor typeP, long[] maxPoints, boolean excludeEdgesNow,
boolean isEDM, float globalMin, double tolerance, boolean strict, int outputType, float maxSortingError) {
byte[] types = (byte[])typeP.getPixels();
float[] edmPixels = isEDM ? (float[])ip.getPixels() : null;
int nMax = maxPoints.length;
int [] pList = new int[width*height]; xyCoordinates = null;
Roi roi = null;
boolean displayOrCount = outputType==POINT_SELECTION||outputType==LIST||outputType==COUNT;
if (displayOrCount)
xyCoordinates = new Polygon();
if (imp!=null)
roi = imp.getRoi();
for (int iMax=nMax-1; iMax>=0; iMax--) { if (iMax%100 == 0 && Thread.currentThread().isInterrupted()) return;
int offset0 = (int)maxPoints[iMax]; if ((types[offset0]&PROCESSED)!=0) continue;
int x0 = offset0 % width;
int y0 = offset0 / width;
float v0 = isEDM?trueEdmHeight(x0,y0,ip):ip.getPixelValue(x0,y0);
boolean sortingError;
do { pList[0] = offset0;
types[offset0] |= (EQUAL|LISTED); int listLen = 1; int listI = 0; boolean isEdgeMaximum = (x0==0 || x0==width-1 || y0==0 || y0==height-1);
sortingError = false; boolean maxPossible = true; double xEqual = x0; double yEqual = y0; int nEqual = 1; do { int offset = pList[listI];
int x = offset % width;
int y = offset / width;
boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); for (int d=0; d<8; d++) { int offset2 = offset+dirOffset[d];
if ((isInner || isWithin(x, y, d)) && (types[offset2]&LISTED)==0) {
if (isEDM && edmPixels[offset2]<=0)
continue; if ((types[offset2]&PROCESSED)!=0) {
maxPossible = false; break;
}
int x2 = x+DIR_X_OFFSET[d];
int y2 = y+DIR_Y_OFFSET[d];
float v2 = isEDM ? trueEdmHeight(x2, y2, ip) : ip.getPixelValue(x2, y2);
if (v2 > v0 + maxSortingError) {
maxPossible = false; break;
} else if (v2 >= v0-(float)tolerance) {
if (v2 > v0) { sortingError = true;
offset0 = offset2;
v0 = v2;
x0 = x2;
y0 = y2;
}
pList[listLen] = offset2;
listLen++; types[offset2] |= LISTED;
if ((x2==0 || x2==width-1 || y2==0 || y2==height-1) && (strict || v2 >= v0)) {
isEdgeMaximum = true;
if (excludeEdgesNow) {
maxPossible = false;
break; }
}
if (v2==v0) { types[offset2] |= EQUAL;
xEqual += x2;
yEqual += y2;
nEqual ++;
}
}
} } listI++;
} while (listI < listLen);
if (sortingError) { for (listI=0; listI<listLen; listI++)
types[pList[listI]] = 0; } else {
int resetMask = ~(maxPossible?LISTED:(LISTED|EQUAL));
xEqual /= nEqual;
yEqual /= nEqual;
double minDist2 = 1e20;
int nearestI = 0;
for (listI=0; listI<listLen; listI++) {
int offset = pList[listI];
int x = offset % width;
int y = offset / width;
types[offset] &= resetMask; types[offset] |= PROCESSED; if (maxPossible) {
types[offset] |= MAX_AREA;
if ((types[offset]&EQUAL)!=0) {
double dist2 = (xEqual-x)*(double)(xEqual-x) + (yEqual-y)*(double)(yEqual-y);
if (dist2 < minDist2) {
minDist2 = dist2; nearestI = listI;
}
}
}
} if (maxPossible) {
int offset = pList[nearestI];
types[offset] |= MAX_POINT;
if (displayOrCount && !(this.excludeOnEdges && isEdgeMaximum)) {
int x = offset % width;
int y = offset / width;
if (roi==null || roi.contains(x, y))
xyCoordinates.addPoint(x, y);
}
}
} } while (sortingError); } if (nMax == 0) Arrays.fill(types, (byte)(PROCESSED|MAX_AREA));
if (Thread.currentThread().isInterrupted()) return;
if (displayOrCount) {
if (xyCoordinates == null) xyCoordinates = new Polygon();
int npoints = xyCoordinates.npoints;
if (outputType == POINT_SELECTION) {
if (imp!=null && npoints>0) {
PointRoi points = new PointRoi(xyCoordinates);
if (npoints<15 && points.getSize()<3)
points.setSize(3);
if (npoints==1)
points.setSize(4);
imp.setRoi(points);
}
} else if (outputType==LIST) {
Analyzer.resetCounter();
ResultsTable rt = ResultsTable.getResultsTable();
for (int i=0; i<npoints; i++) {
rt.incrementCounter();
rt.addValue("X", xyCoordinates.xpoints[i]);
rt.addValue("Y", xyCoordinates.ypoints[i]);
}
rt.show("Results");
} else if (outputType==COUNT) {
ResultsTable rt = ResultsTable.getResultsTable();
if (!IJ.isResultsWindow())
rt = new ResultsTable();
rt.incrementCounter();
rt.setValue("Count", rt.size()-1, npoints);
int measurements = Analyzer.getMeasurements();
if ((measurements&Measurements.LABELS)!=0) {
String s = imp.getTitle();
String roiName = roi!=null?roi.getName():null;
if (roiName!=null)
s += ":"+roiName;
if (imp.getStackSize()>1) {
ImageStack stack = imp.getStack();
int currentSlice = imp.getCurrentSlice();
String label = stack.getShortSliceLabel(currentSlice);
String colon = s.equals("")?"":":";
if (label!=null && !label.equals(""))
s += colon+label;
else
s += colon+currentSlice;
}
rt.setLabel(s, rt.size()-1);
}
rt.show("Results");
}
}
if (previewing && messageArea!=null)
messageArea.setText((xyCoordinates==null ? 0 : xyCoordinates.npoints)+" Maxima");
}
ByteProcessor make8bit(ImageProcessor ip, ByteProcessor typeP, boolean isEDM, float globalMin, float globalMax, double threshold) {
byte[] types = (byte[])typeP.getPixels();
double minValue;
if (isEDM) {
threshold = 0.5;
minValue = 1.;
} else
minValue = (threshold == ImageProcessor.NO_THRESHOLD)?globalMin:threshold;
double offset = minValue - (globalMax-minValue)*(1./253/2-1e-6); double factor = 253/(globalMax-minValue);
if (isEDM && factor>1)
factor = 1; ByteProcessor outIp = new ByteProcessor(width, height);
byte[] pixels = (byte[])outIp.getPixels();
long v;
for (int y=0, i=0; y<height; y++) {
for (int x=0; x<width; x++, i++) {
float rawValue = ip.getPixelValue(x, y);
if (threshold!=ImageProcessor.NO_THRESHOLD && rawValue<threshold)
pixels[i] = (byte)0;
else if ((types[i]&MAX_AREA)!=0)
pixels[i] = (byte)255; else {
v = 1 + Math.round((rawValue-offset)*factor);
if (v < 1) pixels[i] = (byte)1;
else if (v<=254) pixels[i] = (byte)(v&255);
else pixels[i] = (byte)254;
}
}
}
return outIp;
}
float trueEdmHeight(int x, int y, ImageProcessor ip) {
int xmax = width - 1;
int ymax = ip.getHeight() - 1;
float[] pixels = (float[])ip.getPixels();
int offset = x + y*width;
float v = pixels[offset];
if (x==0 || y==0 || x==xmax || y==ymax || v==0) {
return v; } else {
float trueH = v + 0.5f*SQRT2; boolean ridgeOrMax = false;
for (int d=0; d<4; d++) { int d2 = (d+4)%8; float v1 = pixels[offset+dirOffset[d]];
float v2 = pixels[offset+dirOffset[d2]];
float h;
if (v>=v1 && v>=v2) {
ridgeOrMax = true;
h = (v1 + v2)/2;
} else {
h = Math.min(v1, v2);
}
h += (d%2==0) ? 1 : SQRT2; if (trueH > h) trueH = h;
}
if (!ridgeOrMax) trueH = v;
return trueH;
}
}
void cleanupMaxima(ByteProcessor outIp, ByteProcessor typeP, long[] maxPoints) {
byte[] pixels = (byte[])outIp.getPixels();
byte[] types = (byte[])typeP.getPixels();
int nMax = maxPoints.length;
int[] pList = new int[width*height];
for (int iMax = nMax-1; iMax>=0; iMax--) {
int offset0 = (int)maxPoints[iMax]; if ((types[offset0]&(MAX_AREA|ELIMINATED))!=0) continue;
int level = pixels[offset0]&255;
int loLevel = level+1;
pList[0] = offset0; types[offset0] |= LISTED; int listLen = 1; int lastLen = 1;
int listI = 0; boolean saddleFound = false;
while (!saddleFound && loLevel >0) {
loLevel--;
lastLen = listLen; listI = 0; do { int offset = pList[listI];
int x = offset % width;
int y = offset / width;
boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); for (int d=0; d<8; d++) { int offset2 = offset+dirOffset[d];
if ((isInner || isWithin(x, y, d)) && (types[offset2]&LISTED)==0) {
if ((types[offset2]&MAX_AREA)!=0 || (((types[offset2]&ELIMINATED)!=0) && (pixels[offset2]&255)>=loLevel)) {
saddleFound = true; break; } else if ((pixels[offset2]&255)>=loLevel && (types[offset2]&ELIMINATED)==0) {
pList[listLen] = offset2;
listLen++; types[offset2] |= LISTED;
}
} } if (saddleFound) break; listI++;
} while (listI < listLen);
} for (listI=0; listI<listLen; listI++) types[pList[listI]] &= ~LISTED;
for (listI=0; listI<lastLen; listI++) { int offset = pList[listI];
pixels[offset] = (byte)loLevel; types[offset] |= ELIMINATED; }
} }
void cleanupExtraLines(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
for (int y=0, i=0; y<height; y++) {
for (int x=0; x<width; x++,i++) {
int v = pixels[i];
if (v!=(byte)255 && v!=0) {
int nRadii = nRadii(pixels, x, y); if (nRadii==0) pixels[i] = (byte)255;
else if (nRadii==1)
removeLineFrom(pixels, x, y);
} } } }
void removeLineFrom (byte[] pixels, int x, int y) {
pixels[x + width*y] = (byte)255; boolean continues;
do {
continues = false;
boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); for (int d=0; d<8; d+=2) { if (isInner || isWithin(x, y, d)) {
int v = pixels[x + width*y + dirOffset[d]];
if (v!=(byte)255 && v!=0) {
int nRadii = nRadii(pixels, x+DIR_X_OFFSET[d], y+DIR_Y_OFFSET[d]);
if (nRadii<=1) { x += DIR_X_OFFSET[d];
y += DIR_Y_OFFSET[d];
pixels[x + width*y] = (byte)255; continues = nRadii==1; break;
}
}
}
} } while (continues);
}
int nRadii (byte[] pixels, int x, int y) {
int offset = x + y*width;
int countTransitions = 0;
boolean prevPixelSet = true;
boolean firstPixelSet = true; boolean isInner = (y!=0 && y!=height-1) && (x!=0 && x!=width-1); for (int d=0; d<8; d++) { boolean pixelSet = prevPixelSet;
if (isInner || isWithin(x, y, d)) {
boolean isSet = (pixels[offset+dirOffset[d]]!=(byte)255);
if ((d&1)==0) pixelSet = isSet; else if (!isSet) pixelSet = false; } else {
pixelSet = true;
}
if (pixelSet && !prevPixelSet)
countTransitions ++;
prevPixelSet = pixelSet;
if (d==0)
firstPixelSet = pixelSet;
}
if (firstPixelSet && !prevPixelSet)
countTransitions ++;
return countTransitions;
}
private void watershedPostProcess(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
int size = ip.getWidth()*ip.getHeight();
for (int i=0; i<size; i++) {
if ((pixels[i]&255)<255)
pixels[i] = (byte)0;
}
}
void deleteEdgeParticles(ByteProcessor ip, ByteProcessor typeP) {
byte[] pixels = (byte[])ip.getPixels();
byte[] types = (byte[])typeP.getPixels();
width = ip.getWidth();
height = ip.getHeight();
ip.setValue(0);
Wand wand = new Wand(ip);
for (int x=0; x<width; x++) {
int y = 0;
if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
deleteParticle(x,y,ip,wand);
y = height - 1;
if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
deleteParticle(x,y,ip,wand);
}
for (int y=1; y<height-1; y++) {
int x = 0;
if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
deleteParticle(x,y,ip,wand);
x = width - 1;
if ((types[x+y*width]&MAX_AREA) != 0 && pixels[x+y*width] != 0)
deleteParticle(x,y,ip,wand);
}
}
void deleteParticle(int x, int y, ByteProcessor ip, Wand wand) {
wand.autoOutline(x, y, 255, 255);
if (wand.npoints==0) {
IJ.log("wand error selecting edge particle at x, y = "+x+", "+y);
return;
}
Roi roi = new PolygonRoi(wand.xpoints, wand.ypoints, wand.npoints, Roi.TRACED_ROI);
ip.snapshot(); ip.setRoi(roi);
ip.fill();
ip.reset(ip.getMask());
}
private boolean watershedSegment(ByteProcessor ip) {
boolean debug = IJ.debugMode;
ImageStack movie=null;
if (debug) {
movie = new ImageStack(ip.getWidth(), ip.getHeight());
movie.addSlice("pre-watershed EDM", ip.duplicate());
}
byte[] pixels = (byte[])ip.getPixels();
int[] histogram = ip.getHistogram();
int arraySize = width*height - histogram[0] -histogram[255];
int[] coordinates = new int[arraySize]; int highestValue = 0;
int maxBinSize = 0;
int offset = 0;
int[] levelStart = new int[256];
for (int v=1; v<255; v++) {
levelStart[v] = offset;
offset += histogram[v];
if (histogram[v] > 0) highestValue = v;
if (histogram[v] > maxBinSize) maxBinSize = histogram[v];
}
int[] levelOffset = new int[highestValue + 1];
for (int y=0, i=0; y<height; y++) {
for (int x=0; x<width; x++, i++) {
int v = pixels[i]&255;
if (v>0 && v<255) {
offset = levelStart[v] + levelOffset[v];
coordinates[offset] = x | y<<intEncodeShift;
levelOffset[v] ++;
}
} } int[] setPointList = new int[Math.min(maxBinSize, (width*height+2)/3)];
int[] table = makeFateTable();
IJ.showStatus("Segmenting (Esc to cancel)");
final int[] directionSequence = new int[] {7, 3, 1, 5, 0, 4, 2, 6}; for (int level=highestValue; level>=1; level--) {
int remaining = histogram[level]; int idle = 0;
while (remaining>0 && idle<8) {
int sumN = 0;
int dIndex = 0;
do { int n = processLevel(directionSequence[dIndex%8], ip, table,
levelStart[level], remaining, coordinates, setPointList);
remaining -= n; sumN += n;
if (n > 0) idle = 0; dIndex++;
} while (remaining>0 && idle++<8);
addProgress(sumN/(double)arraySize);
if (IJ.escapePressed()) { IJ.beep();
IJ.showProgress(1.0);
return false;
}
}
if (remaining>0 && level>1) { int nextLevel = level; do
nextLevel--;
while (nextLevel>1 && histogram[nextLevel]==0);
if (nextLevel > 0) {
int newNextLevelEnd = levelStart[nextLevel] + histogram[nextLevel];
for (int i=0, p=levelStart[level]; i<remaining; i++, p++) {
int xy = coordinates[p];
int x = xy&intEncodeXMask;
int y = (xy&intEncodeYMask)>>intEncodeShift;
int pOffset = x + y*width;
if ((pixels[pOffset]&255)==255) IJ.log("ERROR");
boolean addToNext = false;
if (x==0 || y==0 || x==width-1 || y==height-1)
addToNext = true; else for (int d=0; d<8; d++)
if (isWithin(x, y, d) && pixels[pOffset+dirOffset[d]]==0) {
addToNext = true; break;
}
if (addToNext)
coordinates[newNextLevelEnd++] = xy;
}
histogram[nextLevel] = newNextLevelEnd - levelStart[nextLevel];
}
}
if (debug && (level>170 || level>100 && level<110 || level<10))
movie.addSlice("level "+level, ip.duplicate());
}
if (debug)
new ImagePlus("Segmentation Movie", movie).show();
return true;
}
private int processLevel(int pass, ImageProcessor ip, int[] fateTable,
int levelStart, int levelNPoints, int[] coordinates, int[] setPointList) {
int xmax = width - 1;
int ymax = height - 1;
byte[] pixels = (byte[])ip.getPixels();
int nChanged = 0;
int nUnchanged = 0;
for (int i=0, p=levelStart; i<levelNPoints; i++, p++) {
int xy = coordinates[p];
int x = xy&intEncodeXMask;
int y = (xy&intEncodeYMask)>>intEncodeShift;
int offset = x + y*width;
int index = 0; if (y>0 && (pixels[offset-width]&255)==255)
index ^= 1;
if (x<xmax && y>0 && (pixels[offset-width+1]&255)==255)
index ^= 2;
if (x<xmax && (pixels[offset+1]&255)==255)
index ^= 4;
if (x<xmax && y<ymax && (pixels[offset+width+1]&255)==255)
index ^= 8;
if (y<ymax && (pixels[offset+width]&255)==255)
index ^= 16;
if (x>0 && y<ymax && (pixels[offset+width-1]&255)==255)
index ^= 32;
if (x>0 && (pixels[offset-1]&255)==255)
index ^= 64;
if (x>0 && y>0 && (pixels[offset-width-1]&255)==255)
index ^= 128;
int mask = 1<<pass;
if ((fateTable[index]&mask)==mask)
setPointList[nChanged++] = offset; else
coordinates[levelStart+(nUnchanged++)] = xy;
} for (int i=0; i<nChanged; i++)
pixels[setPointList[i]] = (byte)255;
return nChanged;
}
private int[] makeFateTable() {
int[] table = new int[256];
boolean[] isSet = new boolean[8];
for (int item=0; item<256; item++) { for (int i=0, mask=1; i<8; i++) {
isSet[i] = (item&mask)==mask;
mask*=2;
}
for (int i=0, mask=1; i<8; i++) { if (isSet[(i+4)%8]) table[item] |= mask;
mask*=2;
}
for (int i=0; i<8; i+=2) if (isSet[i]) {
isSet[(i+1)%8] = true;
isSet[(i+7)%8] = true;
}
int transitions=0;
for (int i=0, mask=1; i<8; i++) {
if (isSet[i] != isSet[(i+1)%8])
transitions++;
}
if (transitions>=4) { table[item] = 0;
} else {
}
}
return table;
}
void makeDirectionOffsets(ImageProcessor ip) {
width = ip.getWidth();
height = ip.getHeight();
int shift = 0, mult=1;
do {
shift++; mult*=2;
}
while (mult < width);
intEncodeXMask = mult-1;
intEncodeYMask = ~intEncodeXMask;
intEncodeShift = shift;
dirOffset = new int[] {-width, -width+1, +1, +width+1, +width, +width-1, -1, -width-1 };
}
boolean isWithin(int x, int y, int direction) {
int xmax = width - 1;
int ymax = height -1;
switch(direction) {
case 0:
return (y>0);
case 1:
return (x<xmax && y>0);
case 2:
return (x<xmax);
case 3:
return (x<xmax && y<ymax);
case 4:
return (y<ymax);
case 5:
return (x>0 && y<ymax);
case 6:
return (x>0);
case 7:
return (x>0 && y>0);
}
return false; }
private void addProgress(double deltaProgress) {
if (nPasses==0) return;
progressDone += deltaProgress;
IJ.showProgress(progressDone/nPasses);
}
}