package gdsc.foci;
/*-----------------------------------------------------------------------------
* GDSC Plugins for ImageJ
*
* Copyright (C) 2011 Alex Herbert
* Genome Damage and Stability Centre
* University of Sussex, UK
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*---------------------------------------------------------------------------*/
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.WindowManager;
import ij.gui.GenericDialog;
import ij.gui.Roi;
import ij.plugin.PlugIn;
import ij.plugin.ZProjector;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;
import ij.text.TextWindow;
import java.awt.Color;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import gdsc.UsageTracker;
import gdsc.core.ij.Utils;
import gdsc.core.match.MatchResult;
/**
* Analyses the image using the FindFoci algorithm to identify and assign pixels to maxima.
* Realigns the marked PointROI with the appropriate peak. Any points that cannot be aligned
* are identified as problem points.
*/
public class PointAlignerPlugin implements PlugIn
{
private static String TITLE = "Point Aligner";
private static TextWindow resultsWindow = null;
private static String resultTitle = "-";
private static String maskImage = "";
private static String[] limitMethods = new String[] { "None", "Q1 - f * IQR", "Mean - f * SD", "nth Percentile",
"% Missed < f" };
private static int limitMethod = 4;
private static double factor = 15;
private static boolean logAlignments = true;
private static boolean showMoved = false;
private static boolean updateRoi = false;
private static boolean showOverlay = true;
private static boolean updateOverlay = true;
private static boolean showUnaligned = false;
private static int unalignedBorder = 10;
private static String resultsDirectory = "";
private static boolean writeHeader = true;
private ImagePlus imp;
private boolean saveResults;
/*
* (non-Javadoc)
*
* @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)
*/
public void run(String arg)
{
UsageTracker.recordPlugin(this.getClass(), arg);
imp = WindowManager.getCurrentImage();
if (null == imp)
{
IJ.showMessage("There must be at least one image open");
return;
}
if (!FindFoci.isSupported(imp.getBitDepth()))
{
IJ.showMessage("Error", "Only " + FindFoci.getSupported() + " images are supported");
return;
}
if (imp.getNChannels() != 1 || imp.getNFrames() != 1)
{
IJ.showMessage("Error", "Only single channel, single frame images are supported");
return;
}
Roi roi = imp.getRoi();
if (roi == null || roi.getType() != Roi.POINT)
{
IJ.showMessage("Error", "The image does not contain Point ROI");
return;
}
if (!showDialog())
{
return;
}
AssignedPoint[] points = FindFociOptimiser.extractRoiPoints(roi, imp, null);
FindFoci ff = new FindFoci();
//ImagePlus mask = WindowManager.getImage(maskImage);
ImagePlus mask = null;
int backgroundMethod = FindFoci.BACKGROUND_ABSOLUTE;
double backgroundParameter = getBackgroundLevel(points);
String autoThresholdMethod = "";
int searchMethod = FindFoci.SEARCH_ABOVE_BACKGROUND;
double searchParameter = 0;
int maxPeaks = 33000;
int minSize = 1;
int peakMethod = FindFoci.PEAK_RELATIVE;
double peakParameter = 0;
int outputType = FindFoci.OUTPUT_MASK_PEAKS | FindFoci.OUTPUT_MASK_NO_PEAK_DOTS;
int sortIndex = FindFoci.SORT_MAX_VALUE;
int options = 0;
double blur = 0;
int centreMethod = FindFoci.CENTRE_MAX_VALUE_ORIGINAL;
double centreParameter = 0;
FindFociResults results = ff.findMaxima(imp, mask, backgroundMethod, backgroundParameter, autoThresholdMethod,
searchMethod, searchParameter, maxPeaks, minSize, peakMethod, peakParameter, outputType, sortIndex,
options, blur, centreMethod, centreParameter, 1);
if (results == null)
{
IJ.showMessage("Error", "FindFoci failed");
return;
}
alignPoints(points, results);
}
private boolean showDialog()
{
ArrayList<String> maskList = FindFoci.buildMaskList(imp);
GenericDialog gd = new GenericDialog(TITLE);
gd.addMessage("Realigns the marked PointROI with the appropriate peak");
gd.addStringField("Title", resultTitle);
gd.addChoice("Mask", maskList.toArray(new String[0]), maskImage);
gd.addChoice("Limit_method", limitMethods, limitMethods[limitMethod]);
gd.addNumericField("Factor", factor, 2);
gd.addCheckbox("Log_alignments", logAlignments);
gd.addCheckbox("Update_ROI", updateRoi);
gd.addCheckbox("Show_moved", showMoved);
gd.addCheckbox("Show_overlay", showOverlay);
gd.addCheckbox("Update_overlay", updateOverlay);
gd.addCheckbox("Show_unaligned", showUnaligned);
gd.addNumericField("Unaligned_border", unalignedBorder, 0);
gd.addStringField("Results_directory", resultsDirectory, 30);
gd.addHelp(gdsc.help.URL.FIND_FOCI);
gd.showDialog();
if (gd.wasCanceled())
return false;
resultTitle = gd.getNextString();
maskImage = gd.getNextChoice();
limitMethod = gd.getNextChoiceIndex();
factor = gd.getNextNumber();
logAlignments = gd.getNextBoolean();
updateRoi = gd.getNextBoolean();
showMoved = gd.getNextBoolean();
showOverlay = gd.getNextBoolean();
updateOverlay = gd.getNextBoolean();
showUnaligned = gd.getNextBoolean();
unalignedBorder = (int) gd.getNextNumber();
resultsDirectory = gd.getNextString();
if (!resultsDirectory.equals(""))
{
File dir = new File(resultsDirectory);
if (!dir.isDirectory())
{
try
{
dir.mkdirs();
}
catch (SecurityException e)
{
IJ.log("Failed to create directory: " + resultsDirectory + ". " + e.getMessage());
}
}
saveResults = new File(resultsDirectory).isDirectory();
}
else
saveResults = false;
return true;
}
private float getBackgroundLevel(AssignedPoint[] points)
{
// Get a maximum intensity project of the image
ZProjector projector = new ZProjector(imp);
projector.setMethod(ZProjector.MAX_METHOD);
projector.doProjection();
ImageProcessor ip = projector.getProjection().getProcessor();
// Set background using the lowest currently picked point
float background = Float.POSITIVE_INFINITY;
for (AssignedPoint point : points)
{
final float v = ip.getf(point.getXint(), point.getYint());
if (background > v)
background = v;
}
// Subtract the image Std.Dev. to get a buffer for error.
ImageStatistics stats = ip.getStatistics();
background -= stats.stdDev;
return background;
}
private void alignPoints(AssignedPoint[] points, FindFociResults results)
{
if (showOverlay || logAlignments)
IJ.log(String.format("%s : %s", TITLE, imp.getTitle()));
// Get the results
ImagePlus maximaImp = results.mask;
ArrayList<FindFociResult> resultsArray = results.results;
// We would like the order of the results to correspond to the maxima image pixel values.
Collections.reverse(resultsArray);
// Use a stack for 3D support
ImageStack impStack = imp.getStack();
ImageStack maximaStack = maximaImp.getStack();
boolean is3d = maximaStack.getSize() > 1;
// Assign points to maxima
int[] assigned = new int[resultsArray.size()];
Arrays.fill(assigned, -1);
for (AssignedPoint point : points)
point.setAssignedId(-1);
float[] pointHeight = new float[points.length];
float minHeight = Float.POSITIVE_INFINITY;
sortPoints(points, impStack);
// TODO - Why is there no maximum move distance?
for (AssignedPoint point : points)
{
int pointId = point.getId();
point.setAssignedId(-1);
int x = point.getXint();
int y = point.getYint();
int z = point.getZint(); // TODO - Deal with 3D images
pointHeight[pointId] = impStack.getProcessor(z + 1).getf(x, y);
if (minHeight > pointHeight[pointId])
{
minHeight = pointHeight[pointId];
}
ImageProcessor ip = maximaStack.getProcessor(z + 1);
int maximaId = ip.get(x, y) - 1;
if (maximaId >= 0)
{
if (assigned[maximaId] >= 0)
{
// Already assigned - The previous point is higher so it wins.
// See if any unassigned maxima are closer. This could be an ROI marking error.
FindFociResult result = resultsArray.get(maximaId);
double d = distance2(x, y, z, result.x, result.y, result.z);
float maxHeight = Float.NEGATIVE_INFINITY;
for (int id = 0; id < assigned.length; id++)
{
// Only check the maxima that have not been assigned
if (assigned[id] < 0)
{
result = resultsArray.get(id);
double newD = distance2(x, y, z, result.x, result.y, result.z);
if (newD < d)
{
// Pick the closest
//maximaId = id;
//d = newD;
// Pick the highest
final float v = result.maxValue;
if (maxHeight < v)
{
maximaId = id;
maxHeight = v;
}
}
}
}
}
if (assigned[maximaId] < 0)
{
// Assign this ROI point to the maxima
assigned[maximaId] = pointId;
}
point.setAssignedId(maximaId);
}
}
// Analyse assigned points for possible errors
final float thresholdHeight = getThresholdHeight(points, assigned, resultsArray);
// Output results
LinkedList<AssignedPoint> ok = new LinkedList<AssignedPoint>();
LinkedList<AssignedPoint> moved = new LinkedList<AssignedPoint>();
LinkedList<AssignedPoint> conflict = new LinkedList<AssignedPoint>();
LinkedList<AssignedPoint> noAlign = new LinkedList<AssignedPoint>();
double averageMovedDistance = 0;
float minAssignedHeight = Float.POSITIVE_INFINITY;
// List of ROI after moving to the assigned peak
ArrayList<AssignedPoint> newRoiPoints = new ArrayList<AssignedPoint>(points.length);
for (AssignedPoint point : points)
{
int pointId = point.getId();
AssignedPoint newPoint = point;
int x = point.getXint();
int y = point.getYint();
int z = point.getZint();
int maximaId = point.getAssignedId();
if (maximaId >= 0)
{
FindFociResult result = resultsArray.get(maximaId);
int newX = result.x;
int newY = result.y;
int newZ = result.z;
double d = 0;
if (newX != x || newY != y || newZ != z)
{
d = point.distance(newX, newY, newZ);
}
if (result.maxValue < thresholdHeight)
{
if (logAlignments)
log("Point [%d] %s @ %s ~> %s @ %s (%s) below height threshold (< %s)", pointId + 1,
Utils.rounded(pointHeight[pointId]), getCoords(is3d, x, y, z),
Utils.rounded(result.maxValue), getCoords(is3d, newX, newY, newZ), IJ.d2s(d, 2),
Utils.rounded(thresholdHeight));
noAlign.add(point);
extractPoint(impStack, "below_threshold", pointId + 1, x, y, z, newX, newY, newZ);
newPoint = null; // remove unaligned points from the updated ROI
}
else
{
final float v = (float) result.maxValue;
if (minAssignedHeight > v)
{
minAssignedHeight = v;
}
if (assigned[maximaId] == pointId)
{
// This is the highest point assigned to the maxima.
// Check if it is being moved.
if (logAlignments)
log("Point [%d] %s @ %s => %s @ %s (%s)", pointId + 1, Utils.rounded(pointHeight[pointId]),
getCoords(is3d, x, y, z), Utils.rounded(result.maxValue),
getCoords(is3d, newX, newY, newZ), IJ.d2s(d, 2));
newPoint = new AssignedPoint(newX, newY, newZ, point.getId());
if (showMoved && d > 0)
moved.add((updateOverlay) ? newPoint : point);
else
ok.add((updateOverlay) ? newPoint : point);
averageMovedDistance += d;
}
else
{
// This point is lower than another assigned to the maxima
if (logAlignments)
log("Point [%d] %s @ %s conflicts for assigned point [%d]", pointId + 1,
Utils.rounded(pointHeight[pointId]), getCoords(is3d, x, y, z),
assigned[maximaId] + 1);
conflict.add(point);
// Output an image showing the pixels
extractPoint(impStack, "conflict", pointId + 1, x, y, z, newX, newY, newZ);
}
}
}
else
{
if (logAlignments)
log("Point [%d] %s @ %s cannot be aligned", pointId + 1, Utils.rounded(pointHeight[pointId]),
getCoords(is3d, x, y, z));
noAlign.add(point);
extractPoint(impStack, "noalign", pointId + 1, x, y, z, x, y, z);
newPoint = null; // remove unaligned points from the updated ROI
}
if (newPoint != null)
newRoiPoints.add(newPoint);
}
if (logAlignments)
{
log("Minimum picked value = " + Utils.rounded(minHeight));
log("Threshold = " + Utils.rounded(thresholdHeight));
log("Minimum assigned peak height = " + Utils.rounded(minAssignedHeight));
}
if (averageMovedDistance > 0)
averageMovedDistance /= (moved.isEmpty()) ? ok.size() : moved.size();
LinkedList<AssignedPoint> missed = findMissedPoints(resultsArray, assigned, minAssignedHeight);
updateRoi(newRoiPoints);
showOverlay(ok, moved, conflict, noAlign, missed);
createResultsWindow();
addResult(minHeight, thresholdHeight, minAssignedHeight, ok, moved, averageMovedDistance, conflict, noAlign,
missed);
}
private double distance2(int x, int y, int z, int x2, int y2, int z2)
{
final int dx = x - x2;
final int dy = y - y2;
final int dz = z - z2;
return dx * dx + dy * dy + dz * dz;
}
public void sortPoints(AssignedPoint[] points, ImageStack impStack)
{
if (points == null || impStack == null)
return;
int[] pointHeight = new int[points.length];
// Do this in descending height order
for (AssignedPoint point : points)
{
int x = point.getXint();
int y = point.getYint();
int z = point.getZint();
pointHeight[point.getId()] = impStack.getProcessor(z + 1).get(x, y);
}
Arrays.sort(points, new PointHeightComparator(pointHeight));
for (int pointId = 0; pointId < points.length; pointId++)
{
points[pointId].setId(pointId);
}
}
private LinkedList<AssignedPoint> findMissedPoints(ArrayList<FindFociResult> resultsArray, int[] assigned,
float minAssignedHeight)
{
// List maxima above the minimum height that have not been picked
ImageStack maskStack = getMaskStack();
LinkedList<AssignedPoint> missed = new LinkedList<AssignedPoint>();
for (int maximaId = 0; maximaId < resultsArray.size(); maximaId++)
{
FindFociResult result = resultsArray.get(maximaId);
final float v = (float) result.maxValue;
if (assigned[maximaId] < 0 && v > minAssignedHeight)
{
int x = result.x;
int y = result.y;
int z = result.z;
// Check if the point is within the mask
if (maskStack != null)
{
if (maskStack.getProcessor(z + 1).get(x, y) == 0)
continue;
}
missed.add(new AssignedPoint(x, y, z, maximaId));
}
}
return missed;
}
private LinkedList<Float> findMissedHeights(ArrayList<FindFociResult> resultsArray, int[] assigned, double t)
{
// List maxima above the minimum height that have not been picked
ImageStack maskStack = getMaskStack();
LinkedList<Float> missed = new LinkedList<Float>();
for (int maximaId = 0; maximaId < resultsArray.size(); maximaId++)
{
FindFociResult result = resultsArray.get(maximaId);
if (assigned[maximaId] < 0 && result.maxValue > t)
{
int x = result.x;
int y = result.y;
int z = result.z;
// Check if the point is within the mask
if (maskStack != null)
{
if (maskStack.getProcessor(z + 1).get(x, y) == 0)
continue;
}
missed.add(result.maxValue);
}
}
Collections.sort(missed);
return missed;
}
/**
* Analyse the assigned heights and attempt to identify any errornous points
*
* @return The height below which any point is considered an error
*/
private float getThresholdHeight(AssignedPoint[] points, int[] assigned, ArrayList<FindFociResult> resultsArray)
{
ArrayList<Float> heights = new ArrayList<Float>(points.length);
for (int maximaId = 0; maximaId < assigned.length; maximaId++)
{
if (assigned[maximaId] >= 0)
{
FindFociResult result = resultsArray.get(maximaId);
heights.add(result.maxValue);
}
}
Collections.sort(heights);
// Box plot type analysis:
// The bottom and top of the box are always the 25th and 75th percentile (the lower and upper quartiles, respectively),
// and the band near the middle of the box is always the 50th percentile (the median).
// But the ends of the whiskers can represent several possible alternative values, among them:
// - the minimum and maximum of all the data
// - the lowest datum still within 1.5 IQR of the lower quartile,
// and the highest datum still within 1.5 IQR of the upper quartile
// - one standard deviation above and below the mean of the data
// - 2nd/9th percentile etc.
if (heights.isEmpty())
return 0;
double t = heights.get(0);
switch (limitMethod)
{
case 1:
// Factor of inter-quartile range
float q1 = getQuartileBoundary(heights, 0.25);
float q2 = getQuartileBoundary(heights, 0.5);
t = q1 - factor * (q2 - q1);
if (logAlignments)
log("Limiting peaks %s: %s - %s * %s = %s", limitMethods[limitMethod], Utils.rounded(q1),
Utils.rounded(factor), Utils.rounded(q2 - q1), Utils.rounded(t));
break;
case 2:
// n * Std.Dev. below the mean
double[] stats = getStatistics(heights);
t = stats[0] - factor * stats[1];
if (logAlignments)
log("Limiting peaks %s: %s - %s * %s = %s", limitMethods[limitMethod], Utils.rounded(stats[0]),
Utils.rounded(factor), Utils.rounded(stats[1]), Utils.rounded(t));
break;
case 3:
// nth Percentile
t = getQuartileBoundary(heights, 0.01 * factor);
if (logAlignments)
log("Limiting peaks %s: %sth = %s", limitMethods[limitMethod], Utils.rounded(factor),
Utils.rounded(t));
break;
case 4:
// Number of missed points is a factor below picked points,
// i.e. the number of potential maxima is a fraction of the number of assigned maxima.
List<Float> missedHeights = findMissedHeights(resultsArray, assigned, t);
double fraction = factor / 100;
for (int pointId = 0; pointId < heights.size(); pointId++)
{
t = heights.get(pointId);
// Count points
int missedCount = countPoints(missedHeights, t);
int assignedCount = heights.size() - pointId;
int totalCount = missedCount + assignedCount;
if ((missedCount / (double) totalCount) < fraction)
{
break;
}
}
if (t == heights.get(heights.size() - 1))
{
log("Warning: Maximum height threshold reached when attempting to limit the number of missed peaks");
}
if (logAlignments)
log("Limiting peaks %s: %s %% = %s", limitMethods[limitMethod], Utils.rounded(factor),
Utils.rounded(t));
break;
default:
}
// Round for integer data
if (integerData(heights))
t = round(t);
return (float) t;
}
private boolean integerData(ArrayList<Float> heights)
{
for (double d : heights)
if ((int) d != d)
return false;
return true;
}
/**
* Count points that are above the given height
*
* @param missedHeights
* @param height
* @return The count
*/
private int countPoints(List<Float> missedHeights, double height)
{
int count = missedHeights.size();
for (double h : missedHeights)
{
if (h < height)
{
count--;
}
else
{
break;
}
}
return count;
}
private int round(double d)
{
return (int) (d + 0.5);
}
/**
* Get the quartile boundary for the given fraction, e.g. fraction 0.25 is Q1-Q2 interquartile.
*
* @param heights
* @param fraction
* @return The boundary
*/
public static float getQuartileBoundary(ArrayList<Float> heights, double fraction)
{
if (heights.isEmpty())
return 0;
if (heights.size() == 1)
return heights.get(0).floatValue();
int upper = (int) Math.ceil(heights.size() * fraction);
int lower = (int) Math.floor(heights.size() * fraction);
upper = Math.min(Math.max(upper, 0), heights.size() - 1);
lower = Math.min(Math.max(lower, 0), heights.size() - 1);
return (float) ((heights.get(upper) + heights.get(lower)) / 2.0);
}
private double[] getStatistics(ArrayList<Float> heights)
{
double sum = 0.0;
double sum2 = 0.0;
int n = heights.size();
for (double h : heights)
{
sum += h;
sum2 += (h * h);
}
double av = sum / n;
// Get the Std.Dev
double stdDev;
if (n > 0)
{
double d = n;
stdDev = (d * sum2 - sum * sum) / d;
if (stdDev > 0.0)
stdDev = Math.sqrt(stdDev / (d - 1.0));
else
stdDev = 0.0;
}
else
stdDev = 0.0;
return new double[] { av, stdDev };
}
private ImageStack getMaskStack()
{
ImagePlus mask = WindowManager.getImage(maskImage);
if (mask != null)
{
return mask.getStack();
}
return null;
}
private Object getCoords(boolean is3d, int x, int y, int z)
{
if (is3d)
return String.format("%d,%d,%d", x, y, z);
return String.format("%d,%d", x, y);
}
private void log(String format, Object... args)
{
if (logAlignments)
{
IJ.log(String.format(format, args));
}
}
private void extractPoint(ImageStack impStack, String type, int Id, int x, int y, int z, int newX, int newY,
int newZ)
{
if (unalignedBorder <= 0)
return;
if (showUnaligned || saveResults)
{
int xx = impStack.getWidth() - 1;
int yy = impStack.getHeight() - 1;
int minX = Math.min(xx, Math.max(0, Math.min(x, newX) - unalignedBorder));
int minY = Math.min(yy, Math.max(0, Math.min(y, newY) - unalignedBorder));
int maxX = Math.min(xx, Math.max(0, Math.max(x, newX) + unalignedBorder));
int maxY = Math.min(yy, Math.max(0, Math.max(y, newY) + unalignedBorder));
int w = maxX - minX + 1;
int h = maxY - minY + 1;
ImageStack newStack = new ImageStack(w, h);
for (int slice = 1; slice <= impStack.getSize(); slice++)
{
ImageProcessor ip = impStack.getProcessor(slice).duplicate();
ip.setRoi(minX, minY, w, h);
ip = ip.crop();
newStack.addSlice(null, ip);
}
String title = imp.getShortTitle() + "_" + type + "_" + Id;
ImagePlus pointImp = WindowManager.getImage(title);
if (pointImp == null)
pointImp = new ImagePlus(title, newStack);
else
pointImp.setStack(newStack);
pointImp.setOverlay(null);
if (newX - x != 0 || newY - y != 0)
{
ArrayList<BasePoint> ok = new ArrayList<BasePoint>(1);
ok.add(new BasePoint(newX - minX, newY - minY, newZ));
MatchPlugin.addOverlay(pointImp, ok, MatchPlugin.MATCH);
}
ArrayList<BasePoint> conflict = new ArrayList<BasePoint>(1);
conflict.add(new BasePoint(x - minX, y - minY, z));
MatchPlugin.addOverlay(pointImp, conflict, MatchPlugin.UNMATCH1);
pointImp.updateAndDraw();
if (saveResults)
{
IJ.save(pointImp, resultsDirectory + File.separatorChar + title + ".tif");
}
if (showUnaligned)
{
pointImp.show();
}
}
}
private void updateRoi(ArrayList<? extends BasePoint> newRoiPoints)
{
if (updateRoi)
{
imp.setRoi(PointManager.createROI(newRoiPoints));
}
}
private void showOverlay(LinkedList<AssignedPoint> ok, LinkedList<AssignedPoint> moved,
LinkedList<AssignedPoint> conflict, LinkedList<AssignedPoint> noAlign, LinkedList<AssignedPoint> missed)
{
// Add overlap
if (showOverlay)
{
IJ.log("Overlay key:");
IJ.log(" OK = Green");
if (showMoved)
IJ.log(" Moved = Yellow");
IJ.log(" Conflict = Red");
IJ.log(" NoAlign = Blue");
IJ.log(" Missed = Magenta");
imp.setOverlay(null);
imp.saveRoi();
imp.killRoi();
MatchPlugin.addOverlay(imp, ok, MatchPlugin.MATCH);
MatchPlugin.addOverlay(imp, moved, MatchPlugin.UNMATCH1);
MatchPlugin.addOverlay(imp, conflict, Color.red);
MatchPlugin.addOverlay(imp, noAlign, MatchPlugin.UNMATCH2);
MatchPlugin.addOverlay(imp, missed, Color.magenta);
imp.updateAndDraw();
}
}
private void createResultsWindow()
{
if (java.awt.GraphicsEnvironment.isHeadless())
{
if (writeHeader)
{
writeHeader = false;
IJ.log(createResultsHeader());
}
}
else
{
if (resultsWindow == null || !resultsWindow.isShowing())
{
resultsWindow = new TextWindow(TITLE + " Results", createResultsHeader(), "", 900, 300);
}
}
}
private String createResultsHeader()
{
StringBuilder sb = new StringBuilder();
sb.append("Title\t");
sb.append("Image\t");
sb.append("Method\t");
sb.append("Factor\t");
sb.append("Min Height\t");
sb.append("Threshold\t");
sb.append("Min Assigned Height\t");
sb.append("OK\t");
sb.append("Moved\t");
sb.append("Av.Move\t");
sb.append("Conflict\t");
sb.append("NoAlign\t");
sb.append("Missed\t");
sb.append("N\t");
sb.append("TP\t");
sb.append("FP\t");
sb.append("FN\t");
sb.append("Precision\t");
sb.append("Recall\t");
sb.append("Jaccard\t");
sb.append("F1-score");
return sb.toString();
}
private void addResult(float minHeight, float thresholdHeight, float minAssignedHeight,
LinkedList<AssignedPoint> ok, LinkedList<AssignedPoint> moved, double averageMovedDistance,
LinkedList<AssignedPoint> conflict, LinkedList<AssignedPoint> noAlign, LinkedList<AssignedPoint> missed)
{
StringBuilder sb = new StringBuilder();
sb.append(resultTitle).append("\t");
sb.append(imp.getTitle()).append("\t");
sb.append(limitMethods[limitMethod]).append("\t");
sb.append(factor).append("\t");
sb.append(Utils.rounded(minHeight)).append("\t");
sb.append(Utils.rounded(thresholdHeight)).append("\t");
sb.append(Utils.rounded(minAssignedHeight)).append("\t");
sb.append(ok.size()).append("\t");
sb.append(moved.size()).append("\t");
sb.append(IJ.d2s(averageMovedDistance, 2)).append("\t");
sb.append(conflict.size()).append("\t");
sb.append(noAlign.size()).append("\t");
sb.append(missed.size()).append("\t");
int tp = ok.size() + moved.size();
int fp = conflict.size() + noAlign.size();
int fn = missed.size();
MatchResult match = new MatchResult(tp, fp, fn, 0);
sb.append(match.getNumberPredicted()).append("\t");
sb.append(tp).append("\t");
sb.append(fp).append("\t");
sb.append(fn).append("\t");
sb.append(IJ.d2s(match.getPrecision(), 4)).append("\t");
sb.append(IJ.d2s(match.getRecall(), 4)).append("\t");
sb.append(IJ.d2s(match.getJaccard(), 4)).append("\t");
sb.append(IJ.d2s(match.getFScore(1), 4));
if (java.awt.GraphicsEnvironment.isHeadless())
{
IJ.log(sb.toString());
}
else
{
resultsWindow.append(sb.toString());
}
}
public class PointHeightComparator implements Comparator<AssignedPoint>
{
private int[] pointHeight;
public PointHeightComparator(int[] pointHeight)
{
this.pointHeight = pointHeight;
}
public int compare(AssignedPoint o1, AssignedPoint o2)
{
int diff = pointHeight[o1.getId()] - pointHeight[o2.getId()];
if (diff > 0)
return -1;
if (diff < 0)
return 1;
return 0;
}
}
}