use of ij.gui.Plot in project GDSC-SMLM by aherbert.
the class PcPalmFitting method analyse.
/**
* Perform the PC Analysis.
*
* <p>Spatial domain results can just be combined to an average curve.
*
* <p>Frequency domain results can be fit using the g(r) model.
*/
private void analyse() {
latestResult.set(new CorrelationCurveResult(gr, peakDensity, spatialDomain));
String axisTitle;
if (spatialDomain) {
offset = 0;
axisTitle = "molecules/um^2";
} else {
// Ignore the r=0 value by starting with an offset if necessary
offset = (gr[0][0] == 0) ? 1 : 0;
axisTitle = "g(r)";
}
final String title = TITLE + " " + axisTitle;
final Plot plot = PcPalmAnalysis.plotCorrelation(gr, offset, title, axisTitle, spatialDomain, settings.showErrorBars);
if (spatialDomain) {
saveCorrelationCurve(gr);
IJ.log("Created correlation curve from the spatial domain (Plot title = " + title + ")");
return;
}
// -------------
// Model fitting for g(r) correlation curves
// -------------
IJ.log("Fitting g(r) correlation curve from the frequency domain");
ImageJUtils.log("Average peak density = %s um^-2. Blinking estimate = %s", MathUtils.rounded(peakDensity, 4), MathUtils.rounded(settings.blinkingRate, 4));
resultsTable = createResultsTable();
// Get the protein density in nm^2.
final double peakDensityNm2 = peakDensity / 1e6;
// Use the blinking rate estimate to estimate the density
// (factors in the over-counting of the same molecules)
final double proteinDensity = peakDensityNm2 / settings.blinkingRate;
final ArrayList<double[]> curves = new ArrayList<>();
// Fit the g(r) curve for r>0 to equation 2
Color color = Color.red;
String resultColour = "Red";
double[] parameters = fitRandomModel(gr, settings.estimatedPrecision, proteinDensity, resultColour);
if (parameters != null) {
ImageJUtils.log(" Plot %s: Over-counting estimate = %s", randomModel.getName(), MathUtils.rounded(peakDensityNm2 / parameters[1], 4));
ImageJUtils.log(" Plot %s == %s", randomModel.getName(), resultColour);
plot.setColor(color);
plot.addPoints(randomModel.getX(), randomModel.value(parameters), Plot.LINE);
addNonFittedPoints(plot, gr, randomModel, parameters);
ImageJUtils.display(title, plot);
if (settings.saveCorrelationCurve) {
curves.add(extractCurve(gr, randomModel, parameters));
}
}
// Fit the clustered models if the random model fails or if chosen as an option
if (!valid1 || settings.fitClusteredModels) {
// Fit the g(r) curve for r>0 to equation 3
color = Color.blue;
resultColour = "Blue";
parameters = fitClusteredModel(gr, settings.estimatedPrecision, proteinDensity, resultColour);
if (parameters != null) {
ImageJUtils.log(" Plot %s: Over-counting estimate = %s", clusteredModel.getName(), MathUtils.rounded(peakDensityNm2 / parameters[1], 4));
ImageJUtils.log(" Plot %s == %s", clusteredModel.getName(), resultColour.toString());
plot.setColor(color);
plot.addPoints(clusteredModel.getX(), clusteredModel.value(parameters), Plot.LINE);
addNonFittedPoints(plot, gr, clusteredModel, parameters);
ImageJUtils.display(title, plot);
if (settings.saveCorrelationCurve) {
curves.add(extractCurve(gr, clusteredModel, parameters));
}
}
// Fit to an emulsion model for a distribution confined to circles
color = Color.magenta;
resultColour = "Magenta";
parameters = fitEmulsionModel(gr, settings.estimatedPrecision, proteinDensity, resultColour);
if (parameters != null) {
ImageJUtils.log(" Plot %s: Over-counting estimate = %s", emulsionModel.getName(), MathUtils.rounded(peakDensityNm2 / parameters[1], 4));
ImageJUtils.log(" Plot %s == %s", emulsionModel.getName(), resultColour.toString());
plot.setColor(color);
plot.addPoints(emulsionModel.getX(), emulsionModel.value(parameters), Plot.LINE);
addNonFittedPoints(plot, gr, emulsionModel, parameters);
ImageJUtils.display(title, plot);
if (settings.saveCorrelationCurve) {
curves.add(extractCurve(gr, emulsionModel, parameters));
}
}
}
saveCorrelationCurve(gr, curves.toArray(new double[0][0]));
}
use of ij.gui.Plot in project GDSC-SMLM by aherbert.
the class Fire method run.
@Override
public void run(String arg) {
extraOptions = ImageJUtils.isExtraOptions();
SmlmUsageTracker.recordPlugin(this.getClass(), arg);
// Require some fit results and selected regions
final int size = MemoryPeakResults.countMemorySize();
if (size == 0) {
IJ.error(pluginTitle, "There are no fitting results in memory");
return;
}
settings = Settings.load();
settings.save();
if ("q".equals(arg)) {
pluginTitle += " Q estimation";
runQEstimation();
return;
}
IJ.showStatus(pluginTitle + " ...");
if (!showInputDialog()) {
return;
}
MemoryPeakResults inputResults1 = ResultsManager.loadInputResults(settings.inputOption, false, null, null);
if (MemoryPeakResults.isEmpty(inputResults1)) {
IJ.error(pluginTitle, "No results could be loaded");
return;
}
MemoryPeakResults inputResults2 = ResultsManager.loadInputResults(settings.inputOption2, false, null, null);
inputResults1 = cropToRoi(inputResults1);
if (inputResults1.size() < 2) {
IJ.error(pluginTitle, "No results within the crop region");
return;
}
if (inputResults2 != null) {
inputResults2 = cropToRoi(inputResults2);
if (inputResults2.size() < 2) {
IJ.error(pluginTitle, "No results2 within the crop region");
return;
}
}
initialise(inputResults1, inputResults2);
if (!showDialog()) {
return;
}
final long start = System.currentTimeMillis();
// Compute FIRE
String name = inputResults1.getName();
final double fourierImageScale = Settings.scaleValues[settings.imageScaleIndex];
final int imageSize = Settings.imageSizeValues[settings.imageSizeIndex];
if (this.results2 != null) {
name += " vs " + this.results2.getName();
final FireResult result = calculateFireNumber(fourierMethod, samplingMethod, thresholdMethod, fourierImageScale, imageSize);
if (result != null) {
logResult(name, result);
if (settings.showFrcCurve) {
showFrcCurve(name, result, thresholdMethod);
}
}
} else {
FireResult result = null;
final int repeats = (settings.randomSplit) ? Math.max(1, settings.repeats) : 1;
setProgress(repeats);
if (repeats == 1) {
result = calculateFireNumber(fourierMethod, samplingMethod, thresholdMethod, fourierImageScale, imageSize);
if (result != null) {
logResult(name, result);
if (settings.showFrcCurve) {
showFrcCurve(name, result, thresholdMethod);
}
}
} else {
// Multi-thread this ...
final int nThreads = MathUtils.min(repeats, getThreads());
final ExecutorService executor = Executors.newFixedThreadPool(nThreads);
final LocalList<Future<?>> futures = new LocalList<>(repeats);
final LocalList<FireWorker> workers = new LocalList<>(repeats);
IJ.showProgress(0);
IJ.showStatus(pluginTitle + " computing ...");
for (int i = 1; i <= repeats; i++) {
final FireWorker w = new FireWorker(i, fourierImageScale, imageSize);
workers.add(w);
futures.add(executor.submit(w));
}
// Wait for all to finish
executor.shutdown();
ConcurrencyUtils.waitForCompletionUnchecked(futures);
IJ.showProgress(1);
// Show a combined FRC curve plot of all the smoothed curves if we have multiples.
final LUT valuesLut = LutHelper.createLut(LutColour.FIRE_GLOW);
final LutHelper.DefaultLutMapper mapper = new LutHelper.DefaultLutMapper(0, repeats);
final FrcCurvePlot curve = new FrcCurvePlot();
final Statistics stats = new Statistics();
final WindowOrganiser wo = new WindowOrganiser();
boolean oom = false;
for (int i = 0; i < repeats; i++) {
final FireWorker w = workers.get(i);
if (w.oom) {
oom = true;
}
if (w.result == null) {
continue;
}
result = w.result;
if (!Double.isNaN(result.fireNumber)) {
stats.add(result.fireNumber);
}
if (settings.showFrcCurveRepeats) {
// Output each FRC curve using a suffix.
logResult(w.name, result);
wo.add(ImageJUtils.display(w.plot.getTitle(), w.plot));
}
if (settings.showFrcCurve) {
final int index = mapper.map(i + 1);
curve.add(name, result, thresholdMethod, LutHelper.getColour(valuesLut, index), Color.blue, null);
}
}
if (result != null) {
wo.cascade();
final double mean = stats.getMean();
logResult(name, result, mean, stats);
if (settings.showFrcCurve) {
curve.addResolution(mean);
final Plot plot = curve.getPlot();
ImageJUtils.display(plot.getTitle(), plot);
}
}
if (oom) {
// @formatter:off
IJ.error(pluginTitle, "ERROR - Parallel computation out-of-memory.\n \n" + TextUtils.wrap("The number of results will be reduced. " + "Please reduce the size of the Fourier image " + "or change the number of threads " + "using the extra options (hold down the 'Shift' " + "key when running the plugin).", 80));
// @formatter:on
}
}
// Only do this once
if (settings.showFrcTimeEvolution && result != null && !Double.isNaN(result.fireNumber)) {
showFrcTimeEvolution(name, result.fireNumber, thresholdMethod, nmPerUnit / result.getNmPerPixel(), imageSize);
}
}
IJ.showStatus(pluginTitle + " complete : " + TextUtils.millisToString(System.currentTimeMillis() - start));
}
use of ij.gui.Plot 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 ij.gui.Plot in project GDSC-SMLM by aherbert.
the class DriftCalculator method plotDrift.
private static PlotWindow plotDrift(PlotWindow parent, double[][] interpolated, double[][] original, String name, int index) {
// Create plot
final double[] xlimits = MathUtils.limits(interpolated[0]);
double[] ylimits = MathUtils.limits(original[index]);
ylimits = MathUtils.limits(ylimits, interpolated[index]);
final Plot plot = new Plot(name, "Frame", "Drift (px)");
plot.setLimits(xlimits[0], xlimits[1], ylimits[0], ylimits[1]);
// De-saturated blue
plot.setColor(new Color(0, 0, 155));
plot.addPoints(original[0], original[index], Plot.CROSS);
plot.setColor(java.awt.Color.RED);
plot.addPoints(interpolated[0], interpolated[index], Plot.LINE);
final WindowOrganiser wo = new WindowOrganiser();
final PlotWindow window = ImageJUtils.display(name, plot, wo);
if (wo.isNotEmpty() && parent != null) {
final Point location = parent.getLocation();
location.y += parent.getHeight();
window.setLocation(location);
}
return window;
}
use of ij.gui.Plot in project GDSC-SMLM by aherbert.
the class EmGainAnalysis method fit.
/**
* Fit the EM-gain distribution (Gaussian * Gamma).
*
* @param histogram The distribution
*/
private void fit(int[] histogram) {
final int[] limits = limits(histogram);
final double[] x = getX(limits);
final double[] y = getY(histogram, limits);
Plot plot = new Plot(TITLE, "ADU", "Frequency");
double yMax = MathUtils.max(y);
plot.setLimits(limits[0], limits[1], 0, yMax);
plot.setColor(Color.black);
plot.addPoints(x, y, Plot.DOT);
ImageJUtils.display(TITLE, plot);
// Estimate remaining parameters.
// Assuming a gamma_distribution(shape,scale) then mean = shape * scale
// scale = gain
// shape = Photons = mean / gain
double mean = getMean(histogram) - settings.bias;
// Note: if the bias is too high then the mean will be negative. Just move the bias.
while (mean < 0) {
settings.bias -= 1;
mean += 1;
}
double photons = mean / settings.gain;
if (settings.settingSimulate) {
ImageJUtils.log("Simulated bias=%d, gain=%s, noise=%s, photons=%s", (int) settings.settingBias, MathUtils.rounded(settings.settingGain), MathUtils.rounded(settings.settingNoise), MathUtils.rounded(settings.settingPhotons));
}
ImageJUtils.log("Estimate bias=%d, gain=%s, noise=%s, photons=%s", (int) settings.bias, MathUtils.rounded(settings.gain), MathUtils.rounded(settings.noise), MathUtils.rounded(photons));
final int max = (int) x[x.length - 1];
double[] pg = pdf(max, photons, settings.gain, settings.noise, (int) settings.bias);
plot.setColor(Color.blue);
plot.addPoints(x, pg, Plot.LINE);
ImageJUtils.display(TITLE, plot);
// Perform a fit
final CustomPowellOptimizer o = new CustomPowellOptimizer(1e-6, 1e-16, 1e-6, 1e-16);
final double[] startPoint = new double[] { photons, settings.gain, settings.noise, settings.bias };
int maxEval = 3000;
final String[] paramNames = { "Photons", "Gain", "Noise", "Bias" };
// Set bounds
final double[] lower = new double[] { 0, 0.5 * settings.gain, 0, settings.bias - settings.noise };
final double[] upper = new double[] { 2 * photons, 2 * settings.gain, settings.gain, settings.bias + settings.noise };
// Restart until converged.
// TODO - Maybe fix this with a better optimiser. This needs to be tested on real data.
PointValuePair solution = null;
for (int iter = 0; iter < 3; iter++) {
IJ.showStatus("Fitting histogram ... Iteration " + iter);
try {
// Basic Powell optimiser
final MultivariateFunction fun = getFunction(limits, y, max, maxEval);
final PointValuePair optimum = o.optimize(new MaxEval(maxEval), new ObjectiveFunction(fun), GoalType.MINIMIZE, new InitialGuess((solution == null) ? startPoint : solution.getPointRef()));
if (solution == null || optimum.getValue() < solution.getValue()) {
final double[] point = optimum.getPointRef();
// Check the bounds
for (int i = 0; i < point.length; i++) {
if (point[i] < lower[i] || point[i] > upper[i]) {
throw new ComputationException(String.format("Fit out of of estimated range: %s %f", paramNames[i], point[i]));
}
}
solution = optimum;
}
} catch (final Exception ex) {
IJ.log("Powell error: " + ex.getMessage());
if (ex instanceof TooManyEvaluationsException) {
maxEval = (int) (maxEval * 1.5);
}
}
try {
// Bounded Powell optimiser
final MultivariateFunction fun = getFunction(limits, y, max, maxEval);
final MultivariateFunctionMappingAdapter adapter = new MultivariateFunctionMappingAdapter(fun, lower, upper);
PointValuePair optimum = o.optimize(new MaxEval(maxEval), new ObjectiveFunction(adapter), GoalType.MINIMIZE, new InitialGuess(adapter.boundedToUnbounded((solution == null) ? startPoint : solution.getPointRef())));
final double[] point = adapter.unboundedToBounded(optimum.getPointRef());
optimum = new PointValuePair(point, optimum.getValue());
if (solution == null || optimum.getValue() < solution.getValue()) {
solution = optimum;
}
} catch (final Exception ex) {
IJ.log("Bounded Powell error: " + ex.getMessage());
if (ex instanceof TooManyEvaluationsException) {
maxEval = (int) (maxEval * 1.5);
}
}
}
ImageJUtils.finished();
if (solution == null) {
ImageJUtils.log("Failed to fit the distribution");
return;
}
final double[] point = solution.getPointRef();
photons = point[0];
settings.gain = point[1];
settings.noise = point[2];
settings.bias = (int) Math.round(point[3]);
final String label = String.format("Fitted bias=%d, gain=%s, noise=%s, photons=%s", (int) settings.bias, MathUtils.rounded(settings.gain), MathUtils.rounded(settings.noise), MathUtils.rounded(photons));
ImageJUtils.log(label);
if (settings.settingSimulate) {
ImageJUtils.log("Relative Error bias=%s, gain=%s, noise=%s, photons=%s", MathUtils.rounded(relativeError(settings.bias, settings.settingBias)), MathUtils.rounded(relativeError(settings.gain, settings.settingGain)), MathUtils.rounded(relativeError(settings.noise, settings.settingNoise)), MathUtils.rounded(relativeError(photons, settings.settingPhotons)));
}
// Show the PoissonGammaGaussian approximation
double[] approxValues = null;
if (settings.showApproximation) {
approxValues = new double[x.length];
final PoissonGammaGaussianFunction fun = new PoissonGammaGaussianFunction(1.0 / settings.gain, settings.noise);
final double expected = photons * settings.gain;
for (int i = 0; i < approxValues.length; i++) {
approxValues[i] = fun.likelihood(x[i] - settings.bias, expected);
}
yMax = MathUtils.maxDefault(yMax, approxValues);
}
// Replot
pg = pdf(max, photons, settings.gain, settings.noise, (int) settings.bias);
plot = new Plot(TITLE, "ADU", "Frequency");
plot.setLimits(limits[0], limits[1], 0, yMax * 1.05);
plot.setColor(Color.black);
plot.addPoints(x, y, Plot.DOT);
plot.setColor(Color.red);
plot.addPoints(x, pg, Plot.LINE);
plot.addLabel(0, 0, label);
if (settings.showApproximation) {
plot.setColor(Color.blue);
plot.addPoints(x, approxValues, Plot.LINE);
}
ImageJUtils.display(TITLE, plot);
}
Aggregations