package gdsc.foci;
import gdsc.UsageTracker;
/*-----------------------------------------------------------------------------
* 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 gdsc.foci.gui.OptimiserView;
import gdsc.core.ij.Utils;
import gdsc.core.match.Coordinate;
import gdsc.core.match.MatchCalculator;
import gdsc.core.match.MatchResult;
import gdsc.core.threshold.AutoThreshold;
import gdsc.core.utils.StoredData;
import gdsc.core.utils.UnicodeReader;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.WindowManager;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.gui.Overlay;
import ij.gui.PointRoi;
import ij.gui.Roi;
import ij.gui.YesNoCancelDialog;
import ij.io.FileInfo;
import ij.plugin.PlugIn;
import ij.plugin.frame.Recorder;
import ij.process.ImageProcessor;
import ij.text.TextWindow;
import java.awt.AWTEvent;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import javax.swing.JFrame;
/**
* Runs the FindFoci plugin with various settings and compares the results to the reference image point ROI.
*/
public class FindFociOptimiser
implements PlugIn, MouseListener, WindowListener, DialogListener, TextListener, ItemListener
{
private static OptimiserView instance;
private static String TITLE = "FindFoci Optimiser";
private static TextWindow resultsWindow = null;
private static int STEP_LIMIT = 10000;
private static final int RESULT_PRECISION = 4;
private static String myMaskImage = "";
private static boolean myBackgroundStdDevAboveMean = true;
private static boolean myBackgroundAuto = true;
private static boolean myBackgroundAbsolute = false;
private static String myBackgroundParameter = "2.5, 3.5, 0.5";
private static String myThresholdMethod = AutoThreshold.Method.OTSU.name;
private static String myStatisticsMode = "Both";
private static boolean mySearchAboveBackground = true;
private static boolean mySearchFractionOfPeak = true;
private static String mySearchParameter = "0, 0.6, 0.2";
private static String myMinSizeParameter = "1, 9, 2";
private static String[] saddleOptions = { "Yes", "Yes - Connected", "No", "All" };
private static int myMinimumAboveSaddle = 0;
private static int myPeakMethod = FindFoci.PEAK_RELATIVE_ABOVE_BACKGROUND;
private static String myPeakParameter = "0, 0.6, 0.2";
private static String mySortMethod = "" + FindFoci.SORT_INTENSITY;
private static int myMaxPeaks = 500;
private static String myGaussianBlur = "0, 0.5, 1";
private static String myCentreMethod = "" + FindFoci.CENTRE_MAX_VALUE_SEARCH;
private static String myCentreParameter = "2";
/**
* The list of recognised methods for sorting the results
*/
public final static String[] sortMethods = { "None", "Precision", "Recall", "F0.5", "F1", "F2", "F-beta", "Jaccard",
"RMSD" };
/**
* Do not sort the results
*/
public final static int SORT_NONE = 0;
/**
* Sort the results using the Precision
*/
public final static int SORT_PRECISION = 1;
/**
* Sort the results using the Recall
*/
public final static int SORT_RECALL = 2;
/**
* Sort the results using the F0.5 score (weights precision over recall)
*/
public final static int SORT_F05 = 3;
/**
* Sort the results using the F1 score (precision and recall equally weighted)
*/
public final static int SORT_F1 = 4;
/**
* Sort the results using the F2 score (weights recall over precision)
*/
public final static int SORT_F2 = 5;
/**
* Sort the results using the F-beta score
*/
public final static int SORT_FBETA = 6;
/**
* Sort the results using the Jaccard
*/
public final static int SORT_JACCARD = 7;
/**
* Sort the results using the RMSD. Note that the RMSD is only computed using the TP so is therefore only of use
* when the TP values are the same (i.e. for a tie breaker)
*/
public final static int SORT_RMSD = 8;
public final static String[] matchSearchMethods = { "Relative", "Absolute" };
private static int myMatchSearchMethod = 0;
private static double myMatchSearchDistance = 0.05;
private static int myResultsSortMethod = SORT_JACCARD;
private static double myBeta = 4.0;
private static int myMaxResults = 100;
private static boolean myShowScoreImages = false;
private static String myResultFile = "";
private int[] optionsArray = {};
private String[] thresholdMethods = null;
private String[] statisticsModes = null;
private int[] backgroundMethodArray = {};
private double myBackgroundParameterMin;
private double myBackgroundParameterMax;
private double myBackgroundParameterInterval;
private double[] myBackgroundParameterMinArray;
private int[] searchMethodArray = {};
private double mySearchParameterMin;
private double mySearchParameterMax;
private double mySearchParameterInterval;
private double[] mySearchParameterMinArray;
private int myMinSizeMin;
private int myMinSizeMax;
private int myMinSizeInterval;
private double myPeakParameterMin;
private double myPeakParameterMax;
private double myPeakParameterInterval;
private int[] sortMethodArray = {};
private double[] blurArray = {};
private int[] centreMethodArray = {};
private int myCentreParameterMin;
private int myCentreParameterMax;
private int myCentreParameterInterval;
private int[] myCentreParameterMinArray;
private int[] myCentreParameterMaxArray;
private int[] myCentreParameterIntervalArray;
// For the multi-image mode
private boolean multiMode = false;
private static String INPUT_DIRECTORY = "findfoci.optimiser.inputDirectory";
private static String MASK_DIRECTORY = "findfoci.optimiser.maskDirectory";
private static String OUTPUT_DIRECTORY = "findfoci.optimiser.outputDirectory";
private static String SCORING_MODE = "findfoci.optimiser.scoringMode";
private static String REUSE_RESULTS = "findfoci.optimiser.reuseResults";
private static String inputDirectory = Prefs.get(INPUT_DIRECTORY, "");
private static String maskDirectory = Prefs.get(MASK_DIRECTORY, "");
private static String outputDirectory = Prefs.get(OUTPUT_DIRECTORY, "");
private static String[] SCORING_MODES = new String[] { "Raw score metric", "Relative (% drop from top)", "Z-score",
"Rank" };
private static final int SCORE_RAW = 0;
private static final int SCORE_RELATIVE = 1;
private static final int SCORE_Z = 2;
private static final int SCORE_RANK = 3;
private static int scoringMode = Prefs.getInt(SCORING_MODE, SCORE_RAW);
private static boolean reuseResults = Prefs.getBoolean(REUSE_RESULTS, true);
@SuppressWarnings("rawtypes")
private Vector checkbox, choice;
private GenericDialog listenerGd;
// Stored to allow the display of any of the latest results from the result table
private static ImagePlus lastImp, lastMask;
private static ArrayList<Result> lastResults;
private static String optimiserCommandOptions;
// The number of combinations
private int combinations;
/*
* (non-Javadoc)
*
* @see ij.plugin.PlugIn#run(java.lang.String)
*/
public void run(String arg)
{
UsageTracker.recordPlugin(this.getClass(), arg);
if (arg.equals("frame"))
{
showOptimiserWindow();
}
else
{
ImagePlus imp = (arg.equals("multi")) ? null : WindowManager.getCurrentImage();
run(imp);
}
}
/**
* Run the optimiser on the given image. If the image is null then process in multi-image mode.
*
* @param imp
*/
public void run(ImagePlus imp)
{
if (!showDialog(imp))
return;
IJ.log("---\n" + TITLE);
IJ.log(combinations + " combinations");
if (multiMode)
{
// Get the list of images
String[] imageList = FindFoci.getBatchImages(inputDirectory);
if (imageList == null || imageList.length == 0)
{
IJ.error(TITLE, "No input images in folder: " + inputDirectory);
return;
}
if (reuseResults && resultsExist(imageList))
IJ.log("Output directory contains existing results that will be re-used if they have the correct number of combinations");
IJ.showStatus("Running optimiser ...");
// For each image start an optimiser run:
// - Run the optimiser
// - save the results to the output directory
int size = imageList.length;
ExecutorService threadPool = Executors.newFixedThreadPool(Prefs.getThreads());
List<Future<?>> futures = new ArrayList<Future<?>>(size);
ArrayList<OptimisationWorker> workers = new ArrayList<FindFociOptimiser.OptimisationWorker>(size);
// Allow progress to be tracked across all threads
Counter counter = new SynchronizedCounter(combinations * size);
for (String image : imageList)
{
OptimisationWorker w = new OptimisationWorker(image, counter);
workers.add(w);
futures.add(threadPool.submit(w));
}
// Collect all the results
Utils.waitForCompletion(futures);
threadPool.shutdown();
IJ.showProgress(1);
IJ.showStatus("");
if (Utils.isInterrupted())
return;
// Check all results are the same size
ArrayList<ArrayList<Result>> allResults = new ArrayList<ArrayList<Result>>(size);
for (OptimisationWorker w : workers)
{
if (w.result == null)
continue;
ArrayList<Result> results = w.result.results;
if (!allResults.isEmpty() && results.size() != allResults.get(0).size())
{
IJ.error(TITLE, "Some optimisation runs produced a different number of results");
return;
}
allResults.add(results);
}
if (allResults.isEmpty())
{
IJ.error(TITLE, "No optimisation runs produced results");
return;
}
IJ.showStatus("Calculating scores ...");
// Combine using the chosen ranking score.
// Use the first set of results to accumulate scores.
ArrayList<Result> results = allResults.get(0);
getScore(results);
size = allResults.size();
for (int i = 1; i < size; i++)
{
ArrayList<Result> results2 = allResults.get(i);
getScore(results2);
for (int j = 0; j < results.size(); j++)
{
// Combine all the metrics
Result r1 = results.get(j);
Result r2 = results2.get(j);
r1.add(r2);
}
}
// Average the scores
final double factor = 1.0 / size;
for (int j = 0; j < results.size(); j++)
{
double[] metrics = results.get(j).metrics;
// Do not average the RMSD
for (int i = 0; i < metrics.length - 1; i++)
metrics[i] *= factor;
// We must reset the score with the original RMSD
if (myResultsSortMethod == SORT_RMSD)
metrics[Result.SCORE] = metrics[Result.RMSD];
}
// Now sort the results using the combined scores. Check is the scored metric is lowest first
final boolean lowestFirst = myResultsSortMethod == SORT_RMSD;
sortResultsByScore(results, lowestFirst);
// Output the combined results
saveResults(null, null, results, null, outputDirectory + "all");
// Show in a table
showResults(null, null, results);
IJ.showStatus("");
}
else
{
ImagePlus mask = WindowManager.getImage(myMaskImage);
OptimiserResult result = runOptimiser(imp, mask, new StandardCounter(combinations));
IJ.showProgress(1);
if (Utils.isInterrupted())
return;
if (result == null)
{
IJ.error(TITLE, "No ROI points fall inside the mask image");
return;
}
// For a single image we use the raw score (since no results are combined)
ArrayList<Result> results = result.results;
getScore(results, SCORE_RAW);
showResults(imp, mask, results);
// Re-run Find_Peaks and output the best result
if (!results.isEmpty())
{
IJ.log("Top result = " + IJ.d2s(results.get(0).metrics[getSortIndex(myResultsSortMethod)], 4));
Options bestOptions = results.get(0).options;
AssignedPoint[] predictedPoints = showResult(imp, mask, bestOptions);
saveResults(imp, mask, results, predictedPoints, myResultFile);
checkOptimisationSpace(result, imp);
}
}
}
/**
* Check if the output directory has any results already
*
* @param imageList
* @return true if results exist
*/
private boolean resultsExist(String[] imageList)
{
// List the results in the output directory
String[] results = new File(outputDirectory).list(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.endsWith(".results.xls");
}
});
if (results == null || results.length == 0)
return false;
for (String name : imageList)
{
name = getShortTitle(name);
for (String result : results)
if (result.startsWith(name))
return true;
}
return false;
}
/**
* Copied from ImagePlus.getShortTitle()
*
* @param title
* @return the title with no spaces or extension
*/
private String getShortTitle(String title)
{
int index = title.indexOf(' ');
if (index > -1)
title = title.substring(0, index);
index = title.lastIndexOf('.');
if (index > 0)
title = title.substring(0, index);
return title;
}
/**
* Check if the optimal results was obtained at the edge of the optimisation search space
*
* @param result
* @param imp
*/
private void checkOptimisationSpace(OptimiserResult result, ImagePlus imp)
{
Options bestOptions = result.results.get(0).options;
if (bestOptions == null)
return;
if (result.time != 0)
{
final double seconds = result.time / 1e9;
IJ.log(String.format("%s Optimisation time = %.3f sec (%.3f ms / combination). Speed up = %.3fx",
imp.getTitle(), seconds, result.time / 1e6 / combinations, result.total / (double) result.time));
}
// Check if a sub-optimal best result was obtained at the limit of the optimisation range
if (result.results.get(0).metrics[Result.F1] < 1.0)
{
StringBuilder sb = new StringBuilder();
if (backgroundMethodHasParameter(bestOptions.backgroundMethod))
{
if (bestOptions.backgroundParameter == myBackgroundParameterMin)
append(sb, "- Background parameter @ lower limit (%g)\n", bestOptions.backgroundParameter);
else if (bestOptions.backgroundParameter + myBackgroundParameterInterval > myBackgroundParameterMax)
append(sb, "- Background parameter @ upper limit (%g)\n", bestOptions.backgroundParameter);
}
if (searchMethodHasParameter(bestOptions.searchMethod))
{
if (bestOptions.searchParameter == mySearchParameterMin && mySearchParameterMin > 0)
append(sb, "- Search parameter @ lower limit (%g)\n", bestOptions.searchParameter);
else if (bestOptions.searchParameter + mySearchParameterInterval > mySearchParameterMax &&
mySearchParameterMax < 1)
append(sb, "- Search parameter @ upper limit (%g)\n", bestOptions.searchParameter);
}
if (bestOptions.minSize == myMinSizeMin && myMinSizeMin > 1)
append(sb, "- Min Size @ lower limit (%d)\n", bestOptions.minSize);
else if (bestOptions.minSize + myMinSizeInterval > myMinSizeMax)
append(sb, "- Min Size @ upper limit (%d)\n", bestOptions.minSize);
if (bestOptions.peakParameter == myPeakParameterMin && myPeakParameterMin > 0)
append(sb, "- Peak parameter @ lower limit (%g)\n", bestOptions.peakParameter);
else if (bestOptions.peakParameter + myPeakParameterInterval > myPeakParameterMax && myPeakParameterMax < 1)
append(sb, "- Peak parameter @ upper limit (%g)\n", bestOptions.peakParameter);
if (bestOptions.blur == blurArray[0] && blurArray[0] > 0)
append(sb, "- Gaussian blur @ lower limit (%g)\n", bestOptions.blur);
else if (bestOptions.blur == blurArray[blurArray.length - 1])
append(sb, "- Gaussian blur @ upper limit (%g)\n", bestOptions.blur);
if (bestOptions.maxPeaks == result.results.get(0).n)
append(sb, "- Total peaks == Maximum Peaks (%d)\n", bestOptions.maxPeaks);
if (sb.length() > 0)
{
sb.insert(0,
"Optimal result (" +
IJ.d2s(result.results.get(0).metrics[getSortIndex(myResultsSortMethod)], 4) + ") for " +
imp.getShortTitle() + " obtained at the following limits:\n");
sb.append("You may want to increase the optimisation space.");
showIncreaseSpaceMessage(sb);
}
}
}
private synchronized void showIncreaseSpaceMessage(StringBuilder sb)
{
IJ.log("---");
IJ.log(sb.toString());
// Do not show messages when running in batch
if (!(java.awt.GraphicsEnvironment.isHeadless() || multiMode))
IJ.showMessage(sb.toString());
}
private void getScore(ArrayList<Result> results)
{
getScore(results, scoringMode);
}
/**
* Gets the score for each item in the results set and sets it to the score field. The score is determined using the
* configured resultsSortMethod and scoringMode. It is assumed that all the scoring metrics start at zero and higher
* is better.
*
* @param results
*/
private void getScore(ArrayList<Result> results, int scoringMode)
{
// Extract the score from the results
final int scoreIndex;
switch (scoringMode)
{
case SCORE_RAW:
case SCORE_Z:
case SCORE_RELATIVE:
scoreIndex = getSortIndex(myResultsSortMethod);
break;
// If scoring using the rank then note that the rank was assigned
// using the chosen metric in myResultsSortMethod within sortResults(...)
case SCORE_RANK:
default:
scoreIndex = Result.RANK;
}
// Only Raw/Rank are valid for RMSD
if (scoreIndex == Result.RMSD && (scoringMode != SCORE_RAW || scoringMode != SCORE_RANK))
{
scoringMode = SCORE_RAW;
}
double[] score = new double[results.size()];
for (int i = 0; i < score.length; i++)
score[i] = results.get(i).metrics[scoreIndex];
// Perform additional score adjustment
if (scoringMode == SCORE_Z)
{
// Use the z-score
double[] stats = getStatistics(score);
final double av = stats[0];
final double sd = stats[1];
if (sd > 0)
{
final double factor = 1.0 / sd;
for (int i = 0; i < score.length; i++)
score[i] = (score[i] - av) * factor;
}
else
{
score = new double[score.length]; // all have z=0
}
}
else if (scoringMode == SCORE_RELATIVE)
{
// Use the relative (%) from the top score. Assumes the bottom score is zero.
final double top = getTop(score);
final double factor = 100 / top;
for (int i = 0; i < score.length; i++)
score[i] = factor * (score[i] - top);
}
// Set the score into the results
for (int i = 0; i < score.length; i++)
results.get(i).metrics[Result.SCORE] = score[i];
}
/**
* Get the statistics
*
* @param score
* @return The average and standard deviation
*/
private double[] getStatistics(double[] score)
{
// Get the average
double sum = 0.0;
double sum2 = 0.0;
int n = score.length;
for (double value : score)
{
sum += value;
sum2 += (value * value);
}
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 };
}
/**
* Get the highest score. Assumes the lowest is zero
*
* @param score
* @return The top score
*/
private double getTop(double[] score)
{
double top = score[0];
for (int i = 1; i < score.length; i++)
if (top < score[i])
top = score[i];
return top;
}
private class StopWatch
{
long base;
long time;
StopWatch()
{
this(0);
}
StopWatch create()
{
return new StopWatch(time());
}
private StopWatch(long base)
{
time = System.nanoTime();
this.base = base;
}
long stop()
{
time = System.nanoTime() - time;
return time();
}
long time()
{
return time + base;
}
}
private class OptimiserResult
{
ArrayList<Result> results;
long time;
long total;
OptimiserResult(ArrayList<Result> results, long time)
{
this.results = results;
this.time = time;
total = 0;
if (results == null)
return;
for (int i = 0; i < results.size(); i++)
total += results.get(i).time;
}
}
/**
* Enumerate the parameters for FindFoci on the provided image
*
* @param imp
* The image
* @param mask
* The mask
* @return The results (or null if there are no ROI points inside the mask)
*/
private OptimiserResult runOptimiser(ImagePlus imp, ImagePlus mask, Counter counter)
{
if (invalidImage(imp))
return null;
AssignedPoint[] roiPoints = extractRoiPoints(imp.getRoi(), imp, mask);
if (roiPoints.length == 0)
{
IJ.showMessage("Error", "Image must have a point ROI or corresponding ROI file");
return null;
}
final boolean is3D = is3D(roiPoints);
ArrayList<Result> results = new ArrayList<Result>(combinations);
// Set the threshold for assigning points matches as a fraction of the image size
double dThreshold = getDistanceThreshold(imp);
StopWatch sw = new StopWatch();
FindFoci ff = new FindFoci();
int id = 0;
for (int blurCount = 0; blurCount < blurArray.length; blurCount++)
{
double blur = blurArray[blurCount];
StopWatch sw0 = new StopWatch();
ImagePlus imp2 = ff.blur(imp, blur);
sw0.stop();
// Iterate over the options
int thresholdMethodIndex = 0;
for (int b = 0; b < backgroundMethodArray.length; b++)
{
String thresholdMethod = (backgroundMethodArray[b] == FindFoci.BACKGROUND_AUTO_THRESHOLD)
? thresholdMethods[thresholdMethodIndex++] : "";
String[] myStatsModes = backgroundMethodHasStatisticsMode(backgroundMethodArray[b]) ? statisticsModes
: new String[] { "Both" };
for (String statsMode : myStatsModes)
{
int statisticsMode = convertStatisticsMode(statsMode);
StopWatch sw1 = sw0.create();
FindFociInitResults initResults = ff.findMaximaInit(imp, imp2, mask, backgroundMethodArray[b],
thresholdMethod, statisticsMode);
sw1.stop();
if (initResults == null)
return null;
//Object[] clonedInitArray = null;
FindFociInitResults searchInitArray = null;
for (double backgroundParameter = myBackgroundParameterMinArray[b]; backgroundParameter <= myBackgroundParameterMax; backgroundParameter += myBackgroundParameterInterval)
{
boolean logBackground = (blurCount == 0) && !multiMode; // Log on first blur iteration
// Use zero when there is no parameter
final double thisBackgroundParameter = (backgroundMethodHasParameter(backgroundMethodArray[b]))
? backgroundParameter : 0;
for (int s = 0; s < searchMethodArray.length; s++)
for (double searchParameter = mySearchParameterMinArray[s]; searchParameter <= mySearchParameterMax; searchParameter += mySearchParameterInterval)
{
// Use zero when there is no parameter
double thisSearchParameter = (searchMethodHasParameter(searchMethodArray[s]))
? searchParameter : 0;
searchInitArray = ff.clone(initResults, searchInitArray);
StopWatch sw2 = sw1.create();
FindFociSearchResults searchArray = ff.findMaximaSearch(searchInitArray,
backgroundMethodArray[b], thisBackgroundParameter, searchMethodArray[s],
thisSearchParameter);
sw2.stop();
if (searchArray == null)
return null;
FindFociInitResults mergeInitArray = null;
if (logBackground)
{
final float backgroundLevel = searchInitArray.stats.background;
logBackground = false;
IJ.log(String.format("Background level - %s %s: %s = %g",
FindFoci.backgroundMethods[backgroundMethodArray[b]],
backgroundMethodHasStatisticsMode(backgroundMethodArray[b])
? "(" + statsMode + ") " : "",
((backgroundMethodHasParameter(backgroundMethodArray[b]))
? IJ.d2s(backgroundParameter, 2) : thresholdMethod),
backgroundLevel));
}
for (double peakParameter = myPeakParameterMin; peakParameter <= myPeakParameterMax; peakParameter += myPeakParameterInterval)
{
StopWatch sw3 = sw2.create();
FindFociMergeTempResults mergePeakResults = ff.findMaximaMergePeak(searchInitArray,
searchArray, myPeakMethod, peakParameter);
sw3.stop();
for (int minSize = myMinSizeMin; minSize <= myMinSizeMax; minSize += myMinSizeInterval)
{
StopWatch sw4 = sw3.create();
FindFociMergeTempResults mergeSizeResults = ff
.findMaximaMergeSize(searchInitArray, mergePeakResults, minSize);
sw4.stop();
for (int options : optionsArray)
{
mergeInitArray = ff.clone(searchInitArray, mergeInitArray);
StopWatch sw5 = sw4.create();
FindFociMergeResults mergeArray = ff.findMaximaMergeFinal(mergeInitArray,
mergeSizeResults, minSize, options, blur);
sw5.stop();
if (mergeArray == null)
return null;
options += statisticsMode;
for (int sortMethod : sortMethodArray)
{
for (int c = 0; c < centreMethodArray.length; c++)
{
for (double centreParameter = myCentreParameterMinArray[c]; centreParameter <= myCentreParameterMaxArray[c]; centreParameter += myCentreParameterIntervalArray[c])
{
StopWatch sw6 = sw5.create();
FindFociResults peakResults = ff.findMaximaResults(
mergeInitArray, mergeArray, myMaxPeaks, sortMethod,
centreMethodArray[c], centreParameter);
final long time = sw6.stop();
counter.increment();
if (peakResults != null)
{
// Get the results
Options runOptions = new Options(blur,
backgroundMethodArray[b], thisBackgroundParameter,
thresholdMethod, searchMethodArray[s],
thisSearchParameter, myMaxPeaks, minSize,
myPeakMethod, peakParameter, sortMethod, options,
centreMethodArray[c], centreParameter);
Result result = analyseResults(id, roiPoints,
peakResults.results, dThreshold, runOptions, time,
myBeta, is3D);
results.add(result);
}
id++;
}
}
}
}
}
}
}
}
}
}
}
sw.stop();
// All possible results sort methods are highest first
sortResults(results, myResultsSortMethod);
return new OptimiserResult(results, sw.time());
}
private void showResults(ImagePlus imp, ImagePlus mask, ArrayList<Result> results)
{
createResultsWindow(imp, mask, results);
// Limit the number of results
int noOfResults = results.size();
if (myMaxResults > 0 && noOfResults > myMaxResults)
noOfResults = myMaxResults;
for (int i = noOfResults; i-- > 0;)
{
Result result = results.get(i);
StringBuilder sb = new StringBuilder();
sb.append(IJ.d2s(result.metrics[Result.RANK], 0)).append("\t");
sb.append(result.getParameters());
sb.append(result.n).append("\t");
sb.append(result.tp).append("\t");
sb.append(result.fp).append("\t");
sb.append(result.fn).append("\t");
sb.append(IJ.d2s(result.metrics[Result.JACCARD], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.PRECISION], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.RECALL], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F05], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F1], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F2], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.Fb], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.SCORE], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.RMSD], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.time / 1000000.0, RESULT_PRECISION)).append("\n");
resultsWindow.append(sb.toString());
}
resultsWindow.append("\n");
}
/**
* Saves the optimiser results to the given file. Also saves the predicted points (from the best scoring options) if
* provided.
*
* @param imp
* @param mask
* @param results
* @param predictedPoints
* can be null
* @param resultFile
*/
private void saveResults(ImagePlus imp, ImagePlus mask, ArrayList<Result> results, AssignedPoint[] predictedPoints,
String resultFile)
{
if (Utils.isNullOrEmpty(resultFile))
return;
Options bestOptions = results.get(0).options;
double fractionParameter = 0;
int outputType = FindFoci.OUTPUT_RESULTS_TABLE | FindFoci.OUTPUT_ROI_SELECTION |
FindFoci.OUTPUT_MASK_ROI_SELECTION | FindFoci.OUTPUT_LOG_MESSAGES;
// TODO - Add support for saving the channel, slice & frame
FindFoci.saveParameters(resultFile + ".params", null, null, null, null, bestOptions.backgroundMethod,
bestOptions.backgroundParameter, bestOptions.autoThresholdMethod, bestOptions.searchMethod,
bestOptions.searchParameter, bestOptions.maxPeaks, bestOptions.minSize, bestOptions.peakMethod,
bestOptions.peakParameter, outputType, bestOptions.sortIndex, bestOptions.options, bestOptions.blur,
bestOptions.centreMethod, bestOptions.centreParameter, fractionParameter, "");
OutputStreamWriter out = createResultsFile(bestOptions, imp, mask, resultFile);
if (out == null)
return;
try
{
out.write("#\n# Results\n# " + createResultsHeader(true, false));
// Output all results in ascending rank order
for (int i = 0; i < results.size(); i++)
{
Result result = results.get(i);
StringBuilder sb = new StringBuilder();
sb.append(IJ.d2s(result.metrics[Result.RANK], 0)).append("\t");
sb.append(result.getParameters());
sb.append(result.n).append("\t");
sb.append(result.tp).append("\t");
sb.append(result.fp).append("\t");
sb.append(result.fn).append("\t");
sb.append(IJ.d2s(result.metrics[Result.JACCARD], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.PRECISION], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.RECALL], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F05], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F1], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.F2], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.Fb], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.SCORE], RESULT_PRECISION)).append("\t");
sb.append(IJ.d2s(result.metrics[Result.RMSD], RESULT_PRECISION)).append("\t");
sb.append(result.time).append("\n");
out.write(sb.toString());
}
// Save the identified points
if (predictedPoints != null)
PointManager.savePoints(predictedPoints, resultFile + ".points.csv");
}
catch (IOException e)
{
IJ.log("Failed to write to the output file '" + resultFile + ".points.csv': " + e.getMessage());
}
finally
{
try
{
out.close();
}
catch (IOException e)
{
}
}
}
private synchronized void addFindFociCommand(OutputStreamWriter out, Options bestOptions, String maskTitle)
throws IOException
{
if (bestOptions == null)
return;
// This is the only way to clear the recorder.
// It will save the current optimiser command to the recorder and then clear it.
Recorder.saveCommand();
// Use the recorder to build the options for the FindFoci plugin
Recorder.setCommand("FindFoci");
Recorder.recordOption("mask", maskTitle);
Recorder.recordOption("background_method", FindFoci.backgroundMethods[bestOptions.backgroundMethod]);
Recorder.recordOption("Background_parameter", "" + bestOptions.backgroundParameter);
Recorder.recordOption("Auto_threshold", bestOptions.autoThresholdMethod);
if (backgroundMethodHasStatisticsMode(bestOptions.backgroundMethod))
Recorder.recordOption("Statistics_mode", FindFoci.getStatisticsMode(bestOptions.options));
Recorder.recordOption("Search_method", FindFoci.searchMethods[bestOptions.searchMethod]);
Recorder.recordOption("Search_parameter", "" + bestOptions.searchParameter);
Recorder.recordOption("Minimum_size", "" + bestOptions.minSize);
if ((bestOptions.options & FindFoci.OPTION_MINIMUM_ABOVE_SADDLE) != 0)
Recorder.recordOption("Minimum_above_saddle");
if ((bestOptions.options & FindFoci.OPTION_CONTIGUOUS_ABOVE_SADDLE) != 0)
Recorder.recordOption("Connected_above_saddle");
Recorder.recordOption("Minimum_peak_height", FindFoci.peakMethods[bestOptions.peakMethod]);
Recorder.recordOption("Peak_parameter", "" + bestOptions.peakParameter);
Recorder.recordOption("Sort_method", FindFoci.sortIndexMethods[bestOptions.sortIndex]);
Recorder.recordOption("Maximum_peaks", "" + bestOptions.maxPeaks);
Recorder.recordOption("Show_mask", FindFoci.maskOptions[3]);
Recorder.recordOption("Show_table");
Recorder.recordOption("Mark_maxima");
Recorder.recordOption("Mark_peak_maxima");
Recorder.recordOption("Show_log_messages");
if (bestOptions.blur > 0)
Recorder.recordOption("Gaussian_blur", "" + bestOptions.blur);
Recorder.recordOption("Centre_method", FindFoci.getCentreMethods()[bestOptions.centreMethod]);
if (bestOptions.centreMethod == FindFoci.CENTRE_OF_MASS_SEARCH)
Recorder.recordOption("Centre_parameter", "" + bestOptions.centreParameter);
out.write(String.format("# run(\"FindFoci\", \"%s\")\n", Recorder.getCommandOptions()));
// Ensure the new command we have just added does not get recorded
Recorder.setCommand(null);
}
private static double getDistanceThreshold(ImagePlus imp)
{
if (myMatchSearchMethod == 1)
return myMatchSearchDistance;
final int length = (imp.getWidth() < imp.getHeight()) ? imp.getWidth() : imp.getHeight();
return Math.ceil(myMatchSearchDistance * length);
}
private void append(StringBuilder sb, String format, Object... args)
{
sb.append(String.format(format, args));
}
private static AssignedPoint[] showResult(ImagePlus imp, ImagePlus mask, Options options)
{
if (imp == null)
return null;
int outputType = FindFoci.OUTPUT_MASK_PEAKS | FindFoci.OUTPUT_MASK_ABOVE_SADDLE |
FindFoci.OUTPUT_MASK_ROI_SELECTION | FindFoci.OUTPUT_ROI_SELECTION | FindFoci.OUTPUT_LOG_MESSAGES;
// Clone the input image to allow display of the peaks on the original
ImagePlus clone = cloneImage(imp, imp.getTitle() + " clone");
clone.show();
FindFoci ff = new FindFoci();
ff.exec(clone, mask, options.backgroundMethod, options.backgroundParameter, options.autoThresholdMethod,
options.searchMethod, options.searchParameter, options.maxPeaks, options.minSize, options.peakMethod,
options.peakParameter, outputType, options.sortIndex, options.options, options.blur,
options.centreMethod, options.centreParameter, 1);
// Add 3D support here by getting the results from the results table not the clone image which only supports 2D
ArrayList<FindFociResult> results = FindFoci.getResults();
//AssignedPoint[] predictedPoints = PointManager.extractRoiPoints(clone.getRoi());
AssignedPoint[] predictedPoints = new AssignedPoint[results.size()];
for (int i = 0; i < predictedPoints.length; i++)
{
FindFociResult result = results.get(i);
predictedPoints[i] = new AssignedPoint(result.x, result.y, result.z + 1, i);
}
maskImage(clone, mask);
if (myShowScoreImages)
{
AssignedPoint[] actualPoints = extractRoiPoints(imp.getRoi(), imp, mask);
List<Coordinate> TP = new LinkedList<Coordinate>();
List<Coordinate> FP = new LinkedList<Coordinate>();
List<Coordinate> FN = new LinkedList<Coordinate>();
final boolean is3D = is3D(actualPoints);
if (is3D)
MatchCalculator.analyseResults3D(actualPoints, predictedPoints, getDistanceThreshold(imp), TP, FP, FN);
else
MatchCalculator.analyseResults2D(actualPoints, predictedPoints, getDistanceThreshold(imp), TP, FP, FN);
// Show image with TP, FP and FN. Use an overlay to support 3D images
ImagePlus tpImp = cloneImage(imp, mask, imp.getTitle() + " TP");
//tpImp.setRoi(createRoi(TP));
tpImp.setOverlay(createOverlay(TP, imp));
tpImp.show();
ImagePlus fpImp = cloneImage(imp, mask, imp.getTitle() + " FP");
//fpImp.setRoi(createRoi(FP));
fpImp.setOverlay(createOverlay(FP, imp));
fpImp.show();
ImagePlus fnImp = cloneImage(imp, mask, imp.getTitle() + " FN");
//fnImp.setRoi(createRoi(FN));
fnImp.setOverlay(createOverlay(FN, imp));
fnImp.show();
}
else
{
// Leaving old results would be confusing so close them
closeImage(imp.getTitle() + " TP");
closeImage(imp.getTitle() + " FP");
closeImage(imp.getTitle() + " FN");
}
return predictedPoints;
}
private static void closeImage(String title)
{
ImagePlus imp = WindowManager.getImage(title);
if (imp != null)
{
imp.close();
}
}
private static ImagePlus cloneImage(ImagePlus imp, String cloneTitle)
{
ImagePlus clone = WindowManager.getImage(cloneTitle);
if (clone == null)
{
clone = imp.duplicate();
clone.setTitle(cloneTitle);
}
else
{
ImageStack s1 = imp.getImageStack();
ImageStack s2 = clone.getImageStack();
for (int n = 1; n <= s1.getSize(); n++)
{
s2.setPixels(s1.getProcessor(n).duplicate().getPixels(), n);
}
clone.setStack(s2);
}
clone.setOverlay(null);
return clone;
}
private static ImagePlus cloneImage(ImagePlus imp, ImagePlus mask, String cloneTitle)
{
ImagePlus clone = WindowManager.getImage(cloneTitle);
Integer maskId = (mask != null) ? new Integer(mask.getID()) : 0;
if (clone == null || !clone.getProperty("MASK").equals(maskId))
{
if (clone != null)
{
clone.close();
}
clone = imp.duplicate();
clone.setTitle(cloneTitle);
clone.setProperty("MASK", maskId);
clone.setOverlay(null);
// Exclude outside the mask
maskImage(clone, mask);
}
return clone;
}
private static void maskImage(ImagePlus clone, ImagePlus mask)
{
if (validMask(clone, mask))
{
ImageStack cloneStack = clone.getImageStack();
ImageStack maskStack = mask.getImageStack();
boolean reloadMask = cloneStack.getSize() == maskStack.getSize();
for (int slice = 1; slice <= cloneStack.getSize(); slice++)
{
ImageProcessor ipClone = cloneStack.getProcessor(slice);
ImageProcessor ipMask = maskStack.getProcessor(reloadMask ? slice : 1);
for (int i = ipClone.getPixelCount(); i-- > 0;)
{
if (ipMask.get(i) == 0)
{
ipClone.set(i, 0);
}
}
}
clone.updateAndDraw();
}
}
private int[] createOptionsArray()
{
if (myMinimumAboveSaddle == 0)
return new int[] { FindFoci.OPTION_MINIMUM_ABOVE_SADDLE };
else if (myMinimumAboveSaddle == 1)
return new int[] { FindFoci.OPTION_MINIMUM_ABOVE_SADDLE | FindFoci.OPTION_CONTIGUOUS_ABOVE_SADDLE };
else if (myMinimumAboveSaddle == 2)
return new int[] { 0 };
else
return new int[] { FindFoci.OPTION_MINIMUM_ABOVE_SADDLE,
FindFoci.OPTION_MINIMUM_ABOVE_SADDLE | FindFoci.OPTION_CONTIGUOUS_ABOVE_SADDLE, 0 };
}
private boolean invalidImage(ImagePlus imp)
{
if (null == imp)
{
IJ.noImage();
return true;
}
if (!FindFoci.isSupported(imp.getBitDepth()))
{
IJ.showMessage("Error", "Only " + FindFoci.getSupported() + " images are supported");
return true;
}
// This error is now handled later
//Roi roi = imp.getRoi();
//if (roi != null && roi.getType() != Roi.POINT)
//{
// IJ.showMessage("Error", "Image must have a point ROI");
//}
return false;
}
private boolean showDialog(ImagePlus imp)
{
// Ensure the Dialog options are recorded. These are used later to write to file.
boolean recorderOn = Recorder.record;
if (!recorderOn)
{
Recorder.saveCommand(); // Clear the old command options
Recorder.record = true;
}
if (imp == null)
{
multiMode = true;
if (!showMultiDialog())
{
Recorder.record = recorderOn; // Reset the recorder
return false;
}
}
// Get the optimisation search settings
GenericDialog gd = new GenericDialog(TITLE);
ArrayList<String> newImageList = (multiMode) ? null : FindFoci.buildMaskList(imp);
// Column 1
gd.addMessage("Runs the FindFoci algorithm using different parameters.\n" +
"Results are compared to reference ROI points.\n\n" +
"Input range fields accept 3 values: min,max,interval\n" +
"Gaussian blur accepts comma-delimited values for the blur.");
createSettings();
gd.addChoice("Settings", SETTINGS, SETTINGS[0]);
if (!multiMode)
gd.addChoice("Mask", newImageList.toArray(new String[0]), myMaskImage);
// Do not allow background above mean and background absolute to both be enabled.
gd.addCheckbox("Background_SD_above_mean", myBackgroundStdDevAboveMean);
gd.addCheckbox("Background_Absolute", (myBackgroundStdDevAboveMean) ? false : myBackgroundAbsolute);
gd.addStringField("Background_parameter", myBackgroundParameter, 12);
gd.addCheckbox("Background_Auto_Threshold", myBackgroundAuto);
gd.addStringField("Auto_threshold", myThresholdMethod, 25);
gd.addStringField("Statistics_mode", myStatisticsMode, 25);
gd.addCheckbox("Search_above_background", mySearchAboveBackground);
gd.addCheckbox("Search_fraction_of_peak", mySearchFractionOfPeak);
gd.addStringField("Search_parameter", mySearchParameter, 12);
gd.addChoice("Minimum_peak_height", FindFoci.peakMethods, FindFoci.peakMethods[myPeakMethod]);
gd.addStringField("Peak_parameter", myPeakParameter, 12);
gd.addStringField("Minimum_size", myMinSizeParameter, 12);
gd.addChoice("Minimum_above_saddle", saddleOptions, saddleOptions[myMinimumAboveSaddle]);
// Column 2
gd.addMessage(createSortOptions());
Component sortOptionsLabel = gd.getMessage(); // Note the component that will start column 2
gd.addStringField("Sort_method", mySortMethod);
gd.addNumericField("Maximum_peaks", myMaxPeaks, 0);
gd.addStringField("Gaussian_blur", myGaussianBlur);
gd.addMessage(createCentreOptions());
gd.addStringField("Centre_method", myCentreMethod);
gd.addStringField("Centre_parameter", myCentreParameter);
gd.addMessage("Optimisation options:");
gd.addChoice("Match_search_method", matchSearchMethods, matchSearchMethods[myMatchSearchMethod]);
gd.addNumericField("Match_search_distance", myMatchSearchDistance, 2);
gd.addChoice("Result_sort_method", sortMethods, sortMethods[myResultsSortMethod]);
gd.addNumericField("F-beta", myBeta, 2);
gd.addNumericField("Maximum_results", myMaxResults, 0);
gd.addNumericField("Step_limit", STEP_LIMIT, 0);
if (!multiMode)
{
gd.addCheckbox("Show_score_images", myShowScoreImages);
gd.addStringField("Result_file", myResultFile, 35);
// Add a message about double clicking the result table to show the result
gd.addMessage("Note: Double-click an entry in the optimiser results table\n" +
"to view the FindFoci output. This only works for the most recent\n" +
"set of results in the table.");
}
gd.addHelp(gdsc.help.URL.FIND_FOCI);
if (!java.awt.GraphicsEnvironment.isHeadless())
{
checkbox = gd.getCheckboxes();
choice = gd.getChoices();
saveCustomSettings(gd);
// Listen for changes
addListeners(gd);
}
// Re-arrange the standard layout which has a GridBagLayout with 2 columns (label,field)
// to 4 columns: (label,field) x 2
if (gd.getLayout() != null)
{
GridBagLayout grid = (GridBagLayout) gd.getLayout();
int xOffset = 0, yOffset = 0;
int lastY = -1, rowCount = 0;
for (Component comp : gd.getComponents())
{
// Check if this should be the second major column
if (comp == sortOptionsLabel)
{
xOffset += 2;
yOffset -= rowCount;
}
// Reposition the field
GridBagConstraints c = grid.getConstraints(comp);
if (lastY != c.gridy)
rowCount++;
lastY = c.gridy;
c.gridx = c.gridx + xOffset;
c.gridy = c.gridy + yOffset;
c.insets.left = c.insets.left + 10 * xOffset;
c.insets.top = 0;
c.insets.bottom = 0;
grid.setConstraints(comp, c);
}
if (IJ.isLinux())
gd.setBackground(new Color(238, 238, 238));
}
gd.showDialog();
if (gd.wasCanceled())
{
Recorder.record = recorderOn; // Reset the recorder
return false;
}
// Ignore the settings field
gd.getNextChoiceIndex();
if (!multiMode)
myMaskImage = gd.getNextChoice();
myBackgroundStdDevAboveMean = gd.getNextBoolean();
myBackgroundAbsolute = gd.getNextBoolean();
myBackgroundParameter = gd.getNextString();
myBackgroundAuto = gd.getNextBoolean();
myThresholdMethod = gd.getNextString();
myStatisticsMode = gd.getNextString();
mySearchAboveBackground = gd.getNextBoolean();
mySearchFractionOfPeak = gd.getNextBoolean();
mySearchParameter = gd.getNextString();
myPeakMethod = gd.getNextChoiceIndex();
myPeakParameter = gd.getNextString();
myMinSizeParameter = gd.getNextString();
myMinimumAboveSaddle = gd.getNextChoiceIndex();
mySortMethod = gd.getNextString();
myMaxPeaks = (int) gd.getNextNumber();
myGaussianBlur = gd.getNextString();
myCentreMethod = gd.getNextString();
myCentreParameter = gd.getNextString();
myMatchSearchMethod = gd.getNextChoiceIndex();
myMatchSearchDistance = gd.getNextNumber();
myResultsSortMethod = gd.getNextChoiceIndex();
myBeta = gd.getNextNumber();
myMaxResults = (int) gd.getNextNumber();
STEP_LIMIT = (int) gd.getNextNumber();
if (!multiMode)
{
myShowScoreImages = gd.getNextBoolean();
myResultFile = gd.getNextString();
}
Recorder.record = recorderOn; // Reset the recorder
// This only works if we do not attach as a dialogListener to the GenericDialog
optimiserCommandOptions = Recorder.getCommandOptions();
// Validate the chosen parameters
if (myBackgroundStdDevAboveMean && myBackgroundAbsolute)
{
IJ.error("Cannot optimise background methods 'SD above mean' and 'Absolute' using the same parameters");
return false;
}
if (!(myBackgroundStdDevAboveMean | myBackgroundAbsolute | myBackgroundAuto))
{
IJ.error("Require at least one background method");
return false;
}
if (!(mySearchAboveBackground | mySearchFractionOfPeak))
{
IJ.error("Require at least one background search method");
return false;
}
// Check which options to optimise
optionsArray = createOptionsArray();
parseThresholdMethods();
if (myBackgroundAuto && thresholdMethods.length == 0)
{
IJ.error("No recognised methods for auto-threshold");
return false;
}
parseStatisticsModes();
backgroundMethodArray = createBackgroundArray();
parseBackgroundLimits();
if (myBackgroundParameterMax < myBackgroundParameterMin)
{
IJ.error("Background parameter max must be greater than min");
return false;
}
myBackgroundParameterMinArray = createBackgroundLimits();
searchMethodArray = createSearchArray();
parseSearchLimits();
if (mySearchParameterMax < mySearchParameterMin)
{
IJ.error("Search parameter max must be greater than min");
return false;
}
mySearchParameterMinArray = createSearchLimits();
parseMinSizeLimits();
if (myMinSizeMax < myMinSizeMin)
{
IJ.error("Size max must be greater than min");
return false;
}
parsePeakParameterLimits();
if (myPeakParameterMax < myPeakParameterMin)
{
IJ.error("Peak parameter max must be greater than min");
return false;
}
sortMethodArray = createSortArray();
if (sortMethodArray.length == 0)
{
IJ.error("Require at least one sort method");
return false;
}
blurArray = createBlurArray();
centreMethodArray = createCentreArray();
parseCentreLimits();
if (myCentreParameterMax < myCentreParameterMin)
{
IJ.error("Centre parameter max must be greater than min");
return false;
}
myCentreParameterMinArray = createCentreMinLimits();
myCentreParameterMaxArray = createCentreMaxLimits();
myCentreParameterIntervalArray = createCentreIntervals();
if (!multiMode)
{
ImagePlus mask = WindowManager.getImage(myMaskImage);
if (!validMask(imp, mask))
{
statisticsModes = new String[] { "Both" };
}
}
if (myMatchSearchMethod == 1 && myMatchSearchDistance < 1)
{
IJ.log("WARNING: Absolute peak match distance is less than 1 pixel: " + myMatchSearchDistance);
}
// Count the number of options
combinations = countSteps();
if (combinations >= STEP_LIMIT)
{
IJ.error("Maximum number of optimisation steps exceeded: " + combinations + " >> " + STEP_LIMIT);
return false;
}
YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), TITLE,
combinations + " combinations. Do you wish to proceed?");
if (!d.yesPressed())
return false;
return true;
}
private boolean showMultiDialog()
{
GenericDialog gd = new GenericDialog(TITLE);
gd.addMessage("Run " + TITLE +
" on a set of images.\n \nAll images in a directory will be processed.\n \nOptional mask images in the input directory should be named:\n[image_name].mask.[ext]\nor placed in the mask directory with the same name as the parent image.");
gd.addStringField("Input_directory", inputDirectory);
gd.addStringField("Mask_directory", maskDirectory);
gd.addStringField("Output_directory", outputDirectory);
gd.addMessage("[Note: Double-click a text field to open a selection dialog]");
gd.addMessage(
"The score metric for each parameter combination is computed per image.\nThe scores are converted then averaged across all images.");
gd.addChoice("Score_conversion", SCORING_MODES, SCORING_MODES[scoringMode]);
gd.addCheckbox("Re-use_results", reuseResults);
@SuppressWarnings("unchecked")
Vector<TextField> texts = (Vector<TextField>) gd.getStringFields();
for (TextField tf : texts)
{
tf.addMouseListener(this);
tf.setColumns(50);
}
gd.addHelp(gdsc.help.URL.FIND_FOCI);
gd.showDialog();
if (gd.wasCanceled())
return false;
inputDirectory = gd.getNextString();
if (!new File(inputDirectory).isDirectory())
{
IJ.error(TITLE, "Input directory is not a valid directory: " + inputDirectory);
return false;
}
maskDirectory = gd.getNextString();
if ((maskDirectory != null && maskDirectory.length() > 0) && !new File(maskDirectory).isDirectory())
{
IJ.error(TITLE, "Mask directory is not a valid directory: " + maskDirectory);
return false;
}
outputDirectory = gd.getNextString();
if (!new File(outputDirectory).isDirectory())
{
IJ.error(TITLE, "Output directory is not a valid directory: " + outputDirectory);
return false;
}
inputDirectory = Utils.addFileSeparator(inputDirectory);
maskDirectory = Utils.addFileSeparator(maskDirectory);
outputDirectory = Utils.addFileSeparator(outputDirectory);
scoringMode = gd.getNextChoiceIndex();
reuseResults = gd.getNextBoolean();
Prefs.set(INPUT_DIRECTORY, inputDirectory);
Prefs.set(MASK_DIRECTORY, maskDirectory);
Prefs.set(OUTPUT_DIRECTORY, outputDirectory);
Prefs.set(SCORING_MODE, scoringMode);
Prefs.set(REUSE_RESULTS, reuseResults);
return true;
}
private String createSortOptions()
{
StringBuilder sb = new StringBuilder();
sb.append("Sort options (comma-delimited). Use if total peaks > max peaks:\n");
addSortOption(sb, FindFoci.SORT_COUNT).append("; ");
addSortOption(sb, FindFoci.SORT_INTENSITY).append("; ");
addSortOption(sb, FindFoci.SORT_MAX_VALUE).append("; ");
addSortOption(sb, FindFoci.SORT_AVERAGE_INTENSITY).append(";\n");
addSortOption(sb, FindFoci.SORT_INTENSITY_MINUS_BACKGROUND).append("; ");
addSortOption(sb, FindFoci.SORT_AVERAGE_INTENSITY_MINUS_BACKGROUND).append(";\n");
addSortOption(sb, FindFoci.SORT_SADDLE_HEIGHT).append("; ");
addSortOption(sb, FindFoci.SORT_COUNT_ABOVE_SADDLE).append("; ");
addSortOption(sb, FindFoci.SORT_INTENSITY_ABOVE_SADDLE).append(";\n");
addSortOption(sb, FindFoci.SORT_ABSOLUTE_HEIGHT);
return sb.toString();
}
private StringBuilder addSortOption(StringBuilder sb, int method)
{
sb.append("[").append(method).append("] ").append(FindFoci.sortIndexMethods[method]);
return sb;
}
private String createCentreOptions()
{
StringBuilder sb = new StringBuilder();
sb.append("Centre options (comma-delimited):\n");
for (int method = 0; method < 4; method++)
{
addCentreOption(sb, method).append("; ");
if ((method + 1) % 2 == 0)
sb.append("\n");
}
return sb.toString();
}
private StringBuilder addCentreOption(StringBuilder sb, int method)
{
sb.append("[").append(method).append("] ").append(FindFoci.getCentreMethods()[method]);
return sb;
}
private void parseThresholdMethods()
{
String[] values = myThresholdMethod.split("\\s*;\\s*|\\s*,\\s*|\\s*:\\s*");
LinkedList<String> methods = new LinkedList<String>();
for (String method : values)
validThresholdMethod(method, methods);
thresholdMethods = methods.toArray(new String[0]);
}
private void validThresholdMethod(String method, LinkedList<String> methods)
{
for (String m : FindFoci.autoThresholdMethods)
if (m.equalsIgnoreCase(method))
{
methods.add(m);
return;
}
}
private void parseStatisticsModes()
{
String[] values = myStatisticsMode.split("\\s*;\\s*|\\s*,\\s*|\\s*:\\s*");
LinkedList<String> modes = new LinkedList<String>();
for (String mode : values)
validStatisticsMode(mode, modes);
if (modes.isEmpty())
modes.add("both");
statisticsModes = modes.toArray(new String[0]);
}
private void validStatisticsMode(String mode, LinkedList<String> modes)
{
for (String m : FindFoci.statisticsModes)
if (m.equalsIgnoreCase(mode))
{
modes.add(m);
return;
}
}
private int convertStatisticsMode(String mode)
{
if (mode.equalsIgnoreCase("inside"))
return FindFoci.OPTION_STATS_INSIDE;
if (mode.equalsIgnoreCase("outside"))
return FindFoci.OPTION_STATS_OUTSIDE;
return 0;
}
private int[] createBackgroundArray()
{
int[] array = new int[countBackgroundFlags()];
int i = 0;
if (myBackgroundAbsolute)
{
array[i] = FindFoci.BACKGROUND_ABSOLUTE;
i++;
}
if (myBackgroundAuto)
{
for (@SuppressWarnings("unused")
String method : thresholdMethods)
{
array[i] = FindFoci.BACKGROUND_AUTO_THRESHOLD;
i++;
}
}
if (myBackgroundStdDevAboveMean)
{
array[i] = FindFoci.BACKGROUND_STD_DEV_ABOVE_MEAN;
i++;
}
return array;
}
private int countBackgroundFlags()
{
int i = 0;
if (myBackgroundAbsolute)
i++;
if (myBackgroundAuto)
i += thresholdMethods.length;
if (myBackgroundStdDevAboveMean)
i++;
return i;
}
private void parseBackgroundLimits()
{
double[] values = splitValues(myBackgroundParameter);
myBackgroundParameterMin = values[0];
if (values.length == 3)
{
myBackgroundParameterMax = values[1];
myBackgroundParameterInterval = values[2];
}
else
{
myBackgroundParameterMax = values[0];
myBackgroundParameterInterval = 1;
}
if (myBackgroundParameterInterval < 0)
myBackgroundParameterInterval = -myBackgroundParameterInterval;
while (myBackgroundParameterMin < 0)
myBackgroundParameterMin += myBackgroundParameterInterval;
}
private double[] splitValues(String text)
{
String[] tokens = text.split(";|,|:");
StoredData list = new StoredData(tokens.length);
for (String token : tokens)
{
try
{
list.add(Double.parseDouble(token));
}
catch (Exception e)
{
}
}
return list.getValues();
}
private double[] createBackgroundLimits()
{
double[] limits = new double[backgroundMethodArray.length];
for (int i = limits.length; i-- > 0;)
{
limits[i] = getBackgroundLimit(backgroundMethodArray[i]);
}
return limits;
}
private double getBackgroundLimit(int backgroundMethod)
{
return backgroundMethodHasParameter(backgroundMethod) ? myBackgroundParameterMin : myBackgroundParameterMax;
}
private static boolean backgroundMethodHasStatisticsMode(int backgroundMethod)
{
return !(backgroundMethod == FindFoci.BACKGROUND_NONE || backgroundMethod == FindFoci.BACKGROUND_ABSOLUTE);
}
private static boolean backgroundMethodHasParameter(int backgroundMethod)
{
return !(backgroundMethod == FindFoci.BACKGROUND_NONE || backgroundMethod == FindFoci.BACKGROUND_MEAN ||
backgroundMethod == FindFoci.BACKGROUND_AUTO_THRESHOLD);
}
private int[] createSearchArray()
{
int[] array = new int[countSearchFlags()];
int i = 0;
if (mySearchAboveBackground)
{
array[i] = FindFoci.SEARCH_ABOVE_BACKGROUND;
i++;
}
if (mySearchFractionOfPeak)
{
array[i] = FindFoci.SEARCH_FRACTION_OF_PEAK_MINUS_BACKGROUND;
i++;
}
return array;
}
private int countSearchFlags()
{
int i = 0;
if (mySearchAboveBackground)
i++;
if (mySearchFractionOfPeak)
i++;
return i;
}
private void parseSearchLimits()
{
double[] values = splitValues(mySearchParameter);
mySearchParameterMin = values[0];
if (values.length == 3)
{
mySearchParameterMax = values[1];
mySearchParameterInterval = values[2];
}
else
{
mySearchParameterMax = values[0];
mySearchParameterInterval = 1;
}
if (mySearchParameterInterval < 0)
mySearchParameterInterval = -mySearchParameterInterval;
while (mySearchParameterMin < 0)
mySearchParameterMin += mySearchParameterInterval;
}
private double[] createSearchLimits()
{
double[] limits = new double[searchMethodArray.length];
for (int i = limits.length; i-- > 0;)
{
limits[i] = getSearchLimit(searchMethodArray[i]);
}
return limits;
}
private double getSearchLimit(int searchMethod)
{
return searchMethodHasParameter(searchMethod) ? mySearchParameterMin : mySearchParameterMax;
}
private static boolean searchMethodHasParameter(int searchMethod)
{
return !(searchMethod == FindFoci.SEARCH_ABOVE_BACKGROUND);
}
private void parseMinSizeLimits()
{
double[] values = splitValues(myMinSizeParameter);
myMinSizeMin = (int) values[0];
if (values.length == 3)
{
myMinSizeMax = (int) values[1];
myMinSizeInterval = (int) values[2];
}
else
{
myMinSizeMax = (int) values[0];
myMinSizeInterval = 1;
}
if (myMinSizeInterval < 1)
myMinSizeInterval = -myMinSizeInterval;
// The minimum size should not be zero (this would not be a peak)
while (myMinSizeMin <= 0)
myMinSizeMin += myMinSizeInterval;
}
private void parsePeakParameterLimits()
{
double[] values = splitValues(myPeakParameter);
myPeakParameterMin = values[0];
if (values.length == 3)
{
myPeakParameterMax = values[1];
myPeakParameterInterval = values[2];
}
else
{
myPeakParameterMax = values[0];
myPeakParameterInterval = 1;
}
if (myPeakParameterInterval < 0)
myPeakParameterInterval = -myPeakParameterInterval;
while (myPeakParameterMin < 0)
myPeakParameterMin += myPeakParameterInterval;
}
private int[] createSortArray()
{
double[] values = splitValues(mySortMethod);
int[] array = new int[values.length];
for (int i = 0; i < array.length; i++)
array[i] = (int) values[i];
return array;
}
private double[] createBlurArray()
{
double[] array = splitValues(myGaussianBlur);
Arrays.sort(array);
return array;
}
private int[] createCentreArray()
{
double[] values = splitValues(myCentreMethod);
if (values.length == 0)
return new int[] { FindFoci.CENTRE_MAX_VALUE_SEARCH }; // Default
int[] array = new int[values.length];
for (int i = 0; i < array.length; i++)
array[i] = (int) values[i];
return array;
}
private void parseCentreLimits()
{
double[] values = splitValues(myCentreParameter);
myCentreParameterMin = (int) values[0];
if (values.length == 3)
{
myCentreParameterMax = (int) values[1];
myCentreParameterInterval = (int) values[2];
}
else
{
myCentreParameterMax = (int) values[0];
myCentreParameterInterval = 1;
}
if (myCentreParameterInterval < 1)
myCentreParameterInterval = -myCentreParameterInterval;
while (myCentreParameterMin < 0)
myCentreParameterMin += myCentreParameterInterval;
}
private int[] createCentreMinLimits()
{
int[] limits = new int[centreMethodArray.length];
for (int i = limits.length; i-- > 0;)
{
limits[i] = getCentreMinLimit(centreMethodArray[i]);
}
return limits;
}
private int getCentreMinLimit(int centreMethod)
{
// If a range has been specified then run the optimiser for average and maximum projection, otherwise use average projection only.
if (centreMethod == FindFoci.CENTRE_GAUSSIAN_SEARCH)
return (myCentreParameterMin < myCentreParameterMax) ? 0 : 1;
if (centreMethod == FindFoci.CENTRE_OF_MASS_SEARCH)
return myCentreParameterMin;
return 0; // Other methods have no parameters
}
private int[] createCentreMaxLimits()
{
int[] limits = new int[centreMethodArray.length];
for (int i = limits.length; i-- > 0;)
{
limits[i] = getCentreMaxLimit(centreMethodArray[i]);
}
return limits;
}
private int getCentreMaxLimit(int centreMethod)
{
if (centreMethod == FindFoci.CENTRE_GAUSSIAN_SEARCH)
return 1; // Average projection
if (centreMethod == FindFoci.CENTRE_OF_MASS_SEARCH)
return myCentreParameterMax; // Limit can be any value above zero
return 0; // Other methods have no parameters
}
private int[] createCentreIntervals()
{
int[] limits = new int[centreMethodArray.length];
for (int i = limits.length; i-- > 0;)
{
limits[i] = getCentreInterval(centreMethodArray[i]);
}
return limits;
}
private int getCentreInterval(int centreMethod)
{
if (centreMethod == FindFoci.CENTRE_GAUSSIAN_SEARCH)
return 1;
return myCentreParameterInterval;
}
private static boolean centreMethodHasParameter(int centreMethod)
{
return (centreMethod == FindFoci.CENTRE_OF_MASS_SEARCH || centreMethod == FindFoci.CENTRE_GAUSSIAN_SEARCH);
}
// Do not get warnings about unused variables
@SuppressWarnings("unused")
private int countSteps()
{
int stepLimit = STEP_LIMIT;
int steps = 0;
for (double blur : blurArray)
for (int b = 0; b < backgroundMethodArray.length; b++)
{
String[] myStatsModes = backgroundMethodHasStatisticsMode(backgroundMethodArray[b]) ? statisticsModes
: new String[] { "Both" };
for (String statsMode : myStatsModes)
for (double backgroundParameter = myBackgroundParameterMinArray[b]; backgroundParameter <= myBackgroundParameterMax; backgroundParameter += myBackgroundParameterInterval)
for (int minSize = myMinSizeMin; minSize <= myMinSizeMax; minSize += myMinSizeInterval)
for (int s = 0; s < searchMethodArray.length; s++)
for (double searchParameter = mySearchParameterMinArray[s]; searchParameter <= mySearchParameterMax; searchParameter += mySearchParameterInterval)
for (double peakParameter = myPeakParameterMin; peakParameter <= myPeakParameterMax; peakParameter += myPeakParameterInterval)
for (int options : optionsArray)
for (int sortMethod : sortMethodArray)
for (int c = 0; c < centreMethodArray.length; c++)
for (double centreParameter = myCentreParameterMinArray[c]; centreParameter <= myCentreParameterMaxArray[c]; centreParameter += myCentreParameterIntervalArray[c])
{
// Simple check to ensure the user has not configured something incorrectly
if (steps++ >= stepLimit)
{
return steps;
}
}
}
return steps;
}
private void createResultsWindow(ImagePlus imp, ImagePlus mask, ArrayList<Result> results)
{
String heading = null;
lastImp = imp;
lastMask = mask;
lastResults = results;
if (resultsWindow == null || !resultsWindow.isShowing())
{
heading = createResultsHeader(true, true);
resultsWindow = new TextWindow(TITLE + " Results", heading, "", 1000, 300);
if (resultsWindow.getTextPanel() != null)
{
resultsWindow.getTextPanel().addMouseListener(this);
}
}
}
private OutputStreamWriter createResultsFile(Options bestOptions, ImagePlus imp, ImagePlus mask, String resultFile)
{
OutputStreamWriter out = null;
try
{
String filename = resultFile + ".results.xls";
File file = new File(filename);
if (!file.exists())
{
if (file.getParent() != null)
new File(file.getParent()).mkdirs();
}
// Save results to file
FileOutputStream fos = new FileOutputStream(filename);
out = new OutputStreamWriter(fos, "UTF-8");
String maskTitle = "";
if (imp != null)
{
out.write("# ImageJ Script to repeat the optimiser and then run the optimal parameters\n#\n");
if (mask != null)
{
out.write("# open(\"" + getFilename(mask) + "\");\n");
maskTitle = mask.getTitle();
}
out.write("# open(\"" + getFilename(imp) + "\");\n");
}
// Write the ImageJ macro command
out.write(String.format("# run(\"FindFoci Optimiser\", \"%s\")\n", optimiserCommandOptions));
addFindFociCommand(out, bestOptions, maskTitle);
return out;
}
catch (Exception e)
{
IJ.log("Failed to create results file '" + resultFile + ".results.xls': " + e.getMessage());
if (out != null)
{
try
{
out.close();
}
catch (IOException ioe)
{
}
}
}
return null;
}
private String getFilename(ImagePlus imp)
{
FileInfo info = imp.getOriginalFileInfo();
if (info != null)
{
return Utils.combinePath(info.directory, info.fileName);
}
else
{
return imp.getTitle();
}
}
private String createResultsHeader(boolean withScore, boolean milliSeconds)
{
StringBuilder sb = new StringBuilder();
sb.append("Rank\t");
sb.append("Blur\t");
sb.append("Background method\t");
sb.append("Max\t");
sb.append("Min\t");
sb.append("Search method\t");
sb.append("Peak method\t");
sb.append("Sort method\t");
sb.append("Centre method\t");
sb.append("N\t");
sb.append("TP\t");
sb.append("FP\t");
sb.append("FN\t");
sb.append("Jaccard\t");
sb.append("Precision\t");
sb.append("Recall\t");
sb.append("F0.5\t");
sb.append("F1\t");
sb.append("F2\t");
sb.append("F-beta\t");
if (withScore)
{
sb.append("Score\t");
}
sb.append("RMSD\t");
if (milliSeconds)
sb.append("mSec\n");
else
sb.append("nanoSec\n");
return sb.toString();
}
private Result analyseResults(int id, AssignedPoint[] roiPoints, ArrayList<FindFociResult> resultsArray,
double dThreshold, Options options, long time, double beta, boolean is3D)
{
// Extract results for analysis
AssignedPoint[] predictedPoints = extractedPredictedPoints(resultsArray);
MatchResult matchResult;
if (is3D)
matchResult = MatchCalculator.analyseResults3D(roiPoints, predictedPoints, dThreshold);
else
matchResult = MatchCalculator.analyseResults2D(roiPoints, predictedPoints, dThreshold);
return new Result(id, options, matchResult.getNumberPredicted(), matchResult.getTruePositives(),
matchResult.getFalsePositives(), matchResult.getFalseNegatives(), time, beta, matchResult.getRMSD());
}
private AssignedPoint[] extractedPredictedPoints(ArrayList<FindFociResult> resultsArray)
{
final AssignedPoint[] predictedPoints = new AssignedPoint[resultsArray.size()];
for (int i = 0; i < resultsArray.size(); i++)
{
final FindFociResult result = resultsArray.get(i);
predictedPoints[i] = new AssignedPoint(result.x, result.y, result.z, i);
}
return predictedPoints;
}
/**
* Convert the FindFoci parameters into a text representation
*
* @param blur
* @param backgroundMethod
* @param thresholdMethod
* @param backgroundParameter
* @param maxPeaks
* @param minSize
* @param searchMethod
* @param searchParameter
* @param peakMethod
* @param peakParameter
* @param sortMethod
* @param options
* @param centreMethod
* @param centreParameter
* @return
*/
static String createParameters(double blur, int backgroundMethod, String thresholdMethod,
double backgroundParameter, int maxPeaks, int minSize, int searchMethod, double searchParameter,
int peakMethod, double peakParameter, int sortMethod, int options, int centreMethod, double centreParameter)
{
// Output results
String spacer = " : ";
StringBuilder sb = new StringBuilder();
sb.append(blur).append("\t");
sb.append(FindFoci.backgroundMethods[backgroundMethod]);
if (backgroundMethodHasStatisticsMode(backgroundMethod))
{
sb.append(" (").append(FindFoci.getStatisticsMode(options)).append(") ");
}
sb.append(spacer);
sb.append(backgroundMethodHasParameter(backgroundMethod) ? IJ.d2s(backgroundParameter, 2) : thresholdMethod)
.append("\t");
sb.append(maxPeaks).append("\t");
sb.append(minSize);
if ((options & FindFoci.OPTION_MINIMUM_ABOVE_SADDLE) != 0)
{
sb.append(" >saddle");
if ((options & FindFoci.OPTION_CONTIGUOUS_ABOVE_SADDLE) != 0)
sb.append(" conn");
}
sb.append("\t");
sb.append(FindFoci.searchMethods[searchMethod]);
if (searchMethodHasParameter(searchMethod))
sb.append(spacer).append(IJ.d2s(searchParameter, 2));
sb.append("\t");
sb.append(FindFoci.peakMethods[peakMethod]).append(spacer);
sb.append(IJ.d2s(peakParameter, 2)).append("\t");
sb.append(FindFoci.sortIndexMethods[sortMethod]).append("\t");
sb.append(FindFoci.getCentreMethods()[centreMethod]);
if (centreMethodHasParameter(centreMethod))
sb.append(spacer).append(IJ.d2s(centreParameter, 2));
sb.append("\t");
return sb.toString();
}
/**
* Convert the FindFoci text representation into Options
*
* @param parameters
* @return the options
*/
private Options createOptions(String parameters)
{
String[] fields = p.split(parameters);
try
{
double blur = Double.parseDouble(fields[0]);
int backgroundMethod = -1;
for (int i = 0; i < FindFoci.backgroundMethods.length; i++)
if (fields[1].startsWith(FindFoci.backgroundMethods[i]))
{
backgroundMethod = i;
break;
}
if (backgroundMethod < 0)
throw new Exception("No background method");
int options = 0;
if (backgroundMethodHasStatisticsMode(backgroundMethod))
{
int first = fields[1].indexOf('(') + 1;
int last = fields[1].indexOf(')', first);
String mode = fields[1].substring(first, last);
if (mode.equals("Inside"))
options |= FindFoci.OPTION_STATS_INSIDE;
else if (mode.equals("Outside"))
options |= FindFoci.OPTION_STATS_OUTSIDE;
else
options |= FindFoci.OPTION_STATS_INSIDE | FindFoci.OPTION_STATS_OUTSIDE;
}
int index = fields[1].indexOf(" : ") + 3;
String thresholdMethod = fields[1].substring(index);
double backgroundParameter = 0;
if (backgroundMethodHasParameter(backgroundMethod))
{
backgroundParameter = Double.parseDouble(thresholdMethod);
thresholdMethod = "";
}
int maxPeaks = Integer.parseInt(fields[2]);
// XXX
index = fields[3].indexOf(" ");
if (index > 0)
{
options |= FindFoci.OPTION_MINIMUM_ABOVE_SADDLE;
if (fields[3].contains("conn"))
options |= FindFoci.OPTION_CONTIGUOUS_ABOVE_SADDLE;
fields[3] = fields[3].substring(0, index);
}
int minSize = Integer.parseInt(fields[3]);
int searchMethod = -1;
for (int i = 0; i < FindFoci.searchMethods.length; i++)
if (fields[4].startsWith(FindFoci.searchMethods[i]))
{
searchMethod = i;
break;
}
if (searchMethod < 0)
throw new Exception("No search method");
double searchParameter = 0;
if (searchMethodHasParameter(searchMethod))
{
index = fields[4].indexOf(" : ") + 3;
searchParameter = Double.parseDouble(fields[4].substring(index));
}
int peakMethod = -1;
for (int i = 0; i < FindFoci.peakMethods.length; i++)
if (fields[5].startsWith(FindFoci.peakMethods[i]))
{
peakMethod = i;
break;
}
if (peakMethod < 0)
throw new Exception("No peak method");
index = fields[5].indexOf(" : ") + 3;
double peakParameter = Double.parseDouble(fields[5].substring(index));
int sortMethod = -1;
for (int i = 0; i < FindFoci.sortIndexMethods.length; i++)
if (fields[6].startsWith(FindFoci.sortIndexMethods[i]))
{
sortMethod = i;
break;
}
if (sortMethod < 0)
throw new Exception("No sort method");
int centreMethod = -1;
for (int i = 0; i < FindFoci.getCentreMethods().length; i++)
if (fields[7].startsWith(FindFoci.getCentreMethods()[i]))
{
centreMethod = i;
break;
}
if (centreMethod < 0)
throw new Exception("No centre method");
double centreParameter = 0;
if (centreMethodHasParameter(centreMethod))
{
index = fields[7].indexOf(" : ") + 3;
centreParameter = Double.parseDouble(fields[7].substring(index));
}
Options o = new Options(blur, backgroundMethod, backgroundParameter, thresholdMethod, searchMethod,
searchParameter, maxPeaks, minSize, peakMethod, peakParameter, sortMethod, options, centreMethod,
centreParameter);
// Debugging
if (!o.createParameters().equals(parameters))
{
System.out.printf("Error converting parameters to FindFoci options:\n%s\n%s\n", parameters,
o.createParameters());
o = null;
}
return o;
}
catch (Exception e)
{
System.out
.println("Error converting parameters to FindFoci options: " + parameters + "\n" + e.getMessage());
return null;
}
}
private static double calculateFScore(double precision, double recall, double beta)
{
double b2 = beta * beta;
double f = ((1.0 + b2) * precision * recall) / (b2 * precision + recall);
return (Double.isNaN(f) ? 0 : f);
}
/**
* Extract the points for the given image. If a file exists in the same directory as the image with the suffix .csv,
* .xyz, or .txt then the program will attempt to load 3D coordinates from file. Otherwise the points are taken from
* the the ROI.
*
* The points are then filtered to include only those within the mask region (if the mask dimensions match those of
* the image).
*
* @param roi
* @param imp
* @param mask
* @return
*/
public static AssignedPoint[] extractRoiPoints(Roi roi, ImagePlus imp, ImagePlus mask)
{
AssignedPoint[] roiPoints = null;
boolean is3D = imp.getNSlices() > 1;
roiPoints = loadPointsFromFile(imp);
if (roiPoints == null)
{
roiPoints = PointManager.extractRoiPoints(roi);
}
if (!is3D)
{
// Discard any potential z-information since the image is not 3D
for (AssignedPoint point : roiPoints)
point.z = 0;
}
// If the mask is not valid or we have no points then return
if (!validMask(imp, mask) || roiPoints.length == 0)
{
return roiPoints;
}
return restrictToMask(mask, roiPoints);
}
/**
* Restrict the given points to the mask
*
* @param mask
* @param roiPoints
* @return
*/
public static AssignedPoint[] restrictToMask(ImagePlus mask, AssignedPoint[] roiPoints)
{
if (mask == null)
return roiPoints;
// Check that the mask should be used in 3D
boolean is3D = is3D(roiPoints) && mask.getNSlices() > 1;
// Look through the ROI points and exclude all outside the mask
ImageStack stack = mask.getStack();
final int c = mask.getChannel();
final int f = mask.getFrame();
int id = 0;
for (AssignedPoint point : roiPoints)
{
if (is3D)
{
// Check within the 3D mask
if (point.z <= mask.getNSlices())
{
int stackIndex = mask.getStackIndex(c, point.z, f);
ImageProcessor ipMask = stack.getProcessor(stackIndex);
if (ipMask.get(point.getXint(), point.getYint()) > 0)
roiPoints[id++] = point;
}
}
else
{
// Check all slices of the mask, i.e. a 2D projection
for (int slice = 1; slice <= mask.getNSlices(); slice++)
{
int stackIndex = mask.getStackIndex(c, slice, f);
ImageProcessor ipMask = stack.getProcessor(stackIndex);
if (ipMask.get(point.getXint(), point.getYint()) > 0)
{
roiPoints[id++] = point;
break;
}
}
}
}
return Arrays.copyOf(roiPoints, id);
}
private static boolean is3D(AssignedPoint[] roiPoints)
{
if (roiPoints.length == 0)
return false;
// All points must have a z-coordinate above zero
for (AssignedPoint point : roiPoints)
if (point.z < 1)
return false;
return true;
}
private static AssignedPoint[] loadPointsFromFile(ImagePlus imp)
{
FileInfo fileInfo = imp.getOriginalFileInfo();
if (fileInfo.directory != null)
{
String title = imp.getTitle();
int index = title.lastIndexOf('.');
if (index != -1)
title = title.substring(0, index);
for (String suffix : new String[] { ".csv", ".xyz", ".txt" })
{
AssignedPoint[] roiPoints = loadPointsFromFile(fileInfo.directory + title + suffix);
if (roiPoints != null)
return roiPoints;
}
}
return null;
}
private static Pattern pointsPattern = Pattern.compile("[, \t]+");
private static AssignedPoint[] loadPointsFromFile(String filename)
{
if (filename == null)
return null;
File file = new File(filename);
if (!file.exists())
return null;
BufferedReader input = null;
try
{
FileInputStream fis = new FileInputStream(filename);
input = new BufferedReader(new UnicodeReader(fis, null));
String line;
int id = 0;
int errors = 0;
ArrayList<AssignedPoint> points = new ArrayList<AssignedPoint>();
while ((line = input.readLine()) != null)
{
if (line.length() == 0)
continue;
if (line.charAt(0) == '#')
continue;
String[] fields = pointsPattern.split(line);
if (fields.length > 1)
{
try
{
int x = (int) Double.parseDouble(fields[0]);
int y = (int) Double.parseDouble(fields[1]);
int z = 0;
if (fields.length > 2)
{
z = (int) Double.parseDouble(fields[2]);
}
points.add(new AssignedPoint(x, y, z, ++id));
}
catch (NumberFormatException e)
{
// Abort if too many errors
if (++errors == 5)
return null;
}
}
}
return points.toArray(new AssignedPoint[points.size()]);
}
catch (IOException e)
{
// ignore
}
finally
{
try
{
if (input != null)
input.close();
}
catch (IOException e)
{
// Ignore
}
}
return null;
}
@SuppressWarnings("unused")
private static Roi createRoi(List<Coordinate> points)
{
int[] ox = new int[points.size()];
int[] oy = new int[points.size()];
int i = 0;
for (Coordinate point : points)
{
ox[i] = point.getXint();
oy[i] = point.getYint();
i++;
}
return new PointRoi(ox, oy, ox.length);
}
private static Overlay createOverlay(List<Coordinate> points, ImagePlus imp)
{
int c = imp.getChannel();
int f = imp.getFrame();
boolean isHyperStack = imp.isDisplayedHyperStack();
int[] ox = new int[points.size()];
int[] oy = new int[points.size()];
int[] oz = new int[points.size()];
int i = 0;
for (Coordinate point : points)
{
ox[i] = point.getXint();
oy[i] = point.getYint();
oz[i] = point.getZint();
i++;
}
Overlay overlay = new Overlay();
int remaining = ox.length;
for (int ii = 0; ii < ox.length; ii++)
{
// Find the next unprocessed slice
if (oz[ii] != -1)
{
final int slice = oz[ii];
// Extract all the points from this slice
int[] x = new int[remaining];
int[] y = new int[remaining];
int count = 0;
for (int j = ii; j < ox.length; j++)
{
if (oz[j] == slice)
{
x[count] = ox[j];
y[count] = oy[j];
count++;
oz[j] = -1; // Mark processed
}
}
x = Arrays.copyOf(x, count);
y = Arrays.copyOf(y, count);
PointRoi roi = new PointRoi(x, y, count);
if (isHyperStack)
roi.setPosition(c, slice, f);
else
roi.setPosition(slice);
roi.setShowLabels(false);
overlay.add(roi);
remaining -= count;
}
}
overlay.setStrokeColor(Color.cyan);
return overlay;
}
private static boolean validMask(ImagePlus imp, ImagePlus mask)
{
return mask != null && mask.getWidth() == imp.getWidth() && mask.getHeight() == imp.getHeight() &&
(mask.getNSlices() == imp.getNSlices() || mask.getStackSize() == 1);
}
private void sortResults(ArrayList<Result> results, int sortMethod)
{
if (sortMethod != SORT_NONE)
{
final int sortIndex = getSortIndex(sortMethod);
ResultComparator c = new ResultComparator(sortIndex);
sortAndAssignRank(results, sortIndex, c);
}
}
private int getSortIndex(int sortMethod)
{
// Most of the sort methods correspond to the first items of the metrics array
if (sortMethod <= SORT_JACCARD)
return sortMethod - 1;
// Process special cases
switch (sortMethod)
{
case SORT_RMSD:
return Result.RMSD;
}
// This is an error
return -1;
}
private void sortResultsByScore(ArrayList<Result> results, boolean lowestFirst)
{
final int sortIndex = Result.SCORE;
ResultComparator c = new ResultComparator(sortIndex, lowestFirst);
sortAndAssignRank(results, sortIndex, c);
}
public void sortAndAssignRank(ArrayList<Result> results, int sortIndex, ResultComparator c)
{
Collections.sort(results, c);
// Cannot assign a rank if we have not sorted
int rank = 1;
int count = 0;
double score = results.get(0).metrics[sortIndex];
for (Result r : results)
{
if (score != r.metrics[sortIndex])
{
rank += count;
count = 0;
score = r.metrics[sortIndex];
}
r.metrics[Result.RANK] = rank;
count++;
}
}
private void sortResultsById(ArrayList<Result> results)
{
Collections.sort(results);
}
private class Result implements Comparable<Result>
{
public static final int PRECISION = 0;
public static final int RECALL = 1;
public static final int F05 = 2;
public static final int F1 = 3;
public static final int F2 = 4;
public static final int Fb = 5;
public static final int JACCARD = 6;
public static final int RANK = 7;
public static final int SCORE = 8;
public static final int RMSD = 9;
public int id;
public Options options;
public String parameters;
public int n, tp, fp, fn;
public long time;
public double[] metrics = new double[10];
public Result(int id, Options options, int n, int tp, int fp, int fn, long time, double beta, double rmsd)
{
this.id = id;
this.options = options;
if (options != null)
this.parameters = options.createParameters();
this.n = n;
this.tp = tp;
this.fp = fp;
this.fn = fn;
this.time = time;
if (tp + fp > 0)
{
metrics[PRECISION] = (double) tp / (tp + fp);
}
if (tp + fn > 0)
{
metrics[RECALL] = (double) tp / (tp + fn);
}
if (tp + fp + fn > 0)
{
metrics[JACCARD] = (double) tp / (tp + fp + fn);
}
metrics[F05] = calculateFScore(metrics[PRECISION], metrics[RECALL], 0.5);
metrics[F1] = calculateFScore(metrics[PRECISION], metrics[RECALL], 1.0);
metrics[F2] = calculateFScore(metrics[PRECISION], metrics[RECALL], 2.0);
metrics[Fb] = calculateFScore(metrics[PRECISION], metrics[RECALL], beta);
metrics[RMSD] = rmsd;
}
/**
* Add the values stored in the given result to the current values
*
* @param result
*/
public void add(Result result)
{
// Create a new RMSD
// rmsd = Math.sqrt(sd / tp);
double sd1 = metrics[RMSD] * metrics[RMSD] * tp;
double sd2 = result.metrics[RMSD] * result.metrics[RMSD] * result.tp;
metrics[RMSD] = Math.sqrt((sd1 + sd2) / (tp + result.tp));
// Combine all other metrics
n += result.n;
tp += result.tp;
fp += result.fp;
fn += result.fn;
time += result.time;
for (int i = 0; i < metrics.length - 1; i++)
metrics[i] += result.metrics[i];
}
public String getParameters()
{
return parameters;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Result paramT)
{
return id - paramT.id;
}
}
/**
* Provides the ability to sort the results arrays in ascending order
*/
private class ResultComparator implements Comparator<Result>
{
private final int sortIndex;
private final int tieIndex;
private final int sortRank;
private final int tieRank;
// Set this to zero to not perform a comparison of the result parameter options
private int tieMethod = 1;
public ResultComparator(int sortIndex, boolean lowestFirst)
{
this.sortIndex = sortIndex;
if (sortIndex == Result.RMSD)
tieIndex = Result.JACCARD;
else
tieIndex = Result.RMSD;
sortRank = (lowestFirst) ? 1 : -1;
tieRank = getRankOfHighest(tieIndex);
}
public ResultComparator(int sortIndex)
{
this.sortIndex = sortIndex;
if (sortIndex == Result.RMSD)
tieIndex = Result.JACCARD;
else
tieIndex = Result.RMSD;
sortRank = getRankOfHighest(sortIndex);
tieRank = getRankOfHighest(tieIndex);
}
private int getRankOfHighest(int index)
{
switch (index)
{
case Result.RANK:
case Result.RMSD:
// Highest last
return 1;
}
// Highest first
return -1;
}
/*
* (non-Javadoc)
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Result o1, Result o2)
{
// Require the highest is first
if (o1.metrics[sortIndex] > o2.metrics[sortIndex])
return sortRank;
if (o1.metrics[sortIndex] < o2.metrics[sortIndex])
return -sortRank;
// Compare using the tie index
if (o1.metrics[tieIndex] > o2.metrics[tieIndex])
return tieRank;
if (o1.metrics[tieIndex] < o2.metrics[tieIndex])
return -tieRank;
if (tieMethod == 1 && o1.options != null && o2.options != null)
{
// Return method with most conservative settings
int[] result = new int[1];
if (compare(o1.options.blur, o2.options.blur, result) != 0)
return result[0];
// Higher background methods are more general
if (compare(o1.options.backgroundMethod, o2.options.backgroundMethod, result) != 0)
return -result[0];
if (compare(o1.options.backgroundParameter, o2.options.backgroundParameter, result) != 0)
return result[0];
// Smallest size is more general
if (compare(o1.options.minSize, o2.options.minSize, result) != 0)
return result[0];
// Lower search methods are more general
if (compare(o1.options.searchMethod, o2.options.searchMethod, result) != 0)
return result[0];
if (compare(o1.options.searchParameter, o2.options.searchParameter, result) != 0)
return result[0];
// Higher peak methods are more general
if (compare(o1.options.peakMethod, o2.options.peakMethod, result) != 0)
return -result[0];
if (compare(o1.options.peakParameter, o2.options.peakParameter, result) != 0)
return result[0];
}
// Return fastest method
if (o1.time < o2.time)
return -1;
if (o1.time > o2.time)
return 1;
System.out.println("Sort error");
return 0;
}
private int compare(double value1, double value2, int[] result)
{
if (value1 < value2)
result[0] = -1;
else if (value1 > value2)
result[0] = 1;
else
result[0] = 0;
return result[0];
}
private int compare(int value1, int value2, int[] result)
{
return result[0] = value1 - value2;
}
}
private class Options
{
private String parameters = null;
public double blur;
public int backgroundMethod;
public double backgroundParameter;
public String autoThresholdMethod;
public int searchMethod;
public double searchParameter;
public int maxPeaks;
public int minSize;
public int peakMethod;
public double peakParameter;
public int sortIndex;
public int options;
public int centreMethod;
public double centreParameter;
public Options(double blur, int backgroundMethod, double backgroundParameter, String autoThresholdMethod,
int searchMethod, double searchParameter, int maxPeaks, int minSize, int peakMethod,
double peakParameter, int sortIndex, int options, int centreMethod, double centreParameter)
{
this.blur = blur;
this.backgroundMethod = backgroundMethod;
this.backgroundParameter = backgroundParameter;
this.autoThresholdMethod = autoThresholdMethod;
this.searchMethod = searchMethod;
this.searchParameter = searchParameter;
this.maxPeaks = maxPeaks;
this.minSize = minSize;
this.peakMethod = peakMethod;
this.peakParameter = peakParameter;
this.sortIndex = sortIndex;
this.options = options;
this.centreMethod = centreMethod;
this.centreParameter = centreParameter;
}
/**
* @return the string representation of the parameters (the value is computed once and cached)
*/
public String createParameters()
{
if (parameters == null)
{
parameters = FindFociOptimiser.createParameters(blur, backgroundMethod, autoThresholdMethod,
backgroundParameter, maxPeaks, minSize, searchMethod, searchParameter, peakMethod,
peakParameter, sortIndex, options, centreMethod, centreParameter);
}
return parameters;
}
}
private abstract class Counter
{
final int total;
final int progressSize;
int last = 0;
public Counter(int total)
{
this.total = total;
this.progressSize = Math.max(1, total / 400);
}
public void increment()
{
if (incrementAndGet() > last)
{
// Don't show progress all the time
last = get() + progressSize;
IJ.showProgress(get(), total);
}
}
public void increment(int delta)
{
if (addAndGet(delta) > last)
{
// Don't show progress all the time
last = get() + progressSize;
IJ.showProgress(get(), total);
}
}
protected abstract int incrementAndGet();
protected abstract int addAndGet(int delta);
protected abstract int get();
}
private class StandardCounter extends Counter
{
int step = 0;
public StandardCounter(int total)
{
super(total);
}
@Override
protected int incrementAndGet()
{
return ++step;
}
@Override
protected int addAndGet(int delta)
{
return step += delta;
}
@Override
protected int get()
{
return step;
}
}
private class SynchronizedCounter extends Counter
{
AtomicInteger step = new AtomicInteger();
public SynchronizedCounter(int total)
{
super(total);
}
@Override
protected int incrementAndGet()
{
return step.incrementAndGet();
}
@Override
protected int addAndGet(int delta)
{
return step.addAndGet(delta);
}
@Override
protected int get()
{
return step.get();
}
}
public class OptimisationWorker implements Runnable
{
String image;
Counter counter;
OptimiserResult result = null;
public OptimisationWorker(String image, Counter counter)
{
this.image = image;
this.counter = counter;
}
public void run()
{
if (IJ.escapePressed())
return;
final ImagePlus imp = FindFoci.openImage(inputDirectory, image);
// The input filename may not be an image
if (imp == null)
{
IJ.log("Skipping file (it may not be an image): " + inputDirectory + image);
// We can skip forward in the progress
counter.increment(combinations);
return;
}
String[] maskPath = FindFoci.getMaskImage(inputDirectory, maskDirectory, image);
final ImagePlus mask = FindFoci.openImage(maskPath[0], maskPath[1]);
final String resultFile = outputDirectory + imp.getShortTitle();
final String fullResultFile = resultFile + ".results.xls";
boolean newResults = false;
if (reuseResults && new File(fullResultFile).exists())
{
ArrayList<Result> results = loadResults(fullResultFile);
if (results != null && results.size() == combinations)
{
IJ.log("Re-using results: " + fullResultFile);
// We can skip forward in the progress
counter.increment(combinations);
result = new OptimiserResult(results, 0);
}
}
if (result == null)
{
IJ.log("Creating results: " + fullResultFile);
newResults = true;
result = runOptimiser(imp, mask, counter);
}
if (result != null)
{
if (newResults)
{
saveResults(imp, mask, result.results, null, resultFile);
}
checkOptimisationSpace(result, imp);
// Reset to the order defined by the ID
sortResultsById(result.results);
}
}
}
private static Pattern p = Pattern.compile("\t");
/**
* Load the results from the specified file. We assign an arbitrary ID to each result using the unique combination
* of parameters.
*
* @param filename
* @return The results
*/
private ArrayList<Result> loadResults(String filename)
{
ArrayList<Result> results = new ArrayList<FindFociOptimiser.Result>();
BufferedReader input = null;
try
{
if (countLines(filename) != combinations)
return null;
FileInputStream fis = new FileInputStream(filename);
input = new BufferedReader(new InputStreamReader(fis));
String line;
boolean isRMSD = false;
while ((line = input.readLine()) != null)
{
if (line.length() == 0)
continue;
if (line.charAt(0) == '#')
{
// Look for the RMSD field which was added later. This supports older results files
if (line.contains("RMSD"))
isRMSD = true;
continue;
}
// Code using split and parse
// # Rank Blur Background method Max Min Search method Peak method Sort method Centre method N TP FP FN Jaccard Precision Recall F0.5 F1 F2 F-beta RMSD mSec
int endIndex = getIndex(line, 8) + 1; // include the final tab
String parameters = line.substring(line.indexOf('\t') + 1, endIndex);
String metrics = line.substring(endIndex);
String[] fields = p.split(metrics);
// Items we require
int id = getId(parameters);
int n = Integer.parseInt(fields[0]);
int tp = Integer.parseInt(fields[1]);
int fp = Integer.parseInt(fields[2]);
int fn = Integer.parseInt(fields[3]);
double rmsd = 0;
if (isRMSD)
rmsd = Double.parseDouble(fields[fields.length - 2]);
long time = Long.parseLong(fields[fields.length - 1]);
Result r = new Result(id, null, n, tp, fp, fn, time, myBeta, rmsd);
// Do not count on the Options being parsed from the parameters.
r.parameters = parameters;
r.options = optionsMap.get(id);
results.add(r);
}
// If the results were loaded then we must sort them to get a rank
sortResults(results, myResultsSortMethod);
}
catch (ArrayIndexOutOfBoundsException e)
{
return null;
}
catch (IOException e)
{
return null;
}
catch (NumberFormatException e)
{
return null;
}
finally
{
try
{
if (input != null)
input.close();
}
catch (IOException e)
{
// Ignore
}
}
return results;
}
/**
* Count the number of valid lines in the file
*
* @param filename
* @return The number of lines
*/
private int countLines(String filename)
{
BufferedReader input = null;
try
{
int count = 0;
FileInputStream fis = new FileInputStream(filename);
input = new BufferedReader(new InputStreamReader(fis));
String line;
while ((line = input.readLine()) != null)
{
if (line.length() == 0)
continue;
if (line.charAt(0) == '#')
continue;
count++;
}
return count;
}
catch (IOException e)
{
return 0;
}
finally
{
try
{
if (input != null)
input.close();
}
catch (IOException e)
{
// Ignore
}
}
}
/**
* Get the index of the nth occurrence of the tab character
*
* @param line
* @param n
* @return
*/
private int getIndex(String line, int n)
{
char[] value = line.toCharArray();
for (int i = 0; i < value.length; i++)
{
if (value[i] == '\t')
{
if (n-- <= 0)
return i;
}
}
return -1;
}
private HashMap<String, Integer> idMap = new HashMap<String, Integer>();
private HashMap<Integer, Options> optionsMap = new HashMap<Integer, Options>();
private int nextId = 1;
/**
* Get a unique ID for the parameters string
*
* @param parameters
* @return the ID
*/
private int getId(String parameters)
{
Integer i = idMap.get(parameters);
if (i == null)
{
i = createId(parameters);
}
return i;
}
/**
* Create a unique ID for the parameters string
*
* @param parameters
* @return the ID
*/
private synchronized Integer createId(String parameters)
{
// Check again in case another thread just created it
Integer i = idMap.get(parameters);
if (i == null)
{
i = new Integer(nextId++);
// Ensure we have options for every ID
optionsMap.put(i, createOptions(parameters));
idMap.put(parameters, i);
}
return i;
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e)
{
// Show the result that was double clicked in the result table
if (e.getClickCount() > 1)
{
// Double-click on the multi-mode dialog text fields
if (e.getSource() instanceof TextField)
{
TextField tf = (TextField) e.getSource();
String path = tf.getText();
final boolean recording = Recorder.record;
Recorder.record = false;
path = Utils.getDirectory("Choose_a_directory", path);
Recorder.record = recording;
if (path != null)
tf.setText(path);
}
// Double-click on the result table
else if (lastImp != null && lastImp.isVisible())
{
// An extra line is added at the end of the results so remove this
int rank = resultsWindow.getTextPanel().getLineCount() -
resultsWindow.getTextPanel().getSelectionStart() - 1;
// Show the result that was double clicked. Results are stored in reverse order.
if (rank > 0 && rank <= lastResults.size())
{
showResult(lastImp, lastMask, lastResults.get(rank - 1).options);
}
}
}
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
private void showOptimiserWindow()
{
if (instance != null)
{
showInstance();
return;
}
IJ.showStatus("Initialising FindFoci Optimiser ...");
String errorMessage = null;
Throwable exception = null;
try
{
Class.forName("org.jdesktop.beansbinding.Property", false, this.getClass().getClassLoader());
// it exists on the classpath
instance = new OptimiserView();
instance.addWindowListener(this);
instance.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
IJ.register(OptimiserView.class);
showInstance();
IJ.showStatus("FindFoci Optimiser ready");
}
catch (ExceptionInInitializerError e)
{
exception = e;
errorMessage = "Failed to initialize class: " + e.getMessage();
}
catch (LinkageError e)
{
exception = e;
errorMessage = "Failed to link class: " + e.getMessage();
}
catch (ClassNotFoundException ex)
{
exception = ex;
errorMessage = "Failed to find class: " + ex.getMessage() +
"\nCheck you have beansbinding-1.2.1.jar on your classpath\n";
}
catch (Throwable ex)
{
exception = ex;
errorMessage = ex.getMessage();
}
finally
{
if (exception != null)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.write(errorMessage);
pw.append('\n');
exception.printStackTrace(pw);
IJ.log(sw.toString());
}
}
}
private void showInstance()
{
WindowManager.addWindow(instance);
instance.setVisible(true);
instance.toFront();
}
public void windowOpened(WindowEvent e)
{
}
public void windowClosing(WindowEvent e)
{
WindowManager.removeWindow(instance);
}
public void windowClosed(WindowEvent e)
{
}
public void windowIconified(WindowEvent e)
{
}
public void windowDeiconified(WindowEvent e)
{
}
public void windowActivated(WindowEvent e)
{
}
public void windowDeactivated(WindowEvent e)
{
}
// ---------------------------------------------------------------------------
// Start preset values.
// The following code is for setting the dialog fields with preset values
// ---------------------------------------------------------------------------
private class DialogSettings
{
String name;
ArrayList<String> text = new ArrayList<String>();
ArrayList<Boolean> option = new ArrayList<Boolean>();
public DialogSettings(String name)
{
this.name = name;
}
}
private String[] SETTINGS;
private ArrayList<DialogSettings> settings;
private boolean updating = false;
private long lastTime = 0;
private boolean custom = true;
// Store the preset values for the Text fields, Choices, Numeric field.
// Preceed with a '-' character if the field is for single mode only.
//@formatter:off
private String[][] textPreset = new String[][] { { "Testing", // preset name
// Text fields
"3", // Background_parameter
AutoThreshold.Method.OTSU.name, // Auto_threshold
"Both", // Statistics_mode
"0, 0.6, 0.2", // Search_parameter
"3, 9, 2", // Minimum_size
"0, 0.6, 0.2", // Peak_parameter
"1", // Sort_method
"1", // Gaussian_blur
"0", // Centre_method
"2", // Centre_parameter
// Choices
"-", // Mask
"Yes", // Minimum_above_saddle
"Relative above background", // Minimum_peak_height
"Relative", // Match_search_method
"Jaccard", // Result_sort_method
// Numeric fields
"500", // Maximum_peaks
"0.05", // Match_search_distance
"4.0", // F-beta
"100", // Maximum_results
"10000", // Step_limit
}, { "Default", // preset name
// Text fields
"2.5, 3.5, 0.5", // Background_parameter
AutoThreshold.Method.OTSU.name, // Auto_threshold
"Both", // Statistics_mode
"0, 0.6, 0.2", // Search_parameter
"1, 9, 2", // Minimum_size
"0, 0.6, 0.2", // Peak_parameter
"1", // Sort_method
"0, 0.5, 1", // Gaussian_blur
"0", // Centre_method
"2", // Centre_parameter
// Choices
"-", // Mask
"Yes", // Minimum_above_saddle
"Relative above background", // Minimum_peak_height
"Relative", // Match_search_method
"Jaccard", // Result_sort_method
// Numeric fields
"500", // Maximum_peaks
"0.05", // Match_search_distance
"4.0", // F-beta
"100", // Maximum_results
"10000", // Step_limit
}, { "Benchmark", // preset name
// Text fields
"0, 4.7, 0.667", // Background_parameter
AutoThreshold.Method.OTSU.name + ", "+AutoThreshold.Method.RENYI_ENTROPY.name+
", "+AutoThreshold.Method.TRIANGLE.name, // Auto_threshold
"Both", // Statistics_mode
"0, 0.8, 0.1", // Search_parameter
"1, 9, 2", // Minimum_size
"0, 0.8, 0.1", // Peak_parameter
"1", // Sort_method
"0, 0.5, 1, 2", // Gaussian_blur
"0", // Centre_method
"2", // Centre_parameter
// Choices
"-", // Mask
"Yes", // Minimum_above_saddle
"Relative above background", // Minimum_peak_height
"Relative", // Match_search_method
"Jaccard", // Result_sort_method
// Numeric fields
"500", // Maximum_peaks
"0.05", // Match_search_distance
"4.0", // F-beta
"100", // Maximum_results
"30000", // Step_limit
} };
// Store the preset values for the Checkboxes.
// Use int so that the flags can be checked if they are for single mode only.
private final int FLAG_FALSE = 0;
private final int FLAG_TRUE = 1;
private final int FLAG_SINGLE = 2;
private int[][] optionPreset = new int[][] { { FLAG_FALSE, // Background_SD_above_mean
FLAG_FALSE, // Background_Absolute
FLAG_TRUE, // Background_Auto_Threshold
FLAG_TRUE, // Search_above_background
FLAG_FALSE, // Search_fraction_of_peak
FLAG_FALSE + FLAG_SINGLE // Show_score_images
}, { FLAG_TRUE, // Background_SD_above_mean
FLAG_FALSE, // Background_Absolute
FLAG_TRUE, // Background_Auto_Threshold
FLAG_TRUE, // Search_above_background
FLAG_TRUE, // Search_fraction_of_peak
FLAG_FALSE + FLAG_SINGLE // Show_score_images
}, { FLAG_TRUE, // Background_SD_above_mean
FLAG_FALSE, // Background_Absolute
FLAG_TRUE, // Background_Auto_Threshold
FLAG_TRUE, // Search_above_background
FLAG_TRUE, // Search_fraction_of_peak
FLAG_FALSE + FLAG_SINGLE // Show_score_images
} };
//@formatter:on
private void createSettings()
{
settings = new ArrayList<FindFociOptimiser.DialogSettings>();
settings.add(new DialogSettings("Custom"));
for (int i = 0; i < textPreset.length; i++)
{
// First field is the name
DialogSettings s = new DialogSettings(textPreset[i][0]);
// We only need the rest of the settings if there is a dialog
if (!java.awt.GraphicsEnvironment.isHeadless())
{
for (int j = 1; j < textPreset[i].length; j++)
{
if (textPreset[i][j].startsWith("-"))
{
if (multiMode)
continue;
textPreset[i][j] = textPreset[i][j].substring(1);
}
s.text.add(textPreset[i][j]);
}
for (int j = 0; j < optionPreset[i].length; j++)
{
if (multiMode && (optionPreset[i][j] & FLAG_SINGLE) != 0)
{
continue;
}
s.option.add((optionPreset[i][j] & FLAG_TRUE) != 0);
}
}
settings.add(s);
}
SETTINGS = new String[settings.size()];
for (int i = 0; i < settings.size(); i++)
SETTINGS[i] = settings.get(i).name;
}
/**
* Add our own custom listeners to the dialog. If we use dialogListerner in the GenericDialog then it turns the
* macro recorder off before we read the fields.
*
* @param gd
*/
@SuppressWarnings("unchecked")
private void addListeners(GenericDialog gd)
{
listenerGd = gd;
Vector<TextField> fields = (Vector<TextField>) gd.getStringFields();
// Optionally Ignore final text field (it is the result file field)
int stringFields = fields.size() - ((multiMode) ? 0 : 1);
for (int i = 0; i < stringFields; i++)
fields.get(i).addTextListener(this);
for (Choice field : (Vector<Choice>) gd.getChoices())
field.addItemListener(this);
for (TextField field : (Vector<TextField>) gd.getNumericFields())
field.addTextListener(this);
for (Checkbox field : (Vector<Checkbox>) gd.getCheckboxes())
field.addItemListener(this);
}
@SuppressWarnings("unchecked")
private void saveCustomSettings(GenericDialog gd)
{
DialogSettings s = settings.get(0);
s.text.clear();
s.option.clear();
Vector<TextField> fields = (Vector<TextField>) gd.getStringFields();
// Optionally Ignore final text field (it is the result file field)
int stringFields = fields.size() - ((multiMode) ? 0 : 1);
for (int i = 0; i < stringFields; i++)
s.text.add(fields.get(i).getText());
// The first choice is the settings name which we ignore
Vector<Choice> cfields = (Vector<Choice>) gd.getChoices();
for (int i = 1; i < cfields.size(); i++)
s.text.add(cfields.get(i).getSelectedItem());
for (TextField field : (Vector<TextField>) gd.getNumericFields())
s.text.add(field.getText());
for (Checkbox field : (Vector<Checkbox>) gd.getCheckboxes())
s.option.add(field.getState());
}
@SuppressWarnings("unchecked")
private void applySettings(GenericDialog gd, DialogSettings s)
{
//System.out.println("Applying " + s.name + " " + updating);
lastTime = System.currentTimeMillis();
int index = 0, index2 = 0;
Vector<TextField> fields = (Vector<TextField>) gd.getStringFields();
// Optionally Ignore final text field (it is the result file field)
int stringFields = fields.size() - ((multiMode) ? 0 : 1);
for (int i = 0; i < stringFields; i++)
fields.get(i).setText(s.text.get(index++));
// The first choice is the settings name
Vector<Choice> cfields = (Vector<Choice>) gd.getChoices();
cfields.get(0).select(s.name);
for (int i = 1; i < cfields.size(); i++)
cfields.get(i).select(s.text.get(index++));
for (TextField field : (Vector<TextField>) gd.getNumericFields())
field.setText(s.text.get(index++));
for (Checkbox field : (Vector<Checkbox>) gd.getCheckboxes())
field.setState(s.option.get(index2++));
//System.out.println("Done Applying " + s.name + " " + updating);
}
public void actionPerformed(ActionEvent e)
{
dialogItemChanged(listenerGd, e);
}
public void textValueChanged(TextEvent e)
{
dialogItemChanged(listenerGd, e);
}
public void itemStateChanged(ItemEvent e)
{
dialogItemChanged(listenerGd, e);
}
/*
* (non-Javadoc)
*
* @see ij.gui.DialogListener#dialogItemChanged(ij.gui.GenericDialog, java.awt.AWTEvent)
*/
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e)
{
if (e == null || e.getSource() == null || !aquireLock())
return true;
//System.out.println("changed " + e.getSource());
// Check if this is the settings checkbox
if (e.getSource() == choice.get(0))
{
Choice thisChoice = (Choice) choice.get(0);
// If the choice is currently custom save the current values so they can be restored
if (custom)
saveCustomSettings(gd);
// Update the other fields with preset values
int index = thisChoice.getSelectedIndex();
if (index != 0)
custom = false;
applySettings(gd, settings.get(index));
}
else
{
// This is a change to another field. Note that the dialogItemChanged method is called
// for each field modified in applySettings. This appears to happen after the applySettings
// method has ended (as if the dialogItemChanged events are in a queue or are delayed until
// the previous call to dialogItemChanged has ended).
// To prevent processing these events ignore anything that happens within x milliseconds
// of the call to applySettings
if (System.currentTimeMillis() - lastTime > 300)
{
// A change to any other field makes this a custom setting
// => Set the settings drop-down to custom
Choice thisChoice = (Choice) choice.get(0);
if (thisChoice.getSelectedIndex() != 0)
{
custom = true;
thisChoice.select(0);
}
// Esnure that checkboxes 1 & 2 are complementary
if (e.getSource() instanceof Checkbox)
{
Checkbox cb = (Checkbox) e.getSource();
// If just checked then we must uncheck the complementing checkbox
if (cb.getState())
{
// Only checkbox 1 & 2 are complementary
if (cb.equals(checkbox.get(0)))
{
((Checkbox) checkbox.get(1)).setState(false);
}
else if (cb.equals(checkbox.get(1)))
{
((Checkbox) checkbox.get(0)).setState(false);
}
}
}
}
}
updating = false;
return true;
}
private synchronized boolean aquireLock()
{
if (updating)
return false;
updating = true;
return true;
}
// ---------------------------------------------------------------------------
// End preset values
// ---------------------------------------------------------------------------
}