package gdsc.smlm.function.gaussian.erf;
//import org.apache.commons.math3.special.Erf;
import org.apache.commons.math3.util.FastMath;
import gdsc.smlm.function.Erf;
import gdsc.smlm.function.Gradient1Procedure;
import gdsc.smlm.function.Gradient2Procedure;
import gdsc.smlm.function.gaussian.Gaussian2DFunction;
/*-----------------------------------------------------------------------------
* GDSC SMLM Software
*
* Copyright (C) 2017 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.
*---------------------------------------------------------------------------*/
/**
* Evaluates a 2-dimensional Gaussian function for a single peak.
*/
public class MultiFreeCircularErfGaussian2DFunction extends MultiErfGaussian2DFunction
{
/**
* Constructor.
*
* @param nPeaks
* The number of peaks
* @param maxx
* The maximum x value of the 2-dimensional data (used to unpack a linear index into coordinates)
* @param maxy
* The maximum y value of the 2-dimensional data (used to unpack a linear index into coordinates)
*/
public MultiFreeCircularErfGaussian2DFunction(int nPeaks, int maxx, int maxy)
{
super(nPeaks, maxx, maxy);
}
@Override
protected int[] createGradientIndices()
{
return replicateGradientIndices(SingleFreeCircularErfGaussian2DFunction.gradientIndices);
}
@Override
public ErfGaussian2DFunction copy()
{
return new MultiFreeCircularErfGaussian2DFunction(nPeaks, maxx, maxy);
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.gaussian.Gaussian2DFunction#initialise0(double[])
*/
public void initialise0(double[] a)
{
tB = a[Gaussian2DFunction.BACKGROUND];
for (int n = 0, i = 0; n < nPeaks; n++, i += 6)
{
tI[n] = a[i + Gaussian2DFunction.SIGNAL];
// Pre-compute the offset by 0.5
final double tx = a[i + Gaussian2DFunction.X_POSITION] + 0.5;
final double ty = a[i + Gaussian2DFunction.Y_POSITION] + 0.5;
final double tsx = a[i + Gaussian2DFunction.X_SD];
final double tsy = a[i + Gaussian2DFunction.Y_SD];
createDeltaETable(n, maxx, ONE_OVER_ROOT2 / tsx, deltaEx, tx);
createDeltaETable(n, maxy, ONE_OVER_ROOT2 / tsy, deltaEy, ty);
}
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.gaussian.Gaussian2DFunction#initialise1(double[])
*/
public void initialise1(double[] a)
{
tB = a[Gaussian2DFunction.BACKGROUND];
for (int n = 0, i = 0; n < nPeaks; n++, i += 6)
{
tI[n] = a[i + Gaussian2DFunction.SIGNAL];
// Pre-compute the offset by 0.5
final double tx = a[i + Gaussian2DFunction.X_POSITION] + 0.5;
final double ty = a[i + Gaussian2DFunction.Y_POSITION] + 0.5;
final double tsx = a[i + Gaussian2DFunction.X_SD];
final double tsy = a[i + Gaussian2DFunction.Y_SD];
// We can pre-compute part of the derivatives for position and sd in arrays
// since the Gaussian is XY separable
create1Arrays();
createFirstOrderTables(n, maxx, tI[n], deltaEx, du_dtx, du_dtsx, tx, tsx);
createFirstOrderTables(n, maxy, tI[n], deltaEy, du_dty, du_dtsy, ty, tsy);
}
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.Gradient2Function#initialise2(double[])
*/
public void initialise2(double[] a)
{
tB = a[Gaussian2DFunction.BACKGROUND];
for (int n = 0, i = 0; n < nPeaks; n++, i += 6)
{
tI[n] = a[i + Gaussian2DFunction.SIGNAL];
// Pre-compute the offset by 0.5
final double tx = a[i + Gaussian2DFunction.X_POSITION] + 0.5;
final double ty = a[i + Gaussian2DFunction.Y_POSITION] + 0.5;
final double tsx = a[i + Gaussian2DFunction.X_SD];
final double tsy = a[i + Gaussian2DFunction.Y_SD];
// We can pre-compute part of the derivatives for position and sd in arrays
// since the Gaussian is XY separable
create2Arrays();
createSecondOrderTables(n, maxx, tI[n], deltaEx, du_dtx, du_dtsx, d2u_dtx2, d2u_dtsx2, tx, tsx);
createSecondOrderTables(n, maxy, tI[n], deltaEy, du_dty, du_dtsy, d2u_dty2, d2u_dtsy2, ty, tsy);
}
}
/**
* Creates the delta E array. This is the sum of the Gaussian function using the error function for each of the
* pixels from 0 to n.
*
* @param n
* the peak number
* @param max
* the maximum for the dimension
* @param one_sSqrt2
* one over (s times sqrt(2))
* @param deltaE
* the delta E for dimension 0 (difference between the error function at the start and end of each pixel)
* @param u
* the mean of the Gaussian for dimension 0
*/
protected static void createDeltaETable(int n, int max, double one_sSqrt2, double[] deltaE, double u)
{
// For documentation see SingleFreeCircularErfGaussian2DFunction.createSecondOrderTables(...)
double x_u_p12 = -u;
double erf_x_minus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
for (int i = 0, j = n * max; i < max; i++, j++)
{
x_u_p12 += 1.0;
final double erf_x_plus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
deltaE[j] = erf_x_plus - erf_x_minus;
erf_x_minus = erf_x_plus;
}
}
/**
* Creates the first order derivatives.
*
* @param n
* the peak number
* @param max
* the maximum for the dimension
* @param tI
* the target intensity
* @param deltaE
* the delta E for dimension 0 (difference between the error function at the start and end of each pixel)
* @param du_dx
* the first order x derivative for dimension 0
* @param du_ds
* the first order s derivative for dimension 0
* @param u
* the mean of the Gaussian for dimension 0
* @param s
* the standard deviation of the Gaussian for dimension 0
*/
protected static void createFirstOrderTables(int n, int max, double tI, double[] deltaE, double[] du_dx,
double[] du_ds, double u, double s)
{
createFirstOrderTables(n, max, ONE_OVER_ROOT2 / s, 0.5 / (s * s), tI * ONE_OVER_ROOT2PI / s,
tI * ONE_OVER_ROOT2PI / (s * s), deltaE, du_dx, du_ds, u);
}
/**
* Creates the first order derivatives.
*
* @param n
* the peak number
* @param max
* the maximum for the dimension
* @param one_sSqrt2
* one over (s times sqrt(2))
* @param one_2ss
* one over (2 * s^2)
* @param I_sSqrt2pi
* the intensity over (s * sqrt(2*pi))
* @param I_ssSqrt2pi
* the intensity over (s^2 * sqrt(2*pi))
* @param deltaE
* the delta E for dimension 0 (difference between the error function at the start and end of each pixel)
* @param du_dx
* the first order x derivative for dimension 0
* @param du_ds
* the first order s derivative for dimension 0
* @param u
* the mean of the Gaussian for dimension 0
*/
protected static void createFirstOrderTables(int n, int max, double one_sSqrt2, double one_2ss, double I_sSqrt2pi,
double I_ssSqrt2pi, double[] deltaE, double[] du_dx, double[] du_ds, double u)
{
// For documentation see SingleFreeCircularErfGaussian2DFunction.createSecondOrderTables(...)
double x_u_p12 = -u;
double erf_x_minus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
double exp_x_minus = FastMath.exp(-(x_u_p12 * x_u_p12 * one_2ss));
for (int i = 0, j = n * max; i < max; i++, j++)
{
double x_u_m12 = x_u_p12;
x_u_p12 += 1.0;
final double erf_x_plus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
deltaE[j] = erf_x_plus - erf_x_minus;
erf_x_minus = erf_x_plus;
final double exp_x_plus = FastMath.exp(-(x_u_p12 * x_u_p12 * one_2ss));
du_dx[j] = I_sSqrt2pi * (exp_x_minus - exp_x_plus);
// Compute: I0 * G21(xk)
du_ds[j] = I_ssSqrt2pi * (x_u_m12 * exp_x_minus - x_u_p12 * exp_x_plus);
exp_x_minus = exp_x_plus;
}
}
/**
* Creates the first and second order derivatives.
*
* @param n
* the peak number
* @param max
* the maximum for the dimension
* @param tI
* the target intensity
* @param deltaE
* the delta E for dimension 0 (difference between the error function at the start and end of each pixel)
* @param du_dx
* the first order x derivative for dimension 0
* @param du_ds
* the first order s derivative for dimension 0
* @param d2u_dx2
* the second order x derivative for dimension 0
* @param d2u_ds2
* the second order s derivative for dimension 0
* @param u
* the mean of the Gaussian for dimension 0
* @param s
* the standard deviation of the Gaussian for dimension 0
*/
protected static void createSecondOrderTables(int n, int max, double tI, double[] deltaE, double[] du_dx,
double[] du_ds, double[] d2u_dx2, double[] d2u_ds2, double u, double s)
{
final double ss = s * s;
final double one_sSqrt2pi = ONE_OVER_ROOT2PI / s;
final double one_ssSqrt2pi = ONE_OVER_ROOT2PI / ss;
final double one_sssSqrt2pi = one_sSqrt2pi / ss;
final double one_sssssSqrt2pi = one_sssSqrt2pi / ss;
createSecondOrderTables(n, max, tI, ONE_OVER_ROOT2 / s, 0.5 / ss, tI * one_sSqrt2pi, tI * one_ssSqrt2pi,
tI * one_sssSqrt2pi, ss, one_sssSqrt2pi, one_sssssSqrt2pi, deltaE, du_dx, du_ds, d2u_dx2, d2u_ds2, u);
}
/**
* Creates the first and second order derivatives.
*
* @param n
* the peak number
* @param max
* the maximum for the dimension
* @param tI
* the target intensity
* @param one_sSqrt2
* one over (s times sqrt(2))
* @param one_2ss
* one over (2 * s^2)
* @param I_sSqrt2pi
* the intensity over (s * sqrt(2*pi))
* @param I_ssSqrt2pi
* the intensity over (s^2 * sqrt(2*pi))
* @param I_sssSqrt2pi
* the intensity over (s^3 * sqrt(2*pi))
* @param ss
* the standard deviation squared
* @param one_sssSqrt2pi
* one over (s^3 * sqrt(2*pi))
* @param one_sssssSqrt2pi
* one over (s^5 * sqrt(2*pi))
* @param deltaE
* the delta E for dimension 0 (difference between the error function at the start and end of each pixel)
* @param du_dx
* the first order x derivative for dimension 0
* @param du_ds
* the first order s derivative for dimension 0
* @param d2u_dx2
* the second order x derivative for dimension 0
* @param d2u_ds2
* the second order s derivative for dimension 0
* @param u
* the mean of the Gaussian for dimension 0
*/
protected static void createSecondOrderTables(int n, int max, double tI, double one_sSqrt2, double one_2ss,
double I_sSqrt2pi, double I_ssSqrt2pi, double I_sssSqrt2pi, double ss, double one_sssSqrt2pi,
double one_sssssSqrt2pi, double[] deltaE, double[] du_dx, double[] du_ds, double[] d2u_dx2,
double[] d2u_ds2, double u)
{
// Note: The paper by Smith, et al computes the integral for the kth pixel centred at (x,y)
// If x=u then the Erf will be evaluated at x-u+0.5 - x-u-0.5 => integral from -0.5 to 0.5.
// This code sets the first pixel at (0,0).
// All computations for pixel k (=(x,y)) that require the exponential can use x,y indices for the
// lower boundary value and x+1,y+1 indices for the upper value.
// Working example of this in GraspJ source code:
// https://github.com/isman7/graspj/blob/master/graspj/src/main/java/eu/brede/graspj/opencl/src/functions/
// I have used the same notation for clarity
// The first position:
// Offset x by the position and get the pixel lower bound.
// (x - u - 0.5) with x=0 and u offset by +0.5
double x_u_p12 = -u;
double erf_x_minus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
double exp_x_minus = FastMath.exp(-(x_u_p12 * x_u_p12 * one_2ss));
for (int i = 0, j = n * max; i < max; i++, j++)
{
double x_u_m12 = x_u_p12;
x_u_p12 += 1.0;
final double erf_x_plus = 0.5 * Erf.erf(x_u_p12 * one_sSqrt2);
deltaE[j] = erf_x_plus - erf_x_minus;
erf_x_minus = erf_x_plus;
final double exp_x_plus = FastMath.exp(-(x_u_p12 * x_u_p12 * one_2ss));
du_dx[j] = I_sSqrt2pi * (exp_x_minus - exp_x_plus);
// Compute: I0 * G21(xk)
final double pre2 = (x_u_m12 * exp_x_minus - x_u_p12 * exp_x_plus);
du_ds[j] = I_ssSqrt2pi * pre2;
// Second derivatives
d2u_dx2[j] = I_sssSqrt2pi * pre2;
// Compute G31(xk)
final double G31 = one_sssSqrt2pi * pre2;
// Compute G53(xk)
x_u_m12 = x_u_m12 * x_u_m12 * x_u_m12;
final double ux = x_u_p12 * x_u_p12 * x_u_p12;
final double G53 = one_sssssSqrt2pi * (x_u_m12 * exp_x_minus - ux * exp_x_plus);
d2u_ds2[j] = tI * (G53 - 2 * G31);
exp_x_minus = exp_x_plus;
}
}
/**
* Evaluates an 2-dimensional Gaussian function for a single peak.
*
* @param i
* Input predictor
* @param duda
* Partial gradient of function with respect to each coefficient
* @return The predicted value
*
* @see gdsc.smlm.function.NonLinearFunction#eval(int, double[])
*/
public double eval(final int i, final double[] duda)
{
// Unpack the predictor into the dimensions
int yy = i / maxx;
int xx = i % maxx;
// Return in order of Gaussian2DFunction.createGradientIndices().
// Use pre-computed gradients
duda[0] = 1.0;
double I = tB;
for (int n = 0, a = 1; n < nPeaks; n++, xx += maxx, yy += maxy)
{
duda[a] = deltaEx[xx] * deltaEy[yy];
I += tI[n] * duda[a++];
duda[a++] = du_dtx[xx] * deltaEy[yy];
duda[a++] = du_dty[yy] * deltaEx[xx];
duda[a++] = du_dtsx[xx] * deltaEy[yy];
duda[a++] = du_dtsy[yy] * deltaEx[xx];
}
return I;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.gaussian.erf.ErfGaussian2DFunction#eval(int, double[], double[])
*/
public double eval(final int i, final double[] duda, final double[] d2uda2)
{
// Unpack the predictor into the dimensions
int yy = i / maxx;
int xx = i % maxx;
// Return in order of Gaussian2DFunction.createGradientIndices().
// Use pre-computed gradients
duda[0] = 1.0;
d2uda2[0] = 0;
double I = tB;
for (int n = 0, a = 1; n < nPeaks; n++, xx += maxx, yy += maxy)
{
duda[a] = deltaEx[xx] * deltaEy[yy];
I += tI[n] * duda[a];
d2uda2[a++] = 0;
duda[a] = du_dtx[xx] * deltaEy[yy];
d2uda2[a++] = d2u_dtx2[xx] * deltaEy[yy];
duda[a] = du_dty[yy] * deltaEx[xx];
d2uda2[a++] = d2u_dty2[yy] * deltaEx[xx];
duda[a] = du_dtsx[xx] * deltaEy[yy];
d2uda2[a++] = d2u_dtsx2[xx] * deltaEy[yy];
duda[a] = du_dtsy[yy] * deltaEx[xx];
d2uda2[a++] = d2u_dtsy2[yy] * deltaEx[xx];
}
return I;
}
@Override
public boolean evaluatesBackground()
{
return true;
}
@Override
public boolean evaluatesSignal()
{
return true;
}
@Override
public boolean evaluatesShape()
{
return false;
}
@Override
public boolean evaluatesPosition()
{
return true;
}
@Override
public boolean evaluatesSD0()
{
return true;
}
@Override
public boolean evaluatesSD1()
{
return true;
}
@Override
public int getParametersPerPeak()
{
return 5;
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.GradientFunction#forEach(gdsc.smlm.function.GradientFunction.Gradient1Procedure)
*/
public void forEach(Gradient1Procedure procedure)
{
final double[] duda = new double[getNumberOfGradients()];
duda[0] = 1.0;
// Note: This unrolling does not perform better in JUnit speed test
// // Unroll for the number of peaks
// if (nPeaks == 2)
// {
// for (int y = 0; y < maxy; y++)
// {
// for (int x = 0, xx = maxx, yy = maxy; x < maxx; x++, xx++, yy++)
// {
// duda[1] = deltaEx[x] * deltaEy[y];
// duda[2] = du_dtx[x] * deltaEy[y];
// duda[3] = du_dty[y] * deltaEx[x];
// duda[4] = du_dtsx[x] * deltaEy[y];
// duda[5] = du_dtsy[y] * deltaEx[x];
// duda[6] = deltaEx[xx] * deltaEy[yy];
// duda[7] = du_dtx[xx] * deltaEy[yy];
// duda[8] = du_dty[yy] * deltaEx[xx];
// duda[9] = du_dtsx[xx] * deltaEy[yy];
// duda[10] = du_dtsy[yy] * deltaEx[xx];
// procedure.execute(tB + tI[0] * duda[1] + tI[1] * duda[6], duda);
// }
// }
// }
// else
// {
for (int y = 0; y < maxy; y++)
{
for (int x = 0; x < maxx; x++)
{
double I = tB;
for (int n = 0, xx = x, yy = y, a = 1; n < nPeaks; n++, xx += maxx, yy += maxy)
{
duda[a] = deltaEx[xx] * deltaEy[yy];
I += tI[n] * duda[a++];
duda[a++] = du_dtx[xx] * deltaEy[yy];
duda[a++] = du_dty[yy] * deltaEx[xx];
duda[a++] = du_dtsx[xx] * deltaEy[yy];
duda[a++] = du_dtsy[yy] * deltaEx[xx];
}
procedure.execute(I, duda);
}
}
// }
}
/*
* (non-Javadoc)
*
* @see gdsc.smlm.function.Gradient2Function#forEach(gdsc.smlm.function.Gradient2Procedure)
*/
public void forEach(Gradient2Procedure procedure)
{
final double[] duda = new double[getNumberOfGradients()];
final double[] d2uda2 = new double[getNumberOfGradients()];
duda[0] = 1.0;
for (int y = 0; y < maxy; y++)
{
for (int x = 0; x < maxx; x++)
{
double I = tB;
for (int n = 0, xx = x, yy = y, a = 1; n < nPeaks; n++, xx += maxx, yy += maxy)
{
duda[a] = deltaEx[xx] * deltaEy[yy];
I += tI[n] * duda[a++];
duda[a] = du_dtx[xx] * deltaEy[yy];
d2uda2[a++] = d2u_dtx2[xx] * deltaEy[yy];
duda[a] = du_dty[yy] * deltaEx[xx];
d2uda2[a++] = d2u_dty2[yy] * deltaEx[xx];
duda[a] = du_dtsx[xx] * deltaEy[yy];
d2uda2[a++] = d2u_dtsx2[xx] * deltaEy[yy];
duda[a] = du_dtsy[yy] * deltaEx[xx];
d2uda2[a++] = d2u_dtsy2[yy] * deltaEx[xx];
}
procedure.execute(I, duda, d2uda2);
}
}
}
}