Java tutorial
/* ***** BEGIN LICENSE BLOCK ***** * * Copyright (c) 2012 Colin J. Fuller * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * ***** END LICENSE BLOCK ***** */ package edu.stanford.cfuller.imageanalysistools.fitting; import java.util.List; import org.apache.commons.math3.exception.ConvergenceException; import org.apache.commons.math3.analysis.UnivariateFunction; import org.apache.commons.math3.analysis.integration.LegendreGaussIntegrator; import org.apache.commons.math3.linear.ArrayRealVector; import org.apache.commons.math3.linear.RealVector; import edu.stanford.cfuller.imageanalysistools.image.Image; import edu.stanford.cfuller.imageanalysistools.image.ImageCoordinate; import edu.stanford.cfuller.imageanalysistools.meta.parameters.ParameterDictionary; /** * An ImageObject that fits to a three-dimensional Gaussian. Unlike GaussianImageObject, this * ImageObject can handle non-xy-symmetric Gaussians as well as Gaussians with covariance. * * @author Colin J. Fuller * */ public class GaussianImageObjectWithCovariance extends ImageObject { static final long serialVersionUID = 2L; /** * Optional Parameters */ static final String Z_WIDTH_PARAM = "z_width"; static final String XY_WIDTH_PARAM = "xy_width"; /** * Creates an empty GaussianImageObject. */ public GaussianImageObjectWithCovariance() { init(); } /** * Creates a GaussianImageObject from the specified masked region in an Image. * @param label The greylevel of the object in the Image mask. * @param mask The mask of objects in the Image, with a unique greylevel assigned to each object. * @param parent The Image that the object occurs in and that is masked by mask. * @param p The parameters associated with this analysis. */ public GaussianImageObjectWithCovariance(int label, Image mask, Image parent, ParameterDictionary p) { init(label, mask, parent, p); } /** * Fits this object to a 3-dimensional gaussian, and estimates error and goodness of fit. * @param p The parameters for the current analysis. */ public void fitPosition(ParameterDictionary p) { if (this.sizeInPixels == 0) { this.nullifyImages(); return; } this.fitParametersByChannel = new java.util.ArrayList<FitParameters>(); this.fitR2ByChannel = new java.util.ArrayList<Double>(); this.fitErrorByChannel = new java.util.ArrayList<Double>(); this.nPhotonsByChannel = new java.util.ArrayList<Double>(); GaussianFitter3DWithCovariance gf = new GaussianFitter3DWithCovariance(); //System.out.println(this.parent.getDimensionSizes().getZ()); int numChannels = 0; if (p.hasKey("num_wavelengths")) { numChannels = p.getIntValueForKey("num_wavelengths"); } else { numChannels = this.parent.getDimensionSizes().get(ImageCoordinate.C); } //for (int channelIndex = 0; channelIndex < this.parent.getDimensionSizes().getC(); channelIndex++) { for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) { RealVector fitParameters = new ArrayRealVector(11, 0.0); double ppg = p.getDoubleValueForKey("photons_per_greylevel"); this.parentBoxMin.set(ImageCoordinate.C, channelIndex); this.parentBoxMax.set(ImageCoordinate.C, channelIndex + 1); this.boxImages(); List<Double> x = new java.util.ArrayList<Double>(); List<Double> y = new java.util.ArrayList<Double>(); List<Double> z = new java.util.ArrayList<Double>(); List<Double> f = new java.util.ArrayList<Double>(); for (ImageCoordinate ic : this.parent) { x.add((double) ic.get(ImageCoordinate.X)); y.add((double) ic.get(ImageCoordinate.Y)); z.add((double) ic.get(ImageCoordinate.Z)); f.add((double) parent.getValue(ic)); } xValues = new double[x.size()]; yValues = new double[y.size()]; zValues = new double[z.size()]; functionValues = new double[f.size()]; double xCentroid = 0; double yCentroid = 0; double zCentroid = 0; double totalCounts = 0; for (int i = 0; i < x.size(); i++) { xValues[i] = x.get(i); yValues[i] = y.get(i); zValues[i] = z.get(i); functionValues[i] = f.get(i) * ppg; xCentroid += xValues[i] * functionValues[i]; yCentroid += yValues[i] * functionValues[i]; zCentroid += zValues[i] * functionValues[i]; totalCounts += functionValues[i]; } xCentroid /= totalCounts; yCentroid /= totalCounts; zCentroid /= totalCounts; //z sometimes seems to be a bit off... trying (20110415) to go back to max value pixel at x,y centroid int xRound = (int) Math.round(xCentroid); int yRound = (int) Math.round(yCentroid); double maxVal = 0; int maxInd = 0; double minZ = Double.MAX_VALUE; double maxZ = 0; for (int i = 0; i < x.size(); i++) { if (zValues[i] < minZ) minZ = zValues[i]; if (zValues[i] > maxZ) maxZ = zValues[i]; if (xValues[i] == xRound && yValues[i] == yRound) { if (functionValues[i] > maxVal) { maxVal = functionValues[i]; maxInd = (int) zValues[i]; } } } zCentroid = maxInd; //parameter ordering: amplitude, var x-y, var z, x/y/z coords, background //amplitude: find the max value; background: find the min value double maxValue = 0; double minValue = Double.MAX_VALUE; for (ImageCoordinate ic : this.parent) { if (parent.getValue(ic) > maxValue) maxValue = parent.getValue(ic); if (parent.getValue(ic) < minValue) minValue = parent.getValue(ic); } fitParameters.setEntry(0, (maxValue - minValue) * 0.95); fitParameters.setEntry(10, minValue + 0.05 * (maxValue - minValue)); //positions fitParameters.setEntry(7, xCentroid); fitParameters.setEntry(8, yCentroid); fitParameters.setEntry(9, zCentroid); //variances final double limitedWidthxy = 200; final double limitedWidthz = 500; double sizex = limitedWidthxy / p.getDoubleValueForKey("pixelsize_nm"); double sizey = limitedWidthxy / p.getDoubleValueForKey("pixelsize_nm"); double sizez = limitedWidthz / p.getDoubleValueForKey("z_sectionsize_nm"); if (p.hasKey(Z_WIDTH_PARAM)) { sizez = p.getDoubleValueForKey(Z_WIDTH_PARAM); } if (p.hasKey(XY_WIDTH_PARAM)) { sizex = p.getDoubleValueForKey(XY_WIDTH_PARAM); sizey = p.getDoubleValueForKey(XY_WIDTH_PARAM); } fitParameters.setEntry(1, sizex / 2); fitParameters.setEntry(2, sizey / 2); fitParameters.setEntry(3, sizez / 2); //covariances -- guess zero to start fitParameters.setEntry(4, 0.0); fitParameters.setEntry(5, 0.0); fitParameters.setEntry(6, 0.0); //amplitude and background are in arbitrary intensity units; convert to photon counts fitParameters.setEntry(0, fitParameters.getEntry(0) * ppg); fitParameters.setEntry(10, fitParameters.getEntry(10) * ppg); //System.out.println("guess: " + fitParameters); //do the fit //System.out.println("Initial for object " + this.label + ": " + fitParameters.toString()); fitParameters = gf.fit(this, fitParameters, ppg); //System.out.println("Parameters for object " + this.label + ": " + fitParameters.toString()); //System.out.println("fit: " + fitParameters); FitParameters fp = new FitParameters(); fp.setPosition(ImageCoordinate.X, fitParameters.getEntry(7)); fp.setPosition(ImageCoordinate.Y, fitParameters.getEntry(8)); fp.setPosition(ImageCoordinate.Z, fitParameters.getEntry(9)); fp.setSize(ImageCoordinate.X, fitParameters.getEntry(1)); fp.setSize(ImageCoordinate.Y, fitParameters.getEntry(2)); fp.setSize(ImageCoordinate.Z, fitParameters.getEntry(3)); fp.setAmplitude(fitParameters.getEntry(0)); fp.setBackground(fitParameters.getEntry(10)); fp.setOtherParameter("corr_xy", fitParameters.getEntry(4)); fp.setOtherParameter("corr_xz", fitParameters.getEntry(5)); fp.setOtherParameter("corr_yz", fitParameters.getEntry(6)); fitParametersByChannel.add(fp); // System.out.println(fitParameters); //calculate R2 double residualSumSquared = 0; double mean = 0; double variance = 0; double R2 = 0; double n_photons = 0; for (int i = 0; i < this.xValues.length; i++) { residualSumSquared += Math.pow(GaussianFitter3DWithCovariance.fitResidual(functionValues[i], xValues[i], yValues[i], zValues[i], fitParameters), 2); mean += functionValues[i]; n_photons += functionValues[i] - fitParameters.getEntry(10); } mean /= functionValues.length; for (int i = 0; i < this.xValues.length; i++) { variance += Math.pow(functionValues[i] - mean, 2); } R2 = 1 - (residualSumSquared / variance); this.fitR2ByChannel.add(R2); this.unboxImages(); //calculate fit error -- these are wrong for the case with unequal x-y variances and covariance allowed, but leave them for now. double s_xy = fitParameters.getEntry(1) * fitParameters.getEntry(1) * Math.pow(p.getDoubleValueForKey("pixelsize_nm"), 2); double s_z = fitParameters.getEntry(3) * fitParameters.getEntry(3) * Math.pow(p.getDoubleValueForKey("z_sectionsize_nm"), 2); //s_z = 0; //remove!! double error = (2 * s_xy + s_z) / (n_photons - 1);// + 4*Math.sqrt(Math.PI) * Math.pow(2*s_xy,1.5)*Math.pow(fitParameters.getEntry(6),2)/(p.getDoubleValueForKey("pixelsize_nm")*n_photons*n_photons); double b = fitParameters.getEntry(10); double a = p.getDoubleValueForKey("pixelsize_nm"); double alpha = p.getDoubleValueForKey("z_sectionsize_nm"); double sa_x = s_xy + Math.pow(a, 2) / 12; double sa_z = s_z + Math.pow(alpha, 2) / 12; double error_x = sa_x / n_photons * (16.0 / 9.0 + 8 * Math.PI * sa_x * b * b / (n_photons * Math.pow(p.getDoubleValueForKey("pixelsize_nm"), 2))); double error_z = sa_z / n_photons * (16.0 / 9.0 + 8 * Math.PI * sa_z * b * b / (n_photons * Math.pow(p.getDoubleValueForKey("z_sectionsize_nm"), 2))); double A = 1.0 / (2 * Math.sqrt(2) * Math.pow(Math.PI, 1.5) * Math.sqrt(sa_z) * sa_x); ErrIntFunc eif = new ErrIntFunc(); eif.setParams(b, n_photons, A, sa_z, sa_x, a, alpha); //LegendreGaussIntegrator lgi = new LegendreGaussIntegrator(5, 10, 1000); //integrate over 10*width of PSF in z //double size = 10*Math.sqrt(sa_z); //double intpart = 0; // try { // // if (b < 0) throw new ConvergenceException(new DummyLocalizable("negative background!")); // a negative value for b seems to cause the integration to hang, preventing the program from progressing // // intpart = lgi.integrate(10000, eif, -size, size); // // double fullIntPart = intpart + Math.pow(2*Math.PI, 1.5)*sa_x*A/Math.sqrt(sa_z); // // error_x = Math.sqrt(2/(n_photons*sa_z/(2*sa_z + sa_x)*fullIntPart)); // error_z = Math.sqrt(2/(n_photons*sa_x/(2*sa_z + sa_x)*fullIntPart)); // // } catch (ConvergenceException e) { // LoggingUtilities.getLogger().severe("Integration error: " + e.getMessage()); // error_x = -1; // error_z = -1; // } // if (error_x > 0 && error_z > 0) { error = Math.sqrt(2 * error_x * error_x + error_z * error_z); } else { error = Double.NaN; } error = 0; // until a better error calculation this.fitErrorByChannel.add(error); this.positionsByChannel.add(fitParameters.getSubVector(7, 3)); this.nPhotonsByChannel.add(n_photons); } this.hadFittingError = false; this.nullifyImages(); } protected class DI1Func implements UnivariateFunction { private double z; private double b; private double n; private double A; private double sa_z; private double a; private double alpha; public double value(double t) { double tau = b / (n * a * a * alpha * A * Math.exp(-z * z / (2 * sa_z))); return (-1.0 * t * Math.log(t) / (t + tau)); } public void setZ(double z) { this.z = z; } public void setParams(double b, double n, double A, double sa_z, double a, double alpha) { this.b = b; this.n = n; this.A = A; this.sa_z = sa_z; this.a = a; this.alpha = alpha; } } protected class ErrIntFunc implements UnivariateFunction { private double b; private double n; private double A; private double sa_z; private double sa_x; private double a; private double alpha; private LegendreGaussIntegrator lgi; private DI1Func di1; public ErrIntFunc() { this.lgi = new LegendreGaussIntegrator(5, 10, 1000); this.di1 = new DI1Func(); } public void setParams(double b, double n, double A, double sa_z, double sa_x, double a, double alpha) { this.b = b; this.n = n; this.A = A; this.sa_z = sa_z; this.sa_x = sa_x; this.a = a; this.alpha = alpha; this.di1.setParams(b, n, A, sa_z, a, alpha); } public double value(double z) throws IllegalArgumentException { this.di1.setZ(z); double I1 = 0; try { I1 = lgi.integrate(10000, di1, 0, 1); } catch (ConvergenceException e) { throw new IllegalArgumentException(e); } double part1 = 4 * Math.PI * A * Math.exp(-z * z / (2 * sa_z)) * I1; double part2 = 2 * Math.PI * sa_x * b / (sa_z * sa_z * n * a * a * alpha) * z * z * Math.log(1 / (1 + n * A * a * a * alpha * Math.exp(-z * z / (2 * sa_z)) / b)); return part1 + part2; } } }