use of org.apache.commons.math3.stat.descriptive.rank.Median in project GDSC-SMLM by aherbert.
the class Fire method runQEstimation.
@SuppressWarnings("null")
private void runQEstimation() {
IJ.showStatus(pluginTitle + " ...");
if (!showQEstimationInputDialog()) {
return;
}
MemoryPeakResults inputResults = ResultsManager.loadInputResults(settings.inputOption, false, null, null);
if (MemoryPeakResults.isEmpty(inputResults)) {
IJ.error(pluginTitle, "No results could be loaded");
return;
}
if (inputResults.getCalibration() == null) {
IJ.error(pluginTitle, "The results are not calibrated");
return;
}
inputResults = cropToRoi(inputResults);
if (inputResults.size() < 2) {
IJ.error(pluginTitle, "No results within the crop region");
return;
}
initialise(inputResults, null);
// We need localisation precision.
// Build a histogram of the localisation precision.
// Get the initial mean and SD and plot as a Gaussian.
final PrecisionHistogram histogram = calculatePrecisionHistogram();
if (histogram == null) {
IJ.error(pluginTitle, "No localisation precision available.\n \nPlease choose " + PrecisionMethod.FIXED + " and enter a precision mean and SD.");
return;
}
final StoredDataStatistics precision = histogram.precision;
final double fourierImageScale = Settings.scaleValues[settings.imageScaleIndex];
final int imageSize = Settings.imageSizeValues[settings.imageSizeIndex];
// Create the image and compute the numerator of FRC.
// Do not use the signal so results.size() is the number of localisations.
IJ.showStatus("Computing FRC curve ...");
final FireImages images = createImages(fourierImageScale, imageSize, false);
// DEBUGGING - Save the two images to disk. Load the images into the Matlab
// code that calculates the Q-estimation and make this plugin match the functionality.
// IJ.save(new ImagePlus("i1", images.ip1), "/scratch/i1.tif");
// IJ.save(new ImagePlus("i2", images.ip2), "/scratch/i2.tif");
final Frc frc = new Frc();
frc.setTrackProgress(progress);
frc.setFourierMethod(fourierMethod);
frc.setSamplingMethod(samplingMethod);
frc.setPerimeterSamplingFactor(settings.perimeterSamplingFactor);
final FrcCurve frcCurve = frc.calculateFrcCurve(images.ip1, images.ip2, images.nmPerPixel);
if (frcCurve == null) {
IJ.error(pluginTitle, "Failed to compute FRC curve");
return;
}
IJ.showStatus("Running Q-estimation ...");
// Note:
// The method implemented here is based on Matlab code provided by Bernd Rieger.
// The idea is to compute the spurious correlation component of the FRC Numerator
// using an initial estimate of distribution of the localisation precision (assumed
// to be Gaussian). This component is the contribution of repeat localisations of
// the same molecule to the numerator and is modelled as an exponential decay
// (exp_decay). The component is scaled by the Q-value which
// is the average number of times a molecule is seen in addition to the first time.
// At large spatial frequencies the scaled component should match the numerator,
// i.e. at high resolution (low FIRE number) the numerator is made up of repeat
// localisations of the same molecule and not actual structure in the image.
// The best fit is where the numerator equals the scaled component, i.e. num / (q*exp_decay) ==
// 1.
// The FRC Numerator is plotted and Q can be determined by
// adjusting Q and the precision mean and SD to maximise the cost function.
// This can be done interactively by the user with the effect on the FRC curve
// dynamically updated and displayed.
// Compute the scaled FRC numerator
final double qNorm = (1 / frcCurve.mean1 + 1 / frcCurve.mean2);
final double[] frcnum = new double[frcCurve.getSize()];
for (int i = 0; i < frcnum.length; i++) {
final FrcCurveResult r = frcCurve.get(i);
frcnum[i] = qNorm * r.getNumerator() / r.getNumberOfSamples();
}
// Compute the spatial frequency and the region for curve fitting
final double[] q = Frc.computeQ(frcCurve, false);
int low = 0;
int high = q.length;
while (high > 0 && q[high - 1] > settings.maxQ) {
high--;
}
while (low < q.length && q[low] < settings.minQ) {
low++;
}
// Require we fit at least 10% of the curve
if (high - low < q.length * 0.1) {
IJ.error(pluginTitle, "Not enough points for Q estimation");
return;
}
// Obtain initial estimate of Q plateau height and decay.
// This can be done by fitting the precision histogram and then fixing the mean and sigma.
// Or it can be done by allowing the precision to be sampled and the mean and sigma
// become parameters for fitting.
// Check if we can sample precision values
final boolean sampleDecay = precision != null && settings.sampleDecay;
double[] expDecay;
if (sampleDecay) {
// Random sample of precision values from the distribution is used to
// construct the decay curve
final int[] sample = RandomUtils.sample(10000, precision.getN(), UniformRandomProviders.create());
final double four_pi2 = 4 * Math.PI * Math.PI;
final double[] pre = new double[q.length];
for (int i = 1; i < q.length; i++) {
pre[i] = -four_pi2 * q[i] * q[i];
}
// Sample
final int n = sample.length;
final double[] hq = new double[n];
for (int j = 0; j < n; j++) {
// Scale to SR pixels
double s2 = precision.getValue(sample[j]) / images.nmPerPixel;
s2 *= s2;
for (int i = 1; i < q.length; i++) {
hq[i] += StdMath.exp(pre[i] * s2);
}
}
for (int i = 1; i < q.length; i++) {
hq[i] /= n;
}
expDecay = new double[q.length];
expDecay[0] = 1;
for (int i = 1; i < q.length; i++) {
final double sinc_q = sinc(Math.PI * q[i]);
expDecay[i] = sinc_q * sinc_q * hq[i];
}
} else {
// Note: The sigma mean and std should be in the units of super-resolution
// pixels so scale to SR pixels
expDecay = computeExpDecay(histogram.mean / images.nmPerPixel, histogram.sigma / images.nmPerPixel, q);
}
// Smoothing
double[] smooth;
if (settings.loessSmoothing) {
// Note: This computes the log then smooths it
final double bandwidth = 0.1;
final int robustness = 0;
final double[] l = new double[expDecay.length];
for (int i = 0; i < l.length; i++) {
// Original Matlab code computes the log for each array.
// This is equivalent to a single log on the fraction of the two.
// Perhaps the two log method is more numerically stable.
// l[i] = Math.log(Math.abs(frcnum[i])) - Math.log(exp_decay[i]);
l[i] = Math.log(Math.abs(frcnum[i] / expDecay[i]));
}
try {
final LoessInterpolator loess = new LoessInterpolator(bandwidth, robustness);
smooth = loess.smooth(q, l);
} catch (final Exception ex) {
IJ.error(pluginTitle, "LOESS smoothing failed");
return;
}
} else {
// Note: This smooths the curve before computing the log
final double[] norm = new double[expDecay.length];
for (int i = 0; i < norm.length; i++) {
norm[i] = frcnum[i] / expDecay[i];
}
// Median window of 5 == radius of 2
final DoubleMedianWindow mw = DoubleMedianWindow.wrap(norm, 2);
smooth = new double[expDecay.length];
for (int i = 0; i < norm.length; i++) {
smooth[i] = Math.log(Math.abs(mw.getMedian()));
mw.increment();
}
}
// Fit with quadratic to find the initial guess.
// Note: example Matlab code frc_Qcorrection7.m identifies regions of the
// smoothed log curve with low derivative and only fits those. The fit is
// used for the final estimate. Fitting a subset with low derivative is not
// implemented here since the initial estimate is subsequently optimised
// to maximise a cost function.
final Quadratic curve = new Quadratic();
final SimpleCurveFitter fit = SimpleCurveFitter.create(curve, new double[2]);
final WeightedObservedPoints points = new WeightedObservedPoints();
for (int i = low; i < high; i++) {
points.add(q[i], smooth[i]);
}
final double[] estimate = fit.fit(points.toList());
double qvalue = StdMath.exp(estimate[0]);
// This could be made an option. Just use for debugging
final boolean debug = false;
if (debug) {
// Plot the initial fit and the fit curve
final double[] qScaled = Frc.computeQ(frcCurve, true);
final double[] line = new double[q.length];
for (int i = 0; i < q.length; i++) {
line[i] = curve.value(q[i], estimate);
}
final String title = pluginTitle + " Initial fit";
final Plot plot = new Plot(title, "Spatial Frequency (nm^-1)", "FRC Numerator");
final String label = String.format("Q = %.3f", qvalue);
plot.addPoints(qScaled, smooth, Plot.LINE);
plot.setColor(Color.red);
plot.addPoints(qScaled, line, Plot.LINE);
plot.setColor(Color.black);
plot.addLabel(0, 0, label);
ImageJUtils.display(title, plot, ImageJUtils.NO_TO_FRONT);
}
if (settings.fitPrecision) {
// Q - Should this be optional?
if (sampleDecay) {
// If a sample of the precision was used to construct the data for the initial fit
// then update the estimate using the fit result since it will be a better start point.
histogram.sigma = precision.getStandardDeviation();
// Normalise sum-of-squares to the SR pixel size
final double meanSumOfSquares = (precision.getSumOfSquares() / (images.nmPerPixel * images.nmPerPixel)) / precision.getN();
histogram.mean = images.nmPerPixel * Math.sqrt(meanSumOfSquares - estimate[1] / (4 * Math.PI * Math.PI));
}
// Do a multivariate fit ...
final SimplexOptimizer opt = new SimplexOptimizer(1e-6, 1e-10);
PointValuePair pair = null;
final MultiPlateauness f = new MultiPlateauness(frcnum, q, low, high);
final double[] initial = new double[] { histogram.mean / images.nmPerPixel, histogram.sigma / images.nmPerPixel, qvalue };
pair = findMin(pair, opt, f, scale(initial, 0.1));
pair = findMin(pair, opt, f, scale(initial, 0.5));
pair = findMin(pair, opt, f, initial);
pair = findMin(pair, opt, f, scale(initial, 2));
pair = findMin(pair, opt, f, scale(initial, 10));
if (pair != null) {
final double[] point = pair.getPointRef();
histogram.mean = point[0] * images.nmPerPixel;
histogram.sigma = point[1] * images.nmPerPixel;
qvalue = point[2];
}
} else {
// If so then this should be optional.
if (sampleDecay) {
if (precisionMethod != PrecisionMethod.FIXED) {
histogram.sigma = precision.getStandardDeviation();
// Normalise sum-of-squares to the SR pixel size
final double meanSumOfSquares = (precision.getSumOfSquares() / (images.nmPerPixel * images.nmPerPixel)) / precision.getN();
histogram.mean = images.nmPerPixel * Math.sqrt(meanSumOfSquares - estimate[1] / (4 * Math.PI * Math.PI));
}
expDecay = computeExpDecay(histogram.mean / images.nmPerPixel, histogram.sigma / images.nmPerPixel, q);
}
// Estimate spurious component by promoting plateauness.
// The Matlab code used random initial points for a Simplex optimiser.
// A Brent line search should be pretty deterministic so do simple repeats.
// However it will proceed downhill so if the initial point is wrong then
// it will find a sub-optimal result.
final UnivariateOptimizer o = new BrentOptimizer(1e-3, 1e-6);
final Plateauness f = new Plateauness(frcnum, expDecay, low, high);
UnivariatePointValuePair result = null;
result = findMin(result, o, f, qvalue, 0.1);
result = findMin(result, o, f, qvalue, 0.2);
result = findMin(result, o, f, qvalue, 0.333);
result = findMin(result, o, f, qvalue, 0.5);
// Do some Simplex repeats as well
final SimplexOptimizer opt = new SimplexOptimizer(1e-6, 1e-10);
result = findMin(result, opt, f, qvalue * 0.1);
result = findMin(result, opt, f, qvalue * 0.5);
result = findMin(result, opt, f, qvalue);
result = findMin(result, opt, f, qvalue * 2);
result = findMin(result, opt, f, qvalue * 10);
if (result != null) {
qvalue = result.getPoint();
}
}
final QPlot qplot = new QPlot(frcCurve, qvalue, low, high);
// Interactive dialog to estimate Q (blinking events per flourophore) using
// sliders for the mean and standard deviation of the localisation precision.
showQEstimationDialog(histogram, qplot, images.nmPerPixel);
IJ.showStatus(pluginTitle + " complete");
}
use of org.apache.commons.math3.stat.descriptive.rank.Median in project GDSC-SMLM by aherbert.
the class MeanVarianceTest method run.
@Override
public void run(String arg) {
SmlmUsageTracker.recordPlugin(this.getClass(), arg);
settings = Settings.load();
settings.save();
String helpKey = "mean-variance-test";
if (ImageJUtils.isExtraOptions()) {
final ImagePlus imp = WindowManager.getCurrentImage();
if (imp.getStackSize() > 1) {
final GenericDialog gd = new GenericDialog(TITLE);
gd.addMessage("Perform single image analysis on the current image?");
gd.addNumericField("Bias", settings.bias, 0);
gd.addHelp(HelpUrls.getUrl(helpKey));
gd.showDialog();
if (gd.wasCanceled()) {
return;
}
singleImage = true;
settings.bias = Math.abs(gd.getNextNumber());
} else {
IJ.error(TITLE, "Single-image mode requires a stack");
return;
}
}
List<ImageSample> images;
String inputDirectory = "";
if (singleImage) {
IJ.showStatus("Loading images...");
images = getImages();
if (images.size() == 0) {
IJ.error(TITLE, "Not enough images for analysis");
return;
}
} else {
inputDirectory = IJ.getDirectory("Select image series ...");
if (inputDirectory == null) {
return;
}
final SeriesOpener series = new SeriesOpener(inputDirectory);
series.setVariableSize(true);
if (series.getNumberOfImages() < 3) {
IJ.error(TITLE, "Not enough images in the selected directory");
return;
}
if (!IJ.showMessageWithCancel(TITLE, String.format("Analyse %d images, first image:\n%s", series.getNumberOfImages(), series.getImageList()[0]))) {
return;
}
IJ.showStatus("Loading images");
images = getImages(series);
if (images.size() < 3) {
IJ.error(TITLE, "Not enough images for analysis");
return;
}
if (images.get(0).exposure != 0) {
IJ.error(TITLE, "First image in series must have exposure 0 (Bias image)");
return;
}
}
final boolean emMode = (arg != null && arg.contains("em"));
GenericDialog gd = new GenericDialog(TITLE);
gd.addMessage("Set the output options:");
gd.addCheckbox("Show_table", settings.showTable);
gd.addCheckbox("Show_charts", settings.showCharts);
if (emMode) {
// Ask the user for the camera gain ...
gd.addMessage("Estimating the EM-gain requires the camera gain without EM readout enabled");
gd.addNumericField("Camera_gain (Count/e-)", settings.cameraGain, 4);
}
if (emMode) {
helpKey += "-em-ccd";
}
gd.addHelp(HelpUrls.getUrl(helpKey));
gd.showDialog();
if (gd.wasCanceled()) {
return;
}
settings.showTable = gd.getNextBoolean();
settings.showCharts = gd.getNextBoolean();
if (emMode) {
settings.cameraGain = gd.getNextNumber();
}
IJ.showStatus("Computing mean & variance");
final double nImages = images.size();
for (int i = 0; i < images.size(); i++) {
IJ.showStatus(String.format("Computing mean & variance %d/%d", i + 1, images.size()));
images.get(i).compute(singleImage, i / nImages, (i + 1) / nImages);
}
IJ.showProgress(1);
IJ.showStatus("Computing results");
// Allow user to input multiple bias images
int start = 0;
final Statistics biasStats = new Statistics();
final Statistics noiseStats = new Statistics();
final double bias;
if (singleImage) {
bias = settings.bias;
} else {
while (start < images.size()) {
final ImageSample sample = images.get(start);
if (sample.exposure == 0) {
biasStats.add(sample.means);
for (final PairSample pair : sample.samples) {
noiseStats.add(pair.variance);
}
start++;
} else {
break;
}
}
bias = biasStats.getMean();
}
// Get the mean-variance data
int total = 0;
for (int i = start; i < images.size(); i++) {
total += images.get(i).samples.size();
}
if (settings.showTable && total > 2000) {
gd = new GenericDialog(TITLE);
gd.addMessage("Table output requires " + total + " entries.\n \nYou may want to disable the table.");
gd.addCheckbox("Show_table", settings.showTable);
gd.showDialog();
if (gd.wasCanceled()) {
return;
}
settings.showTable = gd.getNextBoolean();
}
final TextWindow results = (settings.showTable) ? createResultsWindow() : null;
double[] mean = new double[total];
double[] variance = new double[mean.length];
final Statistics gainStats = (singleImage) ? new StoredDataStatistics(total) : new Statistics();
final WeightedObservedPoints obs = new WeightedObservedPoints();
for (int i = (singleImage) ? 0 : start, j = 0; i < images.size(); i++) {
final StringBuilder sb = (settings.showTable) ? new StringBuilder() : null;
final ImageSample sample = images.get(i);
for (final PairSample pair : sample.samples) {
if (j % 16 == 0) {
IJ.showProgress(j, total);
}
mean[j] = pair.getMean();
variance[j] = pair.variance;
// Gain is in Count / e
double gain = variance[j] / (mean[j] - bias);
gainStats.add(gain);
obs.add(mean[j], variance[j]);
if (emMode) {
gain /= (2 * settings.cameraGain);
}
if (sb != null) {
sb.append(sample.title).append('\t');
sb.append(sample.exposure).append('\t');
sb.append(pair.slice1).append('\t');
sb.append(pair.slice2).append('\t');
sb.append(IJ.d2s(pair.mean1, 2)).append('\t');
sb.append(IJ.d2s(pair.mean2, 2)).append('\t');
sb.append(IJ.d2s(mean[j], 2)).append('\t');
sb.append(IJ.d2s(variance[j], 2)).append('\t');
sb.append(MathUtils.rounded(gain, 4)).append("\n");
}
j++;
}
if (results != null && sb != null) {
results.append(sb.toString());
}
}
IJ.showProgress(1);
if (singleImage) {
StoredDataStatistics stats = (StoredDataStatistics) gainStats;
ImageJUtils.log(TITLE);
if (emMode) {
final double[] values = stats.getValues();
MathArrays.scaleInPlace(0.5, values);
stats = StoredDataStatistics.create(values);
}
if (settings.showCharts) {
// Plot the gain over time
final String title = TITLE + " Gain vs Frame";
final Plot plot = new Plot(title, "Slice", "Gain");
plot.addPoints(SimpleArrayUtils.newArray(gainStats.getN(), 1, 1.0), stats.getValues(), Plot.LINE);
final PlotWindow pw = ImageJUtils.display(title, plot);
// Show a histogram
final String label = String.format("Mean = %s, Median = %s", MathUtils.rounded(stats.getMean()), MathUtils.rounded(stats.getMedian()));
final WindowOrganiser wo = new WindowOrganiser();
final PlotWindow pw2 = new HistogramPlotBuilder(TITLE, stats, "Gain").setRemoveOutliersOption(1).setPlotLabel(label).show(wo);
if (wo.isNotEmpty()) {
final Point point = pw.getLocation();
point.y += pw.getHeight();
pw2.setLocation(point);
}
}
ImageJUtils.log("Single-image mode: %s camera", (emMode) ? "EM-CCD" : "Standard");
final double gain = stats.getMedian();
if (emMode) {
final double totalGain = gain;
final double emGain = totalGain / settings.cameraGain;
ImageJUtils.log(" Gain = 1 / %s (Count/e-)", MathUtils.rounded(settings.cameraGain, 4));
ImageJUtils.log(" EM-Gain = %s", MathUtils.rounded(emGain, 4));
ImageJUtils.log(" Total Gain = %s (Count/e-)", MathUtils.rounded(totalGain, 4));
} else {
settings.cameraGain = gain;
ImageJUtils.log(" Gain = 1 / %s (Count/e-)", MathUtils.rounded(settings.cameraGain, 4));
}
} else {
IJ.showStatus("Computing fit");
// Sort
final int[] indices = rank(mean);
mean = reorder(mean, indices);
variance = reorder(variance, indices);
// Compute optimal coefficients.
// a - b x
final double[] init = { 0, 1 / gainStats.getMean() };
final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(2).withStartPoint(init);
final double[] best = fitter.fit(obs.toList());
// Construct the polynomial that best fits the data.
final PolynomialFunction fitted = new PolynomialFunction(best);
if (settings.showCharts) {
// Plot mean verses variance. Gradient is gain in Count/e.
final String title = TITLE + " results";
final Plot plot = new Plot(title, "Mean", "Variance");
final double[] xlimits = MathUtils.limits(mean);
final double[] ylimits = MathUtils.limits(variance);
double xrange = (xlimits[1] - xlimits[0]) * 0.05;
if (xrange == 0) {
xrange = 0.05;
}
double yrange = (ylimits[1] - ylimits[0]) * 0.05;
if (yrange == 0) {
yrange = 0.05;
}
plot.setLimits(xlimits[0] - xrange, xlimits[1] + xrange, ylimits[0] - yrange, ylimits[1] + yrange);
plot.setColor(Color.blue);
plot.addPoints(mean, variance, Plot.CROSS);
plot.setColor(Color.red);
plot.addPoints(new double[] { mean[0], mean[mean.length - 1] }, new double[] { fitted.value(mean[0]), fitted.value(mean[mean.length - 1]) }, Plot.LINE);
ImageJUtils.display(title, plot);
}
final double avBiasNoise = Math.sqrt(noiseStats.getMean());
ImageJUtils.log(TITLE);
ImageJUtils.log(" Directory = %s", inputDirectory);
ImageJUtils.log(" Bias = %s +/- %s (Count)", MathUtils.rounded(bias, 4), MathUtils.rounded(avBiasNoise, 4));
ImageJUtils.log(" Variance = %s + %s * mean", MathUtils.rounded(best[0], 4), MathUtils.rounded(best[1], 4));
if (emMode) {
// The gradient is the observed gain of the noise.
// In an EM-CCD there is a noise factor of 2.
// Q. Is this true for a correct noise factor calibration:
// double noiseFactor = (Read Noise EM-CCD) / (Read Noise CCD)
// Em-gain is the observed gain divided by the noise factor multiplied by camera gain
final double emGain = best[1] / (2 * settings.cameraGain);
// Compute total gain
final double totalGain = emGain * settings.cameraGain;
final double readNoise = avBiasNoise / settings.cameraGain;
// Effective noise is standard deviation of the bias image divided by the total gain (in
// Count/e-)
final double readNoiseE = avBiasNoise / totalGain;
ImageJUtils.log(" Read Noise = %s (e-) [%s (Count)]", MathUtils.rounded(readNoise, 4), MathUtils.rounded(avBiasNoise, 4));
ImageJUtils.log(" Gain = 1 / %s (Count/e-)", MathUtils.rounded(1 / settings.cameraGain, 4));
ImageJUtils.log(" EM-Gain = %s", MathUtils.rounded(emGain, 4));
ImageJUtils.log(" Total Gain = %s (Count/e-)", MathUtils.rounded(totalGain, 4));
ImageJUtils.log(" Effective Read Noise = %s (e-) (Read Noise/Total Gain)", MathUtils.rounded(readNoiseE, 4));
} else {
// The gradient is the observed gain of the noise.
settings.cameraGain = best[1];
// Noise is standard deviation of the bias image divided by the gain (in Count/e-)
final double readNoise = avBiasNoise / settings.cameraGain;
ImageJUtils.log(" Read Noise = %s (e-) [%s (Count)]", MathUtils.rounded(readNoise, 4), MathUtils.rounded(avBiasNoise, 4));
ImageJUtils.log(" Gain = 1 / %s (Count/e-)", MathUtils.rounded(1 / settings.cameraGain, 4));
}
}
IJ.showStatus("");
}
Aggregations