package ij.plugin.filter; import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.plugin.filter.ParticleAnalyzer;
import ij.measure.*;
import ij.util.*;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
public class LineGraphAnalyzer implements PlugInFilter, Measurements {
static final int MAX_EXTRAPOLATE = 10; static final int MAX_Y_JUMP = 10; ImagePlus imp;
public int setup(String arg, ImagePlus imp) {
this.imp = imp;
return DOES_ALL|NO_CHANGES;
}
public void run(ImageProcessor ip) {
analyze(imp);
}
public void analyze(ImagePlus imp) {
Calibration cal = imp.getCalibration();
boolean invertedLut = imp.isInvertedLut();
Roi roi = imp.getRoi();
ImageProcessor ip = imp.getProcessor();
Rectangle rect = ip.getRoi();
int height = ip.getHeight();
double minThreshold = ip.getMinThreshold();
double maxThreshold = ip.getMaxThreshold();
if (minThreshold == ImageProcessor.NO_THRESHOLD) { double midValue = cal.getCValue(0.5*(ip.getMin() + ip.getMax()));
minThreshold = invertedLut ? midValue : -Float.MAX_VALUE;
maxThreshold = invertedLut ? Float.MAX_VALUE : midValue;
}
int xStart = -1; FloatArray xData = new FloatArray(rect.width);
ArrayList<FloatArray> yDataAll = new ArrayList<FloatArray>(10); FloatArray yValuesLo = new FloatArray(10); FloatArray yValuesHi = new FloatArray(10);
FloatArray curveValuesLo = new FloatArray(10); FloatArray curveValuesHi = new FloatArray(10);
Extrapolator extrapolator = new Extrapolator();
for (int x=rect.x; x < rect.x+rect.width; x++) {
boolean lastIsForeground = false;
for (int y=rect.y; y<rect.y+rect.height; y++) { boolean isForeground = false;
if (roi == null || roi.contains(x,y)) {
float value = ip.getPixelValue(x, y);
isForeground = value >= minThreshold && value <= maxThreshold;
}
if (isForeground) {
if (!lastIsForeground) { yValuesLo.add(y);
lastIsForeground = true;
if (xStart < 0) xStart = x;
}
} else if (lastIsForeground) {
yValuesHi.add(y-1); lastIsForeground = false;
}
}
if (yValuesLo.size() > yValuesHi.size())
yValuesHi.add(rect.y+rect.height-1);
if (xStart >= 0) {
float xScaled = (float)cal.getX(x);
xData.add(xScaled);
}
if (yValuesLo.size() > 0) {
double[] missing = new double[yDataAll.size()];
for (int n=0; n<yDataAll.size(); n++) { FloatArray arr = yDataAll.get(n);
for (int ix = x-xStart-1; ix >= 0 && ix >= x-xStart-MAX_EXTRAPOLATE; ix--) {
if (ix >= arr.size() || Float.isNaN(arr.get(ix)))
missing[n]++;
}
}
int[] ranks = Tools.rank(missing);
for (int i=0; i<ranks.length; i++) { int n = ranks[i]; double yExtrapolated = extrapolated(yDataAll.get(n), x-xStart, extrapolator);
double minDistance = Double.MAX_VALUE;
int jOfMinDist = -1;
for (int j=0; j<yValuesLo.size(); j++) {
float yValue = 0.5f*(yValuesLo.get(j) + yValuesHi.get(j));
double distance = Math.abs(yExtrapolated - yValue);
double tolerance = yValuesHi.get(j) - yValuesLo.get(j) + 1;
distance = Math.sqrt(distance*distance + tolerance*tolerance) - tolerance; float overlap = Float.NaN;
if (!Float.isNaN(curveValuesLo.get(n))) {
overlap = Math.min(curveValuesHi.get(n) - yValuesLo.get(j), yValuesHi.get(j) - curveValuesLo.get(n));
if (overlap >= -1)
distance *= 0.2; }
if (distance < minDistance && (distance <= MAX_Y_JUMP || overlap > -1)) {
minDistance = distance;
jOfMinDist = j;
}
}
if (jOfMinDist >= 0) {
addPoint(yDataAll, curveValuesLo, curveValuesHi, n, x-xStart, yValuesLo.get(jOfMinDist), yValuesHi.get(jOfMinDist));
yValuesLo.set(jOfMinDist, Float.NaN); }
}
for (int j=0; j<yValuesLo.size(); j++) { if (!Float.isNaN(yValuesLo.get(j))) {
for (int n=0; n<yDataAll.size(); n++) {
FloatArray arr = yDataAll.get(n);
if (arr.size() < x-xStart-MAX_EXTRAPOLATE) {
addPoint(yDataAll, curveValuesLo, curveValuesHi, n, x-xStart, yValuesLo.get(j), yValuesHi.get(j));
yValuesLo.set(j, Float.NaN);
break;
}
}
}
if (!Float.isNaN(yValuesLo.get(j))) addPoint(yDataAll, curveValuesLo, curveValuesHi, -1, x-xStart, yValuesLo.get(j), yValuesHi.get(j));
}
yValuesLo.clear();
yValuesHi.clear();
}
for (int n=0; n<yDataAll.size(); n++) {
FloatArray arr = yDataAll.get(n);
if (arr.size() < x-xStart) curveValuesLo.set(n, Float.NaN); }
}
if (xData.size() == 0)
return;
int maxlen = 0; for (int n=0; n<yDataAll.size(); n++)
if (yDataAll.get(n).size() > maxlen)
maxlen = yDataAll.get(n).size();
double[] points = new double[yDataAll.size()];
for (int n=0; n<yDataAll.size(); n++) { FloatArray arr = yDataAll.get(n);
for (int ix=0; ix<arr.size(); ix++) {
if (!Float.isNaN(arr.get(ix)))
points[n]++;
}
}
int[] ranks = Tools.rank(points);
String xLabel = "X ("+cal.getUnits()+")";
String yLabel = "Y ("+cal.getYUnit()+")";
Plot plot = new Plot(WindowManager.getUniqueName("Line Graph"), xLabel, yLabel);
float[] xArray = xData.toArray();
if (xArray.length > maxlen)
xArray = Arrays.copyOf(xArray, maxlen);
for (int i=ranks.length-1; i>=0; i--) { int n = ranks[i];
float[] yArray = yDataAll.get(n).toArray();
for (int j=0; j<yArray.length; j++) {
if (cal.scaled())
yArray[j] = (float)cal.getY(yArray[j], height);
else
yArray[j] = height - 1 - yArray[j];
}
plot.addPoints(xArray, yArray, null, Plot.LINE, "y"+(ranks.length-i));
}
plot.setLimitsToFit(false);
plot.show();
}
void addPoint(ArrayList<FloatArray> yDataAll, FloatArray curveValuesLo, FloatArray curveValuesHi, int n, int x, float yLo, float yHi) {
if (n < 0) {
yDataAll.add(new FloatArray());
n = yDataAll.size() - 1;
curveValuesLo.add(yLo);
curveValuesHi.add(yHi);
}
FloatArray arr = yDataAll.get(n);
for (int i=arr.size(); i<x; i++) arr.add(Float.NaN);
arr.add(0.5f*(yLo + yHi));
curveValuesLo.set(n, yLo);
curveValuesHi.set(n, yHi);
}
float extrapolated(FloatArray arr, int x, Extrapolator extrapolator) {
extrapolator.clear();
for (int i=0, ix=x-1; ix>=0; ix--) {
if (ix >= arr.size()) continue; double y = arr.get(ix);
if (Double.isNaN(y) && ix < x-MAX_EXTRAPOLATE && i==0)
return Float.NaN; if (ix < x-MAX_EXTRAPOLATE)
break; if (!Double.isNaN(y)) {
extrapolator.add(ix, y);
i++;
}
}
return (float)extrapolator.extrapolate(x);
}
class Extrapolator {
int counter = 0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
double firstX, firstY = Double.NaN;
public void clear() {
counter = 0;
sumX = 0; sumY = 0;
sumXY = 0; sumX2 = 0;
firstY = Double.NaN;
}
public void add(double x, double y) {
counter++;
sumX+=x; sumY+=y;
sumXY+=x*y; sumX2+=x*x;
if (Double.isNaN(firstY)) {
firstX = x;
firstY = y;
}
}
public double extrapolate(double x) {
if (counter <= 0) return Double.NaN;
double slope=(sumXY-sumX*sumY/counter)/(sumX2-sumX*sumX/counter);
if (Double.isNaN(slope) && Math.abs(x-firstX) <= 3) slope=0; return firstY + slope*(x-firstX);
}
}
}