package gdsc.smlm.ij.plugins;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import gdsc.core.ij.BufferedTextWindow;
import gdsc.core.ij.Utils;
import gdsc.core.threshold.AutoThreshold;
import gdsc.core.utils.Maths;
import gdsc.core.utils.NoiseEstimator;
import gdsc.core.utils.Statistics;
import gdsc.smlm.engine.DataEstimator;
import gdsc.smlm.ij.settings.SettingsManager;
import gdsc.smlm.ij.utils.ImageConverter;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.gui.NonBlockingGenericDialog;
import ij.gui.Plot2;
import ij.gui.PlotWindow;
import ij.plugin.WindowOrganiser;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.process.ImageProcessor;
import ij.text.TextWindow;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2016 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 3 of the License, or
* (at your option) any later version.
*---------------------------------------------------------------------------*/
/**
* Contains methods to find the noise in the provided image data.
*/
public class BackgroundEstimator implements ExtendedPlugInFilter, DialogListener
{
private static final String TITLE = "Background Estimator";
private List<double[]> results;
private final int FLAGS = DOES_8G | DOES_16 | DOES_32 | PARALLELIZE_STACKS | FINAL_PROCESSING | NO_CHANGES;
private PlugInFilterRunner pfr;
private ImagePlus imp;
private GenericDialog gd;
private static double percentile;
private static NoiseEstimator.Method noiseMethod = NoiseEstimator.Method.QUICK_RESIDUALS_LEAST_TRIMMED_OF_SQUARES;
private static AutoThreshold.Method thresholdMethod = AutoThreshold.Method.DEFAULT;
private static float fraction;
private static int histogramSize;
static
{
DataEstimator de = new DataEstimator(new float[0], 0, 0);
fraction = de.getFraction();
histogramSize = de.getHistogramSize();
}
public int setup(String arg, ImagePlus imp)
{
if (arg.equalsIgnoreCase("final"))
{
showResults();
return DONE;
}
SMLMUsageTracker.recordPlugin(this.getClass(), arg);
if (imp == null)
{
IJ.noImage();
return DONE;
}
this.imp = imp;
results = Collections.synchronizedList(new ArrayList<double[]>(imp.getStackSize()));
return FLAGS;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr)
{
// If using a stack, provide a preview graph of the noise for two methods
if (imp.getStackSize() > 1)
{
this.pfr = pfr;
drawPlot();
gd = new NonBlockingGenericDialog(TITLE);
gd.addHelp(About.HELP_URL);
gd.addSlider("Percential", 0, 100, percentile);
String[] noiseMethods = SettingsManager.getNames((Object[]) NoiseEstimator.Method.values());
gd.addChoice("Noise_method", noiseMethods, noiseMethods[noiseMethod.ordinal()]);
// For background based on pixel below a threshold
String[] thresholdMethods = AutoThreshold.getMethods(true);
gd.addChoice("Threshold_method", thresholdMethods, thresholdMethods[thresholdMethod.ordinal() - 1]);
gd.addSlider("Fraction", 0, 0.999, fraction);
gd.addNumericField("Histogram_size", histogramSize, 0);
gd.addDialogListener(this);
gd.addMessage("Click OK to compute table for all slices");
gd.showDialog();
if (gd.wasCanceled() || !dialogItemChanged(gd, null))
return DONE;
}
return IJ.setupDialog(imp, FLAGS);
}
/*
* (non-Javadoc)
*
* @see ij.gui.DialogListener#dialogItemChanged(ij.gui.GenericDialog, java.awt.AWTEvent)
*/
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e)
{
percentile = gd.getNextNumber();
noiseMethod = NoiseEstimator.Method.values()[gd.getNextChoiceIndex()];
thresholdMethod = AutoThreshold.getMethod(gd.getNextChoiceIndex(), true);
fraction = (float) gd.getNextNumber();
histogramSize = (int) gd.getNextNumber();
if (gd.isShowing())
drawPlot();
return true;
}
/**
* Build a plot of the noise estimate from the current frame.
* Limit the preview to 100 frames.
*/
private void drawPlot()
{
IJ.showStatus("Estimating background ...");
int start = imp.getCurrentSlice();
int end = FastMath.min(imp.getStackSize(), start + 100);
int size = end - start + 1;
double[] xValues = new double[size];
double[] noise1 = new double[size];
double[] noise2 = new double[size];
double[] background = new double[size];
double[] threshold = new double[size];
double[] percentile = new double[size];
ImageStack stack = imp.getImageStack();
Rectangle bounds = imp.getProcessor().getRoi();
float[] buffer = null;
for (int slice = start, i = 0; slice <= end; slice++, i++)
{
IJ.showProgress(i, size);
final ImageProcessor ip = stack.getProcessor(slice);
buffer = ImageConverter.getData(ip.getPixels(), ip.getWidth(), ip.getHeight(), bounds, buffer);
final DataEstimator de = new DataEstimator(buffer, bounds.width, bounds.height);
de.setFraction(fraction);
de.setHistogramSize(histogramSize);
de.setThresholdMethod(thresholdMethod);
xValues[i] = slice;
try
{
noise1[i] = de.getNoise();
noise2[i] = de.getNoise(noiseMethod);
background[i] = de.getBackground();
threshold[i] = de.getThreshold();
percentile[i] = de.getPercentile(BackgroundEstimator.percentile);
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
}
IJ.showProgress(1);
IJ.showStatus("Plotting background ...");
WindowOrganiser wo = new WindowOrganiser();
plot(wo, xValues, noise1, noise2, null, "Noise", "Background Noise", "Global Noise", null);
plot(wo, xValues, background, threshold, percentile, "Background", "Background", "Threshold", "Percentile");
wo.tile();
IJ.showStatus("");
}
private void plot(WindowOrganiser wo, double[] xValues, double[] data1, double[] data2, double[] data3,
String title, String title1, String title2, String title3)
{
// Get limits
double[] a = Maths.limits(xValues);
double[] b = Maths.limits(data1);
b = Maths.limits(b, data2);
if (data3 != null)
b = Maths.limits(b, data3);
title = imp.getTitle() + " " + title;
Plot2 plot = new Plot2(title, "Slice", title);
double range = b[1] - b[0];
if (range == 0)
range = 1;
plot.setLimits(a[0], a[1], b[0] - 0.05 * range, b[1] + 0.05 * range);
plot.setColor(Color.blue);
plot.addPoints(xValues, data1, Plot2.LINE);
plot.draw();
Statistics stats = new Statistics(data1);
String label = String.format("%s (Blue) = %s +/- %s", title1, Utils.rounded(stats.getMean()),
Utils.rounded(stats.getStandardDeviation()));
plot.setColor(Color.red);
plot.addPoints(xValues, data2, Plot2.LINE);
stats = new Statistics(data2);
label += String.format(", %s (Red) = %s +/- %s", title2, Utils.rounded(stats.getMean()),
Utils.rounded(stats.getStandardDeviation()));
if (data3 != null)
{
plot.setColor(Color.green);
plot.addPoints(xValues, data3, Plot2.LINE);
stats = new Statistics(data3);
label += String.format(", %s (Green) = %s +/- %s", title3, Utils.rounded(stats.getMean()),
Utils.rounded(stats.getStandardDeviation()));
}
plot.setColor(Color.black);
plot.addLabel(0, 0, label);
PlotWindow pw = Utils.display(title, plot);
if (Utils.isNewWindow())
wo.add(pw);
}
/*
* (non-Javadoc)
*
* @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)
*/
public void run(ImageProcessor ip)
{
// Perform all methods and add to the results
double[] result = new double[8];
int i = 0;
result[i++] = (pfr == null) ? 1 : pfr.getSliceNumber();
Rectangle bounds = ip.getRoi();
float[] buffer = ImageConverter.getData(ip.getPixels(), ip.getWidth(), ip.getHeight(), bounds, null);
final DataEstimator de = new DataEstimator(buffer, bounds.width, bounds.height);
de.setFraction(fraction);
de.setHistogramSize(histogramSize);
de.setThresholdMethod(thresholdMethod);
result[i++] = de.isBackgroundRegion() ? 1 : 0;
result[i++] = de.getNoise();
result[i++] = de.getNoise(noiseMethod);
result[i++] = de.getBackground();
result[i++] = de.getThreshold();
result[i++] = de.getBackgroundSize();
result[i++] = de.getPercentile(percentile);
results.add(result);
}
/*
* (non-Javadoc)
*
* @see ij.plugin.filter.ExtendedPlugInFilter#setNPasses(int)
*/
public void setNPasses(int nPasses)
{
// Do nothing
}
private void showResults()
{
Collections.sort(results, new Comparator<double[]>()
{
public int compare(double[] o1, double[] o2)
{
// Sort on slice number
return (o1[0] < o2[0]) ? -1 : 1;
}
});
BufferedTextWindow tw = new BufferedTextWindow(
new TextWindow(imp.getTitle() + " Background", createHeader(), "", 800, 400));
for (double[] result : results)
tw.append(createResult(result));
tw.flush();
}
private String createHeader()
{
StringBuilder sb = new StringBuilder(imp.getTitle());
sb.append("\t").append(Utils.rounded(percentile));
sb.append("\t").append(noiseMethod.toString());
sb.append("\t").append(thresholdMethod.toString());
sb.append("\t").append(Utils.rounded(fraction));
sb.append("\t").append(histogramSize).append("\t");
prefix = sb.toString();
sb = new StringBuilder("Image");
sb.append("\tPercentile");
sb.append("\tNoiseMethod");
sb.append("\tThresholdMethod");
sb.append("\tFraction");
sb.append("\tHistogramSize");
sb.append("\tSlice");
sb.append("\tIsBackground");
sb.append("\tNoise");
sb.append("\tGlobalNoise");
sb.append("\tBackground");
sb.append("\tThreshold");
sb.append("\tBackgroundSize");
sb.append("\tPercentile");
return sb.toString();
}
private String prefix;
private String createResult(double[] result)
{
StringBuilder sb = new StringBuilder(prefix);
sb.append((int) result[0]);
for (int i = 1; i < result.length; i++)
{
sb.append("\t").append(Utils.rounded(result[i]));
}
return sb.toString();
}
}