package cz.cuni.lf1.lge.ThunderSTORM.estimators;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.PSFModel;
import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.PSFModel.Params;
import ij.plugin.filter.Convolver;
import ij.process.FloatProcessor;
/**
* Rewritten to java from the matlab implementation in supplement of the article
* "Rapid, accurate particle tracking by calculation of radial symmetry centers"
* by Raghuveer Parthasarathy
*
*/
public class RadialSymmetryFitter implements IOneLocationFitter {
@Override
public Molecule fit(SubImage img) {
float[] dIdu = computeGradientImage(img, false);
float[] dIdv = computeGradientImage(img, true);
smooth(dIdu, img.size_x);
smooth(dIdv, img.size_y);
float[] m = calculateSlope(dIdu, dIdv);
float[] xMesh = createMesh(img.size_x, true);
float[] yMesh = createMesh(img.size_y, false);
float[] yInterceptB = calculateYIntercept(xMesh, yMesh, m);
float[] weights = calculateWeights(dIdu, dIdv, xMesh, yMesh);
double[] coordinates = lsRadialCenterFit(m, yInterceptB, weights);
return new Molecule(new Params(new int[]{PSFModel.Params.X, PSFModel.Params.Y}, coordinates, false));
}
/**
* Computes gradient image along 45-degree shifted coordinates.
*
* @param img
* @param mainDiagonalDirection specifies the direction in which the
* gradient will be computed, true for main diagonal and false for
* antidiagonal
* @return
*/
private float[] computeGradientImage(SubImage img, boolean mainDiagonalDirection) {
float[] dI = new float[(img.size_x - 1) * (img.size_y - 1)];
int idx1 = mainDiagonalDirection ? 0 : 1;
int idx2 = mainDiagonalDirection ? img.size_x + 1 : img.size_x;
int resIdx = 0;
for(int i = 0; i < img.size_x - 1; i++) {
for(int j = 0; j < img.size_y - 1; j++) {
dI[resIdx++] = (float) (img.values[idx1++] - img.values[idx2++]);
}
idx1++;
idx2++;
}
return dI;
}
/**
* smoothing by 3*3 box filter
*/
private void smooth(float[] dIdu, int size) {
float[] kernel = {1f / 3f, 1f / 3f, 1f / 3f};
Convolver convolver = new Convolver();
FloatProcessor imp = new FloatProcessor(size - 1, size - 1, dIdu, null);
convolver.convolve(imp, kernel, kernel.length, 1);
convolver.convolve(imp, kernel, 1, kernel.length);
}
private float[] calculateSlope(float[] dIdu, float[] dIdv) {
float[] m = new float[dIdu.length];
for(int i = 0; i < m.length; i++) {
float val = -(dIdu[i] + dIdv[i]) / (dIdu[i] - dIdv[i]);
val = Float.isNaN(val) ? 0 : val;
val = Float.isInfinite(val) ? Float.MAX_VALUE / 1e5f : val; //replace inf by some big value - Not max_value because it could overflow to infinity in next step
m[i] = val;
}
return m;
}
private float[] createMesh(int size, boolean xMesh) {
float[] mesh = new float[(size - 1) * (size - 1)];
int smallSize = (size - 1) / 2;
int idx = 0;
for(int i = 0; i < size - 1; i++) {
float iVal = -smallSize + 0.5f + i;
for(int j = 0; j < size - 1; j++) {
float jVal = -smallSize + 0.5f + j;
mesh[idx++] = xMesh ? jVal : iVal;
}
}
return mesh;
}
/**
* b in original matlab implementation
*/
private float[] calculateYIntercept(float[] xMesh, float[] yMesh, float[] m) {
float[] intercept = new float[m.length];
for(int i = 0; i < intercept.length; i++) {
intercept[i] = yMesh[i] - m[i] * xMesh[i];
}
return intercept;
}
/**
* weight by square of gradient magnitude and inverse distance to gradient
* intensity centroid.
*/
private float[] calculateWeights(float[] dIdu, float[] dIdv, float[] xMesh, float[] yMesh) {
float gradientMagnitudeSum = 0;
float xCentroid = 0;
float yCentroid = 0;
for(int i = 0; i < dIdu.length; i++) {
float gradientMagnitude = dIdu[i] * dIdu[i] + dIdv[i] * dIdv[i];
gradientMagnitudeSum += gradientMagnitude;
xCentroid += xMesh[i] * gradientMagnitude;
yCentroid += yMesh[i] * gradientMagnitude;
}
xCentroid /= gradientMagnitudeSum;
yCentroid /= gradientMagnitudeSum;
float[] weights = new float[dIdu.length];
for(int i = 0; i < weights.length; i++) {
float gradientMagnitude = dIdu[i] * dIdu[i] + dIdv[i] * dIdv[i];
double distanceToCentroid = Math.sqrt((xMesh[i] - xCentroid) * (xMesh[i] - xCentroid) + (yMesh[i] - yCentroid) * (yMesh[i] - yCentroid));
weights[i] = (float) (gradientMagnitude / distanceToCentroid);
}
return weights;
}
/**
* least-squares minimization to determine the translated coordinate system
* origin (xc, yc) such that lines y = mx+b have the minimal total
* distance^2 to the origin:
*/
private double[] lsRadialCenterFit(float[] m, float[] b, float[] weights) {
double sw = 0;
double smmw = 0;
double smw = 0;
double smbw = 0;
double sbw = 0;
for(int i = 0; i < m.length; i++) {
double weighted = weights[i] / (m[i] * m[i] + 1); //wm2p1
sw += weighted;
double mw = weighted * m[i];
smw += mw;
smmw += mw * m[i];
smbw += mw * b[i];
sbw += weighted * b[i];
}
double det = smw * smw - smmw * sw;
double xc = (smbw * sw - smw * sbw) / det; // relative to image center
double yc = (smbw * smw - smmw * sbw) / det; // relative to image center
return new double[]{xc, yc};
}
}