package ij.plugin.filter;
import ij.IJ;
import ij.ImagePlus;
import ij.gui.Roi;
import ij.gui.ShapeRoi;
import ij.process.*;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
public class ThresholdToSelection implements PlugInFilter {
ImagePlus image;
ImageProcessor ip;
float min, max;
int w, h;
boolean showStatus;
final static double PROGRESS_FRACTION_OUTLINING = 0.9;
public void run(ImageProcessor ip) {
showStatus = true;
image.setRoi(convert(ip));
}
public static Roi run(ImagePlus imp) {
ThresholdToSelection tts = new ThresholdToSelection();
return tts.convert(imp.getProcessor());
}
public Roi convert(ImageProcessor ip) {
this.ip = ip;
min = (float)ip.getMinThreshold();
max = (float)ip.getMaxThreshold();
w = ip.getWidth();
h = ip.getHeight();
return getRoi();
}
final boolean selected(int x, int y) {
float v = ip.getf(x,y);
return v>=min && v<=max;
}
static class Outline {
int[] x, y;
int first, last, reserved;
final int GROW = 10;
public Outline() {
reserved = GROW;
x = new int[reserved];
y = new int[reserved];
first = last = GROW / 2;
}
private void needs(int neededAtBegin, int neededAtEnd) {
if (neededAtBegin > first || neededAtEnd > reserved - last) {
int extraSpace = Math.max(GROW, Math.abs(x[last-1] - x[first])); int newSize = reserved + neededAtBegin + neededAtEnd + extraSpace;
int newFirst = neededAtBegin + extraSpace/2;
int[] newX = new int[newSize];
int[] newY = new int[newSize];
System.arraycopy(x, first, newX, newFirst, last-first);
System.arraycopy(y, first, newY, newFirst, last-first);
x = newX;
y = newY;
last += newFirst - first;
first = newFirst;
reserved = newSize;
}
}
public void append(int x, int y) {
if (last-first>=2 && collinear(this.x[last-2], this.y[last-2], this.x[last-1], this.y[last-1], x , y)) {
this.x[last-1] = x; this.y[last-1] = y;
} else {
needs(0, 1); this.x[last] = x;
this.y[last] = y;
last++;
}
}
public void prepend(int x, int y) {
if (last-first>=2 && collinear(this.x[first+1], this.y[first+1], this.x[first], this.y[first], x , y)) {
this.x[first] = x; this.y[first] = y;
} else {
needs(1, 0); first--;
this.x[first] = x;
this.y[first] = y;
}
}
public void append(Outline o) {
int size = last - first;
int oSize = o.last - o.first;
if (size <= o.first && oSize > reserved - last) { System.arraycopy(x, first, o.x, o.first - size, size); System.arraycopy(y, first, o.y, o.first - size, size);
x = o.x;
y = o.y;
first = o.first - size;
last = o.last;
reserved = o.reserved;
} else { needs(0, oSize);
System.arraycopy(o.x, o.first, x, last, oSize);
System.arraycopy(o.y, o.first, y, last, oSize);
last += oSize;
}
}
public void prepend(Outline o) {
int size = last - first;
int oSize = o.last - o.first;
if (size <= o.reserved - o.last && oSize > first) { System.arraycopy(x, first, o.x, o.last, size); System.arraycopy(y, first, o.y, o.last, size);
x = o.x;
y = o.y;
first = o.first;
last = o.last + size;
reserved = o.reserved;
} else { needs(oSize, 0);
first -= oSize;
System.arraycopy(o.x, o.first, x, first, oSize);
System.arraycopy(o.y, o.first, y, first, oSize);
}
}
public Polygon getPolygon() {
int i, j=first+1;
for (i=first+1; i+1<last; j++) {
if (collinear(x[j - 1], y[j - 1], x[j], y[j], x[j + 1], y[j + 1])) {
last--;
continue;
}
if (i != j) {
x[i] = x[j];
y[i] = y[j];
}
i++;
}
if (collinear(x[j - 1], y[j - 1], x[j], y[j], x[first], y[first]))
last--;
else {
x[i] = x[j];
y[i] = y[j];
}
if (last - first > 2 && collinear(x[last - 1], y[last - 1], x[first], y[first], x[first + 1], y[first + 1]))
first++;
int count = last - first;
int[] xNew = new int[count];
int[] yNew = new int[count];
System.arraycopy(x, first, xNew, 0, count);
System.arraycopy(y, first, yNew, 0, count);
return new Polygon(xNew, yNew, count);
}
boolean collinear (int x1, int y1, int x2, int y2, int x3, int y3) {
return (x2-x1)*(y3-y2) == (y2-y1)*(x3-x2);
}
public String toString() {
String res = "[first:" + first + ",last:" + last +
",reserved:" + reserved + ":";
if (last > x.length) System.err.println("ERROR!");
int nmax = 10; for (int i = first; i < last && i < x.length; i++) {
if (last-first > nmax && i-first > nmax/2) {
i = last - nmax/2;
res += "...";
nmax = last-first; } else
res += "(" + x[i] + "," + y[i] + ")";
}
return res + "]";
}
}
Roi getRoi() {
if (showStatus)
IJ.showStatus("Converting threshold to selection");
boolean[] prevRow, thisRow;
ArrayList polygons = new ArrayList();
Outline[] outline;
int progressInc = Math.max(h/50, 1);
prevRow = new boolean[w + 2];
thisRow = new boolean[w + 2];
outline = new Outline[w + 1];
for (int y = 0; y <= h; y++) {
boolean[] b = prevRow; prevRow = thisRow; thisRow = b;
int xAfterLowerRightCorner = -1; Outline oAfterLowerRightCorner = null; thisRow[1] = y < h ? selected(0, y) : false;
for (int x = 0; x <= w; x++) {
if (y < h && x < w - 1)
thisRow[x + 2] = selected(x + 1, y); else if (x < w - 1)
thisRow[x + 2] = false;
if (thisRow[x + 1]) { if (!prevRow[x + 1]) {
if (outline[x] == null) {
if (outline[x + 1] == null) {
outline[x + 1] = outline[x] = new Outline();
outline[x].append(x + 1, y);
outline[x].append(x, y);
} else {
outline[x] = outline[x + 1]; outline[x + 1] = null;
outline[x].append(x, y);
}
} else if (outline[x + 1] == null) {
if (x == xAfterLowerRightCorner) {
outline[x + 1] = outline[x];
outline[x] = oAfterLowerRightCorner;
outline[x].append(x, y);
outline[x + 1].prepend(x + 1, y);
} else {
outline[x + 1] = outline[x];
outline[x] = null;
outline[x + 1].prepend(x + 1, y);
}
} else if (outline[x + 1] == outline[x]) {
if (x < w - 1 && y < h && x != xAfterLowerRightCorner
&& !thisRow[x + 2] && prevRow[x + 2]) { outline[x] = null;
outline[x + 1].prepend(x + 1,y);
xAfterLowerRightCorner = x + 1;
oAfterLowerRightCorner = outline[x + 1];
} else {
polygons.add(outline[x].getPolygon()); outline[x + 1] = null;
outline[x] = (x == xAfterLowerRightCorner) ? oAfterLowerRightCorner : null;
}
} else {
outline[x].prepend(outline[x + 1]); for (int x1 = 0; x1 <= w; x1++)
if (x1 != x + 1 && outline[x1] == outline[x + 1]) {
outline[x1] = outline[x]; outline[x + 1] = null; outline[x] = (x == xAfterLowerRightCorner) ? oAfterLowerRightCorner : null;
break;
}
if (outline[x + 1] != null)
throw new RuntimeException("assertion failed");
}
}
if (!thisRow[x]) {
if (outline[x] == null)
throw new RuntimeException("assertion failed");
outline[x].append(x, y + 1);
}
} else { if (prevRow[x + 1]) {
if (outline[x] == null) {
if (outline[x + 1] == null) {
outline[x] = outline[x + 1] = new Outline();
outline[x].append(x, y);
outline[x].append(x + 1, y);
} else {
outline[x] = outline[x + 1];
outline[x + 1] = null;
outline[x].prepend(x, y);
}
} else if (outline[x + 1] == null) {
if (x == xAfterLowerRightCorner) {
outline[x + 1] = outline[x];
outline[x] = oAfterLowerRightCorner;
outline[x].prepend(x, y);
outline[x + 1].append(x + 1, y);
} else {
outline[x + 1] = outline[x];
outline[x] = null;
outline[x + 1].append(x + 1, y);
}
} else if (outline[x + 1] == outline[x]) {
if (x < w - 1 && y < h && x != xAfterLowerRightCorner
&& thisRow[x + 2] && !prevRow[x + 2]) { outline[x] = null;
outline[x + 1].append(x + 1,y);
xAfterLowerRightCorner = x + 1;
oAfterLowerRightCorner = outline[x + 1];
} else {
polygons.add(outline[x].getPolygon()); outline[x + 1] = null;
outline[x] = x == xAfterLowerRightCorner ? oAfterLowerRightCorner : null;
}
} else {
if (x < w - 1 && y < h && x != xAfterLowerRightCorner
&& thisRow[x + 2] && !prevRow[x + 2]) { outline[x].append(x + 1, y);
outline[x + 1].prepend(x + 1,y);
xAfterLowerRightCorner = x + 1;
oAfterLowerRightCorner = outline[x];
outline[x] = null;
} else {
outline[x].append(outline[x + 1]); for (int x1 = 0; x1 <= w; x1++)
if (x1 != x + 1 && outline[x1] == outline[x + 1]) {
outline[x1] = outline[x]; outline[x + 1] = null; outline[x] = (x == xAfterLowerRightCorner) ? oAfterLowerRightCorner : null;
break;
}
if (outline[x + 1] != null)
throw new RuntimeException("assertion failed");
}
}
}
if (thisRow[x]) {
if (outline[x] == null)
throw new RuntimeException("assertion failed");
outline[x].prepend(x, y + 1);
}
}
}
if (y%progressInc==0) {
if (Thread.currentThread().isInterrupted()) return null;
if (showStatus)
IJ.showProgress(y*(PROGRESS_FRACTION_OUTLINING/h));
}
}
if (polygons.size()==0)
return null;
if (showStatus) IJ.showStatus("Converting threshold to selection...");
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
progressInc = Math.max(polygons.size()/10, 1);
for (int i = 0; i < polygons.size(); i++) {
path.append((Polygon)polygons.get(i), false);
if (Thread.currentThread().isInterrupted()) return null;
if (showStatus && i%progressInc==0)
IJ.showProgress(PROGRESS_FRACTION_OUTLINING + i*(1.-PROGRESS_FRACTION_OUTLINING)/polygons.size());
}
ShapeRoi shape = new ShapeRoi(path);
Roi roi = shape!=null ? shape.trySimplify():null; if (showStatus)
IJ.showProgress(1.0);
return roi;
}
public int setup(String arg, ImagePlus imp) {
image = imp;
return DOES_8G | DOES_16 | DOES_32 | NO_CHANGES;
}
public void showStatus(boolean showStatus) {
this.showStatus = showStatus;
}
}