package gdsc.smlm.ij.plugins;
import gdsc.smlm.ij.settings.SettingsManager;
import gdsc.smlm.ij.utils.ImageConverter;
import gdsc.core.ij.Utils;
import gdsc.core.utils.NoiseEstimator;
import gdsc.core.utils.Statistics;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.gui.Plot2;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.process.ImageProcessor;
import ij.text.TextWindow;
import ij.util.Tools;
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;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2013 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 Noise implements ExtendedPlugInFilter, DialogListener
{
private static final String TITLE = "Noise 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 int algorithm = 0;
private static int algorithm2 = 1;
private static int lowestPixelsRange = 6;
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 GenericDialog(TITLE);
gd.addHelp(About.HELP_URL);
String[] methodNames = SettingsManager.getNames((Object[]) NoiseEstimator.Method.values());
gd.addChoice("Method1 (blue)", methodNames, methodNames[algorithm]);
gd.addChoice("Method2 (red)", methodNames, methodNames[algorithm2]);
gd.addSlider("Lowest_radius", 1, 15, lowestPixelsRange);
//gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
gd.addMessage("Click OK to compute noise table using all methods");
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)
{
algorithm = gd.getNextChoiceIndex();
algorithm2 = gd.getNextChoiceIndex();
lowestPixelsRange = (int) gd.getNextNumber();
if (gd.invalidNumber() || lowestPixelsRange < 1)
return false;
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()
{
NoiseEstimator.Method method1 = NoiseEstimator.Method.values()[algorithm];
NoiseEstimator.Method method2 = NoiseEstimator.Method.values()[algorithm2];
IJ.showStatus("Estimating noise ...");
boolean twoMethods = method1 != method2;
boolean preserveResiduals = method1.name().contains("Residuals") && method2.name().contains("Residuals") &&
twoMethods;
int start = imp.getCurrentSlice();
int end = FastMath.min(imp.getStackSize(), start + 100);
int size = end - start + 1;
double[] xValues = new double[size];
double[] yValues1 = new double[size];
double[] yValues2 = (twoMethods) ? new double[size] : null;
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 NoiseEstimator ne = new NoiseEstimator(buffer, bounds.width, bounds.height);
ne.preserveResiduals = preserveResiduals;
ne.setRange(lowestPixelsRange);
xValues[i] = slice;
yValues1[i] = ne.getNoise(method1);
if (twoMethods)
yValues2[i] = ne.getNoise(method2);
}
IJ.showProgress(1);
IJ.showStatus("Plotting noise ...");
// Get limits
double[] a = Tools.getMinMax(xValues);
double[] b1 = Tools.getMinMax(yValues1);
if (twoMethods)
{
double[] b2 = Tools.getMinMax(yValues2);
b1[0] = FastMath.min(b1[0], b2[0]);
b1[1] = FastMath.max(b1[1], b2[1]);
}
String title = imp.getTitle() + " Noise";
Plot2 plot = new Plot2(title, "Slice", "Noise", xValues, yValues1);
double range = b1[1] - b1[0];
if (range == 0)
range = 1;
plot.setLimits(a[0], a[1], b1[0]-0.05*range, b1[1]+0.05*range);
plot.setColor(Color.blue);
plot.draw();
String label = String.format("Blue = %s", Utils.rounded(new Statistics(yValues1).getMean()));
if (twoMethods)
{
plot.setColor(Color.red);
plot.addPoints(xValues, yValues2, Plot2.LINE);
label += String.format(", Red = %s", Utils.rounded(new Statistics(yValues2).getMean()));
}
plot.addLabel(0, 0, label);
Utils.display(title, plot);
IJ.showStatus("");
}
/*
* (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[NoiseEstimator.Method.values().length + 1];
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);
NoiseEstimator ne = new NoiseEstimator(buffer, bounds.width, bounds.height);
ne.preserveResiduals = true;
for (NoiseEstimator.Method m : NoiseEstimator.Method.values())
{
result[i++] = ne.getNoise(m);
}
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;
}
});
// Slow when there are lots of results ... Could change the output options in the future
TextWindow tw = new TextWindow(imp.getTitle() + " Noise", createHeader(), "", 800, 400);
for (double[] result : results)
{
tw.append(createResult(result));
}
// TODO - ImageJ plotting is not very good. Change to use a Java plotting library
//plotResults();
}
private String createHeader()
{
StringBuilder sb = new StringBuilder("Slice");
for (NoiseEstimator.Method m : NoiseEstimator.Method.values())
{
sb.append("\t").append(m);
}
return sb.toString();
}
private String createResult(double[] result)
{
StringBuilder sb = new StringBuilder("" + (int) result[0]);
for (int i = 1; i < result.length; i++)
{
sb.append("\t").append(result[i]);
}
return sb.toString();
}
}